diff --git a/.gitattributes b/.gitattributes index d18643924..683555503 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,4 +5,4 @@ *.patch linguist-language=C++ *.d.ts linguist-language=TypeScript -src/zen/tests/* linguist-language=C++ +src/zen/tests/** linguist-language=C++ diff --git a/src/-prettierignore.patch b/src/-prettierignore.patch index ed444531d..3142c427a 100644 --- a/src/-prettierignore.patch +++ b/src/-prettierignore.patch @@ -1,8 +1,8 @@ diff --git a/.prettierignore b/.prettierignore -index 949896ff064ae0b54b6a657ea074bc88e12820f7..5249f420972667bece4d85fe8d35073afaebeb8a 100644 +index bd8ab46ff7175f5039b69b04c1afe53ab8efe746..7ae12a1ff3724e99aeec2e93518640a2fa9611e6 100644 --- a/.prettierignore +++ b/.prettierignore -@@ -1803,3 +1803,12 @@ tools/ts/test/baselines/ +@@ -1807,3 +1807,12 @@ tools/ts/test/baselines/ try_task_config.json xpcom/idl-parser/xpidl/fixtures/xpctest.d.json **/package-lock.json @@ -12,6 +12,6 @@ index 949896ff064ae0b54b6a657ea074bc88e12820f7..5249f420972667bece4d85fe8d35073a +*.min.js +*.min.mjs +*.inc -+*/mochitests/* ++**/mochitests/** +*.svg + diff --git a/src/browser/components/urlbar/UrlbarPrefs-sys-mjs.patch b/src/browser/components/urlbar/UrlbarPrefs-sys-mjs.patch index 9b41ab880..9644cca42 100644 --- a/src/browser/components/urlbar/UrlbarPrefs-sys-mjs.patch +++ b/src/browser/components/urlbar/UrlbarPrefs-sys-mjs.patch @@ -1,8 +1,18 @@ diff --git a/browser/components/urlbar/UrlbarPrefs.sys.mjs b/browser/components/urlbar/UrlbarPrefs.sys.mjs -index 2d21248256c6c2bfb8dac958133c10e3251ef564..f788bd10ec2c08e4b27b77cd3bb0489fb04e8b7a 100644 +index 89f0d44d462de3d7d0f581d6371606f475443822..0d45ce5f6455e9c7276c54f504906bade5b3d286 100644 --- a/browser/components/urlbar/UrlbarPrefs.sys.mjs +++ b/browser/components/urlbar/UrlbarPrefs.sys.mjs -@@ -462,6 +462,7 @@ const PREF_URLBAR_DEFAULTS = /** @type {PreferenceDefinition[]} */ ([ +@@ -163,6 +163,9 @@ const PREF_URLBAR_DEFAULTS = /** @type {PreferenceDefinition[]} */ ([ + // Feature gate pref for flight status suggestions in the urlbar. + ["flightStatus.featureGate", false], + ++ // Whether to close the urlbar when blurring the window while the urlbar is focused. ++ ["closeOnWindowBlur", true], ++ + // The minimum prefix length of a flight status keyword the user must type to + // trigger the suggestion. 0 means the min length should be taken from Nimbus + // or remote settings. +@@ -482,6 +485,7 @@ const PREF_URLBAR_DEFAULTS = /** @type {PreferenceDefinition[]} */ ([ ["shortcuts.tabs", true], ["shortcuts.history", true], ["shortcuts.actions", true], @@ -10,7 +20,7 @@ index 2d21248256c6c2bfb8dac958133c10e3251ef564..f788bd10ec2c08e4b27b77cd3bb0489f // Boolean to determine if the providers defined in `exposureResults` // should be displayed in search results. This can be set by a -@@ -799,6 +800,8 @@ function makeDefaultResultGroups({ showSearchSuggestionsFirst }) { +@@ -840,6 +844,8 @@ function makeDefaultResultGroups({ */ let rootGroup = { children: [ diff --git a/src/browser/components/urlbar/content/UrlbarInput-mjs.patch b/src/browser/components/urlbar/content/UrlbarInput-mjs.patch index 354834a4a..b6b5daebe 100644 --- a/src/browser/components/urlbar/content/UrlbarInput-mjs.patch +++ b/src/browser/components/urlbar/content/UrlbarInput-mjs.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/urlbar/content/UrlbarInput.mjs b/browser/components/urlbar/content/UrlbarInput.mjs -index e7e9495e56076cd112beafb8297dece4ae4cea58..330104d245ddd0956b95377082a6182579cbaf4d 100644 +index e7e9495e56076cd112beafb8297dece4ae4cea58..95e4333866a5f0d41fbdbe23910c396406a247e9 100644 --- a/browser/components/urlbar/content/UrlbarInput.mjs +++ b/browser/components/urlbar/content/UrlbarInput.mjs @@ -101,6 +101,13 @@ const lazy = XPCOMUtils.declareLazy({ @@ -280,7 +280,20 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..330104d245ddd0956b95377082a61825 l10nId, l10nId == "urlbar-placeholder-with-name" ? { name: engineName } -@@ -5424,6 +5540,11 @@ ${ +@@ -5347,6 +5463,12 @@ ${ + } + + lazy.logger.debug("Blur Event"); ++ if ( ++ this.document.commandDispatcher.focusedElement == this.inputField && ++ !lazy.UrlbarPrefs.get("closeOnWindowBlur") ++ ) { ++ return; ++ } + // We cannot count every blur events after a missed engagement as abandoment + // because the user may have clicked on some view element that executes + // a command causing a focus change. For example opening preferences from +@@ -5424,6 +5546,11 @@ ${ } _on_click(event) { @@ -292,7 +305,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..330104d245ddd0956b95377082a61825 switch (event.target) { case this.inputField: case this._inputContainer: -@@ -5513,10 +5634,11 @@ ${ +@@ -5513,10 +5640,11 @@ ${ } if (untrim) { this.setValue(this._untrimmedValue); @@ -305,7 +318,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..330104d245ddd0956b95377082a61825 this.view.autoOpen({ event }); } else { if (this._untrimOnFocusAfterKeydown) { -@@ -5556,9 +5678,16 @@ ${ +@@ -5556,9 +5684,16 @@ ${ } _on_mousedown(event) { @@ -323,7 +336,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..330104d245ddd0956b95377082a61825 if ( event.composedTarget != this.inputField && event.composedTarget != this._inputContainer -@@ -5568,6 +5697,10 @@ ${ +@@ -5568,6 +5703,10 @@ ${ this.focusedViaMousedown = !this.focused; this.#preventClickSelectsAll = this.focused; @@ -334,7 +347,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..330104d245ddd0956b95377082a61825 // Keep the focus status, since the attribute may be changed // upon calling this.focus(). -@@ -5605,7 +5738,7 @@ ${ +@@ -5605,7 +5744,7 @@ ${ // view open on tab switch, and the TabSelect event arrived earlier. // Also ignore mousedown on the urlbarView context menu: opening/closing the // view is already handled by the result opening flow. @@ -343,7 +356,7 @@ index e7e9495e56076cd112beafb8297dece4ae4cea58..330104d245ddd0956b95377082a61825 break; } -@@ -5894,7 +6027,7 @@ ${ +@@ -5894,7 +6033,7 @@ ${ // When we are in actions search mode we can show more results so // increase the limit. let maxResults = diff --git a/src/browser/components/urlbar/content/UrlbarView-mjs.patch b/src/browser/components/urlbar/content/UrlbarView-mjs.patch index 21d69f2b0..7225a807e 100644 --- a/src/browser/components/urlbar/content/UrlbarView-mjs.patch +++ b/src/browser/components/urlbar/content/UrlbarView-mjs.patch @@ -1,8 +1,8 @@ diff --git a/browser/components/urlbar/content/UrlbarView.mjs b/browser/components/urlbar/content/UrlbarView.mjs -index 8afb2bf4a757b3ef7902f5a6e6eed6b70ca2845e..440dea1e66540ca1998cb7464b6695ac823fedcb 100644 +index e465806c4ae4fc372d9aa2b71aa7f0e74c65d026..c0b3781901298bedee2736819a93edbf545507a7 100644 --- a/browser/components/urlbar/content/UrlbarView.mjs +++ b/browser/components/urlbar/content/UrlbarView.mjs -@@ -674,7 +674,7 @@ export class UrlbarView { +@@ -803,7 +803,7 @@ export class UrlbarView { !this.input.value || this.input.getAttribute("pageproxystate") == "valid" ) { @@ -11,7 +11,7 @@ index 8afb2bf4a757b3ef7902f5a6e6eed6b70ca2845e..440dea1e66540ca1998cb7464b6695ac // Try to reuse the cached top-sites context. If it's not cached, then // there will be a gap of time between when the input is focused and // when the view opens that can be perceived as flicker. -@@ -812,10 +812,6 @@ export class UrlbarView { +@@ -941,10 +941,6 @@ export class UrlbarView { } // If search mode isn't active, close the view. @@ -22,7 +22,7 @@ index 8afb2bf4a757b3ef7902f5a6e6eed6b70ca2845e..440dea1e66540ca1998cb7464b6695ac // Search mode is active. If the one-offs should be shown, make sure they // are enabled and show the view. -@@ -3031,6 +3027,8 @@ export class UrlbarView { +@@ -3203,6 +3199,8 @@ export class UrlbarView { if (row?.hasAttribute("row-selectable")) { row?.toggleAttribute("selected", true); } @@ -31,7 +31,7 @@ index 8afb2bf4a757b3ef7902f5a6e6eed6b70ca2845e..440dea1e66540ca1998cb7464b6695ac if (element != row) { row?.toggleAttribute("descendant-selected", true); } -@@ -3544,7 +3542,7 @@ export class UrlbarView { +@@ -3715,7 +3713,7 @@ export class UrlbarView { } #enableOrDisableRowWrap() { @@ -40,3 +40,16 @@ index 8afb2bf4a757b3ef7902f5a6e6eed6b70ca2845e..440dea1e66540ca1998cb7464b6695ac this.#rows.toggleAttribute("wrap", wrap); this.oneOffSearchButtons?.container.toggleAttribute("wrap", wrap); } +@@ -4144,6 +4142,12 @@ export class UrlbarView { + } + + on_blur() { ++ if ( ++ this.document.commandDispatcher.focusedElement == this.input.inputField && ++ !lazy.UrlbarPrefs.get("closeOnWindowBlur") ++ ) { ++ return; ++ } + // If the view is open without the input being focused, it will not close + // automatically when the window loses focus. We might be in this state + // after a Search Tip is shown on an engine homepage. diff --git a/src/external-patches/firefox/add_urlbar_closeonwindowblur_preference.patch b/src/external-patches/firefox/add_urlbar_closeonwindowblur_preference.patch deleted file mode 100644 index 0f29eec74..000000000 --- a/src/external-patches/firefox/add_urlbar_closeonwindowblur_preference.patch +++ /dev/null @@ -1,76 +0,0 @@ -diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js ---- a/browser/app/profile/firefox.js -+++ b/browser/app/profile/firefox.js -@@ -411,10 +411,13 @@ - pref("browser.urlbar.maxRichResults", 10); - - // The maximum number of historical search results to show. - pref("browser.urlbar.maxHistoricalSearchSuggestions", 2); - -+// Whether to close the urlbar when blurring the window while the urlbar is focused. -+pref("browser.urlbar.closeOnWindowBlur", true); -+ - // The default behavior for the urlbar can be configured to use any combination - // of the match filters with each additional filter adding more results (union). - pref("browser.urlbar.suggest.bookmark", true); - pref("browser.urlbar.suggest.clipboard", true); - pref("browser.urlbar.suggest.history", true); -diff --git a/browser/components/urlbar/UrlbarPrefs.sys.mjs b/browser/components/urlbar/UrlbarPrefs.sys.mjs ---- a/browser/components/urlbar/UrlbarPrefs.sys.mjs -+++ b/browser/components/urlbar/UrlbarPrefs.sys.mjs -@@ -152,10 +152,13 @@ - ["filter.javascript", true], - - // Feature gate pref for flight status suggestions in the urlbar. - ["flightStatus.featureGate", false], - -+ // Whether to close the urlbar when blurring the window while the urlbar is focused. -+ ["closeOnWindowBlur", true], -+ - // The minimum prefix length of a flight status keyword the user must type to - // trigger the suggestion. 0 means the min length should be taken from Nimbus - // or remote settings. - ["flightStatus.minKeywordLength", 0], - -diff --git a/browser/components/urlbar/content/UrlbarView.mjs b/browser/components/urlbar/content/UrlbarView.mjs ---- a/browser/components/urlbar/content/UrlbarView.mjs -+++ b/browser/components/urlbar/content/UrlbarView.mjs -@@ -3909,10 +3909,16 @@ - } - } - } - - on_blur() { -+ if ( -+ this.document.commandDispatcher.focusedElement == this.input.inputField && -+ !lazy.UrlbarPrefs.get("closeOnWindowBlur") -+ ) { -+ return; -+ } - // If the view is open without the input being focused, it will not close - // automatically when the window loses focus. We might be in this state - // after a Search Tip is shown on an engine homepage. - if (!lazy.UrlbarPrefs.get("ui.popup.disable_autohide")) { - this.close(); -diff --git a/browser/components/urlbar/content/UrlbarInput.mjs b/browser/components/urlbar/content/UrlbarInput.mjs ---- a/browser/components/urlbar/content/UrlbarInput.mjs -+++ b/browser/components/urlbar/content/UrlbarInput.mjs -@@ -5460,11 +5460,17 @@ - _on_blur(event) { - if (this.view.resultMenu.hasAttribute("open")) { - return; - } - - lazy.logger.debug("Blur Event"); -+ if ( -+ this.document.commandDispatcher.focusedElement == this.inputField && -+ !lazy.UrlbarPrefs.get("closeOnWindowBlur") -+ ) { -+ return; -+ } - // We cannot count every blur events after a missed engagement as abandoment - // because the user may have clicked on some view element that executes - // a command causing a focus change. For example opening preferences from - // the oneoff settings button. - // For now we detect that case by discarding the event on command, but we - // may want to figure out a more robust way to detect abandonment. \ No newline at end of file diff --git a/src/external-patches/firefox/bug_2011236.patch b/src/external-patches/firefox/bug_2011236.patch new file mode 100644 index 000000000..73b1221f3 --- /dev/null +++ b/src/external-patches/firefox/bug_2011236.patch @@ -0,0 +1,166 @@ +diff --git a/browser/components/sessionstore/test/browser.toml b/browser/components/sessionstore/test/browser.toml +--- a/browser/components/sessionstore/test/browser.toml ++++ b/browser/components/sessionstore/test/browser.toml +@@ -14,10 +14,12 @@ + "browser_frame_history_c.html", + "browser_frame_history_c1.html", + "browser_frame_history_c2.html", + "browser_formdata_format_sample.html", + "browser_sessionHistory_slow.sjs", ++ "browser_policy_container_sample.html", ++ "browser_policy_container_sample_frame.html", + "browser_scrollPositions_sample.html", + "browser_scrollPositions_sample2.html", + "browser_scrollPositions_sample_frameset.html", + "browser_scrollPositions_readerModeArticle.html", + "browser_sessionStorage.html", +@@ -226,10 +228,12 @@ + ["browser_pinned_tabs.js"] + skip-if = [ + "ccov", # Bug 1625525 + ] + ++["browser_policy_container_not_stored.js"] ++ + ["browser_privatetabs.js"] + + ["browser_purge_domaindata.js"] + + ["browser_purge_shistory.js"] +diff --git a/browser/components/sessionstore/test/browser_policy_container_not_stored.js b/browser/components/sessionstore/test/browser_policy_container_not_stored.js +new file mode 100644 +--- /dev/null ++++ b/browser/components/sessionstore/test/browser_policy_container_not_stored.js +@@ -0,0 +1,51 @@ ++/* This Source Code Form is subject to the terms of the Mozilla Public ++ * License, v. 2.0. If a copy of the MPL was not distributed with this ++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ ++ ++"use strict"; ++ ++/** ++ * A session history entry only needs to retain the policy container for loads ++ * that inherit their policies (about:blank, about:srcdoc, blob:, data:, ...). ++ * Anything fetched over the network gets its policies from the response again, ++ * so storing them would only bloat the session store (bug 2011236). ++ */ ++ ++const TEST_ROOT = getRootDirectory(gTestPath).replace( ++ "chrome://mochitests/content", ++ "https://example.com" ++); ++const PARENT_URL = TEST_ROOT + "browser_policy_container_sample.html"; ++const FRAME_URL = TEST_ROOT + "browser_policy_container_sample_frame.html"; ++ ++add_task(async function test_policy_container_only_for_inheriting_loads() { ++ const tab = BrowserTestUtils.addTab(gBrowser, PARENT_URL); ++ gBrowser.selectedTab = tab; ++ await promiseBrowserLoaded(tab.linkedBrowser, true, PARENT_URL); ++ await TabStateFlusher.flush(tab.linkedBrowser); ++ ++ const state = JSON.parse(ss.getTabState(tab)); ++ const topEntry = state.entries.at(-1); ++ is(topEntry.url, PARENT_URL, "collected the parent entry"); ++ ++ const children = topEntry.children ?? []; ++ const networkFrame = children.find(child => child.url === FRAME_URL); ++ const blankFrame = children.find(child => child.url === "about:blank"); ++ ++ ok(networkFrame, "collected the network-scheme subframe entry"); ++ ok(blankFrame, "collected the about:blank subframe entry"); ++ ++ // The parent has a CSP, so both subframes inherit a policy container onto ++ // their load state. Only the one that cannot recover it from a response ++ // should keep it in session history. ++ ok( ++ !("policyContainer" in networkFrame), ++ "https subframe entry does not store a policyContainer" ++ ); ++ ok( ++ "policyContainer" in blankFrame, ++ "about:blank subframe entry still stores its inherited policyContainer" ++ ); ++ ++ BrowserTestUtils.removeTab(tab); ++}); +diff --git a/browser/components/sessionstore/test/browser_policy_container_sample.html b/browser/components/sessionstore/test/browser_policy_container_sample.html +new file mode 100644 +--- /dev/null ++++ b/browser/components/sessionstore/test/browser_policy_container_sample.html +@@ -0,0 +1,12 @@ ++ ++ ++
++ ++ +++ Radio 1 + Radio 2 + Radio 3 + +
pending
diff --git a/src/zen/tests/mochitests/sessionstore/browser_464620_a.js b/src/zen/tests/mochitests/sessionstore/browser_464620_a.js new file mode 100644 index 000000000..2ab0ca261 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_464620_a.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + /** Test for Bug 464620 (injection on input) */ + + waitForExplicitFinish(); + + let testURL = + "http://mochi.test:8888/browser/" + + "browser/components/sessionstore/test/browser_464620_a.html"; + + var frameCount = 0; + let tab = BrowserTestUtils.addTab(gBrowser, testURL); + tab.linkedBrowser.addEventListener( + "load", + function loadListener(aEvent) { + // wait for all frames to load completely + if (frameCount++ < 4) { + return; + } + this.removeEventListener("load", loadListener, true); + + executeSoon(function () { + frameCount = 0; + let tab2 = gBrowser.duplicateTab(tab); + tab2.linkedBrowser.addEventListener( + "464620_a", + function listener() { + tab2.linkedBrowser.removeEventListener("464620_a", listener, true); + is(aEvent.data, "done", "XSS injection was attempted"); + + // let form restoration complete and take into account the + // setTimeout(..., 0) in sss_restoreDocument_proxy + executeSoon(function () { + setTimeout(function () { + let win = tab2.linkedBrowser.contentWindow; + isnot( + win.frames[0].document.location, + testURL, + "cross domain document was loaded" + ); + ok( + !/XXX/.test(win.frames[0].document.body.innerHTML), + "no content was injected" + ); + + // clean up + gBrowser.removeTab(tab2); + gBrowser.removeTab(tab); + + finish(); + }, 0); + }); + }, + true, + true + ); + }); + }, + true + ); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_464620_b.html b/src/zen/tests/mochitests/sessionstore/browser_464620_b.html new file mode 100644 index 000000000..e7fde55c2 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_464620_b.html @@ -0,0 +1,57 @@ + + +pending
diff --git a/src/zen/tests/mochitests/sessionstore/browser_464620_b.js b/src/zen/tests/mochitests/sessionstore/browser_464620_b.js new file mode 100644 index 000000000..a8aef26c5 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_464620_b.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + /** Test for Bug 464620 (injection on DOM node insertion) */ + + waitForExplicitFinish(); + + let testURL = + "http://mochi.test:8888/browser/" + + "browser/components/sessionstore/test/browser_464620_b.html"; + + var frameCount = 0; + let tab = BrowserTestUtils.addTab(gBrowser, testURL); + tab.linkedBrowser.addEventListener( + "load", + function loadListener(aEvent) { + // wait for all frames to load completely + if (frameCount++ < 6) { + return; + } + this.removeEventListener("load", loadListener, true); + + executeSoon(function () { + frameCount = 0; + let tab2 = gBrowser.duplicateTab(tab); + tab2.linkedBrowser.addEventListener( + "464620_b", + function listener() { + tab2.linkedBrowser.removeEventListener("464620_b", listener, true); + is(aEvent.data, "done", "XSS injection was attempted"); + + // let form restoration complete and take into account the + // setTimeout(..., 0) in sss_restoreDocument_proxy + executeSoon(function () { + setTimeout(function () { + let win = tab2.linkedBrowser.contentWindow; + isnot( + win.frames[1].document.location, + testURL, + "cross domain document was loaded" + ); + ok( + !/XXX/.test(win.frames[1].document.body.innerHTML), + "no content was injected" + ); + + // clean up + gBrowser.removeTab(tab2); + gBrowser.removeTab(tab); + + finish(); + }, 0); + }); + }, + true, + true + ); + }); + }, + true + ); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_464620_xd.html b/src/zen/tests/mochitests/sessionstore/browser_464620_xd.html new file mode 100644 index 000000000..9ec51c4c7 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_464620_xd.html @@ -0,0 +1,5 @@ +top
"; + + // preferences that we use + const PREF_INTERVAL = "browser.sessionstore.interval"; + + // make sure sessionstore.js is saved ASAP on all events + Services.prefs.setIntPref(PREF_INTERVAL, 0); + + // get the initial sessionstore.js mtime (-1 if it doesn't exist yet) + let mtime0 = getSessionstorejsModificationTime(); + + // create and select a first tab + let tab = BrowserTestUtils.addTab(gBrowser, TEST_URL); + promiseBrowserLoaded(tab.linkedBrowser).then(() => { + // step1: the above has triggered some saveStateDelayed(), sleep until + // it's done, and get the initial sessionstore.js mtime + setTimeout(function step1() { + let mtime1 = getSessionstorejsModificationTime(); + isnot(mtime1, mtime0, "initial sessionstore.js update"); + + // step2: test sessionstore.js is not updated on tab selection + // or content scrolling + gBrowser.selectedTab = tab; + tab.linkedBrowser.contentWindow.scrollTo(1100, 1200); + setTimeout(function step2() { + let mtime2 = getSessionstorejsModificationTime(); + is( + mtime2, + mtime1, + "tab selection and scrolling: sessionstore.js not updated" + ); + + // ok, done, cleanup and finish + if (Services.prefs.prefHasUserValue(PREF_INTERVAL)) { + Services.prefs.clearUserPref(PREF_INTERVAL); + } + gBrowser.removeTab(tab); + finish(); + }, 3500); // end of sleep after tab selection and scrolling + }, 3500); // end of sleep after initial saveStateDelayed() + }); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_514751.js b/src/zen/tests/mochitests/sessionstore/browser_514751.js new file mode 100644 index 000000000..bcb944c3e --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_514751.js @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function test_malformedURI() { + /** Test for Bug 514751 (Wallpaper) */ + + let state = { + windows: [ + { + tabs: [ + { + entries: [ + { + url: "about:mozilla", + triggeringPrincipal_base64, + title: "Mozilla", + }, + {}, + ], + }, + ], + }, + ], + }; + + var theWin = openDialog(location, "", "chrome,all,dialog=no"); + await promiseWindowLoaded(theWin); + + var gotError = false; + try { + await setWindowState(theWin, state, true); + } catch (e) { + if (/NS_ERROR_MALFORMED_URI/.test(e)) { + gotError = true; + } + } + + ok(!gotError, "Didn't get a malformed URI error."); + await BrowserTestUtils.closeWindow(theWin); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_522375.js b/src/zen/tests/mochitests/sessionstore/browser_522375.js new file mode 100644 index 000000000..07d04f933 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_522375.js @@ -0,0 +1,25 @@ +function test() { + var startup_info = Services.startup.getStartupInfo(); + // No .process info on mac + + Assert.lessOrEqual( + startup_info.process, + startup_info.main, + "process created before main is run " + uneval(startup_info) + ); + + // on linux firstPaint can happen after everything is loaded (especially with remote X) + if (startup_info.firstPaint) { + Assert.lessOrEqual( + startup_info.main, + startup_info.firstPaint, + "main ran before first paint " + uneval(startup_info) + ); + } + + Assert.less( + startup_info.main, + startup_info.sessionRestored, + "Session restored after main " + uneval(startup_info) + ); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_522545.js b/src/zen/tests/mochitests/sessionstore/browser_522545.js new file mode 100644 index 000000000..505cb6876 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_522545.js @@ -0,0 +1,443 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + /** Test for Bug 522545 */ + + waitForExplicitFinish(); + requestLongerTimeout(4); + + // This tests the following use case: + // User opens a new tab which gets focus. The user types something into the + // address bar, then crashes or quits. + function test_newTabFocused() { + let state = { + windows: [ + { + tabs: [ + { entries: [{ url: "about:mozilla", triggeringPrincipal_base64 }] }, + { entries: [], userTypedValue: "example.com", userTypedClear: 0 }, + ], + selected: 2, + }, + ], + }; + + waitForBrowserState(state, function () { + let browser = gBrowser.selectedBrowser; + is( + browser.currentURI.spec, + "about:blank", + "No history entries still sets currentURI to about:blank" + ); + is( + browser.userTypedValue, + "example.com", + "userTypedValue was correctly restored" + ); + ok( + !browser.didStartLoadSinceLastUserTyping(), + "We still know that no load is ongoing" + ); + is( + gURLBar.value, + "example.com", + "Address bar's value correctly restored" + ); + + // Change tabs to make sure address bar value gets updated. If tab is + // lazy, wait for SSTabRestored to ensure address bar has time to update. + let tabToSelect = gBrowser.tabContainer.getItemAtIndex(0); + if (tabToSelect.linkedBrowser.isConnected) { + gBrowser.selectedTab = tabToSelect; + is( + gURLBar.value, + "about:mozilla", + "Address bar's value correctly updated" + ); + runNextTest(); + } else { + gBrowser.tabContainer.addEventListener( + "SSTabRestored", + function SSTabRestored(event) { + if (event.target == tabToSelect) { + gBrowser.tabContainer.removeEventListener( + "SSTabRestored", + SSTabRestored, + true + ); + is( + gURLBar.value, + "about:mozilla", + "Address bar's value correctly updated" + ); + runNextTest(); + } + }, + true + ); + gBrowser.selectedTab = tabToSelect; + } + }); + } + + // This tests the following use case: + // User opens a new tab which gets focus. The user types something into the + // address bar, switches back to the first tab, then crashes or quits. + function test_newTabNotFocused() { + let state = { + windows: [ + { + tabs: [ + { entries: [{ url: "about:mozilla", triggeringPrincipal_base64 }] }, + { entries: [], userTypedValue: "example.org", userTypedClear: 0 }, + ], + selected: 1, + }, + ], + }; + + waitForBrowserState(state, function () { + let browser = gBrowser.getBrowserAtIndex(1); + is( + browser.currentURI.spec, + "about:blank", + "No history entries still sets currentURI to about:blank" + ); + is( + browser.userTypedValue, + "example.org", + "userTypedValue was correctly restored" + ); + // didStartLoadSinceLastUserTyping does not exist on lazy tabs. + if (browser.didStartLoadSinceLastUserTyping) { + ok( + !browser.didStartLoadSinceLastUserTyping(), + "We still know that no load is ongoing" + ); + } + is( + gURLBar.value, + "about:mozilla", + "Address bar's value correctly restored" + ); + + // Change tabs to make sure address bar value gets updated. If tab is + // lazy, wait for SSTabRestored to ensure address bar has time to update. + let tabToSelect = gBrowser.tabContainer.getItemAtIndex(1); + if (tabToSelect.linkedBrowser.isConnected) { + gBrowser.selectedTab = tabToSelect; + is( + gURLBar.value, + "example.org", + "Address bar's value correctly updated" + ); + runNextTest(); + } else { + gBrowser.tabContainer.addEventListener( + "SSTabRestored", + function SSTabRestored(event) { + if (event.target == tabToSelect) { + gBrowser.tabContainer.removeEventListener( + "SSTabRestored", + SSTabRestored, + true + ); + is( + gURLBar.value, + "example.org", + "Address bar's value correctly updated" + ); + runNextTest(); + } + }, + true + ); + gBrowser.selectedTab = tabToSelect; + } + }); + } + + // This tests the following use case: + // User is in a tab with session history, then types something in the + // address bar, then crashes or quits. + function test_existingSHEnd_noClear() { + let state = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "about:mozilla", triggeringPrincipal_base64 }, + { url: "about:config", triggeringPrincipal_base64 }, + ], + index: 2, + userTypedValue: "example.com", + userTypedClear: 0, + }, + ], + }, + ], + }; + + waitForBrowserState(state, function () { + let browser = gBrowser.selectedBrowser; + is( + browser.currentURI.spec, + "about:config", + "browser.currentURI set to current entry in SH" + ); + is( + browser.userTypedValue, + "example.com", + "userTypedValue was correctly restored" + ); + ok( + !browser.didStartLoadSinceLastUserTyping(), + "We still know that no load is ongoing" + ); + is( + gURLBar.value, + "example.com", + "Address bar's value correctly restored to userTypedValue" + ); + runNextTest(); + }); + } + + // This tests the following use case: + // User is in a tab with session history, presses back at some point, then + // types something in the address bar, then crashes or quits. + function test_existingSHMiddle_noClear() { + let state = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "about:mozilla", triggeringPrincipal_base64 }, + { url: "about:config", triggeringPrincipal_base64 }, + ], + index: 1, + userTypedValue: "example.org", + userTypedClear: 0, + }, + ], + }, + ], + }; + + waitForBrowserState(state, function () { + let browser = gBrowser.selectedBrowser; + is( + browser.currentURI.spec, + "about:mozilla", + "browser.currentURI set to current entry in SH" + ); + is( + browser.userTypedValue, + "example.org", + "userTypedValue was correctly restored" + ); + ok( + !browser.didStartLoadSinceLastUserTyping(), + "We still know that no load is ongoing" + ); + is( + gURLBar.value, + "example.org", + "Address bar's value correctly restored to userTypedValue" + ); + runNextTest(); + }); + } + + // This test simulates lots of tabs opening at once and then quitting/crashing. + function test_getBrowserState_lotsOfTabsOpening() { + gBrowser.stop(); + + let uris = []; + for (let i = 0; i < 25; i++) { + uris.push("http://example.com/" + i); + } + + // We're waiting for the first location change, which should indicate + // one of the tabs has loaded and the others haven't. So one should + // be in a non-userTypedValue case, while others should still have + // userTypedValue and userTypedClear set. + gBrowser.addTabsProgressListener({ + onLocationChange(aBrowser) { + if (uris.indexOf(aBrowser.currentURI.spec) > -1) { + gBrowser.removeTabsProgressListener(this); + firstLocationChange(); + } + }, + }); + + function firstLocationChange() { + let state = JSON.parse(ss.getBrowserState()); + let hasUTV = state.windows[0].tabs.some(function (aTab) { + return ( + aTab.userTypedValue && aTab.userTypedClear && !aTab.entries.length + ); + }); + + ok( + hasUTV, + "At least one tab has a userTypedValue with userTypedClear with no loaded URL" + ); + + BrowserTestUtils.waitForMessage( + gBrowser.selectedBrowser.messageManager, + "SessionStore:update" + ).then(firstLoad); + } + + function firstLoad() { + let state = JSON.parse(ss.getTabState(gBrowser.selectedTab)); + let hasSH = !("userTypedValue" in state) && state.entries[0].url; + ok(hasSH, "The selected tab has its entry in SH"); + + runNextTest(); + } + + gBrowser.loadTabs(uris, { + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }); + } + + // This simulates setting a userTypedValue and ensures that just typing in the + // URL bar doesn't set userTypedClear as well. + function test_getBrowserState_userTypedValue() { + let state = { + windows: [ + { + tabs: [{ entries: [] }], + }, + ], + }; + + waitForBrowserState(state, function () { + let browser = gBrowser.selectedBrowser; + // Make sure this tab isn't loading and state is clear before we test. + is(browser.userTypedValue, null, "userTypedValue is empty to start"); + ok( + !browser.didStartLoadSinceLastUserTyping(), + "Initially, no load should be ongoing" + ); + + let inputText = "example.org"; + gURLBar.focus(); + gURLBar.value = inputText.slice(0, -1); + EventUtils.sendString(inputText.slice(-1)); + + executeSoon(function () { + is( + browser.userTypedValue, + "example.org", + "userTypedValue was set when changing URLBar value" + ); + ok( + !browser.didStartLoadSinceLastUserTyping(), + "No load started since changing URLBar value" + ); + + // Now make sure ss gets these values too + let newState = JSON.parse(ss.getBrowserState()); + is( + newState.windows[0].tabs[0].userTypedValue, + "example.org", + "sessionstore got correct userTypedValue" + ); + is( + newState.windows[0].tabs[0].userTypedClear, + 0, + "sessionstore got correct userTypedClear" + ); + runNextTest(); + }); + }); + } + + // test_getBrowserState_lotsOfTabsOpening tested userTypedClear in a few cases, + // but not necessarily any that had legitimate URIs in the state of loading + // (eg, "http://example.com"), so this test will cover that case. + function test_userTypedClearLoadURI() { + let state = { + windows: [ + { + tabs: [ + { + entries: [], + userTypedValue: "http://example.com", + userTypedClear: 2, + }, + ], + }, + ], + }; + + waitForBrowserState(state, function () { + let browser = gBrowser.selectedBrowser; + is( + browser.currentURI.spec, + "http://example.com/", + "userTypedClear=2 caused userTypedValue to be loaded" + ); + is( + browser.userTypedValue, + null, + "userTypedValue was null after loading a URI" + ); + ok( + !browser.didStartLoadSinceLastUserTyping(), + "We should have reset the load state when the tab loaded" + ); + is( + gURLBar.value, + BrowserUIUtils.trimURL("http://example.com/"), + "Address bar's value set after loading URI" + ); + runNextTest(); + }); + } + + let tests = [ + test_newTabFocused, + test_newTabNotFocused, + test_existingSHEnd_noClear, + test_existingSHMiddle_noClear, + test_getBrowserState_lotsOfTabsOpening, + test_getBrowserState_userTypedValue, + test_userTypedClearLoadURI, + ]; + let originalState = JSON.parse(ss.getBrowserState()); + let state = { + windows: [ + { + tabs: [ + { entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }, + ], + }, + ], + }; + function runNextTest() { + if (tests.length) { + waitForBrowserState(state, function () { + gBrowser.selectedBrowser.userTypedValue = null; + gURLBar.setURI(); + tests.shift()(); + }); + } else { + waitForBrowserState(originalState, function () { + gBrowser.selectedBrowser.userTypedValue = null; + gURLBar.setURI(); + finish(); + }); + } + } + + // Run the tests! + runNextTest(); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_524745.js b/src/zen/tests/mochitests/sessionstore/browser_524745.js new file mode 100644 index 000000000..c6dbda124 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_524745.js @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + /** Test for Bug 524745 */ + + let uniqKey = "bug524745"; + let uniqVal = Date.now().toString(); + + waitForExplicitFinish(); + + whenNewWindowLoaded({ private: false }, function (window_B) { + waitForFocus(function () { + // Add identifying information to window_B + ss.setCustomWindowValue(window_B, uniqKey, uniqVal); + let state = JSON.parse(ss.getBrowserState()); + let selectedWindow = state.windows[state.selectedWindow - 1]; + is( + selectedWindow.extData && selectedWindow.extData[uniqKey], + uniqVal, + "selectedWindow is window_B" + ); + + // Now minimize window_B. The selected window shouldn't have the secret data + window_B.minimize(); + waitForFocus(async function () { + state = JSON.parse(ss.getBrowserState()); + selectedWindow = state.windows[state.selectedWindow - 1]; + ok( + !selectedWindow.extData || !selectedWindow.extData[uniqKey], + "selectedWindow is not window_B after minimizing it" + ); + + // Now minimize the last open window (assumes no other tests left windows open) + let promiseSizeModeChange = BrowserTestUtils.waitForEvent( + window, + "sizemodechange" + ); + window.minimize(); + await promiseSizeModeChange; + state = JSON.parse(ss.getBrowserState()); + is( + state.selectedWindow, + 0, + "selectedWindow should be 0 when all windows are minimized" + ); + + // Cleanup + window.restore(); + BrowserTestUtils.closeWindow(window_B).then(finish); + }); + }, window_B); + }); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_526613.js b/src/zen/tests/mochitests/sessionstore/browser_526613.js new file mode 100644 index 000000000..1b2ef48bb --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_526613.js @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + /** Test for Bug 526613 */ + + // test setup + waitForExplicitFinish(); + + function browserWindowsCount(expected) { + let count = 0; + for (let win of Services.wm.getEnumerator("navigator:browser")) { + if (!win.closed) { + ++count; + } + } + is( + count, + expected, + "number of open browser windows according to nsIWindowMediator" + ); + let state = ss.getBrowserState(); + info(state); + is( + JSON.parse(state).windows.length, + expected, + "number of open browser windows according to getBrowserState" + ); + } + + browserWindowsCount(1); + + // backup old state + let oldState = ss.getBrowserState(); + // create a new state for testing + let testState = { + windows: [ + { tabs: [{ entries: [{ url: "http://example.com/" }] }], selected: 1 }, + { tabs: [{ entries: [{ url: "about:mozilla" }] }], selected: 1 }, + ], + // make sure the first window is focused, otherwise when restoring the + // old state, the first window is closed and the test harness gets unloaded + selectedWindow: 1, + }; + + let pass = 1; + function observer(aSubject, aTopic) { + is( + aTopic, + "sessionstore-browser-state-restored", + "The sessionstore-browser-state-restored notification was observed" + ); + + if (pass++ == 1) { + browserWindowsCount(2); + + // let the first window be focused (see above) + function pollMostRecentWindow() { + if (Services.wm.getMostRecentWindow("navigator:browser") == window) { + ss.setBrowserState(oldState); + } else { + info("waiting for the current window to become active"); + setTimeout(pollMostRecentWindow, 0); + window.focus(); // XXX Why is this needed? + } + } + pollMostRecentWindow(); + } else { + browserWindowsCount(1); + ok( + !window.closed, + "Restoring the old state should have left this window open" + ); + Services.obs.removeObserver( + observer, + "sessionstore-browser-state-restored" + ); + finish(); + } + } + Services.obs.addObserver(observer, "sessionstore-browser-state-restored"); + + // set browser to test state + ss.setBrowserState(JSON.stringify(testState)); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_528776.js b/src/zen/tests/mochitests/sessionstore/browser_528776.js new file mode 100644 index 000000000..37b76a766 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_528776.js @@ -0,0 +1,27 @@ +function browserWindowsCount(expected) { + var count = 0; + for (let win of Services.wm.getEnumerator("navigator:browser")) { + if (!win.closed) { + ++count; + } + } + is( + count, + expected, + "number of open browser windows according to nsIWindowMediator" + ); + is( + JSON.parse(ss.getBrowserState()).windows.length, + expected, + "number of open browser windows according to getBrowserState" + ); +} + +add_task(async function () { + browserWindowsCount(1); + + let win = await BrowserTestUtils.openNewBrowserWindow(); + browserWindowsCount(2); + await BrowserTestUtils.closeWindow(win); + browserWindowsCount(1); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_579868.js b/src/zen/tests/mochitests/sessionstore/browser_579868.js new file mode 100644 index 000000000..966a601e4 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_579868.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + waitForExplicitFinish(); + + let tab1 = BrowserTestUtils.addTab(gBrowser, "about:robots"); + let tab2 = BrowserTestUtils.addTab(gBrowser, "about:mozilla"); + + promiseBrowserLoaded(tab1.linkedBrowser).then(() => { + // Tell the session storer that the tab is pinned + let newTabState = + '{"entries":[{"url":"about:robots"}],"pinned":true,"userTypedValue":"Hello World!"}'; + ss.setTabState(tab1, newTabState); + + // Undo pinning + gBrowser.unpinTab(tab1); + + // Close and restore tab + gBrowser.removeTab(tab1); + let savedState = ss.getClosedTabDataForWindow(window)[0].state; + isnot(savedState.pinned, true, "Pinned should not be true"); + tab1 = ss.undoCloseTab(window, 0); + + isnot(tab1.pinned, true, "Should not be pinned"); + gBrowser.removeTab(tab1); + gBrowser.removeTab(tab2); + finish(); + }); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_579879.js b/src/zen/tests/mochitests/sessionstore/browser_579879.js new file mode 100644 index 000000000..083f111a8 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_579879.js @@ -0,0 +1,31 @@ +"use strict"; + +add_task(async function () { + let tab1 = BrowserTestUtils.addTab( + gBrowser, + "data:text/plain;charset=utf-8,foo" + ); + gBrowser.pinTab(tab1); + + await promiseBrowserLoaded(tab1.linkedBrowser); + let tab2 = BrowserTestUtils.addTab(gBrowser); + gBrowser.pinTab(tab2); + + is( + Array.prototype.indexOf.call(gBrowser.tabs, tab1), + 0, + "pinned tab 1 is at the first position" + ); + await promiseRemoveTabAndSessionState(tab1); + + tab1 = SessionWindowUI.undoCloseTab(window); + ok(tab1.pinned, "pinned tab 1 has been restored as a pinned tab"); + is( + Array.prototype.indexOf.call(gBrowser.tabs, tab1), + 0, + "pinned tab 1 has been restored to the first position" + ); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_580512.js b/src/zen/tests/mochitests/sessionstore/browser_580512.js new file mode 100644 index 000000000..e27dc61ba --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_580512.js @@ -0,0 +1,117 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const URIS_PINNED = ["about:license", "about:about"]; +const URIS_NORMAL_A = ["about:mozilla"]; +const URIS_NORMAL_B = ["about:buildconfig"]; + +function test() { + waitForExplicitFinish(); + + isnot( + Services.prefs.getIntPref("browser.startup.page"), + 3, + "pref to save session must not be set for this test" + ); + ok( + !Services.prefs.getBoolPref("browser.sessionstore.resume_session_once"), + "pref to save session once must not be set for this test" + ); + + document.documentElement.setAttribute( + "windowtype", + "navigator:browsertestdummy" + ); + + openWinWithCb(closeFirstWin, URIS_PINNED.concat(URIS_NORMAL_A)); +} + +function closeFirstWin(win) { + win.gBrowser.pinTab(win.gBrowser.tabs[0]); + win.gBrowser.pinTab(win.gBrowser.tabs[1]); + + let winClosed = BrowserTestUtils.windowClosed(win); + // We need to call BrowserCommands.tryToCloseWindow in order to trigger + // the machinery that chooses whether or not to save the session + // for the last window. + win.BrowserCommands.tryToCloseWindow(); + ok(win.closed, "window closed"); + + winClosed.then(() => { + openWinWithCb( + checkSecondWin, + URIS_NORMAL_B, + URIS_PINNED.concat(URIS_NORMAL_B) + ); + }); +} + +function checkSecondWin(win) { + is( + win.gBrowser.browsers[0].currentURI.spec, + URIS_PINNED[0], + "first pinned tab restored" + ); + is( + win.gBrowser.browsers[1].currentURI.spec, + URIS_PINNED[1], + "second pinned tab restored" + ); + ok(win.gBrowser.tabs[0].pinned, "first pinned tab is still pinned"); + ok(win.gBrowser.tabs[1].pinned, "second pinned tab is still pinned"); + + BrowserTestUtils.closeWindow(win).then(() => { + // cleanup + document.documentElement.setAttribute("windowtype", "navigator:browser"); + finish(); + }); +} + +function openWinWithCb(cb, argURIs, expectedURIs) { + if (!expectedURIs) { + expectedURIs = argURIs; + } + + var win = openDialog( + AppConstants.BROWSER_CHROME_URL, + "_blank", + "chrome,all,dialog=no", + argURIs.join("|") + ); + + win.addEventListener( + "load", + function () { + info("the window loaded"); + + var expectedLoads = expectedURIs.length; + + win.gBrowser.addTabsProgressListener({ + onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, _aStatus) { + if ( + aRequest && + aStateFlags & Ci.nsIWebProgressListener.STATE_STOP && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && + expectedURIs.indexOf( + aRequest.QueryInterface(Ci.nsIChannel).originalURI.spec + ) > -1 && + --expectedLoads <= 0 + ) { + win.gBrowser.removeTabsProgressListener(this); + info("all tabs loaded"); + is( + win.gBrowser.tabs.length, + expectedURIs.length, + "didn't load any unexpected tabs" + ); + executeSoon(function () { + cb(win); + }); + } + }, + }); + }, + { once: true } + ); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_581937.js b/src/zen/tests/mochitests/sessionstore/browser_581937.js new file mode 100644 index 000000000..4d4fe9dfe --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_581937.js @@ -0,0 +1,24 @@ +// Tests that an about:blank tab with no history will not be saved into +// session store and thus, it will not show up in Recently Closed Tabs. + +"use strict"; + +add_task(async function () { + let tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser, { + wantLoad: "about:blank", + }); + + is( + tab.linkedBrowser.currentURI.spec, + "about:blank", + "we will be removing an about:blank tab" + ); + + let r = `rand-${Math.random()}`; + ss.setCustomTabValue(tab, "foobar", r); + + await promiseRemoveTabAndSessionState(tab); + let closedTabData = ss.getClosedTabDataForWindow(window); + ok(!closedTabData.includes(r), "tab not stored in _closedTabs"); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_586068-apptabs.js b/src/zen/tests/mochitests/sessionstore/browser_586068-apptabs.js new file mode 100644 index 000000000..24878ba26 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_586068-apptabs.js @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +requestLongerTimeout(2); + +const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand"; + +add_task(async function test() { + Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true); + registerCleanupFunction(function () { + Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND); + }); + + let state = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org/#1", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + pinned: true, + }, + { + entries: [ + { url: "http://example.org/#2", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + pinned: true, + }, + { + entries: [ + { url: "http://example.org/#3", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + pinned: true, + }, + { + entries: [ + { url: "http://example.org/#4", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#5", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#6", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#7", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 5, + }, + ], + }; + + let loadCount = 0; + let promiseRestoringTabs = new Promise(resolve => { + gProgressListener.setCallback(function (aBrowser) { + loadCount++; + + // We'll make sure that the loads we get come from pinned tabs or the + // the selected tab. + + // get the tab + let tab; + for (let i = 0; i < window.gBrowser.tabs.length; i++) { + if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser) { + tab = window.gBrowser.tabs[i]; + } + } + + ok(tab.pinned || tab.selected, "load came from pinned or selected tab"); + + // We should get 4 loads: 3 app tabs + 1 normal selected tab + if (loadCount < 4) { + return; + } + + gProgressListener.unsetCallback(); + resolve(); + }); + }); + + let backupState = ss.getBrowserState(); + ss.setBrowserState(JSON.stringify(state)); + await promiseRestoringTabs; + + // Cleanup. + await promiseBrowserState(backupState); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_586068-apptabs_ondemand.js b/src/zen/tests/mochitests/sessionstore/browser_586068-apptabs_ondemand.js new file mode 100644 index 000000000..69416ecb8 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_586068-apptabs_ondemand.js @@ -0,0 +1,102 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand"; +const PREF_RESTORE_PINNED_TABS_ON_DEMAND = + "browser.sessionstore.restore_pinned_tabs_on_demand"; + +add_task(async function test() { + Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true); + Services.prefs.setBoolPref(PREF_RESTORE_PINNED_TABS_ON_DEMAND, true); + + registerCleanupFunction(function () { + Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND); + Services.prefs.clearUserPref(PREF_RESTORE_PINNED_TABS_ON_DEMAND); + }); + + let state = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org/#1", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + pinned: true, + }, + { + entries: [ + { url: "http://example.org/#2", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + pinned: true, + }, + { + entries: [ + { url: "http://example.org/#3", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + pinned: true, + }, + { + entries: [ + { url: "http://example.org/#4", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#5", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#6", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#7", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 5, + }, + ], + }; + + let promiseRestoringTabs = new Promise(resolve => { + gProgressListener.setCallback( + function (aBrowser, aNeedRestore, aRestoring, aRestored) { + // get the tab + let tab; + for (let i = 0; i < window.gBrowser.tabs.length; i++) { + if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser) { + tab = window.gBrowser.tabs[i]; + } + } + + // Check that the load only comes from the selected tab. + ok(tab.selected, "load came from selected tab"); + is(aNeedRestore, 6, "six tabs left to restore"); + is(aRestoring, 1, "one tab is restoring"); + is(aRestored, 0, "no tabs have been restored, yet"); + + gProgressListener.unsetCallback(); + resolve(); + } + ); + }); + + let backupState = ss.getBrowserState(); + ss.setBrowserState(JSON.stringify(state)); + await promiseRestoringTabs; + + // Cleanup. + await promiseBrowserState(backupState); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_586068-browser_state_interrupted.js b/src/zen/tests/mochitests/sessionstore/browser_586068-browser_state_interrupted.js new file mode 100644 index 000000000..6ef18e2b3 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_586068-browser_state_interrupted.js @@ -0,0 +1,207 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand"; + +requestLongerTimeout(2); + +add_task(async function test() { + Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false); + registerCleanupFunction(function () { + Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND); + }); + + // The first state will be loaded using setBrowserState, followed by the 2nd + // state also being loaded using setBrowserState, interrupting the first restore. + let state1 = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org#1", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#2", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#3", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#4", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 1, + }, + { + tabs: [ + { + entries: [ + { url: "http://example.com#1", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#2", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#3", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#4", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 3, + }, + ], + }; + let state2 = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org#5", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#6", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#7", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#8", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 3, + }, + { + tabs: [ + { + entries: [ + { url: "http://example.com#5", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#6", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#7", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#8", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 1, + }, + ], + }; + + // interruptedAfter will be set after the selected tab from each window have loaded. + let interruptedAfter = 0; + let loadedWindow1 = false; + let loadedWindow2 = false; + let numTabs = state2.windows[0].tabs.length + state2.windows[1].tabs.length; + + let loadCount = 0; + let promiseRestoringTabs = new Promise(resolve => { + gProgressListener.setCallback(function (aBrowser, aNeedRestore) { + loadCount++; + + if ( + aBrowser.currentURI.spec == state1.windows[0].tabs[2].entries[0].url + ) { + loadedWindow1 = true; + } + if ( + aBrowser.currentURI.spec == state1.windows[1].tabs[0].entries[0].url + ) { + loadedWindow2 = true; + } + + if (!interruptedAfter && loadedWindow1 && loadedWindow2) { + interruptedAfter = loadCount; + ss.setBrowserState(JSON.stringify(state2)); + return; + } + + if (loadCount < numTabs + interruptedAfter) { + return; + } + + // We don't actually care about load order in this test, just that they all + // do load. + is(loadCount, numTabs + interruptedAfter, "all tabs were restored"); + is(aNeedRestore, 0, "there are no tabs left needing restore"); + + // Remove the progress listener. + gProgressListener.unsetCallback(); + resolve(); + }); + }); + + // We also want to catch the extra windows (there should be 2), so we need to observe domwindowopened + Services.ww.registerNotification(function observer(aSubject, aTopic) { + if (aTopic == "domwindowopened") { + let win = aSubject; + win.addEventListener( + "load", + function () { + Services.ww.unregisterNotification(observer); + win.gBrowser.addTabsProgressListener(gProgressListener); + }, + { once: true } + ); + } + }); + + let backupState = ss.getBrowserState(); + ss.setBrowserState(JSON.stringify(state1)); + await promiseRestoringTabs; + + // Cleanup. + await promiseAllButPrimaryWindowClosed(); + await promiseBrowserState(backupState); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_586068-cascade.js b/src/zen/tests/mochitests/sessionstore/browser_586068-cascade.js new file mode 100644 index 000000000..8f2b9e71b --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_586068-cascade.js @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand"; + +add_task(async function test() { + Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false); + registerCleanupFunction(function () { + Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND); + }); + + let state = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.com", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + }, + ], + }; + + let expectedCounts = [ + [3, 3, 0], + [2, 3, 1], + [1, 3, 2], + [0, 3, 3], + [0, 2, 4], + [0, 1, 5], + ]; + + let loadCount = 0; + let promiseRestoringTabs = new Promise(resolve => { + gProgressListener.setCallback( + function (aBrowser, aNeedRestore, aRestoring, aRestored) { + loadCount++; + let expected = expectedCounts[loadCount - 1]; + + is( + aNeedRestore, + expected[0], + "load " + loadCount + " - # tabs that need to be restored" + ); + is( + aRestoring, + expected[1], + "load " + loadCount + " - # tabs that are restoring" + ); + is( + aRestored, + expected[2], + "load " + loadCount + " - # tabs that has been restored" + ); + + if (loadCount == state.windows[0].tabs.length) { + gProgressListener.unsetCallback(); + resolve(); + } + } + ); + }); + + let backupState = ss.getBrowserState(); + ss.setBrowserState(JSON.stringify(state)); + await promiseRestoringTabs; + + // Cleanup. + await promiseBrowserState(backupState); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_586068-multi_window.js b/src/zen/tests/mochitests/sessionstore/browser_586068-multi_window.js new file mode 100644 index 000000000..352c5bcef --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_586068-multi_window.js @@ -0,0 +1,110 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand"; + +add_task(async function test() { + Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false); + registerCleanupFunction(function () { + Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND); + }); + + // The first window will be put into the already open window and the second + // window will be opened with _openWindowWithState, which is the source of the problem. + let state = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org#0", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 1, + }, + { + tabs: [ + { + entries: [ + { url: "http://example.com#1", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#2", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#3", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#4", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#5", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#6", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 4, + }, + ], + }; + let numTabs = state.windows[0].tabs.length + state.windows[1].tabs.length; + + let loadCount = 0; + let promiseRestoringTabs = new Promise(resolve => { + gProgressListener.setCallback(function (aBrowser, aNeedRestore) { + if (++loadCount == numTabs) { + // We don't actually care about load order in this test, just that they all + // do load. + is(loadCount, numTabs, "all tabs were restored"); + is(aNeedRestore, 0, "there are no tabs left needing restore"); + + gProgressListener.unsetCallback(); + resolve(); + } + }); + }); + + // We also want to catch the 2nd window, so we need to observe domwindowopened + Services.ww.registerNotification(function observer(aSubject, aTopic) { + if (aTopic == "domwindowopened") { + let win = aSubject; + win.addEventListener( + "load", + function () { + Services.ww.unregisterNotification(observer); + win.gBrowser.addTabsProgressListener(gProgressListener); + }, + { once: true } + ); + } + }); + + let backupState = ss.getBrowserState(); + ss.setBrowserState(JSON.stringify(state)); + await promiseRestoringTabs; + + // Cleanup. + await promiseAllButPrimaryWindowClosed(); + await promiseBrowserState(backupState); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_586068-reload.js b/src/zen/tests/mochitests/sessionstore/browser_586068-reload.js new file mode 100644 index 000000000..b0b368824 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_586068-reload.js @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand"; + +add_task(async function test() { + Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true); + registerCleanupFunction(function () { + Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND); + }); + + let state = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org/#1", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#2", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#3", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#4", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#5", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#6", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#7", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#8", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org/#9", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 1, + }, + ], + }; + + let loadCount = 0; + let promiseRestoringTabs = new Promise(resolve => { + gBrowser.tabContainer.addEventListener( + "SSTabRestored", + function onRestored(event) { + let tab = event.target; + let browser = tab.linkedBrowser; + let tabData = state.windows[0].tabs[loadCount++]; + + // double check that this tab was the right one + is( + browser.currentURI.spec, + tabData.entries[0].url, + "load " + loadCount + " - browser loaded correct url" + ); + is( + ss.getCustomTabValue(tab, "uniq"), + tabData.extData.uniq, + "load " + loadCount + " - correct tab was restored" + ); + + if (loadCount == state.windows[0].tabs.length) { + gBrowser.tabContainer.removeEventListener( + "SSTabRestored", + onRestored + ); + resolve(); + } else { + // reload the next tab + gBrowser.browsers[loadCount].reload(); + } + } + ); + }); + + let backupState = ss.getBrowserState(); + ss.setBrowserState(JSON.stringify(state)); + await promiseRestoringTabs; + + // Cleanup. + await promiseBrowserState(backupState); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_586068-select.js b/src/zen/tests/mochitests/sessionstore/browser_586068-select.js new file mode 100644 index 000000000..4d0f82487 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_586068-select.js @@ -0,0 +1,125 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand"; + +add_task(async function test() { + Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true); + registerCleanupFunction(function () { + Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND); + }); + + let state = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 1, + }, + ], + }; + + let expectedCounts = [ + [5, 1, 0], + [4, 1, 1], + [3, 1, 2], + [2, 1, 3], + [1, 1, 4], + [0, 1, 5], + ]; + let tabOrder = [0, 5, 1, 4, 3, 2]; + + let loadCount = 0; + let promiseRestoringTabs = new Promise(resolve => { + gProgressListener.setCallback( + function (aBrowser, aNeedRestore, aRestoring, aRestored) { + loadCount++; + let expected = expectedCounts[loadCount - 1]; + + is( + aNeedRestore, + expected[0], + "load " + loadCount + " - # tabs that need to be restored" + ); + is( + aRestoring, + expected[1], + "load " + loadCount + " - # tabs that are restoring" + ); + is( + aRestored, + expected[2], + "load " + loadCount + " - # tabs that has been restored" + ); + + if (loadCount < state.windows[0].tabs.length) { + // double check that this tab was the right one + let expectedData = + state.windows[0].tabs[tabOrder[loadCount - 1]].extData.uniq; + let tab; + for (let i = 0; i < window.gBrowser.tabs.length; i++) { + if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser) { + tab = window.gBrowser.tabs[i]; + } + } + + is( + ss.getCustomTabValue(tab, "uniq"), + expectedData, + "load " + loadCount + " - correct tab was restored" + ); + + // select the next tab + window.gBrowser.selectTabAtIndex(tabOrder[loadCount]); + } else { + gProgressListener.unsetCallback(); + resolve(); + } + } + ); + }); + + let backupState = ss.getBrowserState(); + ss.setBrowserState(JSON.stringify(state)); + await promiseRestoringTabs; + + // Cleanup. + await promiseBrowserState(backupState); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_586068-window_state.js b/src/zen/tests/mochitests/sessionstore/browser_586068-window_state.js new file mode 100644 index 000000000..25066a2db --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_586068-window_state.js @@ -0,0 +1,115 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand"; + +add_task(async function test() { + Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false); + registerCleanupFunction(function () { + Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND); + }); + + // We'll use 2 states so that we can make sure calling setWindowState doesn't + // wipe out currently restoring data. + let state1 = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.com#1", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#2", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#3", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#4", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#5", triggeringPrincipal_base64 }, + ], + }, + ], + }, + ], + }; + let state2 = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org#1", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.org#2", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.org#3", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.org#4", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.org#5", triggeringPrincipal_base64 }, + ], + }, + ], + }, + ], + }; + let numTabs = state1.windows[0].tabs.length + state2.windows[0].tabs.length; + + let loadCount = 0; + let promiseRestoringTabs = new Promise(resolve => { + gProgressListener.setCallback(function (aBrowser, aNeedRestore) { + // When loadCount == 2, we'll also restore state2 into the window + if (++loadCount == 2) { + ss.setWindowState(window, JSON.stringify(state2), false); + } + + if (loadCount < numTabs) { + return; + } + + // We don't actually care about load order in this test, just that they all + // do load. + is( + loadCount, + numTabs, + "test_setWindowStateNoOverwrite: all tabs were restored" + ); + is(aNeedRestore, 0, "there are no tabs left needing restore"); + + gProgressListener.unsetCallback(); + resolve(); + }); + }); + + let backupState = ss.getBrowserState(); + ss.setWindowState(window, JSON.stringify(state1), true); + await promiseRestoringTabs; + + // Cleanup. + await promiseBrowserState(backupState); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_586068-window_state_override.js b/src/zen/tests/mochitests/sessionstore/browser_586068-window_state_override.js new file mode 100644 index 000000000..eb3d2c709 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_586068-window_state_override.js @@ -0,0 +1,113 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand"; + +add_task(async function test() { + Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false); + registerCleanupFunction(function () { + Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND); + }); + + // We'll use 2 states so that we can make sure calling setWindowState doesn't + // wipe out currently restoring data. + let state1 = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.com#1", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#2", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#3", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#4", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#5", triggeringPrincipal_base64 }, + ], + }, + ], + }, + ], + }; + let state2 = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org#1", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.org#2", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.org#3", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.org#4", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.org#5", triggeringPrincipal_base64 }, + ], + }, + ], + }, + ], + }; + let numTabs = 2 + state2.windows[0].tabs.length; + + let loadCount = 0; + let promiseRestoringTabs = new Promise(resolve => { + gProgressListener.setCallback(function (aBrowser, aNeedRestore) { + // When loadCount == 2, we'll also restore state2 into the window + if (++loadCount == 2) { + executeSoon(() => + ss.setWindowState(window, JSON.stringify(state2), true) + ); + } + + if (loadCount < numTabs) { + return; + } + + // We don't actually care about load order in this test, just that they all + // do load. + is(loadCount, numTabs, "all tabs were restored"); + is(aNeedRestore, 0, "there are no tabs left needing restore"); + + gProgressListener.unsetCallback(); + resolve(); + }); + }); + + let backupState = ss.getBrowserState(); + ss.setWindowState(window, JSON.stringify(state1), true); + await promiseRestoringTabs; + + // Cleanup. + await promiseBrowserState(backupState); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_586147.js b/src/zen/tests/mochitests/sessionstore/browser_586147.js new file mode 100644 index 000000000..37c369c3a --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_586147.js @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function observeOneRestore(callback) { + let topic = "sessionstore-browser-state-restored"; + Services.obs.addObserver(function onRestore() { + Services.obs.removeObserver(onRestore, topic); + callback(); + }, topic); +} + +function test() { + waitForExplicitFinish(); + + // There should be one tab when we start the test + let [origTab] = gBrowser.visibleTabs; + let hiddenTab = BrowserTestUtils.addTab(gBrowser); + + is(gBrowser.visibleTabs.length, 2, "should have 2 tabs before hiding"); + BrowserTestUtils.showOnlyTheseTabs(gBrowser, [origTab]); + is(gBrowser.visibleTabs.length, 1, "only 1 after hiding"); + ok(hiddenTab.hidden, "sanity check that it's hidden"); + + BrowserTestUtils.addTab(gBrowser); + let state = ss.getBrowserState(); + let stateObj = JSON.parse(state); + let tabs = stateObj.windows[0].tabs; + is(tabs.length, 3, "just checking that browser state is correct"); + ok(!tabs[0].hidden, "first tab is visible"); + ok(tabs[1].hidden, "second is hidden"); + ok(!tabs[2].hidden, "third is visible"); + + // Make the third tab hidden and then restore the modified state object + tabs[2].hidden = true; + + observeOneRestore(function () { + is(gBrowser.visibleTabs.length, 1, "only restored 1 visible tab"); + let restoredTabs = gBrowser.tabs; + + ok(!restoredTabs[0].hidden, "first is still visible"); + ok(restoredTabs[1].hidden, "second tab is still hidden"); + ok(restoredTabs[2].hidden, "third tab is now hidden"); + + // Restore the original state and clean up now that we're done + gBrowser.removeTab(gBrowser.tabs[1]); + gBrowser.removeTab(gBrowser.tabs[1]); + + finish(); + }); + ss.setBrowserState(JSON.stringify(stateObj)); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_588426.js b/src/zen/tests/mochitests/sessionstore/browser_588426.js new file mode 100644 index 000000000..e32867fc6 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_588426.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() { + let state = { + windows: [ + { + tabs: [ + { + entries: [{ url: "about:mozilla", triggeringPrincipal_base64 }], + hidden: true, + }, + { + entries: [{ url: "about:rights", triggeringPrincipal_base64 }], + hidden: true, + }, + ], + }, + ], + }; + + waitForExplicitFinish(); + + newWindowWithState(state, function (win) { + registerCleanupFunction(() => BrowserTestUtils.closeWindow(win)); + + is(win.gBrowser.tabs.length, 2, "two tabs were restored"); + is(win.gBrowser.visibleTabs.length, 1, "one tab is visible"); + + let tab = win.gBrowser.visibleTabs[0]; + is( + tab.linkedBrowser.currentURI.spec, + "about:mozilla", + "visible tab is about:mozilla" + ); + + finish(); + }); +} + +function newWindowWithState(state, callback) { + let opts = "chrome,all,dialog=no,height=800,width=800"; + let win = window.openDialog(AppConstants.BROWSER_CHROME_URL, "_blank", opts); + + win.addEventListener( + "load", + function () { + executeSoon(function () { + win.addEventListener( + "SSWindowStateReady", + function () { + promiseTabRestored(win.gBrowser.tabs[0]).then(() => callback(win)); + }, + { once: true } + ); + + ss.setWindowState(win, JSON.stringify(state), true); + }); + }, + { once: true } + ); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_589246.js b/src/zen/tests/mochitests/sessionstore/browser_589246.js new file mode 100644 index 000000000..e3e98f351 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_589246.js @@ -0,0 +1,288 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Mirrors WINDOW_ATTRIBUTES IN SessionStore.sys.mjs +const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"]; + +var stateBackup = ss.getBrowserState(); + +var originalWarnOnClose = Services.prefs.getBoolPref( + "browser.tabs.warnOnClose" +); +var originalStartupPage = Services.prefs.getIntPref("browser.startup.page"); +var originalWindowType = document.documentElement.getAttribute("windowtype"); + +var gotLastWindowClosedTopic = false; +var shouldPinTab = false; +var shouldOpenTabs = false; +var shouldCloseTab = false; +var testNum = 0; +var afterTestCallback; + +// Set state so we know the closed windows content +var testState = { + windows: [{ tabs: [{ entries: [{ url: "http://example.org" }] }] }], + _closedWindows: [], +}; + +// We'll push a set of conditions and callbacks into this array +// Ideally we would also test win/linux under a complete set of conditions, but +// the tests for osx mirror the other set of conditions possible on win/linux. +var tests = []; + +// the third & fourth test share a condition check, keep it DRY +function checkOSX34Generator(num) { + return function (aPreviousState, aCurState) { + // In here, we should have restored the pinned tab, so only the unpinned tab + // should be in aCurState. So let's shape our expectations. + let expectedState = JSON.parse(aPreviousState); + expectedState[0].tabs.shift(); + // size attributes are stripped out in _prepDataForDeferredRestore in SessionStore.sys.mjs. + // This isn't the best approach, but neither is comparing JSON strings + WINDOW_ATTRIBUTES.forEach(attr => delete expectedState[0][attr]); + + is( + aCurState, + JSON.stringify(expectedState), + "test #" + num + ": closedWindowState is as expected" + ); + }; +} +function checkNoWindowsGenerator(num) { + return function (aPreviousState, aCurState) { + is( + aCurState, + "[]", + "test #" + num + ": there should be no closedWindowsLeft" + ); + }; +} + +// The first test has 0 pinned tabs and 1 unpinned tab +tests.push({ + pinned: false, + extra: false, + close: false, + checkWinLin: checkNoWindowsGenerator(1), + checkOSX(aPreviousState, aCurState) { + is(aCurState, aPreviousState, "test #1: closed window state is unchanged"); + }, +}); + +// The second test has 1 pinned tab and 0 unpinned tabs. +tests.push({ + pinned: true, + extra: false, + close: false, + checkWinLin: checkNoWindowsGenerator(2), + checkOSX: checkNoWindowsGenerator(2), +}); + +// The third test has 1 pinned tab and 2 unpinned tabs. +tests.push({ + pinned: true, + extra: true, + close: false, + checkWinLin: checkNoWindowsGenerator(3), + checkOSX: checkOSX34Generator(3), +}); + +// The fourth test has 1 pinned tab, 2 unpinned tabs, and closes one unpinned tab. +tests.push({ + pinned: true, + extra: true, + close: "one", + checkWinLin: checkNoWindowsGenerator(4), + checkOSX: checkOSX34Generator(4), +}); + +// The fifth test has 1 pinned tab, 2 unpinned tabs, and closes both unpinned tabs. +tests.push({ + pinned: true, + extra: true, + close: "both", + checkWinLin: checkNoWindowsGenerator(5), + checkOSX: checkNoWindowsGenerator(5), +}); + +function test() { + /** + * Test for Bug 589246 - Closed window state getting corrupted when closing + * and reopening last browser window without exiting browser + */ + waitForExplicitFinish(); + // windows opening & closing, so extending the timeout + requestLongerTimeout(2); + + // We don't want the quit dialog pref + Services.prefs.setBoolPref("browser.tabs.warnOnClose", false); + // Ensure that we would restore the session (important for Windows) + Services.prefs.setIntPref("browser.startup.page", 3); + + runNextTestOrFinish(); +} + +function runNextTestOrFinish() { + if (tests.length) { + setupForTest(tests.shift()); + } else { + // some state is cleaned up at the end of each test, but not all + ["browser.tabs.warnOnClose", "browser.startup.page"].forEach(function (p) { + if (Services.prefs.prefHasUserValue(p)) { + Services.prefs.clearUserPref(p); + } + }); + + ss.setBrowserState(stateBackup); + executeSoon(finish); + } +} + +function setupForTest(aConditions) { + // reset some checks + gotLastWindowClosedTopic = false; + shouldPinTab = aConditions.pinned; + shouldOpenTabs = aConditions.extra; + shouldCloseTab = aConditions.close; + testNum++; + + // set our test callback + afterTestCallback = /Mac/.test(navigator.platform) + ? aConditions.checkOSX + : aConditions.checkWinLin; + + // Add observers + Services.obs.addObserver( + onLastWindowClosed, + "browser-lastwindow-close-granted" + ); + + // Set the state + Services.obs.addObserver( + onStateRestored, + "sessionstore-browser-state-restored" + ); + ss.setBrowserState(JSON.stringify(testState)); +} + +function onStateRestored() { + info("test #" + testNum + ": onStateRestored"); + Services.obs.removeObserver( + onStateRestored, + "sessionstore-browser-state-restored" + ); + + // change this window's windowtype so that closing a new window will trigger + // browser-lastwindow-close-granted. + document.documentElement.setAttribute("windowtype", "navigator:testrunner"); + + let newWin = openDialog( + location, + "_blank", + "chrome,all,dialog=no", + "http://example.com" + ); + newWin.addEventListener( + "load", + function () { + promiseBrowserLoaded(newWin.gBrowser.selectedBrowser).then(() => { + // pin this tab + if (shouldPinTab) { + newWin.gBrowser.pinTab(newWin.gBrowser.selectedTab); + } + + newWin.addEventListener( + "unload", + function () { + onWindowUnloaded(); + }, + { once: true } + ); + // Open a new tab as well. On Windows/Linux this will be restored when the + // new window is opened below (in onWindowUnloaded). On OS X we'll just + // restore the pinned tabs, leaving the unpinned tab in the closedWindowsData. + if (shouldOpenTabs) { + let newTab = BrowserTestUtils.addTab(newWin.gBrowser, "about:config"); + let newTab2 = BrowserTestUtils.addTab( + newWin.gBrowser, + "about:buildconfig" + ); + + newTab.linkedBrowser.addEventListener( + "load", + function () { + if (shouldCloseTab == "one") { + newWin.gBrowser.removeTab(newTab2); + } else if (shouldCloseTab == "both") { + newWin.gBrowser.removeTab(newTab); + newWin.gBrowser.removeTab(newTab2); + } + newWin.BrowserCommands.tryToCloseWindow(); + }, + { capture: true, once: true } + ); + } else { + newWin.BrowserCommands.tryToCloseWindow(); + } + }); + }, + { once: true } + ); +} + +// This will be called before the window is actually closed +function onLastWindowClosed() { + info("test #" + testNum + ": onLastWindowClosed"); + Services.obs.removeObserver( + onLastWindowClosed, + "browser-lastwindow-close-granted" + ); + gotLastWindowClosedTopic = true; +} + +// This is the unload event listener on the new window (from onStateRestored). +// Unload is fired after the window is closed, so sessionstore has already +// updated _closedWindows (which is important). We'll open a new window here +// which should actually trigger the bug. +function onWindowUnloaded() { + info("test #" + testNum + ": onWindowClosed"); + ok( + gotLastWindowClosedTopic, + "test #" + testNum + ": browser-lastwindow-close-granted was notified prior" + ); + + let previousClosedWindowData = ss.getClosedWindowData(); + + // Now we want to open a new window + let newWin = openDialog( + location, + "_blank", + "chrome,all,dialog=no", + "about:mozilla" + ); + newWin.addEventListener( + "load", + function () { + newWin.gBrowser.selectedBrowser.addEventListener( + "load", + function () { + // Good enough for checking the state + afterTestCallback(previousClosedWindowData, ss.getClosedWindowData()); + afterTestCleanup(newWin); + }, + { capture: true, once: true } + ); + }, + { once: true } + ); +} + +function afterTestCleanup(aNewWin) { + executeSoon(function () { + BrowserTestUtils.closeWindow(aNewWin).then(() => { + document.documentElement.setAttribute("windowtype", originalWindowType); + runNextTestOrFinish(); + }); + }); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_590268.js b/src/zen/tests/mochitests/sessionstore/browser_590268.js new file mode 100644 index 000000000..086348b12 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_590268.js @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const NUM_TABS = 12; + +var stateBackup = ss.getBrowserState(); + +function test() { + /** Test for Bug 590268 - Provide access to sessionstore tab data sooner */ + waitForExplicitFinish(); + requestLongerTimeout(2); + + // wasLoaded will be used to keep track of tabs that have already had SSTabRestoring + // fired for them. + let wasLoaded = {}; + let restoringTabsCount = 0; + let restoredTabsCount = 0; + let uniq2 = {}; + let uniq2Count = 0; + let state = { windows: [{ tabs: [] }] }; + // We're going to put a bunch of tabs into this state + for (let i = 0; i < NUM_TABS; i++) { + let uniq = r(); + let tabData = { + entries: [ + { url: "http://example.com/#" + i, triggeringPrincipal_base64 }, + ], + extData: { uniq, baz: "qux" }, + }; + state.windows[0].tabs.push(tabData); + wasLoaded[uniq] = false; + } + + function onSSTabRestoring(aEvent) { + restoringTabsCount++; + let uniq = ss.getCustomTabValue(aEvent.originalTarget, "uniq"); + wasLoaded[uniq] = true; + + is( + ss.getCustomTabValue(aEvent.originalTarget, "foo"), + "", + "There is no value for 'foo'" + ); + + // On the first SSTabRestoring we're going to run the the real test. + // We'll keep this listener around so we can keep marking tabs as restored. + if (restoringTabsCount == 1) { + onFirstSSTabRestoring(); + } else if (restoringTabsCount == NUM_TABS) { + onLastSSTabRestoring(); + } + } + + function onSSTabRestored() { + if (++restoredTabsCount < NUM_TABS) { + return; + } + cleanup(); + } + + function onTabOpen(aEvent) { + // To test bug 614708, we'll just set a value on the tab here. This value + // would previously cause us to not recognize the values in extData until + // much later. So testing "uniq" failed. + ss.setCustomTabValue(aEvent.originalTarget, "foo", "bar"); + } + + // This does the actual testing. SSTabRestoring should be firing on tabs from + // left to right, so we're going to start with the rightmost tab. + function onFirstSSTabRestoring() { + info("onFirstSSTabRestoring..."); + for (let i = gBrowser.tabs.length - 1; i >= 0; i--) { + let tab = gBrowser.tabs[i]; + let actualUniq = ss.getCustomTabValue(tab, "uniq"); + let expectedUniq = state.windows[0].tabs[i].extData.uniq; + + if (wasLoaded[actualUniq]) { + info("tab " + i + ": already restored"); + continue; + } + is(actualUniq, expectedUniq, "tab " + i + ": extData was correct"); + + // Now we're going to set a piece of data back on the tab so it can be read + // to test setting a value "early". + uniq2[actualUniq] = r(); + ss.setCustomTabValue(tab, "uniq2", uniq2[actualUniq]); + + // Delete the value we have for "baz". This tests that deleteCustomTabValue + // will delete "early access" values (c.f. bug 617175). If this doesn't throw + // then the test is successful. + try { + ss.deleteCustomTabValue(tab, "baz"); + } catch (e) { + ok(false, "no error calling deleteCustomTabValue - " + e); + } + + // This will be used in the final comparison to make sure we checked the + // same number as we set. + uniq2Count++; + } + } + + function onLastSSTabRestoring() { + let checked = 0; + for (let i = 0; i < gBrowser.tabs.length; i++) { + let tab = gBrowser.tabs[i]; + let uniq = ss.getCustomTabValue(tab, "uniq"); + + // Look to see if we set a uniq2 value for this uniq value + if (uniq in uniq2) { + is( + ss.getCustomTabValue(tab, "uniq2"), + uniq2[uniq], + "tab " + i + " has correct uniq2 value" + ); + checked++; + } + } + Assert.greater( + uniq2Count, + 0, + "at least 1 tab properly checked 'early access'" + ); + is(checked, uniq2Count, "checked the same number of uniq2 as we set"); + } + + function cleanup() { + // remove the event listener and clean up before finishing + gBrowser.tabContainer.removeEventListener( + "SSTabRestoring", + onSSTabRestoring + ); + gBrowser.tabContainer.removeEventListener( + "SSTabRestored", + onSSTabRestored, + true + ); + gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen); + // Put this in an executeSoon because we still haven't called restoreNextTab + // in sessionstore for the last tab (we'll call it after this). We end up + // trying to restore the tab (since we then add a closed tab to the array). + executeSoon(function () { + ss.setBrowserState(stateBackup); + executeSoon(finish); + }); + } + + // Add the event listeners + gBrowser.tabContainer.addEventListener("SSTabRestoring", onSSTabRestoring); + gBrowser.tabContainer.addEventListener( + "SSTabRestored", + onSSTabRestored, + true + ); + gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen); + // Restore state + ss.setBrowserState(JSON.stringify(state)); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_590563.js b/src/zen/tests/mochitests/sessionstore/browser_590563.js new file mode 100644 index 000000000..86843d2e7 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_590563.js @@ -0,0 +1,120 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function () { + let sessionData = { + windows: [ + { + tabs: [ + { + entries: [{ url: "about:mozilla", triggeringPrincipal_base64 }], + hidden: true, + }, + { + entries: [{ url: "about:blank", triggeringPrincipal_base64 }], + hidden: false, + }, + ], + }, + ], + }; + let url = "about:sessionrestore"; + let formdata = { id: { sessionData }, url }; + let state = { + windows: [ + { tabs: [{ entries: [{ url, triggeringPrincipal_base64 }], formdata }] }, + ], + }; + + let win = await newWindowWithState(state); + + registerCleanupFunction(() => BrowserTestUtils.closeWindow(win)); + + is(gBrowser.tabs.length, 1, "The total number of tabs should be 1"); + is( + gBrowser.visibleTabs.length, + 1, + "The total number of visible tabs should be 1" + ); + + await new Promise(resolve => executeSoon(resolve)); + await new Promise(resolve => waitForFocus(resolve, win)); + await middleClickTest(win); +}); + +async function middleClickTest(win) { + let browser = win.gBrowser.selectedBrowser; + let tabsToggle = browser.contentDocument.getElementById("tabsToggle"); + EventUtils.synthesizeMouseAtCenter( + tabsToggle, + { button: 0 }, + browser.contentWindow + ); + let tree = browser.contentDocument.getElementById("tabList"); + await TestUtils.waitForCondition(() => !tree.hasAttribute("hidden")); + // Force a layout flush before accessing coordinates. + tree.getBoundingClientRect(); + + is(tree.view.rowCount, 3, "There should be three items"); + + // click on the first tab item + var rect = tree.getCoordsForCellItem(1, tree.columns[1], "text"); + EventUtils.synthesizeMouse( + tree.body, + rect.x, + rect.y, + { button: 1 }, + browser.contentWindow + ); + // click on the second tab item + rect = tree.getCoordsForCellItem(2, tree.columns[1], "text"); + EventUtils.synthesizeMouse( + tree.body, + rect.x, + rect.y, + { button: 1 }, + browser.contentWindow + ); + + is( + win.gBrowser.tabs.length, + 3, + "The total number of tabs should be 3 after restoring 2 tabs by middle click." + ); + is( + win.gBrowser.visibleTabs.length, + 3, + "The total number of visible tabs should be 3 after restoring 2 tabs by middle click" + ); +} + +async function newWindowWithState(state) { + let opts = "chrome,all,dialog=no,height=800,width=800"; + let win = window.openDialog(AppConstants.BROWSER_CHROME_URL, "_blank", opts); + + await BrowserTestUtils.waitForEvent(win, "load"); + + // The form data will be restored before SSTabRestored, so we want to listen + // for that on the currently selected tab + await new Promise(resolve => { + win.gBrowser.tabContainer.addEventListener( + "SSTabRestored", + function onSSTabRestored(event) { + let tab = event.target; + if (tab.selected) { + win.gBrowser.tabContainer.removeEventListener( + "SSTabRestored", + onSSTabRestored, + true + ); + resolve(); + } + }, + true + ); + + executeSoon(() => ss.setWindowState(win, JSON.stringify(state), true)); + }); + + return win; +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_595601-restore_hidden.js b/src/zen/tests/mochitests/sessionstore/browser_595601-restore_hidden.js new file mode 100644 index 000000000..220039442 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_595601-restore_hidden.js @@ -0,0 +1,165 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var windowState = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.com#1", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#2", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#3", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#4", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#5", triggeringPrincipal_base64 }, + ], + hidden: true, + }, + { + entries: [ + { url: "http://example.com#6", triggeringPrincipal_base64 }, + ], + hidden: true, + }, + { + entries: [ + { url: "http://example.com#7", triggeringPrincipal_base64 }, + ], + hidden: true, + }, + { + entries: [ + { url: "http://example.com#8", triggeringPrincipal_base64 }, + ], + hidden: true, + }, + ], + }, + ], +}; + +function test() { + waitForExplicitFinish(); + requestLongerTimeout(2); + + registerCleanupFunction(function () { + Services.prefs.clearUserPref("browser.sessionstore.restore_hidden_tabs"); + }); + + // First stage: restoreHiddenTabs = true + // Second stage: restoreHiddenTabs = false + test_loadTabs(true, function () { + test_loadTabs(false, finish); + }); +} + +function test_loadTabs(restoreHiddenTabs, callback) { + Services.prefs.setBoolPref( + "browser.sessionstore.restore_hidden_tabs", + restoreHiddenTabs + ); + + let expectedTabs = restoreHiddenTabs ? 8 : 4; + let firstProgress = true; + + newWindowWithState(windowState, function (win, needsRestore, isRestoring) { + if (firstProgress) { + firstProgress = false; + is(isRestoring, 3, "restoring 3 tabs concurrently"); + } else { + Assert.less(isRestoring, 4, "restoring max. 3 tabs concurrently"); + } + + // We're explicity checking for (isRestoring == 1) here because the test + // progress listener is called before the session store one. So when we're + // called with one tab left to restore we know that the last tab has + // finished restoring and will soon be handled by the SS listener. + let tabsNeedingRestore = win.gBrowser.tabs.length - needsRestore; + if (isRestoring == 1 && tabsNeedingRestore == expectedTabs) { + is(win.gBrowser.visibleTabs.length, 4, "only 4 visible tabs"); + + TabsProgressListener.uninit(); + executeSoon(callback); + } + }); +} + +var TabsProgressListener = { + init(win) { + this.window = win; + Services.obs.addObserver(this, "sessionstore-debug-tab-restored"); + }, + + uninit() { + Services.obs.removeObserver(this, "sessionstore-debug-tab-restored"); + + delete this.window; + delete this.callback; + }, + + setCallback(callback) { + this.callback = callback; + }, + + observe(browser) { + TabsProgressListener.onRestored(browser); + }, + + onRestored(browser) { + if ( + this.callback && + ss.getInternalObjectState(browser) == TAB_STATE_RESTORING + ) { + this.callback.apply(null, [this.window].concat(this.countTabs())); + } + }, + + countTabs() { + let needsRestore = 0, + isRestoring = 0; + + for (let i = 0; i < this.window.gBrowser.tabs.length; i++) { + let state = ss.getInternalObjectState( + this.window.gBrowser.tabs[i].linkedBrowser + ); + if (state == TAB_STATE_RESTORING) { + isRestoring++; + } else if (state == TAB_STATE_NEEDS_RESTORE) { + needsRestore++; + } + } + + return [needsRestore, isRestoring]; + }, +}; + +// ---------- +function newWindowWithState(state, callback) { + let opts = "chrome,all,dialog=no,height=800,width=800"; + let win = window.openDialog(AppConstants.BROWSER_CHROME_URL, "_blank", opts); + + registerCleanupFunction(() => BrowserTestUtils.closeWindow(win)); + + whenWindowLoaded(win, function onWindowLoaded(aWin) { + TabsProgressListener.init(aWin); + TabsProgressListener.setCallback(callback); + + ss.setWindowState(aWin, JSON.stringify(state), true); + }); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_597071.js b/src/zen/tests/mochitests/sessionstore/browser_597071.js new file mode 100644 index 000000000..953e46249 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_597071.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 597071 - Closed windows should only be resurrected when there is a single + * popup window + */ +add_task(async function test_close_last_nonpopup_window() { + // Purge the list of closed windows. + forgetClosedWindows(); + + let oldState = ss.getWindowState(window); + + let popupState = { + windows: [{ tabs: [{ entries: [] }], isPopup: true, hidden: "toolbar" }], + }; + + // Set this window to be a popup. + ss.setWindowState(window, JSON.stringify(popupState), true); + + // Open a new window with a tab. + let win = await BrowserTestUtils.openNewBrowserWindow({ private: false }); + let tab = BrowserTestUtils.addTab(win.gBrowser, "http://example.com/"); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + // Make sure sessionstore sees this window. + let state = JSON.parse(ss.getBrowserState()); + is(state.windows.length, 2, "sessionstore knows about this window"); + + // Closed the window and check the closed window count. + await BrowserTestUtils.closeWindow(win); + is(ss.getClosedWindowCount(), 1, "correct closed window count"); + + // Cleanup. + ss.setWindowState(window, oldState, true); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_600545.js b/src/zen/tests/mochitests/sessionstore/browser_600545.js new file mode 100644 index 000000000..5b8315e00 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_600545.js @@ -0,0 +1,123 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +requestLongerTimeout(2); + +var stateBackup = JSON.parse(ss.getBrowserState()); + +function test() { + /** Test for Bug 600545 */ + waitForExplicitFinish(); + testBug600545(); +} + +function testBug600545() { + // Set the pref to false to cause non-app tabs to be stripped out on a save + Services.prefs.setBoolPref("browser.sessionstore.resume_from_crash", false); + Services.prefs.setIntPref("browser.sessionstore.interval", 2000); + + registerCleanupFunction(function () { + Services.prefs.clearUserPref("browser.sessionstore.resume_from_crash"); + Services.prefs.clearUserPref("browser.sessionstore.interval"); + }); + + // This tests the following use case: When multiple windows are open + // and browser.sessionstore.resume_from_crash preference is false, + // tab session data for non-active window is stripped for non-pinned + // tabs. This occurs after "sessionstore-state-write-complete" + // fires which will only fire in this case if there is at least one + // pinned tab. + let state = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org#0", triggeringPrincipal_base64 }, + ], + pinned: true, + }, + { + entries: [ + { url: "http://example.com#1", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#2", triggeringPrincipal_base64 }, + ], + }, + ], + selected: 2, + }, + { + tabs: [ + { + entries: [ + { url: "http://example.com#3", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#4", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#5", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#6", triggeringPrincipal_base64 }, + ], + }, + ], + selected: 3, + }, + ], + }; + + waitForBrowserState(state, function () { + // Need to wait for SessionStore's saveState function to be called + // so that non-pinned tabs will be stripped from non-active window + waitForSaveState(function () { + let expectedNumberOfTabs = getStateTabCount(state); + let retrievedState = JSON.parse(ss.getBrowserState()); + let actualNumberOfTabs = getStateTabCount(retrievedState); + + is( + actualNumberOfTabs, + expectedNumberOfTabs, + "Number of tabs in retreived session data, matches number of tabs set." + ); + + done(); + }); + }); +} + +function done() { + // Enumerate windows and close everything but our primary window. We can't + // use waitForFocus() because apparently it's buggy. See bug 599253. + let closeWinPromises = []; + for (let currentWindow of Services.wm.getEnumerator("navigator:browser")) { + if (currentWindow != window) { + closeWinPromises.push(BrowserTestUtils.closeWindow(currentWindow)); + } + } + + Promise.all(closeWinPromises).then(() => { + waitForBrowserState(stateBackup, finish); + }); +} + +// Count up the number of tabs in the state data +function getStateTabCount(aState) { + let tabCount = 0; + for (let i in aState.windows) { + tabCount += aState.windows[i].tabs.length; + } + return tabCount; +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_601955.js b/src/zen/tests/mochitests/sessionstore/browser_601955.js new file mode 100644 index 000000000..90cf6c182 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_601955.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This tests that pinning/unpinning a tab, on its own, eventually triggers a +// session store. + +function test() { + waitForExplicitFinish(); + // We speed up the interval between session saves to ensure that the test + // runs quickly. + Services.prefs.setIntPref("browser.sessionstore.interval", 2000); + + // Loading a tab causes a save state and this is meant to catch that event. + waitForSaveState(testBug601955_1); + + // Assumption: Only one window is open and it has one tab open. + BrowserTestUtils.addTab(gBrowser, "about:mozilla"); +} + +function testBug601955_1() { + // Because pinned tabs are at the front of |gBrowser.tabs|, pinning tabs + // re-arranges the |tabs| array. + ok(!gBrowser.tabs[0].pinned, "first tab should not be pinned yet"); + ok(!gBrowser.tabs[1].pinned, "second tab should not be pinned yet"); + + waitForSaveState(testBug601955_2); + gBrowser.pinTab(gBrowser.tabs[0]); +} + +function testBug601955_2() { + let state = JSON.parse(ss.getBrowserState()); + ok(state.windows[0].tabs[0].pinned, "first tab should be pinned by now"); + ok(!state.windows[0].tabs[1].pinned, "second tab should still not be pinned"); + + waitForSaveState(testBug601955_3); + gBrowser.unpinTab(window.gBrowser.tabs[0]); +} + +function testBug601955_3() { + let state = JSON.parse(ss.getBrowserState()); + ok(!state.windows[0].tabs[0].pinned, "first tab should not be pinned"); + ok(!state.windows[0].tabs[1].pinned, "second tab should not be pinned"); + + done(); +} + +function done() { + gBrowser.removeTab(window.gBrowser.tabs[1]); + + Services.prefs.clearUserPref("browser.sessionstore.interval"); + + executeSoon(finish); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_607016.js b/src/zen/tests/mochitests/sessionstore/browser_607016.js new file mode 100644 index 000000000..4d8254101 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_607016.js @@ -0,0 +1,155 @@ +"use strict"; + +var stateBackup = ss.getBrowserState(); + +add_task(async function () { + /** Bug 607016 - If a tab is never restored, attributes (eg. hidden) aren't updated correctly */ + ignoreAllUncaughtExceptions(); + + // Set the pref to true so we know exactly how many tabs should be restoring at + // any given time. This guarantees that a finishing load won't start another. + Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true); + // Don't restore tabs lazily. + Services.prefs.setBoolPref("browser.sessionstore.restore_tabs_lazily", false); + + let state = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org#1", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#2", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, // overwriting + { + entries: [ + { url: "http://example.org#3", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, // hiding + { + entries: [ + { url: "http://example.org#4", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, // adding + { + entries: [ + { url: "http://example.org#5", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, // deleting + { + entries: [ + { url: "http://example.org#6", triggeringPrincipal_base64 }, + ], + }, // creating + ], + selected: 1, + }, + ], + }; + + async function progressCallback() { + let curState = JSON.parse(ss.getBrowserState()); + for (let i = 0; i < curState.windows[0].tabs.length; i++) { + let tabState = state.windows[0].tabs[i]; + let tabCurState = curState.windows[0].tabs[i]; + if (tabState.extData) { + is( + tabCurState.extData.uniq, + tabState.extData.uniq, + "sanity check that tab has correct extData" + ); + } else { + // We aren't expecting there to be any data on extData, but panorama + // may be setting something, so we need to make sure that if we do have + // data, we just don't have anything for "uniq". + ok( + !("extData" in tabCurState) || !("uniq" in tabCurState.extData), + "sanity check that tab doesn't have extData or extData doesn't have 'uniq'" + ); + } + } + + // Now we'll set a new unique value on 1 of the tabs + let newUniq = r(); + ss.setCustomTabValue(gBrowser.tabs[1], "uniq", newUniq); + let tabState = JSON.parse(ss.getTabState(gBrowser.tabs[1])); + is( + tabState.extData.uniq, + newUniq, + "(overwriting) new data is stored in extData" + ); + + // hide the next tab before closing it + gBrowser.hideTab(gBrowser.tabs[2]); + tabState = JSON.parse(ss.getTabState(gBrowser.tabs[2])); + ok(tabState.hidden, "(hiding) tab data has hidden == true"); + + // set data that's not in a conflicting key + let stillUniq = r(); + ss.setCustomTabValue(gBrowser.tabs[3], "stillUniq", stillUniq); + tabState = JSON.parse(ss.getTabState(gBrowser.tabs[3])); + is( + tabState.extData.stillUniq, + stillUniq, + "(adding) new data is stored in extData" + ); + + // remove the uniq value and make sure it's not there in the closed data + ss.deleteCustomTabValue(gBrowser.tabs[4], "uniq"); + tabState = JSON.parse(ss.getTabState(gBrowser.tabs[4])); + // Since Panorama might have put data in, first check if there is extData. + // If there is explicitly check that "uniq" isn't in it. Otherwise, we're ok + if ("extData" in tabState) { + ok( + !("uniq" in tabState.extData), + "(deleting) uniq not in existing extData" + ); + } else { + ok(true, "(deleting) no data is stored in extData"); + } + + // set unique data on the tab that never had any set, make sure that's saved + let newUniq2 = r(); + ss.setCustomTabValue(gBrowser.tabs[5], "uniq", newUniq2); + tabState = JSON.parse(ss.getTabState(gBrowser.tabs[5])); + is( + tabState.extData.uniq, + newUniq2, + "(creating) new data is stored in extData where there was none" + ); + + while (gBrowser.tabs.length > 1) { + BrowserTestUtils.removeTab(gBrowser.tabs[1]); + } + } + + // Set the test state. + await setBrowserState(state); + + // Wait until the selected tab is restored and all others are pending. + await Promise.all( + Array.from(gBrowser.tabs, tab => { + return tab == gBrowser.selectedTab + ? promiseTabRestored(tab) + : promiseTabRestoring(tab); + }) + ); + + // Kick off the actual tests. + await progressCallback(); + + // Cleanup. + Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand"); + Services.prefs.clearUserPref("browser.sessionstore.restore_tabs_lazily"); + await promiseBrowserState(stateBackup); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_duplicateTab.js b/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_duplicateTab.js new file mode 100644 index 000000000..a94b056f5 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_duplicateTab.js @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const testState = { + windows: [ + { + tabs: [ + { entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }, + { entries: [{ url: "about:robots", triggeringPrincipal_base64 }] }, + ], + }, + ], +}; + +function test() { + /** Test for Bug 615394 - Session Restore should notify when it is beginning and ending a restore */ + waitForExplicitFinish(); + + waitForBrowserState(testState, test_duplicateTab); +} + +function test_duplicateTab() { + let tab = gBrowser.tabs[1]; + let busyEventCount = 0; + let readyEventCount = 0; + let newTab; + + // We'll look to make sure this value is on the duplicated tab + ss.setCustomTabValue(tab, "foo", "bar"); + + function onSSWindowStateBusy() { + busyEventCount++; + } + + function onSSWindowStateReady() { + newTab = gBrowser.tabs[2]; + readyEventCount++; + is(ss.getCustomTabValue(newTab, "foo"), "bar"); + ss.setCustomTabValue(newTab, "baz", "qux"); + } + + function onSSTabRestoring(aEvent) { + if (aEvent.target == newTab) { + is(busyEventCount, 1); + is(readyEventCount, 1); + is(ss.getCustomTabValue(newTab, "baz"), "qux"); + is(newTab.linkedBrowser.currentURI.spec, "about:robots"); + + window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy); + window.removeEventListener("SSWindowStateReady", onSSWindowStateReady); + gBrowser.tabContainer.removeEventListener( + "SSTabRestoring", + onSSTabRestoring + ); + + gBrowser.removeTab(tab); + gBrowser.removeTab(newTab); + finish(); + } + } + + window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy); + window.addEventListener("SSWindowStateReady", onSSWindowStateReady); + gBrowser.tabContainer.addEventListener("SSTabRestoring", onSSTabRestoring); + + gBrowser._insertBrowser(tab); + newTab = ss.duplicateTab(window, tab); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_setBrowserState.js b/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_setBrowserState.js new file mode 100644 index 000000000..0392beef7 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_setBrowserState.js @@ -0,0 +1,152 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lameMultiWindowState = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org#1", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#2", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#3", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#4", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 1, + }, + { + tabs: [ + { + entries: [ + { url: "http://example.com#1", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#2", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#3", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#4", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 3, + }, + ], +}; + +function getOuterWindowID(aWindow) { + return aWindow.docShell.outerWindowID; +} + +function test() { + /** Test for Bug 615394 - Session Restore should notify when it is beginning and ending a restore */ + waitForExplicitFinish(); + + // We'll track events per window so we are sure that they are each happening once + // pre window. + let windowEvents = {}; + windowEvents[getOuterWindowID(window)] = { + busyEventCount: 0, + readyEventCount: 0, + }; + + // waitForBrowserState does it's own observing for windows, but doesn't attach + // the listeners we want here, so do it ourselves. + let newWindow; + function windowObserver(aSubject, aTopic) { + if (aTopic == "domwindowopened") { + Services.ww.unregisterNotification(windowObserver); + + newWindow = aSubject; + newWindow.addEventListener( + "load", + function () { + windowEvents[getOuterWindowID(newWindow)] = { + busyEventCount: 0, + readyEventCount: 0, + }; + + newWindow.addEventListener("SSWindowStateBusy", onSSWindowStateBusy); + newWindow.addEventListener( + "SSWindowStateReady", + onSSWindowStateReady + ); + }, + { once: true } + ); + } + } + + function onSSWindowStateBusy(aEvent) { + windowEvents[getOuterWindowID(aEvent.originalTarget)].busyEventCount++; + } + + function onSSWindowStateReady(aEvent) { + windowEvents[getOuterWindowID(aEvent.originalTarget)].readyEventCount++; + } + + window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy); + window.addEventListener("SSWindowStateReady", onSSWindowStateReady); + Services.ww.registerNotification(windowObserver); + + waitForBrowserState(lameMultiWindowState, function () { + let checkedWindows = 0; + for (let id of Object.keys(windowEvents)) { + let winEvents = windowEvents[id]; + is( + winEvents.busyEventCount, + 1, + "window" + id + " busy event count correct" + ); + is( + winEvents.readyEventCount, + 1, + "window" + id + " ready event count correct" + ); + checkedWindows++; + } + is(checkedWindows, 2, "checked 2 windows"); + window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy); + window.removeEventListener("SSWindowStateReady", onSSWindowStateReady); + newWindow.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy); + newWindow.removeEventListener("SSWindowStateReady", onSSWindowStateReady); + + newWindow.close(); + while (gBrowser.tabs.length > 1) { + gBrowser.removeTab(gBrowser.tabs[1]); + } + + finish(); + }); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_setTabState.js b/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_setTabState.js new file mode 100644 index 000000000..99ada805b --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_setTabState.js @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const testState = { + windows: [ + { + tabs: [ + { entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }, + { entries: [{ url: "about:policies", triggeringPrincipal_base64 }] }, + ], + }, + ], +}; + +function test() { + /** Test for Bug 615394 - Session Restore should notify when it is beginning and ending a restore */ + waitForExplicitFinish(); + + waitForBrowserState(testState, test_setTabState); +} + +function test_setTabState() { + let tab = gBrowser.tabs[1]; + let newTabState = JSON.stringify({ + entries: [{ url: "http://example.org", triggeringPrincipal_base64 }], + extData: { foo: "bar" }, + }); + let busyEventCount = 0; + let readyEventCount = 0; + + function onSSWindowStateBusy() { + busyEventCount++; + } + + function onSSWindowStateReady() { + readyEventCount++; + is(ss.getCustomTabValue(tab, "foo"), "bar"); + ss.setCustomTabValue(tab, "baz", "qux"); + } + + function onSSTabRestoring() { + is(busyEventCount, 1); + is(readyEventCount, 1); + is(ss.getCustomTabValue(tab, "baz"), "qux"); + is(tab.linkedBrowser.currentURI.spec, "http://example.org/"); + + window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy); + window.removeEventListener("SSWindowStateReady", onSSWindowStateReady); + + gBrowser.removeTab(tab); + finish(); + } + + window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy); + window.addEventListener("SSWindowStateReady", onSSWindowStateReady); + tab.addEventListener("SSTabRestoring", onSSTabRestoring, { once: true }); + // Browser must be inserted in order to restore. + gBrowser._insertBrowser(tab); + ss.setTabState(tab, newTabState); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_setWindowState.js b/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_setWindowState.js new file mode 100644 index 000000000..80c521b1d --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_setWindowState.js @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function test() { + /** Test for Bug 615394 - Session Restore should notify when it is beginning and ending a restore */ + waitForExplicitFinish(); + + let newState = { + windows: [ + { + tabs: [ + { + entries: [{ url: "about:mozilla", triggeringPrincipal_base64 }], + extData: { foo: "bar" }, + }, + { + entries: [ + { url: "http://example.org", triggeringPrincipal_base64 }, + ], + extData: { baz: "qux" }, + }, + ], + }, + ], + }; + + let busyEventCount = 0, + readyEventCount = 0, + tabRestoredCount = 0; + + function onSSWindowStateBusy() { + busyEventCount++; + } + + function onSSWindowStateReady() { + readyEventCount++; + is(ss.getCustomTabValue(gBrowser.tabs[0], "foo"), "bar"); + is(ss.getCustomTabValue(gBrowser.tabs[1], "baz"), "qux"); + } + + function onSSTabRestored() { + if (++tabRestoredCount < 2) { + return; + } + + is(busyEventCount, 1); + is(readyEventCount, 1); + is(gBrowser.tabs[0].linkedBrowser.currentURI.spec, "about:mozilla"); + is(gBrowser.tabs[1].linkedBrowser.currentURI.spec, "http://example.org/"); + + window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy); + window.removeEventListener("SSWindowStateReady", onSSWindowStateReady); + gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored); + + gBrowser.removeTab(gBrowser.tabs[1]); + finish(); + } + + window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy); + window.addEventListener("SSWindowStateReady", onSSWindowStateReady); + gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored); + + ss.setWindowState(window, JSON.stringify(newState), true); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_undoCloseTab.js b/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_undoCloseTab.js new file mode 100644 index 000000000..e028cf6af --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_undoCloseTab.js @@ -0,0 +1,65 @@ +"use strict"; + +const testState = { + windows: [ + { + tabs: [ + { entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }, + { entries: [{ url: "about:robots", triggeringPrincipal_base64 }] }, + ], + }, + ], +}; + +// Test for Bug 615394 - Session Restore should notify when it is beginning and +// ending a restore. +add_task(async function test_undoCloseTab() { + await promiseBrowserState(testState); + + let tab = gBrowser.tabs[1]; + let busyEventCount = 0; + let readyEventCount = 0; + // This will be set inside the `onSSWindowStateReady` method. + let lastTab; + + ss.setCustomTabValue(tab, "foo", "bar"); + + function onSSWindowStateBusy() { + busyEventCount++; + } + + function onSSWindowStateReady() { + Assert.equal(gBrowser.tabs.length, 2, "Should only have 2 tabs"); + lastTab = gBrowser.tabs[1]; + readyEventCount++; + Assert.equal(ss.getCustomTabValue(lastTab, "foo"), "bar"); + ss.setCustomTabValue(lastTab, "baz", "qux"); + } + + window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy); + window.addEventListener("SSWindowStateReady", onSSWindowStateReady); + + let restoredPromise = BrowserTestUtils.waitForEvent( + gBrowser.tabContainer, + "SSTabRestored" + ); + + await promiseRemoveTabAndSessionState(tab); + let reopenedTab = ss.undoCloseTab(window, 0); + + await Promise.all([ + restoredPromise, + BrowserTestUtils.browserLoaded(reopenedTab.linkedBrowser), + ]); + + Assert.equal(reopenedTab, lastTab, "Tabs should be the same one."); + Assert.equal(busyEventCount, 1); + Assert.equal(readyEventCount, 1); + Assert.equal(ss.getCustomTabValue(reopenedTab, "baz"), "qux"); + Assert.equal(reopenedTab.linkedBrowser.currentURI.spec, "about:robots"); + + window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy); + window.removeEventListener("SSWindowStateReady", onSSWindowStateReady); + + BrowserTestUtils.removeTab(reopenedTab); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_undoCloseWindow.js b/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_undoCloseWindow.js new file mode 100644 index 000000000..7f9d72f49 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_615394-SSWindowState_events_undoCloseWindow.js @@ -0,0 +1,146 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lameMultiWindowState = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.org#1", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#2", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#3", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.org#4", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 1, + }, + { + tabs: [ + { + entries: [ + { url: "http://example.com#1", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#2", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#3", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + { + entries: [ + { url: "http://example.com#4", triggeringPrincipal_base64 }, + ], + extData: { uniq: r() }, + }, + ], + selected: 3, + }, + ], +}; + +function test() { + /** Test for Bug 615394 - Session Restore should notify when it is beginning and ending a restore */ + waitForExplicitFinish(); + + let newWindow, reopenedWindow; + + function firstWindowObserver(aSubject, aTopic) { + if (aTopic == "domwindowopened") { + newWindow = aSubject; + Services.ww.unregisterNotification(firstWindowObserver); + } + } + Services.ww.registerNotification(firstWindowObserver); + + waitForBrowserState(lameMultiWindowState, function () { + // Close the window which isn't window + BrowserTestUtils.closeWindow(newWindow).then(() => { + // Now give it time to close + reopenedWindow = ss.undoCloseWindow(0); + reopenedWindow.addEventListener("SSWindowStateBusy", onSSWindowStateBusy); + reopenedWindow.addEventListener( + "SSWindowStateReady", + onSSWindowStateReady + ); + + reopenedWindow.addEventListener( + "load", + function () { + reopenedWindow.gBrowser.tabContainer.addEventListener( + "SSTabRestored", + onSSTabRestored + ); + }, + { once: true } + ); + }); + }); + + let busyEventCount = 0, + readyEventCount = 0, + tabRestoredCount = 0; + // These will listen to the reopened closed window... + function onSSWindowStateBusy() { + busyEventCount++; + } + + function onSSWindowStateReady() { + readyEventCount++; + } + + function onSSTabRestored() { + if (++tabRestoredCount < 4) { + return; + } + + is(busyEventCount, 1); + is(readyEventCount, 1); + + reopenedWindow.removeEventListener( + "SSWindowStateBusy", + onSSWindowStateBusy + ); + reopenedWindow.removeEventListener( + "SSWindowStateReady", + onSSWindowStateReady + ); + reopenedWindow.gBrowser.tabContainer.removeEventListener( + "SSTabRestored", + onSSTabRestored + ); + + reopenedWindow.close(); + while (gBrowser.tabs.length > 1) { + gBrowser.removeTab(gBrowser.tabs[1]); + } + + finish(); + } +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_618151.js b/src/zen/tests/mochitests/sessionstore/browser_618151.js new file mode 100644 index 000000000..4c4b2e482 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_618151.js @@ -0,0 +1,67 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const stateBackup = ss.getBrowserState(); +const testState = { + windows: [ + { + tabs: [ + { entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }, + { entries: [{ url: "about:mozilla", triggeringPrincipal_base64 }] }, + ], + }, + ], +}; + +function test() { + /** Test for Bug 618151 - Overwriting state can lead to unrestored tabs */ + waitForExplicitFinish(); + runNextTest(); +} + +// Just a subset of tests from bug 615394 that causes a timeout. +var tests = [test_setup, test_hang]; +function runNextTest() { + // set an empty state & run the next test, or finish + if (tests.length) { + // Enumerate windows and close everything but our primary window. We can't + // use waitForFocus() because apparently it's buggy. See bug 599253. + let closeWinPromises = []; + for (let currentWindow of Services.wm.getEnumerator("navigator:browser")) { + if (currentWindow != window) { + closeWinPromises.push(BrowserTestUtils.closeWindow(currentWindow)); + } + } + + Promise.all(closeWinPromises).then(() => { + let currentTest = tests.shift(); + info("running " + currentTest.name); + waitForBrowserState(testState, currentTest); + }); + } else { + ss.setBrowserState(stateBackup); + executeSoon(finish); + } +} + +function test_setup() { + function onSSTabRestored() { + gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored); + runNextTest(); + } + + gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored); + ss.setTabState( + gBrowser.tabs[1], + JSON.stringify({ + entries: [{ url: "http://example.org", triggeringPrincipal_base64 }], + extData: { foo: "bar" }, + }) + ); +} + +function test_hang() { + ok(true, "test didn't time out"); + runNextTest(); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_623779.js b/src/zen/tests/mochitests/sessionstore/browser_623779.js new file mode 100644 index 000000000..ce6d1cde1 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_623779.js @@ -0,0 +1,13 @@ +"use strict"; + +add_task(async function () { + gBrowser.pinTab(gBrowser.selectedTab); + + let newTab = gBrowser.duplicateTab(gBrowser.selectedTab); + await promiseTabRestored(newTab); + + ok(!newTab.pinned, "duplicating a pinned tab creates unpinned tab"); + BrowserTestUtils.removeTab(newTab); + + gBrowser.unpinTab(gBrowser.selectedTab); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_624727.js b/src/zen/tests/mochitests/sessionstore/browser_624727.js new file mode 100644 index 000000000..57bb1e30c --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_624727.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var TEST_STATE = { windows: [{ tabs: [{ url: "about:blank" }] }] }; + +add_task(async function () { + function assertNumberOfTabs(num, msg) { + is(gBrowser.tabs.length, num, msg); + } + + function assertNumberOfPinnedTabs(num, msg) { + is(gBrowser.pinnedTabCount, num, msg); + } + + // check prerequisites + assertNumberOfTabs(1, "we start off with one tab"); + assertNumberOfPinnedTabs(0, "no pinned tabs so far"); + + // setup + BrowserTestUtils.addTab(gBrowser, "about:blank"); + assertNumberOfTabs(2, "there are two tabs, now"); + + let [tab1, tab2] = gBrowser.tabs; + gBrowser.pinTab(tab1); + gBrowser.pinTab(tab2); + assertNumberOfPinnedTabs(2, "both tabs are now pinned"); + + // run the test + await promiseBrowserState(TEST_STATE); + + assertNumberOfTabs(1, "one tab left after setBrowserState()"); + assertNumberOfPinnedTabs(0, "there are no pinned tabs"); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_625016.js b/src/zen/tests/mochitests/sessionstore/browser_625016.js new file mode 100644 index 000000000..afe5c1777 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_625016.js @@ -0,0 +1,103 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_setup(async function () { + /** Test for Bug 625016 - Restore windows closed in succession to quit (non-OSX only) */ + + // We'll test this by opening a new window, waiting for the save + // event, then closing that window. We'll observe the + // "sessionstore-state-write-complete" notification and check that + // the state contains no _closedWindows. We'll then add a new tab + // and make sure that the state following that was reset and the + // closed window is now in _closedWindows. + + requestLongerTimeout(2); + + await forceSaveState(); + + // We'll clear all closed windows to make sure our state is clean + // forgetClosedWindow doesn't trigger a delayed save + forgetClosedWindows(); + is(ss.getClosedWindowCount(), 0, "starting with no closed windows"); +}); + +add_task(async function new_window() { + let newWin; + try { + newWin = await promiseNewWindowLoaded(); + let tab = BrowserTestUtils.addTab( + newWin.gBrowser, + "http://example.com/browser_625016.js?" + Math.random() + ); + await promiseBrowserLoaded(tab.linkedBrowser); + + // Double check that we have no closed windows + is(ss.getClosedWindowCount(), 0, "no closed windows on first save"); + + await BrowserTestUtils.closeWindow(newWin); + newWin = null; + + let state = JSON.parse(await promiseRecoveryFileContents()); + is(state.windows.length, 1, "observe1: 1 window in data written to disk"); + is( + state._closedWindows.length, + 0, + "observe1: no closed windows in data written to disk" + ); + + // The API still treats the closed window as closed, so ensure that window is there + is( + ss.getClosedWindowCount(), + 1, + "observe1: 1 closed window according to API" + ); + } finally { + if (newWin) { + await BrowserTestUtils.closeWindow(newWin); + } + await forceSaveState(); + } +}); + +// We'll open a tab, which should trigger another state save which would wipe +// the _shouldRestore attribute from the closed window +add_task(async function new_tab() { + let newTab; + try { + newTab = BrowserTestUtils.addTab(gBrowser, "about:mozilla"); + await promiseBrowserLoaded(newTab.linkedBrowser); + await TabStateFlusher.flush(newTab.linkedBrowser); + + let state = JSON.parse(await promiseRecoveryFileContents()); + is( + state.windows.length, + 1, + "observe2: 1 window in data being written to disk" + ); + is( + state._closedWindows.length, + 1, + "observe2: 1 closed window in data being written to disk" + ); + + // The API still treats the closed window as closed, so ensure that window is there + is( + ss.getClosedWindowCount(), + 1, + "observe2: 1 closed window according to API" + ); + } finally { + if (newTab) { + gBrowser.removeTab(newTab); + } + } +}); + +add_task(async function done() { + // The API still represents the closed window as closed, so we can clear it + // with the API, but just to make sure... + // is(ss.getClosedWindowCount(), 1, "1 closed window according to API"); + await promiseAllButPrimaryWindowClosed(); + forgetClosedWindows(); + Services.prefs.clearUserPref("browser.sessionstore.interval"); +}); diff --git a/src/zen/tests/mochitests/sessionstore/browser_628270.js b/src/zen/tests/mochitests/sessionstore/browser_628270.js new file mode 100644 index 000000000..ec91c1af2 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_628270.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +async function test() { + let assertNumberOfTabs = function (num, msg) { + is(gBrowser.tabs.length, num, msg); + }; + + let assertNumberOfVisibleTabs = function (num, msg) { + is(gBrowser.visibleTabs.length, num, msg); + }; + + waitForExplicitFinish(); + + // check prerequisites + assertNumberOfTabs(1, "we start off with one tab"); + + // setup + let tab = BrowserTestUtils.addTab(gBrowser, "about:mozilla"); + + await promiseBrowserLoaded(tab.linkedBrowser); + + // hide the newly created tab + assertNumberOfVisibleTabs(2, "there are two visible tabs"); + BrowserTestUtils.showOnlyTheseTabs(gBrowser, [gBrowser.tabs[0]]); + assertNumberOfVisibleTabs(1, "there is one visible tab"); + ok(tab.hidden, "newly created tab is now hidden"); + + // close and restore hidden tab + await promiseRemoveTabAndSessionState(tab); + tab = ss.undoCloseTab(window, 0); + + // check that everything was restored correctly, clean up and finish + await promiseBrowserLoaded(tab.linkedBrowser); + is( + tab.linkedBrowser.currentURI.spec, + "about:mozilla", + "restored tab has correct url" + ); + + gBrowser.removeTab(tab); + finish(); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_635418.js b/src/zen/tests/mochitests/sessionstore/browser_635418.js new file mode 100644 index 000000000..c932a3b9f --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_635418.js @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This tests that hiding/showing a tab, on its own, eventually triggers a +// session store. + +function test() { + waitForExplicitFinish(); + + // We speed up the interval between session saves to ensure that the test + // runs quickly. + Services.prefs.setIntPref("browser.sessionstore.interval", 2000); + + // Loading a tab causes a save state and this is meant to catch that event. + waitForSaveState(testBug635418_1); + + // Assumption: Only one window is open and it has one tab open. + BrowserTestUtils.addTab(gBrowser, "about:mozilla"); +} + +function testBug635418_1() { + ok(!gBrowser.tabs[0].hidden, "first tab should not be hidden"); + ok(!gBrowser.tabs[1].hidden, "second tab should not be hidden"); + + waitForSaveState(testBug635418_2); + + // We can't hide the selected tab, so hide the new one + gBrowser.hideTab(gBrowser.tabs[1]); +} + +function testBug635418_2() { + let state = JSON.parse(ss.getBrowserState()); + ok(!state.windows[0].tabs[0].hidden, "first tab should still not be hidden"); + ok(state.windows[0].tabs[1].hidden, "second tab should be hidden by now"); + + waitForSaveState(testBug635418_3); + gBrowser.showTab(gBrowser.tabs[1]); +} + +function testBug635418_3() { + let state = JSON.parse(ss.getBrowserState()); + ok( + !state.windows[0].tabs[0].hidden, + "first tab should still still not be hidden" + ); + ok(!state.windows[0].tabs[1].hidden, "second tab should not be hidden again"); + + done(); +} + +function done() { + gBrowser.removeTab(window.gBrowser.tabs[1]); + + Services.prefs.clearUserPref("browser.sessionstore.interval"); + + executeSoon(finish); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_636279.js b/src/zen/tests/mochitests/sessionstore/browser_636279.js new file mode 100644 index 000000000..4842f145b --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_636279.js @@ -0,0 +1,144 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var stateBackup = ss.getBrowserState(); + +var statePinned = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.com#1", triggeringPrincipal_base64 }, + ], + pinned: true, + }, + ], + }, + ], +}; + +var state = { + windows: [ + { + tabs: [ + { + entries: [ + { url: "http://example.com#1", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#2", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#3", triggeringPrincipal_base64 }, + ], + }, + { + entries: [ + { url: "http://example.com#4", triggeringPrincipal_base64 }, + ], + }, + ], + }, + ], +}; + +function test() { + waitForExplicitFinish(); + + registerCleanupFunction(function () { + TabsProgressListener.uninit(); + ss.setBrowserState(stateBackup); + }); + + TabsProgressListener.init(); + + window.addEventListener( + "SSWindowStateReady", + function () { + let firstProgress = true; + + TabsProgressListener.setCallback(function (needsRestore, isRestoring) { + if (firstProgress) { + firstProgress = false; + is(isRestoring, 3, "restoring 3 tabs concurrently"); + } else { + Assert.lessOrEqual( + isRestoring, + 3, + "restoring max. 2 tabs concurrently" + ); + } + + if (0 == needsRestore) { + TabsProgressListener.unsetCallback(); + waitForFocus(finish); + } + }); + + ss.setBrowserState(JSON.stringify(state)); + }, + { once: true } + ); + + ss.setBrowserState(JSON.stringify(statePinned)); +} + +function countTabs() { + let needsRestore = 0, + isRestoring = 0; + for (let window of Services.wm.getEnumerator("navigator:browser")) { + if (window.closed) { + continue; + } + + for (let i = 0; i < window.gBrowser.tabs.length; i++) { + let browserState = ss.getInternalObjectState( + window.gBrowser.tabs[i].linkedBrowser + ); + if (browserState == TAB_STATE_RESTORING) { + isRestoring++; + } else if (browserState == TAB_STATE_NEEDS_RESTORE) { + needsRestore++; + } + } + } + + return [needsRestore, isRestoring]; +} + +var TabsProgressListener = { + init() { + Services.obs.addObserver(this, "sessionstore-debug-tab-restored"); + }, + + uninit() { + Services.obs.removeObserver(this, "sessionstore-debug-tab-restored"); + this.unsetCallback(); + }, + + setCallback(callback) { + this.callback = callback; + }, + + unsetCallback() { + delete this.callback; + }, + + observe(browser) { + TabsProgressListener.onRestored(browser); + }, + + onRestored(browser) { + if ( + this.callback && + ss.getInternalObjectState(browser) == TAB_STATE_RESTORING + ) { + this.callback.apply(null, countTabs()); + } + }, +}; diff --git a/src/zen/tests/mochitests/sessionstore/browser_637020.js b/src/zen/tests/mochitests/sessionstore/browser_637020.js new file mode 100644 index 000000000..d0c18305c --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_637020.js @@ -0,0 +1,77 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_URL = + "http://mochi.test:8888/browser/browser/components/" + + "sessionstore/test/browser_637020_slow.sjs"; + +const TEST_STATE = { + windows: [ + { + tabs: [ + { entries: [{ url: "about:mozilla", triggeringPrincipal_base64 }] }, + { entries: [{ url: "about:robots", triggeringPrincipal_base64 }] }, + ], + }, + { + tabs: [ + { entries: [{ url: TEST_URL, triggeringPrincipal_base64 }] }, + { entries: [{ url: TEST_URL, triggeringPrincipal_base64 }] }, + ], + }, + ], +}; + +/** + * This test ensures that windows that have just been restored will be marked + * as dirty, otherwise _getCurrentState() might ignore them when collecting + * state for the first time and we'd just save them as empty objects. + * + * The dirty state acts as a cache to not collect data from all windows all the + * time, so at the beginning, each window must be dirty so that we collect + * their state at least once. + */ + +add_task(async function test() { + // Wait until the new window has been opened. + let promiseWindow = new Promise(resolve => { + Services.obs.addObserver(function onOpened(subject) { + Services.obs.removeObserver(onOpened, "domwindowopened"); + resolve(subject); + }, "domwindowopened"); + }); + + // Set the new browser state that will + // restore a window with two slowly loading tabs. + let backupState = SessionStore.getBrowserState(); + SessionStore.setBrowserState(JSON.stringify(TEST_STATE)); + let win = await promiseWindow; + let delayedStartupFinished = new Promise(resolve => + whenDelayedStartupFinished(win, resolve) + ); + let restoring = promiseWindowRestoring(win); + let restored = promiseWindowRestored(win); + await restoring; + await restored; + + // The window has now been opened. Check the state that is returned, + // this should come from the cache while the window isn't restored, yet. + info("the window has been opened"); + checkWindows(); + + // The history has now been restored and the tabs are loading. The data must + // now come from the window, if it's correctly been marked as dirty before. + await delayedStartupFinished; + info("the delayed startup has finished"); + checkWindows(); + + // Cleanup. + await BrowserTestUtils.closeWindow(win); + await promiseBrowserState(backupState); +}); + +function checkWindows() { + let state = JSON.parse(SessionStore.getBrowserState()); + is(state.windows[0].tabs.length, 2, "first window has two tabs"); + is(state.windows[1].tabs.length, 2, "second window has two tabs"); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_637020_slow.sjs b/src/zen/tests/mochitests/sessionstore/browser_637020_slow.sjs new file mode 100644 index 000000000..abb1dee82 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_637020_slow.sjs @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const DELAY_MS = "2000"; + +let timer; + +function handleRequest(req, resp) { + resp.processAsync(); + resp.setHeader("Cache-Control", "no-cache", false); + resp.setHeader("Content-Type", "text/html;charset=utf-8", false); + + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.init( + () => { + resp.write("hi"); + resp.finish(); + }, + DELAY_MS, + Ci.nsITimer.TYPE_ONE_SHOT + ); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_645428.js b/src/zen/tests/mochitests/sessionstore/browser_645428.js new file mode 100644 index 000000000..3916c44a7 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_645428.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const NOTIFICATION = "sessionstore-browser-state-restored"; + +function test() { + waitForExplicitFinish(); + + function observe(subject, topic) { + if (NOTIFICATION == topic) { + finish(); + ok(true, "TOPIC received"); + } + } + + Services.obs.addObserver(observe, NOTIFICATION); + registerCleanupFunction(function () { + Services.obs.removeObserver(observe, NOTIFICATION); + }); + + ss.setBrowserState(JSON.stringify({ windows: [] })); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_659591.js b/src/zen/tests/mochitests/sessionstore/browser_659591.js new file mode 100644 index 000000000..1020d2a39 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_659591.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() { + waitForExplicitFinish(); + + let eventReceived = false; + + registerCleanupFunction(function () { + ok(eventReceived, "SSWindowClosing event received"); + }); + + newWindow(function (win) { + win.addEventListener( + "SSWindowClosing", + function () { + eventReceived = true; + }, + { once: true } + ); + + BrowserTestUtils.closeWindow(win).then(() => { + waitForFocus(finish); + }); + }); +} + +function newWindow(callback) { + let opts = "chrome,all,dialog=no,height=800,width=800"; + let win = window.openDialog(AppConstants.BROWSER_CHROME_URL, "_blank", opts); + + win.addEventListener( + "load", + function () { + executeSoon(() => callback(win)); + }, + { once: true } + ); +} diff --git a/src/zen/tests/mochitests/sessionstore/browser_662743.js b/src/zen/tests/mochitests/sessionstore/browser_662743.js new file mode 100644 index 000000000..c79388a56 --- /dev/null +++ b/src/zen/tests/mochitests/sessionstore/browser_662743.js @@ -0,0 +1,138 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// This tests that session restore component does restore the right