diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /browser/components/newtab/test/browser/abouthomecache | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/newtab/test/browser/abouthomecache')
15 files changed, 1063 insertions, 0 deletions
diff --git a/browser/components/newtab/test/browser/abouthomecache/browser.ini b/browser/components/newtab/test/browser/abouthomecache/browser.ini new file mode 100644 index 0000000000..febe76d92e --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser.ini @@ -0,0 +1,39 @@ +[DEFAULT] +support-files = + head.js + ../ds_layout.json + ../topstories.json +prefs = + browser.tabs.remote.separatePrivilegedContentProcess=true + browser.startup.homepage.abouthome_cache.enabled=true + browser.startup.homepage.abouthome_cache.cache_on_shutdown=false + browser.startup.homepage.abouthome_cache.loglevel=All + browser.startup.homepage.abouthome_cache.testing=true + browser.startup.page=1 + browser.newtabpage.activity-stream.discoverystream.endpoints=data: + browser.newtabpage.activity-stream.feeds.system.topstories=true + browser.newtabpage.activity-stream.feeds.section.topstories=true + browser.newtabpage.activity-stream.feeds.system.topstories=true + browser.newtabpage.activity-stream.feeds.section.topstories.options={"provider_name":""} + browser.newtabpage.activity-stream.telemetry.structuredIngestion=false + browser.ping-centre.telemetry=false + browser.newtabpage.activity-stream.discoverystream.endpoints=https://example.com + dom.ipc.processPrelaunch.delayMs=0 +# Bug 1694957 is why we need dom.ipc.processPrelaunch.delayMs=0 + +[browser_basic_endtoend.js] +[browser_bump_version.js] +[browser_disabled.js] +[browser_experiments_api_control.js] +[browser_locale_change.js] +[browser_no_cache.js] +[browser_no_cache_on_SessionStartup_restore.js] +[browser_no_startup_actions.js] +[browser_overwrite_cache.js] +[browser_process_crash.js] +skip-if = + !crashreporter + os == "mac" && fission # Bug 1659427; medium frequency intermittent on osx: test timed out +[browser_same_consumer.js] +[browser_sanitize.js] +[browser_shutdown_timeout.js] diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_basic_endtoend.js b/browser/components/newtab/test/browser/abouthomecache/browser_basic_endtoend.js new file mode 100644 index 0000000000..bd42dd4af9 --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_basic_endtoend.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests that the about:home cache gets written on shutdown, and read + * from in the subsequent startup. + */ +add_task(async function test_basic_behaviour() { + await withFullyLoadedAboutHome(async browser => { + // First, clear the cache to test the base case. + await clearCache(); + await simulateRestart(browser); + await ensureCachedAboutHome(browser); + + // Next, test that a subsequent restart also shows the cached + // about:home. + await simulateRestart(browser); + await ensureCachedAboutHome(browser); + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_bump_version.js b/browser/components/newtab/test/browser/abouthomecache/browser_bump_version.js new file mode 100644 index 0000000000..726b9aa973 --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_bump_version.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Test that if the "version" metadata on the cache entry doesn't match + * the expectation that we ignore the cache and load the dynamic about:home + * document. + */ +add_task(async function test_bump_version() { + await withFullyLoadedAboutHome(async browser => { + // First, ensure that a pre-existing cache exists. + await simulateRestart(browser); + + let cacheEntry = await AboutHomeStartupCache.ensureCacheEntry(); + Assert.equal( + cacheEntry.getMetaDataElement("version"), + Services.appinfo.appBuildID, + "Cache entry should be versioned on the build ID" + ); + cacheEntry.setMetaDataElement("version", "somethingnew"); + // We don't need to shutdown write or ensure the cache wins the race, + // since we expect the cache to be blown away because the version number + // has been bumped. + await simulateRestart(browser, { + withAutoShutdownWrite: false, + ensureCacheWinsRace: false, + }); + await ensureDynamicAboutHome( + browser, + AboutHomeStartupCache.CACHE_RESULT_SCALARS.INVALIDATED + ); + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_disabled.js b/browser/components/newtab/test/browser/abouthomecache/browser_disabled.js new file mode 100644 index 0000000000..faa79b219c --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_disabled.js @@ -0,0 +1,97 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This file tests scenarios where the cache is disabled due to user + * configuration. + */ + +registerCleanupFunction(async () => { + // When the test completes, make sure we cleanup with a populated cache, + // since this is the default starting state for these tests. + await withFullyLoadedAboutHome(async browser => { + await simulateRestart(browser); + }); +}); + +/** + * Tests the case where the cache is disabled via the pref. + */ +add_task(async function test_cache_disabled() { + await withFullyLoadedAboutHome(async browser => { + await SpecialPowers.pushPrefEnv({ + set: [["browser.startup.homepage.abouthome_cache.enabled", false]], + }); + + await simulateRestart(browser); + + await ensureDynamicAboutHome( + browser, + AboutHomeStartupCache.CACHE_RESULT_SCALARS.DISABLED + ); + + await SpecialPowers.popPrefEnv(); + }); +}); + +/** + * Tests the case where the cache is disabled because the home page is + * not set at about:home. + */ +add_task(async function test_cache_custom_homepage() { + await withFullyLoadedAboutHome(async browser => { + await HomePage.set("https://example.com"); + await simulateRestart(browser); + + await ensureDynamicAboutHome( + browser, + AboutHomeStartupCache.CACHE_RESULT_SCALARS.NOT_LOADING_ABOUTHOME + ); + + HomePage.reset(); + }); +}); + +/** + * Tests the case where the cache is disabled because the session is + * configured to automatically be restored. + */ +add_task(async function test_cache_restore_session() { + await withFullyLoadedAboutHome(async browser => { + await SpecialPowers.pushPrefEnv({ + set: [["browser.startup.page", 3]], + }); + + await simulateRestart(browser); + + await ensureDynamicAboutHome( + browser, + AboutHomeStartupCache.CACHE_RESULT_SCALARS.NOT_LOADING_ABOUTHOME + ); + + await SpecialPowers.popPrefEnv(); + }); +}); + +/** + * Tests the case where the cache is disabled because about:newtab + * preloading is disabled. + */ +add_task(async function test_cache_no_preloading() { + await withFullyLoadedAboutHome(async browser => { + await SpecialPowers.pushPrefEnv({ + set: [["browser.newtab.preload", false]], + }); + + await simulateRestart(browser); + + await ensureDynamicAboutHome( + browser, + AboutHomeStartupCache.CACHE_RESULT_SCALARS.PRELOADING_DISABLED + ); + + await SpecialPowers.popPrefEnv(); + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_experiments_api_control.js b/browser/components/newtab/test/browser/abouthomecache/browser_experiments_api_control.js new file mode 100644 index 0000000000..a94f1fe055 --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_experiments_api_control.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { ExperimentFakes } = ChromeUtils.importESModule( + "resource://testing-common/NimbusTestUtils.sys.mjs" +); + +registerCleanupFunction(async () => { + // When the test completes, make sure we cleanup with a populated cache, + // since this is the default starting state for these tests. + await withFullyLoadedAboutHome(async browser => { + await simulateRestart(browser); + }); +}); + +/** + * Tests that the ExperimentsAPI mechanism can be used to remotely + * enable and disable the about:home startup cache. + */ +add_task(async function test_experiments_api_control() { + // First, the disabled case. + await withFullyLoadedAboutHome(async browser => { + let doEnrollmentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ + featureId: "abouthomecache", + value: { enabled: false }, + }); + + Assert.ok( + !NimbusFeatures.abouthomecache.getVariable("enabled"), + "NimbusFeatures should tell us that the about:home startup cache " + + "is disabled" + ); + + await simulateRestart(browser); + + await ensureDynamicAboutHome( + browser, + AboutHomeStartupCache.CACHE_RESULT_SCALARS.DISABLED + ); + + await doEnrollmentCleanup(); + }); + + // Now the enabled case. + await withFullyLoadedAboutHome(async browser => { + let doEnrollmentCleanup = await ExperimentFakes.enrollWithFeatureConfig({ + featureId: "abouthomecache", + value: { enabled: true }, + }); + + Assert.ok( + NimbusFeatures.abouthomecache.getVariable("enabled"), + "NimbusFeatures should tell us that the about:home startup cache " + + "is enabled" + ); + + await simulateRestart(browser); + await ensureCachedAboutHome(browser); + await doEnrollmentCleanup(); + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_locale_change.js b/browser/components/newtab/test/browser/abouthomecache/browser_locale_change.js new file mode 100644 index 0000000000..e9e3c619ec --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_locale_change.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests that the about:home startup cache is cleared if the app + * locale changes. + */ +add_task(async function test_locale_change() { + await withFullyLoadedAboutHome(async browser => { + await simulateRestart(browser); + await ensureCachedAboutHome(browser); + + Services.obs.notifyObservers(null, "intl:app-locales-changed"); + await AboutHomeStartupCache.ensureCacheEntry(); + + // We're testing that switching locales blows away the cache, so we + // bypass the automatic writing of the cache on shutdown, and we + // also don't need to wait for the cache to be available. + await simulateRestart(browser, { + withAutoShutdownWrite: false, + ensureCacheWinsRace: false, + }); + await ensureDynamicAboutHome( + browser, + AboutHomeStartupCache.CACHE_RESULT_SCALARS.DOES_NOT_EXIST + ); + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_no_cache.js b/browser/components/newtab/test/browser/abouthomecache/browser_no_cache.js new file mode 100644 index 0000000000..fdb51f8712 --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_no_cache.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +requestLongerTimeout(2); + +/** + * Test that if there's no cache written, that we load the dynamic + * about:home document on startup. + */ +add_task(async function test_no_cache() { + await withFullyLoadedAboutHome(async browser => { + await clearCache(); + // We're testing the no-cache case, so we bypass the automatic writing + // of the cache on shutdown, and we also don't need to wait for the + // cache to be available. + await simulateRestart(browser, { + withAutoShutdownWrite: false, + ensureCacheWinsRace: false, + }); + await ensureDynamicAboutHome( + browser, + AboutHomeStartupCache.CACHE_RESULT_SCALARS.DOES_NOT_EXIST + ); + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_no_cache_on_SessionStartup_restore.js b/browser/components/newtab/test/browser/abouthomecache/browser_no_cache_on_SessionStartup_restore.js new file mode 100644 index 0000000000..a312b2b44f --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_no_cache_on_SessionStartup_restore.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests that if somehow about:newtab loads before about:home does, that we + * don't use the cache. This is because about:newtab doesn't use the cache, + * and so it'll inevitably be newer than what's in the about:home cache, + * which will put the about:home cache out of date the next time about:home + * eventually loads. + */ +add_task(async function test_no_cache_on_SessionStartup_restore() { + await withFullyLoadedAboutHome(async browser => { + await simulateRestart(browser, { skipAboutHomeLoad: true }); + + // We remove the preloaded browser to ensure that loading the next + // about:newtab occurs now, and not at preloading time. + NewTabPagePreloading.removePreloadedBrowser(window); + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:newtab" + ); + + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + + // The cache is disqualified because about:newtab was loaded first. + // So now it's too late to use the cache. + await ensureDynamicAboutHome( + newWin.gBrowser.selectedBrowser, + AboutHomeStartupCache.CACHE_RESULT_SCALARS.LATE + ); + + await BrowserTestUtils.closeWindow(newWin); + await BrowserTestUtils.removeTab(tab); + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_no_startup_actions.js b/browser/components/newtab/test/browser/abouthomecache/browser_no_startup_actions.js new file mode 100644 index 0000000000..255b4c9d21 --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_no_startup_actions.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests that upon initializing Activity Stream, the cached about:home + * document does not process any actions caused by that initialization. + * This is because the restored Redux state from the cache should be enough, + * and processing any of the initialization messages from Activity Stream + * could wipe out that state and cause flicker / unnecessary redraws. + */ +add_task(async function test_no_startup_actions() { + await withFullyLoadedAboutHome(async browser => { + // Make sure we have a cached document. We simulate a restart to ensure + // that we start with a cache... that we can then clear without a problem, + // before writing a new cache. This ensures that no matter what, we're in a + // state where we have a fresh cache, regardless of what's happened in earlier + // tests. + await simulateRestart(browser); + await clearCache(); + await simulateRestart(browser); + await ensureCachedAboutHome(browser); + + // Set up a listener to monitor for actions that get dispatched in the + // browser when we fire Activity Stream up again. + await SpecialPowers.spawn(browser, [], async () => { + let xrayWindow = ChromeUtils.waiveXrays(content); + xrayWindow.nonStartupActions = []; + xrayWindow.startupActions = []; + xrayWindow.RPMAddMessageListener("ActivityStream:MainToContent", msg => { + if (msg.data.meta.isStartup) { + xrayWindow.startupActions.push(msg.data); + } else { + xrayWindow.nonStartupActions.push(msg.data); + } + }); + }); + + // The following two statements seem to be enough to simulate Activity + // Stream starting up. + AboutNewTab.activityStream.uninit(); + AboutNewTab.onBrowserReady(); + + // Much of Activity Stream initializes asynchronously. This is the easiest way + // I could find to ensure that enough of the feeds had initialized to produce + // a meaningful cached document. + await TestUtils.waitForCondition(() => { + let feed = AboutNewTab.activityStream.store.feeds.get( + "feeds.discoverystreamfeed" + ); + return feed?.loaded; + }); + + // Wait an additional few seconds for any other actions to get displayed. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 2000)); + + let [startupActions, nonStartupActions] = await SpecialPowers.spawn( + browser, + [], + async () => { + let xrayWindow = ChromeUtils.waiveXrays(content); + return [xrayWindow.startupActions, xrayWindow.nonStartupActions]; + } + ); + + Assert.ok(!!startupActions.length, "Should have seen startup actions."); + info(`Saw ${startupActions.length} startup actions.`); + + Assert.equal( + nonStartupActions.length, + 0, + "Should be no non-startup actions." + ); + + if (nonStartupActions.length) { + for (let action of nonStartupActions) { + info(`Non-startup action: ${action.type}`); + } + } + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_overwrite_cache.js b/browser/components/newtab/test/browser/abouthomecache/browser_overwrite_cache.js new file mode 100644 index 0000000000..22df98794f --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_overwrite_cache.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests that if a pre-existing about:home cache exists, that it can + * be overwritten with new information. + */ +add_task(async function test_overwrite_cache() { + await withFullyLoadedAboutHome(async browser => { + await simulateRestart(browser); + const TEST_ID = "test_overwrite_cache_h1"; + + // We need the CSP meta tag in about: pages, otherwise we hit assertions in + // debug builds. + await injectIntoCache( + ` + <html> + <head> + <meta http-equiv="Content-Security-Policy" content="default-src 'none'; object-src 'none'; script-src resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';"> + </head> + <body> + <h1 id="${TEST_ID}">Something new</h1> + <div id="root"></div> + </body> + <script src="about:home?jscache"></script> + </html>`, + "window.__FROM_STARTUP_CACHE__ = true;" + ); + await simulateRestart(browser, { withAutoShutdownWrite: false }); + + await SpecialPowers.spawn(browser, [TEST_ID], async testID => { + let target = content.document.getElementById(testID); + Assert.ok(target, "Found the target element"); + }); + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_process_crash.js b/browser/components/newtab/test/browser/abouthomecache/browser_process_crash.js new file mode 100644 index 0000000000..2a26bc553d --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_process_crash.js @@ -0,0 +1,81 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Test that if the "privileged about content process" crashes, that it + * drops its internal reference to the "privileged about content process" + * process manager, and that a subsequent restart of that process type + * results in a dynamic document load. Also tests that crashing of + * any other content process type doesn't clear the process manager + * reference. + */ +add_task(async function test_process_crash() { + await withFullyLoadedAboutHome(async browser => { + await simulateRestart(browser); + let origProcManager = AboutHomeStartupCache._procManager; + + await BrowserTestUtils.crashFrame(browser); + Assert.notEqual( + origProcManager, + AboutHomeStartupCache._procManager, + "Should have dropped the reference to the crashed process" + ); + }); + + await withFullyLoadedAboutHome(async browser => { + // The cache should still be considered "valid and used", since it was + // used successfully before the crash. + await ensureDynamicAboutHome( + browser, + AboutHomeStartupCache.CACHE_RESULT_SCALARS.VALID_AND_USED + ); + + // Now simulate a restart to attach the AboutHomeStartupCache to + // the new privileged about content process. + await simulateRestart(browser); + }); + + let latestProcManager = AboutHomeStartupCache._procManager; + + await BrowserTestUtils.withNewTab("http://example.com", async browser => { + await BrowserTestUtils.crashFrame(browser); + Assert.equal( + latestProcManager, + AboutHomeStartupCache._procManager, + "Should still have the reference to the privileged about process" + ); + }); +}); + +/** + * Tests that if the "privileged about content process" crashes while + * a cache request is still underway, that the cache request resolves with + * null input streams. + */ +add_task(async function test_process_crash_while_requesting_streams() { + await withFullyLoadedAboutHome(async browser => { + await simulateRestart(browser); + let cacheStreamsPromise = AboutHomeStartupCache.requestCache(); + await BrowserTestUtils.crashFrame(browser); + let cacheStreams = await cacheStreamsPromise; + + if (!cacheStreams.pageInputStream && !cacheStreams.scriptInputStream) { + Assert.ok(true, "Page and script input streams are null."); + } else { + // It's possible (but probably rare) the parent was able to receive the + // streams before the crash occurred. In that case, we'll make sure that + // we can still read the streams. + info("Received the streams. Checking that they're readable."); + Assert.ok( + cacheStreams.pageInputStream.available(), + "Bytes available for page stream" + ); + Assert.ok( + cacheStreams.scriptInputStream.available(), + "Bytes available for script stream" + ); + } + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_same_consumer.js b/browser/components/newtab/test/browser/abouthomecache/browser_same_consumer.js new file mode 100644 index 0000000000..75f8875f26 --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_same_consumer.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests that if a page attempts to load the script stream without + * having also loaded the page stream, that it will fail and get + * the default non-cached script. + */ +add_task(async function test_same_consumer() { + await withFullyLoadedAboutHome(async browser => { + await simulateRestart(browser); + + // We need the CSP meta tag in about: pages, otherwise we hit assertions in + // debug builds. + // + // We inject a script that sets a __CACHE_CONSUMED__ property to true on + // the window element. We'll test to ensure that if we try to load the + // script cache from a different BrowsingContext that this property is + // not set. + await injectIntoCache( + ` + <html> + <head> + <meta http-equiv="Content-Security-Policy" content="default-src 'none'; object-src 'none'; script-src resource: chrome:; connect-src https:; img-src https: data: blob:; style-src 'unsafe-inline';"> + </head> + <body> + <h1>A fake about:home page</h1> + <div id="root"></div> + </body> + </html>`, + "window.__CACHE_CONSUMED__ = true;" + ); + await simulateRestart(browser, { withAutoShutdownWrite: false }); + + // Attempting to load the script from the cache should fail, and instead load + // the markup. + await BrowserTestUtils.withNewTab("about:home?jscache", async browser2 => { + await SpecialPowers.spawn(browser2, [], async () => { + Assert.ok( + !Cu.waiveXrays(content).__CACHE_CONSUMED__, + "Should not have found __CACHE_CONSUMED__ property" + ); + Assert.ok( + content.document.body.classList.contains("activity-stream"), + "Should have found activity-stream class on <body> element" + ); + }); + }); + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_sanitize.js b/browser/components/newtab/test/browser/abouthomecache/browser_sanitize.js new file mode 100644 index 0000000000..4dc7ba2c89 --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_sanitize.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests that when sanitizing places history, session store or downloads, that + * the about:home cache gets blown away. + */ + +add_task(async function test_sanitize() { + let testFlags = [ + ["downloads", Ci.nsIClearDataService.CLEAR_DOWNLOADS], + ["places history", Ci.nsIClearDataService.CLEAR_HISTORY], + ["session history", Ci.nsIClearDataService.CLEAR_SESSION_HISTORY], + ]; + + await withFullyLoadedAboutHome(async browser => { + for (let [type, flag] of testFlags) { + await simulateRestart(browser); + await ensureCachedAboutHome(browser); + + info( + "Testing that the about:home startup cache is cleared when " + + `clearing ${type}` + ); + + await new Promise((resolve, reject) => { + Services.clearData.deleteData(flag, { + onDataDeleted(resultFlags) { + if (!resultFlags) { + resolve(); + } else { + reject(new Error(`Failed with flags: ${resultFlags}`)); + } + }, + }); + }); + + // For the purposes of the test, we don't want the write-on-shutdown + // behaviour here (because we just want to test that the cache doesn't + // exist on startup if the history data was cleared). We also therefore + // don't need to ensure that the cache wins the race. + await simulateRestart(browser, { + withAutoShutdownWrite: false, + ensureCacheWinsRace: false, + }); + await ensureDynamicAboutHome( + browser, + AboutHomeStartupCache.CACHE_RESULT_SCALARS.DOES_NOT_EXIST + ); + } + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/browser_shutdown_timeout.js b/browser/components/newtab/test/browser/abouthomecache/browser_shutdown_timeout.js new file mode 100644 index 0000000000..52be79338e --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/browser_shutdown_timeout.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests that if there's a substantial delay in getting the cache + * streams from the privileged about content process for any reason + * during shutdown, that we timeout and let the AsyncShutdown proceed, + * rather than letting it block until AsyncShutdown causes a shutdown + * hang crash. + */ +add_task(async function test_shutdown_timeout() { + await withFullyLoadedAboutHome(async browser => { + // First, make sure the cache is populated so that later on, after + // the timeout, simulateRestart doesn't complain about not finding + // a pre-existing cache. This complaining only happens if this test + // is run in isolation. + await clearCache(); + await simulateRestart(browser); + + // Next, manually shutdown the AboutHomeStartupCacheChild so that + // it doesn't respond to requests to the cache streams. + await SpecialPowers.spawn(browser, [], async () => { + let { AboutHomeStartupCacheChild } = ChromeUtils.import( + "resource:///modules/AboutNewTabService.jsm" + ); + AboutHomeStartupCacheChild.uninit(); + }); + + // Then, manually dirty the cache state so that we attempt to write + // on shutdown. + AboutHomeStartupCache.onPreloadedNewTabMessage(); + + await simulateRestart(browser, { expectTimeout: true }); + + Assert.ok( + true, + "We reached here, which means shutdown didn't block forever." + ); + + // Clear the cache so that we're not in a half-persisted state. + await clearCache(); + }); +}); diff --git a/browser/components/newtab/test/browser/abouthomecache/head.js b/browser/components/newtab/test/browser/abouthomecache/head.js new file mode 100644 index 0000000000..a3c8c1434b --- /dev/null +++ b/browser/components/newtab/test/browser/abouthomecache/head.js @@ -0,0 +1,360 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +let { AboutHomeStartupCache } = ChromeUtils.importESModule( + "resource:///modules/BrowserGlue.sys.mjs" +); + +// Some Activity Stream preferences are JSON encoded, and quite complex. +// Hard-coding them here or in browser.ini makes them brittle to change. +// Instead, we pull the default prefs structures and set the values that +// we need and write them to preferences here dynamically. We do this in +// its own scope to avoid polluting the global scope. +{ + const { PREFS_CONFIG } = ChromeUtils.import( + "resource://activity-stream/lib/ActivityStream.jsm" + ); + + let defaultDSConfig = JSON.parse( + PREFS_CONFIG.get("discoverystream.config").getValue({ + geo: "US", + locale: "en-US", + }) + ); + + let newConfig = Object.assign(defaultDSConfig, { + show_spocs: false, + hardcoded_layout: false, + layout_endpoint: + "https://example.com/browser/browser/components/newtab/test/browser/ds_layout.json", + }); + + // Configure Activity Stream to query for the layout JSON file that points + // at the local top stories feed. + Services.prefs.setCharPref( + "browser.newtabpage.activity-stream.discoverystream.config", + JSON.stringify(newConfig) + ); +} + +/** + * Utility function that loads about:home in the current window in a new tab, and waits + * for the Discovery Stream cards to finish loading before running the taskFn function. + * Once taskFn exits, the about:home tab will be closed. + * + * @param {function} taskFn + * A function that will be run after about:home has finished loading. This can be + * an async function. + * @return {Promise} + * @resolves {undefined} + */ +// eslint-disable-next-line no-unused-vars +function withFullyLoadedAboutHome(taskFn) { + return BrowserTestUtils.withNewTab("about:home", async browser => { + await SpecialPowers.spawn(browser, [], async () => { + await ContentTaskUtils.waitForCondition( + () => + content.document.querySelectorAll( + "[data-section-id='topstories'] .ds-card-link" + ).length, + "Waiting for Discovery Stream to be rendered." + ); + }); + + await taskFn(browser); + }); +} + +/** + * Shuts down the AboutHomeStartupCache components in the parent process + * and privileged about content process, and then restarts them, simulating + * the parent process having restarted. + * + * @param browser (<xul:browser>) + * A <xul:browser> with about:home running in it. This will be reloaded + * after the restart simultion is complete, and that reload will attempt + * to read any about:home cache contents. + * @param options (object, optional) + * + * An object with the following properties: + * + * withAutoShutdownWrite (boolean, optional): + * Whether or not the shutdown part of the simulation should cause the + * shutdown handler to run, which normally causes the cache to be + * written. Setting this to false is handy if the cache has been + * specially prepared for the subsequent startup, and we don't want to + * overwrite it. This defaults to true. + * + * ensureCacheWinsRace (boolean, optional): + * Ensures that the privileged about content process will be able to + * read the bytes from the streams sent down from the HTTP cache. Use + * this to avoid the HTTP cache "losing the race" against reading the + * about:home document from the omni.ja. This defaults to true. + * + * expectTimeout (boolean, optional): + * If true, indicates that it's expected that AboutHomeStartupCache will + * timeout when shutting down. If false, such timeouts will result in + * test failures. Defaults to false. + * + * skipAboutHomeLoad (boolean, optional): + * If true, doesn't automatically load about:home after the simulated + * restart. Defaults to false. + * + * @returns Promise + * @resolves undefined + * Resolves once the restart simulation is complete, and the <xul:browser> + * pointed at about:home finishes reloading. + */ +// eslint-disable-next-line no-unused-vars +async function simulateRestart( + browser, + { + withAutoShutdownWrite = true, + ensureCacheWinsRace = true, + expectTimeout = false, + skipAboutHomeLoad = false, + } = {} +) { + info("Simulating restart of the browser"); + if (browser.remoteType !== E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE) { + throw new Error( + "prepareLoadFromCache should only be called on a browser " + + "loaded in the privileged about content process." + ); + } + + if (withAutoShutdownWrite && AboutHomeStartupCache.initted) { + info("Simulating shutdown write"); + let timedOut = !(await AboutHomeStartupCache.onShutdown(expectTimeout)); + if (timedOut && !expectTimeout) { + Assert.ok( + false, + "AboutHomeStartupCache shutdown unexpectedly timed out." + ); + } else if (!timedOut && expectTimeout) { + Assert.ok(false, "AboutHomeStartupCache shutdown failed to time out."); + } + info("Shutdown write done"); + } else { + info("Intentionally skipping shutdown write"); + } + + AboutHomeStartupCache.uninit(); + + info("Waiting for AboutHomeStartupCacheChild to uninit"); + await SpecialPowers.spawn(browser, [], async () => { + let { AboutHomeStartupCacheChild } = ChromeUtils.import( + "resource:///modules/AboutNewTabService.jsm" + ); + AboutHomeStartupCacheChild.uninit(); + }); + info("AboutHomeStartupCacheChild uninitted"); + + AboutHomeStartupCache.init(); + + if (AboutHomeStartupCache.initted) { + let processManager = browser.messageManager.processMessageManager; + let pp = browser.browsingContext.currentWindowGlobal.domProcess; + let { childID } = pp; + AboutHomeStartupCache.onContentProcessCreated(childID, processManager, pp); + + info("Waiting for AboutHomeStartupCache cache entry"); + await AboutHomeStartupCache.ensureCacheEntry(); + info("Got AboutHomeStartupCache cache entry"); + + if (ensureCacheWinsRace) { + info("Ensuring cache bytes are available"); + await SpecialPowers.spawn(browser, [], async () => { + let { AboutHomeStartupCacheChild } = ChromeUtils.import( + "resource:///modules/AboutNewTabService.jsm" + ); + let pageStream = AboutHomeStartupCacheChild._pageInputStream; + let scriptStream = AboutHomeStartupCacheChild._scriptInputStream; + await ContentTaskUtils.waitForCondition(() => { + return pageStream.available() && scriptStream.available(); + }); + }); + } + } + + if (!skipAboutHomeLoad) { + info("Waiting for about:home to load"); + let loaded = BrowserTestUtils.browserLoaded(browser, false, "about:home"); + BrowserTestUtils.loadURIString(browser, "about:home"); + await loaded; + info("about:home loaded"); + } +} + +/** + * Writes a page string and a script string into the cache for + * the next about:home load. + * + * @param page (String) + * The HTML content to write into the cache. This cannot be the empty + * string. Note that this string should contain a node that has an + * id of "root", in order for the newtab scripts to attach correctly. + * Otherwise, an exception might get thrown which can cause shutdown + * leaks. + * @param script (String) + * The JS content to write into the cache that can be loaded via + * about:home?jscache. This cannot be the empty string. + * @returns Promise + * @resolves undefined + * When the page and script content has been successfully written. + */ +// eslint-disable-next-line no-unused-vars +async function injectIntoCache(page, script) { + if (!page || !script) { + throw new Error("Cannot injectIntoCache with falsey values"); + } + + if (!page.includes(`id="root"`)) { + throw new Error("Page markup must include a root node."); + } + + await AboutHomeStartupCache.ensureCacheEntry(); + + let pageInputStream = Cc[ + "@mozilla.org/io/string-input-stream;1" + ].createInstance(Ci.nsIStringInputStream); + + pageInputStream.setUTF8Data(page); + + let scriptInputStream = Cc[ + "@mozilla.org/io/string-input-stream;1" + ].createInstance(Ci.nsIStringInputStream); + + scriptInputStream.setUTF8Data(script); + + await AboutHomeStartupCache.populateCache(pageInputStream, scriptInputStream); +} + +/** + * Clears out any pre-existing about:home cache. + * @returns Promise + * @resolves undefined + * Resolves when the cache is cleared. + */ +// eslint-disable-next-line no-unused-vars +async function clearCache() { + info("Test is clearing the cache"); + AboutHomeStartupCache.clearCache(); + await AboutHomeStartupCache.ensureCacheEntry(); + info("Test has cleared the cache."); +} + +/** + * Checks that the browser.startup.abouthome_cache_result scalar was + * recorded at a particular value. + * + * @param cacheResultScalar (Number) + * One of the AboutHomeStartupCache.CACHE_RESULT_SCALARS values. + */ +function assertCacheResultScalar(cacheResultScalar) { + let parentScalars = Services.telemetry.getSnapshotForScalars("main").parent; + Assert.equal( + parentScalars["browser.startup.abouthome_cache_result"], + cacheResultScalar, + "Expected the right value set to browser.startup.abouthome_cache_result " + + "scalar." + ); +} + +/** + * Tests that the about:home document loaded in a passed <xul:browser> was + * one from the cache. + * + * We test for this by looking for some tell-tale signs of the cached + * document: + * + * 1. The about:home?jscache <script> element + * 2. The __FROM_STARTUP_CACHE__ expando on the window + * 3. The "activity-stream" class on the document body + * 4. The top sites section + * + * @param browser (<xul:browser>) + * A <xul:browser> with about:home running in it. + * @returns Promise + * @resolves undefined + * Resolves once the cache entry has been destroyed. + */ +// eslint-disable-next-line no-unused-vars +async function ensureCachedAboutHome(browser) { + await SpecialPowers.spawn(browser, [], async () => { + let scripts = Array.from(content.document.querySelectorAll("script")); + Assert.ok(!!scripts.length, "There should be page scripts."); + let [lastScript] = scripts.reverse(); + Assert.equal( + lastScript.src, + "about:home?jscache", + "Found about:home?jscache script tag, indicating the cached doc" + ); + Assert.ok( + Cu.waiveXrays(content).__FROM_STARTUP_CACHE__, + "Should have found window.__FROM_STARTUP_CACHE__" + ); + Assert.ok( + content.document.body.classList.contains("activity-stream"), + "Should have found activity-stream class on <body> element" + ); + Assert.ok( + content.document.querySelector("[data-section-id='topsites']"), + "Should have found the Discovery Stream top sites." + ); + }); + assertCacheResultScalar( + AboutHomeStartupCache.CACHE_RESULT_SCALARS.VALID_AND_USED + ); +} + +/** + * Tests that the about:home document loaded in a passed <xul:browser> was + * dynamically generated, and _not_ from the cache. + * + * We test for this by looking for some tell-tale signs of the dynamically + * generated document: + * + * 1. No <script> elements (the scripts are loaded from the ScriptPreloader + * via AboutNewTabChild when the "privileged about content process" is + * enabled) + * 2. No __FROM_STARTUP_CACHE__ expando on the window + * 3. The "activity-stream" class on the document body + * 4. The top sites section + * + * @param browser (<xul:browser>) + * A <xul:browser> with about:home running in it. + * @param expectedResultScalar (Number) + * One of the AboutHomeStartupCache.CACHE_RESULT_SCALARS values. It is + * asserted that the cache result Telemetry scalar will have been set + * to this value to explain why the dynamic about:home was used. + * @returns Promise + * @resolves undefined + * Resolves once the cache entry has been destroyed. + */ +// eslint-disable-next-line no-unused-vars +async function ensureDynamicAboutHome(browser, expectedResultScalar) { + await SpecialPowers.spawn(browser, [], async () => { + let scripts = Array.from(content.document.querySelectorAll("script")); + Assert.equal(scripts.length, 0, "There should be no page scripts."); + + Assert.equal( + Cu.waiveXrays(content).__FROM_STARTUP_CACHE__, + undefined, + "Should not have found window.__FROM_STARTUP_CACHE__" + ); + + Assert.ok( + content.document.body.classList.contains("activity-stream"), + "Should have found activity-stream class on <body> element" + ); + Assert.ok( + content.document.querySelector("[data-section-id='topsites']"), + "Should have found the Discovery Stream top sites." + ); + }); + + assertCacheResultScalar(expectedResultScalar); +} |