/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const TEST_PATH = getRootDirectory(gTestPath).replace( "chrome://mochitests/content", "https://example.com" ); // Matches the _XXXX seed (4 base64url chars) that nsWebBrowserPersist appends // to subresource filenames, immediately before any file extension. const SEED_RE = /_[A-Za-z0-9_-]{4}(?=\.[^.]+$)/; async function savePageToDir(url, name) { return BrowserTestUtils.withNewTab(url, async function (browser) { let doc = await new Promise(resolve => { browser.frameLoader.startPersistence(null, { onDocumentReady: resolve, onError(e) { ok(false, "startPersistence failed: " + e); }, }); }); let browserPersist = Cc[ "@mozilla.org/embedding/browser/nsWebBrowserPersist;1" ].createInstance(Ci.nsIWebBrowserPersist); // saveDocument() requires nsIFile; derive paths from the nsIFile objects. let tmp = Services.dirsvc.get("TmpD", Ci.nsIFile); let savedFile = tmp.clone(); savedFile.append(name + ".html"); let savedDir = tmp.clone(); savedDir.append(name + "_files"); registerCleanupFunction(async () => { await IOUtils.remove(savedFile.path, { ignoreAbsent: true }); await IOUtils.remove(savedDir.path, { ignoreAbsent: true, recursive: true, }); }); await new Promise(resolve => { browserPersist.progressListener = { onProgressChange() {}, onLocationChange() {}, onStatusChange() {}, onSecurityChange() {}, onContentBlockingEvent() {}, onStateChange(_persist, _req, state) { const done = Ci.nsIWebProgressListener.STATE_STOP | Ci.nsIWebProgressListener.STATE_IS_NETWORK; if ((state & done) === done) { resolve(); } }, }; browserPersist.saveDocument(doc, savedFile, savedDir, null, 0, 0); }); let children = await IOUtils.getChildren(savedDir.path, { ignoreAbsent: true, }); return children.map(p => PathUtils.filename(p)); }); } // All subresources saved by one nsWebBrowserPersist instance must have a seed // of the same value (it is generated once in the constructor). add_task(async function test_seed_format_and_consistency() { let files = await savePageToDir( TEST_PATH + "file_browserPersist_filename.html", "browserPersist_seed_consistency" ); Assert.greaterOrEqual( files.length, 2, "At least two subresources were saved" ); for (let name of files) { Assert.ok(SEED_RE.test(name), `${name} has a seed suffix`); } let seeds = new Set(files.map(name => (name.match(SEED_RE) || [""])[0])); Assert.equal( seeds.size, 1, `All saved subresources share the same seed, got: ${Array.from(seeds)}` ); }); // When the filename exceeds kDefaultMaxFilenameLength the base is truncated, // but the seed must still be present in the saved filename. add_task(async function test_long_filename_truncated_with_seed() { let files = await savePageToDir( TEST_PATH + "file_browserPersist_filename.html", "browserPersist_truncation" ); let longFile = files.find(name => name.startsWith("file_browserPersist_a_very_long") ); Assert.ok(longFile, "Long-named CSS file was saved"); Assert.lessOrEqual( longFile.length, 64, `Saved filename length ${longFile.length} is at most 64 chars` ); Assert.ok( SEED_RE.test(longFile), `Truncated filename still has a seed: ${longFile}` ); }); // When two subresource URLs produce the same base filename, the second one gets // a uniqueness counter (_002, _003, …) inserted before the seed. add_task(async function test_uniqueness_counter_with_seed() { let files = await savePageToDir( TEST_PATH + "file_browserPersist_filename.html", "browserPersist_uniqueness" ); let dummyFiles = files.filter(name => name.startsWith("dummy")); Assert.equal( dummyFiles.length, 2, "Both dummy.png references were saved separately" ); let seeds = dummyFiles.map(name => (name.match(SEED_RE) || [""])[0]); Assert.equal(seeds[0], seeds[1], "Both files share the same seed"); Assert.ok( dummyFiles.some(name => /_\d{3}_[A-Za-z0-9_-]{4}\./.test(name)), "One dummy file has a uniqueness counter before the seed" ); });