gh-14990: Prevent policyContainer from bloating the session file (gh-15098)

This commit is contained in:
mr. m
2026-08-25 13:41:10 +02:00
committed by GitHub
parent d70b3d902c
commit 0b7687ec6d
382 changed files with 37824 additions and 108 deletions

2
.gitattributes vendored
View File

@@ -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++

View File

@@ -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
+

View File

@@ -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: [

View File

@@ -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 =

View File

@@ -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.

View File

@@ -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.

View File

@@ -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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Security-Policy" content="default-src * data: blob: 'unsafe-inline'">
+ <title>policyContainer session store sample</title>
+</head>
+<body>
+ <iframe id="network" src="browser_policy_container_sample_frame.html"></iframe>
+ <iframe id="blank" src="about:blank"></iframe>
+</body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_policy_container_sample_frame.html b/browser/components/sessionstore/test/browser_policy_container_sample_frame.html
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_policy_container_sample_frame.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>policyContainer session store sample frame</title>
+</head>
+<body>
+ frame
+</body>
+</html>
diff --git a/docshell/shistory/SessionHistoryEntry.cpp b/docshell/shistory/SessionHistoryEntry.cpp
--- a/docshell/shistory/SessionHistoryEntry.cpp
+++ b/docshell/shistory/SessionHistoryEntry.cpp
@@ -41,10 +41,18 @@
extern mozilla::LazyLogModule gSHLog;
namespace mozilla {
namespace dom {
+// Only store policy container for loads that can't carry it themselves
+// (about:blank, about:srcdoc, blob:, data:, ...)
+// bug 1867137, bug 2011236
+static nsIPolicyContainer* PolicyContainerToStore(
+ nsIURI* aURI, nsIPolicyContainer* aPolicyContainer) {
+ return CSP_ShouldURIInheritCSP(aURI) ? aPolicyContainer : nullptr;
+}
+
SessionHistoryInfo::SessionHistoryInfo(nsDocShellLoadState* aLoadState,
nsIChannel* aChannel)
: mURI(aLoadState->URI()),
mOriginalURI(aLoadState->OriginalURI()),
mResultPrincipalURI(aLoadState->ResultPrincipalURI()),
@@ -59,9 +67,10 @@
mLoadReplace(aLoadState->LoadReplace()),
mHasUserActivation(aLoadState->HasValidUserGestureActivation()),
mSharedState(SharedState::Create(
aLoadState->TriggeringPrincipal(), aLoadState->PrincipalToInherit(),
aLoadState->PartitionedPrincipalToInherit(),
- aLoadState->PolicyContainer(),
+ PolicyContainerToStore(aLoadState->URI(),
+ aLoadState->PolicyContainer()),
/* FIXME Is this correct? */
aLoadState->TypeHint())) {
// Pull the upload stream off of the channel instead of the load state, as
@@ -124,11 +133,12 @@
loadInfo->GetPrincipalToInherit(
getter_AddRefs(mSharedState.Get()->mPrincipalToInherit));
mSharedState.Get()->mPartitionedPrincipalToInherit =
aPartitionedPrincipalToInherit;
- mSharedState.Get()->mPolicyContainer = aPolicyContainer;
+ mSharedState.Get()->mPolicyContainer =
+ PolicyContainerToStore(mURI, aPolicyContainer);
aChannel->GetContentType(mSharedState.Get()->mContentType);
aChannel->GetOriginalURI(getter_AddRefs(mOriginalURI));
uint32_t loadFlags;
aChannel->GetLoadFlags(&loadFlags);

View File

@@ -7,14 +7,6 @@
"id": "D299584",
"name": "Native MacOS popovers fix"
},
{
"type": "phabricator",
"id": "D284404",
"name": "Add urlbar closeOnWindowBlur preference",
"replaces": {
"\n\n": "\n // may want to figure out a more robust way to detect abandonment."
}
},
{
"type": "local",
// TODO: Convert into https://phabricator.services.mozilla.com/D298079
@@ -48,5 +40,16 @@
"this._lastJsonLength = uncompressedBytes;": "this._lastJsonLength = uncompressedBytes.jsonLength;",
" jsonLengthHint,": " lengthHint: jsonLengthHint,"
}
},
{
"type": "phabricator",
"id": "D320897",
"name": "Bug 2011236",
"replaces": {
// The upstream patch context includes a MOZ_DIAGNOSTIC_ASSERT that
// doesn't exist yet in our Firefox version.
"@@ -59,11 +67,12 @@": "@@ -59,9 +67,10 @@",
"aLoadState->TypeHint())) {\n MOZ_DIAGNOSTIC_ASSERT(!mURI->SchemeIs(\"javascript\"));\n \n // Pull": "aLoadState->TypeHint())) {\n // Pull"
}
}
]

View File

@@ -832,8 +832,9 @@ window.gZenCompactModeManager = {
const onLeave = event => {
if (AppConstants.platform == "macosx") {
const buttonRect =
gZenVerticalTabsManager.actualWindowButtons.getBoundingClientRect();
const buttonRect = window.windowUtils.getBoundsWithoutFlushing(
gZenVerticalTabsManager.actualWindowButtons
);
const MAC_WINDOW_BUTTONS_X_BORDER = buttonRect.width + buttonRect.x;
const MAC_WINDOW_BUTTONS_Y_BORDER = buttonRect.height + buttonRect.y;
if (

View File

@@ -1706,6 +1706,15 @@ class nsZenWorkspaces {
}
makeSureEmptyTabIsFirst() {
if (
gZenUIManager.testingEnabled &&
this._emptyTab &&
(this._emptyTab.closing || !this._emptyTab.isConnected)
) {
this._emptyTab = gBrowser.tabs.find(
tab => tab.hasAttribute("zen-empty-tab") && !tab.closing
);
}
const emptyTab = this._emptyTab;
if (emptyTab) {
emptyTab.setAttribute("zen-workspace-id", this.activeWorkspace);
@@ -3236,8 +3245,10 @@ class nsZenWorkspaces {
return width;
}, 0);
// Check if the total width exceeds the parent's width
if (totalWidth > parent.clientWidth) {
// Check if the total width exceeds the parent's width.
const parentWidth =
window.windowUtils.getBoundsWithoutFlushing(parent).width;
if (totalWidth > parentWidth) {
parent.setAttribute("icons-overflow", "true");
} else {
parent.removeAttribute("icons-overflow");
@@ -3245,7 +3256,7 @@ class nsZenWorkspaces {
// Set the width of each icon to the maximum size they can fit on
const widthPerButton = Math.max(
(parent.clientWidth - separation * (parent.children.length - 1)) /
(parentWidth - separation * (parent.children.length - 1)) /
parent.children.length,
minButtonSize
);

View File

@@ -3,6 +3,36 @@
# 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/.
[alerts]
source = "browser/base/content/test/alerts"
is_direct_path = true
[backforward]
source = "browser/base/content/test/backforward"
is_direct_path = true
[caps]
source = "browser/base/content/test/caps"
is_direct_path = true
[gesture]
source = "browser/base/content/test/gesture"
is_direct_path = true
[gesture.replace-manifest]
"[DEFAULT]" = '''[DEFAULT]
prefs = [
"zen.window-sync.enabled=false",
]'''
[linkHandling]
source = "browser/base/content/test/linkHandling"
is_direct_path = true
disable = [
# Glance opens with alt+click, firefox doesnt know that.
"browser_javascript_links.js",
]
[readermode]
source = "toolkit/components/reader/tests/browser"
is_direct_path = true
@@ -33,6 +63,68 @@ xpcshell = true
[services-crypto.replace-manifest]
"../../../" = "../../../../services/"
[sessionstore]
source = "browser/components/sessionstore/test"
is_direct_path = true
disable = [
"browser_1933485_tab_groups_history.js",
"browser_1953801_tab_groups_history_close_other.js",
"browser_aboutSessionRestore.js",
"browser_bfcache_telemetry.js",
"browser_closedId.js",
"browser_closed_objects_changed_notifications_tabs.js",
"browser_closed_objects_changed_notifications_windows.js",
"browser_closed_tabs_closed_windows.js",
"browser_closed_tabs_windows.js",
"browser_cookies_partitioned.js",
"browser_crashedTabs.js",
"browser_dying_cache.js",
"browser_firefoxView_restore.js",
"browser_firefoxView_selected_restore.js",
"browser_forget_closed_tab_window_byId.js",
"browser_movePendingTabToNewWindow.js",
"browser_newtab_userTypedValue.js",
"browser_pinned_tabs.js",
"browser_purge_domaindata.js",
"browser_reopen_all_windows.js",
"browser_restoreLastActionCorrectOrder.js",
"browser_restoreLastClosedTabOrWindowOrSession.js",
"browser_restore_container_tabs_oa.js",
"browser_restore_tabless_window.js",
"browser_restore_verticalPinnedTabs.js",
"browser_restored_window_features.js",
"browser_scrollPositions.js",
"browser_scrollPositionsReaderMode.js",
"browser_searchModeSwitcher_restore.js",
"browser_speculative_connect.js",
"browser_splitview_integer_ids.js",
"browser_splitview_restore_in_closed_window.js",
"browser_splitview_string_migration.js",
"browser_swapDocShells.js",
"browser_tab_groups_restore_closed_in_closed_window.js",
"browser_tab_groups_restore_closed_in_open_window.js",
"browser_tab_groups_restore_closed_many_tabs.js",
"browser_tab_groups_closed.js",
"browser_tab_groups_restore_multiple.js",
"browser_tab_groups_restore_saved.js",
"browser_tab_groups_restore_simple.js",
"browser_tab_groups_save_on_removeAllTabsBut.js",
"browser_tab_groups_save_on_removeTabsToTheEnd.js",
"browser_tab_groups_save_on_removeTabsToTheStart.js",
"browser_tab_groups_save_on_window_close.js",
"browser_tab_groups_state.js",
"browser_tab_groups_undo.js",
"browser_tab_label_during_restore.js",
"browser_undoCloseById.js",
"browser_urlbarSearchMode.js",
"browser_windowStateContainer.js",
"browser_wireframe_basic.js",
]
[sessionstore.replace-manifest]
'prefs = [' = '''prefs = [
"zen.window-sync.enabled=false",'''
[shell]
source = "browser/components/shell/test"
is_direct_path = true
@@ -59,3 +151,10 @@ source = "toolkit/components/tooltiptext"
disable = [
"browser_input_file_tooltips.js",
]
[zoom]
source = "browser/base/content/test/zoom"
is_direct_path = true
[zoom.replace-manifest]
"../general/" = "../../../../browser/base/content/test/general/"

View File

@@ -0,0 +1,32 @@
[DEFAULT]
support-files = [
"head.js",
"file_dom_notifications.html",
]
["browser_notification_close.js"]
https_first_disabled = true
skip-if = [
"os == 'win'", # Bug 1227785
]
["browser_notification_do_not_disturb.js"]
https_first_disabled = true
["browser_notification_open_settings.js"]
https_first_disabled = true
skip-if = [
"os == 'win'", # Bug 1411118
]
["browser_notification_remove_permission.js"]
https_first_disabled = true
skip-if = [
"os == 'win'", # Bug 1411118
]
["browser_notification_tab_switching.js"]
https_first_disabled = true
skip-if = [
"os == 'win'", # Bug 1243263
]

View File

@@ -0,0 +1,78 @@
"use strict";
const { PlacesTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/PlacesTestUtils.sys.mjs"
);
const { PermissionTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/PermissionTestUtils.sys.mjs"
);
let notificationURL =
"https://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
add_task(async function test_notificationClose() {
await addNotificationPermission(notificationURL);
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: notificationURL,
},
async function dummyTabTask(aBrowser) {
await openNotification(aBrowser, "showNotification2");
info("Notification alert showing");
let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
if (!alertWindow) {
ok(true, "Notifications don't use XUL windows on all platforms.");
await closeNotification(aBrowser);
return;
}
let alertTitleLabel =
alertWindow.document.getElementById("alertTitleLabel");
is(
alertTitleLabel.value,
"Test title",
"Title text of notification should be present"
);
let alertTextLabel =
alertWindow.document.getElementById("alertTextLabel");
is(
alertTextLabel.textContent,
"Test body 2",
"Body text of notification should be present"
);
let alertCloseButton = alertWindow.document.querySelector(".close-icon");
is(alertCloseButton.localName, "toolbarbutton", "close button found");
let promiseBeforeUnloadEvent = BrowserTestUtils.waitForEvent(
alertWindow,
"beforeunload"
);
let closedTime = alertWindow.Date.now();
alertCloseButton.click();
info("Clicked on close button");
await promiseBeforeUnloadEvent;
ok(true, "Alert should close when the close button is clicked");
let currentTime = alertWindow.Date.now();
// The notification will self-close at 12 seconds, so this checks
// that the notification closed before the timeout.
Assert.less(
currentTime - closedTime,
5000,
"Close requested at " +
closedTime +
", actually closed at " +
currentTime
);
}
);
});
add_task(async function cleanup() {
PermissionTestUtils.remove(notificationURL, "desktop-notification");
});

View File

@@ -0,0 +1,159 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests that notifications can be silenced using nsIAlertsDoNotDisturb
* on systems where that interface and its methods are implemented for
* the nsIAlertService.
*/
const ALERT_SERVICE = Cc["@mozilla.org/alerts-service;1"]
.getService(Ci.nsIAlertsService)
.QueryInterface(Ci.nsIAlertsDoNotDisturb);
const PAGE =
"https://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
// The amount of time in seconds that we will wait for a notification
// to show up before we decide that it's not coming.
const NOTIFICATION_TIMEOUT_SECS = 2000;
add_setup(async function () {
await addNotificationPermission(PAGE);
});
/**
* Test that the manualDoNotDisturb attribute can prevent
* notifications from appearing.
*/
add_task(async function test_manualDoNotDisturb() {
try {
// Only run the test if the do-not-disturb
// interface has been implemented.
ALERT_SERVICE.manualDoNotDisturb;
ok(true, "Alert service implements do-not-disturb interface");
} catch (e) {
ok(
true,
"Alert service doesn't implement do-not-disturb interface, exiting test"
);
return;
}
// In the event that something goes wrong during this test, make sure
// we put the attribute back to the default setting when this test file
// exits.
registerCleanupFunction(() => {
ALERT_SERVICE.manualDoNotDisturb = false;
});
// Make sure that do-not-disturb is not enabled before we start.
ok(
!ALERT_SERVICE.manualDoNotDisturb,
"Alert service should not be disabled when test starts"
);
await BrowserTestUtils.withNewTab(PAGE, async browser => {
await openNotification(browser, "showNotification2");
info("Notification alert showing");
let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
// For now, only the XUL alert backend implements the manualDoNotDisturb
// method for nsIAlertsDoNotDisturb, so we expect there to be a XUL alert
// window. If the method gets implemented by native backends in the future,
// we'll probably want to branch here and set the manualDoNotDisturb
// attribute manually.
ok(alertWindow, "Expected a XUL alert window.");
// We're using the XUL notification backend. This means that there's
// a menuitem for enabling manualDoNotDisturb. We exercise that
// menuitem here.
let doNotDisturbMenuItem = alertWindow.document.getElementById(
"doNotDisturbMenuItem"
);
is(doNotDisturbMenuItem.localName, "menuitem", "menuitem found");
let unloadPromise = BrowserTestUtils.waitForEvent(
alertWindow,
"beforeunload"
);
doNotDisturbMenuItem.click();
info("Clicked on do-not-disturb menuitem");
await unloadPromise;
// At this point, we should be configured to not display notifications
// to the user.
ok(
ALERT_SERVICE.manualDoNotDisturb,
"Alert service should be disabled after clicking menuitem"
);
// The notification should not appear, but there is no way from the
// client-side to know that it was blocked, except for waiting some time
// and realizing that the "onshow" event never fired.
await Assert.rejects(
openNotification(browser, "showNotification2", NOTIFICATION_TIMEOUT_SECS),
/timed out/,
"The notification should never display."
);
ALERT_SERVICE.manualDoNotDisturb = false;
});
});
/**
* Test that the suppressForScreenSharing attribute can prevent
* notifications from appearing.
*/
add_task(async function test_suppressForScreenSharing() {
try {
// Only run the test if the do-not-disturb
// interface has been implemented.
ALERT_SERVICE.suppressForScreenSharing;
ok(true, "Alert service implements do-not-disturb interface");
} catch (e) {
ok(
true,
"Alert service doesn't implement do-not-disturb interface, exiting test"
);
return;
}
// In the event that something goes wrong during this test, make sure
// we put the attribute back to the default setting when this test file
// exits.
registerCleanupFunction(() => {
ALERT_SERVICE.suppressForScreenSharing = false;
});
// Make sure that do-not-disturb is not enabled before we start.
ok(
!ALERT_SERVICE.suppressForScreenSharing,
"Alert service should not be suppressing for screen sharing when test " +
"starts"
);
await BrowserTestUtils.withNewTab(PAGE, async browser => {
await openNotification(browser, "showNotification2");
info("Notification alert showing");
await closeNotification(browser);
ALERT_SERVICE.suppressForScreenSharing = true;
// The notification should not appear, but there is no way from the
// client-side to know that it was blocked, except for waiting some time
// and realizing that the "onshow" event never fired.
await Assert.rejects(
openNotification(browser, "showNotification2", NOTIFICATION_TIMEOUT_SECS),
/timed out/,
"The notification should never display."
);
});
ALERT_SERVICE.suppressForScreenSharing = false;
});

View File

@@ -0,0 +1,87 @@
"use strict";
var notificationURL =
"https://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
// BrowserGlue passes "privacy-permissions" to openPreferences when the
// notifications-open-settings observer fires; the Settings Redesign
// LegacyPaneMappings shim routes that to the permissionsData pane.
var expectedURL = Services.prefs.getBoolPref(
"browser.settings-redesign.enabled",
false
)
? "about:preferences#permissionsData"
: "about:preferences#privacy";
add_task(async function test_settingsOpen_observer() {
info(
"Opening a dummy tab so openPreferences=>switchToTabHavingURI doesn't use the blank tab."
);
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:robots",
},
async function dummyTabTask() {
// Ensure preferences is loaded before removing the tab.
let syncPaneLoadedPromise = TestUtils.topicObserved(
"sync-pane-loaded",
() => true
);
let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, expectedURL);
info("simulate a notifications-open-settings notification");
let uri = NetUtil.newURI("https://example.com");
let principal = Services.scriptSecurityManager.createContentPrincipal(
uri,
{}
);
Services.obs.notifyObservers(principal, "notifications-open-settings");
let tab = await tabPromise;
ok(tab, "The notification settings tab opened");
await syncPaneLoadedPromise;
BrowserTestUtils.removeTab(tab);
}
);
});
add_task(async function test_settingsOpen_button() {
info("Adding notification permission");
await addNotificationPermission(notificationURL);
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: notificationURL,
},
async function tabTask(aBrowser) {
info("Waiting for notification");
await openNotification(aBrowser, "showNotification2");
let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
if (!alertWindow) {
ok(true, "Notifications don't use XUL windows on all platforms.");
await closeNotification(aBrowser);
return;
}
// Ensure preferences is loaded before removing the tab.
let syncPaneLoadedPromise = TestUtils.topicObserved(
"sync-pane-loaded",
() => true
);
let closePromise = promiseWindowClosed(alertWindow);
let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, expectedURL);
let openSettingsMenuItem = alertWindow.document.getElementById(
"openSettingsMenuItem"
);
openSettingsMenuItem.click();
info("Waiting for notification settings tab");
let tab = await tabPromise;
ok(tab, "The notification settings tab opened");
await syncPaneLoadedPromise;
await closePromise;
BrowserTestUtils.removeTab(tab);
}
);
});

View File

@@ -0,0 +1,85 @@
"use strict";
const { PermissionTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/PermissionTestUtils.sys.mjs"
);
var tab;
var notificationURL =
"https://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
var alertWindowClosed = false;
var permRemoved = false;
function test() {
waitForExplicitFinish();
registerCleanupFunction(function () {
gBrowser.removeTab(tab);
window.restore();
});
addNotificationPermission(notificationURL).then(function openTab() {
tab = BrowserTestUtils.addTab(gBrowser, notificationURL);
gBrowser.selectedTab = tab;
BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => onLoad());
});
}
function onLoad() {
openNotification(tab.linkedBrowser, "showNotification2").then(onAlertShowing);
}
function onAlertShowing() {
info("Notification alert showing");
let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
if (!alertWindow) {
ok(true, "Notifications don't use XUL windows on all platforms.");
closeNotification(tab.linkedBrowser).then(finish);
return;
}
ok(
PermissionTestUtils.testExactPermission(
notificationURL,
"desktop-notification"
),
"Permission should exist prior to removal"
);
let disableForOriginMenuItem = alertWindow.document.getElementById(
"disableForOriginMenuItem"
);
is(disableForOriginMenuItem.localName, "menuitem", "menuitem found");
Services.obs.addObserver(permObserver, "perm-changed");
alertWindow.addEventListener("beforeunload", onAlertClosing);
disableForOriginMenuItem.click();
info("Clicked on disable-for-origin menuitem");
}
function permObserver(subject, topic, data) {
if (topic != "perm-changed") {
return;
}
let permission = subject.QueryInterface(Ci.nsIPermission);
is(
permission.type,
"desktop-notification",
"desktop-notification permission changed"
);
is(data, "deleted", "desktop-notification permission deleted");
Services.obs.removeObserver(permObserver, "perm-changed");
permRemoved = true;
if (alertWindowClosed) {
finish();
}
}
function onAlertClosing(event) {
event.target.removeEventListener("beforeunload", onAlertClosing);
alertWindowClosed = true;
if (permRemoved) {
finish();
}
}

View File

@@ -0,0 +1,116 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
"use strict";
const { PermissionTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/PermissionTestUtils.sys.mjs"
);
var tab;
var notification;
var notificationURL =
"https://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
var newWindowOpenedFromTab;
add_task(async function test_notificationPreventDefaultAndSwitchTabs() {
await addNotificationPermission(notificationURL);
let originalTab = gBrowser.selectedTab;
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: notificationURL,
},
async function dummyTabTask(aBrowser) {
// Put new tab in background so it is obvious when it is re-focused.
await BrowserTestUtils.switchTab(gBrowser, originalTab);
isnot(
gBrowser.selectedBrowser,
aBrowser,
"Notification page loaded as a background tab"
);
// First, show a notification that will be have the tab-switching prevented.
function promiseNotificationEvent(evt) {
return SpecialPowers.spawn(
aBrowser,
[evt],
async function (contentEvt) {
return new Promise(resolve => {
let contentNotification = content.wrappedJSObject._notification;
contentNotification.addEventListener(
contentEvt,
function (event) {
resolve({ defaultPrevented: event.defaultPrevented });
},
{ once: true }
);
});
}
);
}
await openNotification(aBrowser, "showNotification1");
info("Notification alert showing");
let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
if (!alertWindow) {
ok(true, "Notifications don't use XUL windows on all platforms.");
await closeNotification(aBrowser);
return;
}
info("Clicking on notification");
let promiseClickEvent = promiseNotificationEvent("click");
// NB: This executeSoon is needed to allow the non-e10s runs of this test
// a chance to set the event listener on the page. Otherwise, we
// synchronously fire the click event before we listen for the event.
executeSoon(() => {
EventUtils.synthesizeMouseAtCenter(
alertWindow.document.getElementById("alertTitleLabel"),
{},
alertWindow
);
});
let clickEvent = await promiseClickEvent;
ok(
clickEvent.defaultPrevented,
"The event handler for the first notification cancels the event"
);
isnot(
gBrowser.selectedBrowser,
aBrowser,
"Notification page still a background tab"
);
let notificationClosed = promiseNotificationEvent("close");
await closeNotification(aBrowser);
await notificationClosed;
// Second, show a notification that will cause the tab to get switched.
await openNotification(aBrowser, "showNotification2");
alertWindow = Services.wm.getMostRecentWindow("alert:alert");
let promiseTabSelect = BrowserTestUtils.waitForEvent(
gBrowser.tabContainer,
"TabSelect"
);
EventUtils.synthesizeMouseAtCenter(
alertWindow.document.getElementById("alertTitleLabel"),
{},
alertWindow
);
await promiseTabSelect;
is(
gBrowser.selectedBrowser.currentURI.spec,
notificationURL,
"Clicking on the second notification should select its originating tab"
);
notificationClosed = promiseNotificationEvent("close");
await closeNotification(aBrowser);
await notificationClosed;
}
);
});
add_task(async function cleanup() {
PermissionTestUtils.remove(notificationURL, "desktop-notification");
});

View File

@@ -0,0 +1,39 @@
<html>
<head>
<meta charset="utf-8">
<script>
"use strict";
function showNotification1() {
var options = {
dir: undefined,
lang: undefined,
body: "Test body 1",
tag: "Test tag",
icon: undefined,
};
var n = new Notification("Test title", options);
n.addEventListener("click", function(event) {
event.preventDefault();
});
return n;
}
function showNotification2() {
var options = {
dir: undefined,
lang: undefined,
body: "Test body 2",
tag: "Test tag",
icon: undefined,
};
return new Notification("Test title", options);
}
</script>
</head>
<body>
<form id="notificationForm" onsubmit="showNotification();">
<input type="submit" value="Show notification" id="submit"/>
</form>
</body>
</html>

View File

@@ -0,0 +1,73 @@
// Platforms may default to reducing motion. We override this to ensure the
// alert slide animation is enabled in tests.
SpecialPowers.pushPrefEnv({
set: [["ui.prefersReducedMotion", 0]],
});
async function addNotificationPermission(originString) {
return SpecialPowers.pushPermissions([
{
type: "desktop-notification",
allow: true,
context: originString,
},
]);
}
/**
* Similar to `BrowserTestUtils.closeWindow`, but
* doesn't call `window.close()`.
*/
function promiseWindowClosed(window) {
return new Promise(function (resolve) {
Services.ww.registerNotification(function observer(subject, topic) {
if (topic == "domwindowclosed" && subject == window) {
Services.ww.unregisterNotification(observer);
resolve();
}
});
});
}
/**
* These two functions work with file_dom_notifications.html to open the
* notification and close it.
*
* |fn| can be showNotification1 or showNotification2.
* if |timeout| is passed, then the promise returned from this function is
* rejected after the requested number of miliseconds.
*/
function openNotification(aBrowser, fn, timeout) {
info(`openNotification: ${fn}`);
return SpecialPowers.spawn(
aBrowser,
[fn, timeout],
async function (contentFn, contentTimeout) {
await new Promise((resolve, reject) => {
let win = content.wrappedJSObject;
let notification = win[contentFn]();
win._notification = notification;
function listener() {
notification.removeEventListener("show", listener);
resolve();
}
notification.addEventListener("show", listener);
if (contentTimeout) {
content.setTimeout(() => {
notification.removeEventListener("show", listener);
reject("timed out");
}, contentTimeout);
}
});
}
);
}
function closeNotification(aBrowser) {
return SpecialPowers.spawn(aBrowser, [], function () {
content.wrappedJSObject._notification.close();
});
}

View File

@@ -0,0 +1,7 @@
[DEFAULT]
["browser_history_menu.js"]
fail-if = [
"a11y_checks", # Bug 1854233 navigator-toolbox may not be focusable
]
https_first_disabled = true

View File

@@ -0,0 +1,179 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// This test verifies that the back forward button long-press menu and context menu
// shows the correct history items.
add_task(async function mousedown_back() {
await testBackForwardMenu(false);
});
add_task(async function contextmenu_back() {
await testBackForwardMenu(true);
});
async function openHistoryMenu(useContextMenu) {
let backButton = document.getElementById("back-button");
let rect = backButton.getBoundingClientRect();
info("waiting for the history menu to open");
let popupShownPromise = BrowserTestUtils.waitForEvent(
useContextMenu ? document.getElementById("backForwardMenu") : backButton,
"popupshown"
);
if (useContextMenu) {
EventUtils.synthesizeMouseAtCenter(backButton, {
type: "contextmenu",
button: 2,
});
} else {
EventUtils.synthesizeMouseAtCenter(backButton, { type: "mousedown" });
}
EventUtils.synthesizeMouse(backButton, rect.width / 2, rect.height, {
type: "mouseup",
});
let popupEvent = await popupShownPromise;
ok(true, "history menu opened");
return popupEvent;
}
async function testBackForwardMenu(useContextMenu) {
let tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
"http://example.com"
);
for (let iter = 2; iter <= 4; iter++) {
// Iterate three times. For the first two times through the loop, add a new history item.
// But for the last iteration, go back in the history instead.
await SpecialPowers.spawn(
gBrowser.selectedBrowser,
[iter],
async function (iterChild) {
if (iterChild == 4) {
let popStatePromise = new Promise(function (resolve) {
content.onpopstate = resolve;
});
content.history.back();
await popStatePromise;
} else {
// With the back-button intervention enabled, we need to make sure to
// trigger a user activation on each history entry, or they won't
// show in the menu.
content.document.notifyUserGestureActivation();
content.history.pushState({}, "" + iterChild, iterChild + ".html");
}
}
);
// Wait for the session data to be flushed before continuing the test
await new Promise(resolve =>
SessionStore.getSessionHistory(gBrowser.selectedTab, resolve)
);
let popupEvent = await openHistoryMenu(useContextMenu);
// Wait for the session data to be flushed before continuing the test
await new Promise(resolve =>
SessionStore.getSessionHistory(gBrowser.selectedTab, resolve)
);
is(
popupEvent.target.children.length,
iter > 3 ? 3 : iter,
"Correct number of history items"
);
let node = popupEvent.target.lastElementChild;
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
is(node.getAttribute("uri"), "http://example.com/", "'1' item uri");
is(node.getAttribute("index"), "0", "'1' item index");
is(
node.getAttribute("historyindex"),
iter == 3 ? "-2" : "-1",
"'1' item historyindex"
);
node = node.previousElementSibling;
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
is(node.getAttribute("uri"), "http://example.com/2.html", "'2' item uri");
is(node.getAttribute("index"), "1", "'2' item index");
is(
node.getAttribute("historyindex"),
iter == 3 ? "-1" : "0",
"'2' item historyindex"
);
if (iter >= 3) {
node = node.previousElementSibling;
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
is(node.getAttribute("uri"), "http://example.com/3.html", "'3' item uri");
is(node.getAttribute("index"), "2", "'3' item index");
is(
node.getAttribute("historyindex"),
iter == 4 ? "1" : "0",
"'3' item historyindex"
);
}
// Close the popup, but on the last iteration, click on one of the history items
// to ensure it opens in a new tab.
let popupHiddenPromise = BrowserTestUtils.waitForEvent(
popupEvent.target,
"popuphidden"
);
if (iter < 4) {
popupEvent.target.hidePopup();
} else {
let newTabPromise = BrowserTestUtils.waitForNewTab(
gBrowser,
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
url => url == "http://example.com/"
);
popupEvent.target.activateItem(popupEvent.target.children[2], {
button: 1,
});
let newtab = await newTabPromise;
gBrowser.removeTab(newtab);
}
await popupHiddenPromise;
}
gBrowser.removeTab(tab);
}
// Make sure that the history popup appears after navigating around in a preferences page.
add_task(async function test_preferences_page() {
let tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:preferences"
);
openPreferences("search");
let popupEvent = await openHistoryMenu(true);
// Wait for the session data to be flushed before continuing the test
await new Promise(resolve =>
SessionStore.getSessionHistory(gBrowser.selectedTab, resolve)
);
is(popupEvent.target.children.length, 2, "Correct number of history items");
let popupHiddenPromise = BrowserTestUtils.waitForEvent(
popupEvent.target,
"popuphidden"
);
popupEvent.target.hidePopup();
await popupHiddenPromise;
gBrowser.removeTab(tab);
});

View File

@@ -0,0 +1,10 @@
[DEFAULT]
["browser_principalSerialization_csp.js"]
["browser_principalSerialization_json.js"]
skip-if = [
"debug", # deliberately bypass assertions when deserializing. Bug 965637 removed the CSP from Principals, but the remaining bits in such Principals should deserialize correctly.
]
["browser_principalSerialization_version1.js"]

View File

@@ -0,0 +1,106 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/*
* Within Bug 965637 we move the CSP away from the Principal. Serialized Principals however
* might still have CSPs serialized within them. This tests ensures that we do not
* encounter a memory corruption when deserializing. It's fine that the deserialized
* CSP is null, but the Principal itself should deserialize correctly.
*/
add_task(async function test_deserialize_principal_with_csp() {
/*
This test should be resilient to changes in principal serialization, if these are failing then it's likely the code will break session storage.
To recreate this for another version, copy the function into the browser console, browse some pages and printHistory.
Generated with:
function printHistory() {
let tests = [];
let entries = SessionStore.getSessionHistory(gBrowser.selectedTab).entries.map((entry) => { return entry.triggeringPrincipal_base64 });
entries.push(E10SUtils.serializePrincipal(gBrowser.selectedTab.linkedBrowser._contentPrincipal));
for (let entry of entries) {
console.log(entry);
let testData = {};
testData.input = entry;
let principal = E10SUtils.deserializePrincipal(testData.input);
testData.output = {};
if (principal.URI === null) {
testData.output.URI = false;
} else {
testData.output.URISpec = principal.URI.spec;
}
testData.output.originAttributes = principal.originAttributes;
testData.output.cspJSON = principal.cspJSON;
tests.push(testData);
}
return tests;
}
printHistory(); // Copy this into: serializedPrincipalsFromFirefox
*/
let serializedPrincipalsFromFirefox = [
{
input:
"ZT4OTT7kRfqycpfCC8AeuAAAAAAAAAAAwAAAAAAAAEYB3pRy0IA0EdOTmQAQS6D9QJIHOlRteE8wkTq4cYEyCMYAAAAC/////wAAAbsBAAAAHmh0dHBzOi8vd3d3Lm1vemlsbGEub3JnL2VuLVVTLwAAAAAAAAAFAAAACAAAAA8AAAAA/////wAAAAD/////AAAACAAAAA8AAAAXAAAABwAAABcAAAAHAAAAFwAAAAcAAAAeAAAAAAAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AQAAAAAAAAAAAAAAAQnZ7Rrl1EAEv+Anzrkj2ayzxMCuvV5MrYfgjSENuz+fAd6UctCANBHTk5kAEEug/UCSBzpUbXhPMJE6uHGBMgjGAAAAAv////8AAAG7AQAAAB5odHRwczovL3d3dy5tb3ppbGxhLm9yZy9lbi1VUy8AAAAAAAAABQAAAAgAAAAPAAAAAP////8AAAAA/////wAAAAgAAAAPAAAAFwAAAAcAAAAXAAAABwAAABcAAAAHAAAAHgAAAAAAAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wEAAAAAAAAAAAABAAAFtgBzAGMAcgBpAHAAdAAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AIAAnAHUAbgBzAGEAZgBlAC0AaQBuAGwAaQBuAGUAJwAgACcAdQBuAHMAYQBmAGUALQBlAHYAYQBsACcAIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQB0AGEAZwBtAGEAbgBhAGcAZQByAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQAtAGEAbgBhAGwAeQB0AGkAYwBzAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdABhAGcAbQBhAG4AYQBnAGUAcgAuAGcAbwBvAGcAbABlAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgB5AG8AdQB0AHUAYgBlAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AcwAuAHkAdABpAG0AZwAuAGMAbwBtADsAIABpAG0AZwAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AIABkAGEAdABhADoAIABoAHQAdABwAHMAOgAvAC8AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUAdABhAGcAbQBhAG4AYQBnAGUAcgAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALQBhAG4AYQBsAHkAdABpAGMAcwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAGEAZABzAGUAcgB2AGkAYwBlAC4AZwBvAG8AZwBsAGUALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGQAcwBlAHIAdgBpAGMAZQAuAGcAbwBvAGcAbABlAC4AZABlACAAaAB0AHQAcABzADoALwAvAGEAZABzAGUAcgB2AGkAYwBlAC4AZwBvAG8AZwBsAGUALgBkAGsAIABoAHQAdABwAHMAOgAvAC8AYwByAGUAYQB0AGkAdgBlAGMAbwBtAG0AbwBuAHMALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwBhAGQALgBkAG8AdQBiAGwAZQBjAGwAaQBjAGsALgBuAGUAdAA7ACAAZABlAGYAYQB1AGwAdAAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AOwAgAGYAcgBhAG0AZQAtAHMAcgBjACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUAdABhAGcAbQBhAG4AYQBnAGUAcgAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALQBhAG4AYQBsAHkAdABpAGMAcwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AeQBvAHUAdAB1AGIAZQAtAG4AbwBjAG8AbwBrAGkAZQAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHQAcgBhAGMAawBlAHIAdABlAHMAdAAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AcwB1AHIAdgBlAHkAZwBpAHoAbQBvAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAGEAYwBjAG8AdQBuAHQAcwAuAGYAaQByAGUAZgBvAHgALgBjAG8AbQAuAGMAbgAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA7ACAAcwB0AHkAbABlAC0AcwByAGMAIAAnAHMAZQBsAGYAJwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG4AZQB0ACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbwByAGcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBjAG8AbQAgACcAdQBuAHMAYQBmAGUALQBpAG4AbABpAG4AZQAnADsAIABjAG8AbgBuAGUAYwB0AC0AcwByAGMAIAAnAHMAZQBsAGYAJwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG4AZQB0ACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbwByAGcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAHQAYQBnAG0AYQBuAGEAZwBlAHIALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC0AYQBuAGEAbAB5AHQAaQBjAHMALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGMAYwBvAHUAbgB0AHMALgBmAGkAcgBlAGYAbwB4AC4AYwBvAG0ALwAgAGgAdAB0AHAAcwA6AC8ALwBhAGMAYwBvAHUAbgB0AHMALgBmAGkAcgBlAGYAbwB4AC4AYwBvAG0ALgBjAG4ALwA7ACAAYwBoAGkAbABkAC0AcwByAGMAIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQB0AGEAZwBtAGEAbgBhAGcAZQByAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQAtAGEAbgBhAGwAeQB0AGkAYwBzAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgB5AG8AdQB0AHUAYgBlAC0AbgBvAGMAbwBvAGsAaQBlAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdAByAGEAYwBrAGUAcgB0AGUAcwB0AC4AbwByAGcAIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBzAHUAcgB2AGUAeQBnAGkAegBtAG8ALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGMAYwBvAHUAbgB0AHMALgBmAGkAcgBlAGYAbwB4AC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtAC4AYwBuACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AeQBvAHUAdAB1AGIAZQAuAGMAbwBtAAA=",
output: {
// Within Bug 965637 we removed CSP from Principals. Already serialized Principals however should still deserialize correctly (just without the CSP).
// "cspJSON": "{\"csp-policies\":[{\"child-src\":[\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://www.youtube-nocookie.com\",\"https://trackertest.org\",\"https://www.surveygizmo.com\",\"https://accounts.firefox.com\",\"https://accounts.firefox.com.cn\",\"https://www.youtube.com\"],\"connect-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://accounts.firefox.com/\",\"https://accounts.firefox.com.cn/\"],\"default-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\"],\"frame-src\":[\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://www.youtube-nocookie.com\",\"https://trackertest.org\",\"https://www.surveygizmo.com\",\"https://accounts.firefox.com\",\"https://accounts.firefox.com.cn\",\"https://www.youtube.com\"],\"img-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"data:\",\"https://mozilla.org\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://adservice.google.com\",\"https://adservice.google.de\",\"https://adservice.google.dk\",\"https://creativecommons.org\",\"https://ad.doubleclick.net\"],\"report-only\":false,\"script-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"'unsafe-inline'\",\"'unsafe-eval'\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://tagmanager.google.com\",\"https://www.youtube.com\",\"https://s.ytimg.com\"],\"style-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"'unsafe-inline'\"]}]}",
URISpec: "https://www.mozilla.org/en-US/",
originAttributes: {
firstPartyDomain: "",
inIsolatedMozBrowser: false,
privateBrowsingId: 0,
userContextId: 0,
geckoViewSessionContextId: "",
partitionKey: "",
},
},
},
{
input:
"ZT4OTT7kRfqycpfCC8AeuAAAAAAAAAAAwAAAAAAAAEYB3pRy0IA0EdOTmQAQS6D9QJIHOlRteE8wkTq4cYEyCMYAAAAC/////wAAAbsBAAAAL2h0dHBzOi8vd3d3Lm1vemlsbGEub3JnL2VuLVVTL2ZpcmVmb3gvYWNjb3VudHMvAAAAAAAAAAUAAAAIAAAADwAAAAj/////AAAACP////8AAAAIAAAADwAAABcAAAAYAAAAFwAAABgAAAAXAAAAGAAAAC8AAAAAAAAAL/////8AAAAA/////wAAABf/////AAAAF/////8BAAAAAAAAAAAAAAABCdntGuXUQAS/4CfOuSPZrLPEwK69Xkyth+CNIQ27P58B3pRy0IA0EdOTmQAQS6D9QJIHOlRteE8wkTq4cYEyCMYAAAAC/////wAAAbsBAAAAL2h0dHBzOi8vd3d3Lm1vemlsbGEub3JnL2VuLVVTL2ZpcmVmb3gvYWNjb3VudHMvAAAAAAAAAAUAAAAIAAAADwAAAAj/////AAAACP////8AAAAIAAAADwAAABcAAAAYAAAAFwAAABgAAAAXAAAAGAAAAC8AAAAAAAAAL/////8AAAAA/////wAAABf/////AAAAF/////8BAAAAAAAAAAAAAQAABbYAcwBjAHIAaQBwAHQALQBzAHIAYwAgACcAcwBlAGwAZgAnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbgBlAHQAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAGMAbwBtACAAJwB1AG4AcwBhAGYAZQAtAGkAbgBsAGkAbgBlACcAIAAnAHUAbgBzAGEAZgBlAC0AZQB2AGEAbAAnACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUAdABhAGcAbQBhAG4AYQBnAGUAcgAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALQBhAG4AYQBsAHkAdABpAGMAcwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHQAYQBnAG0AYQBuAGEAZwBlAHIALgBnAG8AbwBnAGwAZQAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AeQBvAHUAdAB1AGIAZQAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHMALgB5AHQAaQBtAGcALgBjAG8AbQA7ACAAaQBtAGcALQBzAHIAYwAgACcAcwBlAGwAZgAnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbgBlAHQAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAGMAbwBtACAAZABhAHQAYQA6ACAAaAB0AHQAcABzADoALwAvAG0AbwB6AGkAbABsAGEALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAHQAYQBnAG0AYQBuAGEAZwBlAHIALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC0AYQBuAGEAbAB5AHQAaQBjAHMALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGQAcwBlAHIAdgBpAGMAZQAuAGcAbwBvAGcAbABlAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBkAHMAZQByAHYAaQBjAGUALgBnAG8AbwBnAGwAZQAuAGQAZQAgAGgAdAB0AHAAcwA6AC8ALwBhAGQAcwBlAHIAdgBpAGMAZQAuAGcAbwBvAGcAbABlAC4AZABrACAAaAB0AHQAcABzADoALwAvAGMAcgBlAGEAdABpAHYAZQBjAG8AbQBtAG8AbgBzAC4AbwByAGcAIABoAHQAdABwAHMAOgAvAC8AYQBkAC4AZABvAHUAYgBsAGUAYwBsAGkAYwBrAC4AbgBlAHQAOwAgAGQAZQBmAGEAdQBsAHQALQBzAHIAYwAgACcAcwBlAGwAZgAnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbgBlAHQAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAGMAbwBtADsAIABmAHIAYQBtAGUALQBzAHIAYwAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAHQAYQBnAG0AYQBuAGEAZwBlAHIALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC0AYQBuAGEAbAB5AHQAaQBjAHMALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALQBuAG8AYwBvAG8AawBpAGUALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB0AHIAYQBjAGsAZQByAHQAZQBzAHQALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAHMAdQByAHYAZQB5AGcAaQB6AG0AbwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAGEAYwBjAG8AdQBuAHQAcwAuAGYAaQByAGUAZgBvAHgALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGMAYwBvAHUAbgB0AHMALgBmAGkAcgBlAGYAbwB4AC4AYwBvAG0ALgBjAG4AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgB5AG8AdQB0AHUAYgBlAC4AYwBvAG0AOwAgAHMAdAB5AGwAZQAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AIAAnAHUAbgBzAGEAZgBlAC0AaQBuAGwAaQBuAGUAJwA7ACAAYwBvAG4AbgBlAGMAdAAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQB0AGEAZwBtAGEAbgBhAGcAZQByAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQAtAGEAbgBhAGwAeQB0AGkAYwBzAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtAC8AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtAC4AYwBuAC8AOwAgAGMAaABpAGwAZAAtAHMAcgBjACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUAdABhAGcAbQBhAG4AYQBnAGUAcgAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALQBhAG4AYQBsAHkAdABpAGMAcwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AeQBvAHUAdAB1AGIAZQAtAG4AbwBjAG8AbwBrAGkAZQAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHQAcgBhAGMAawBlAHIAdABlAHMAdAAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AcwB1AHIAdgBlAHkAZwBpAHoAbQBvAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAGEAYwBjAG8AdQBuAHQAcwAuAGYAaQByAGUAZgBvAHgALgBjAG8AbQAuAGMAbgAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQAA",
output: {
URISpec: "https://www.mozilla.org/en-US/firefox/accounts/",
originAttributes: {
firstPartyDomain: "",
inIsolatedMozBrowser: false,
privateBrowsingId: 0,
userContextId: 0,
geckoViewSessionContextId: "",
partitionKey: "",
},
// Within Bug 965637 we removed CSP from Principals. Already serialized Principals however should still deserialize correctly (just without the CSP).
// "cspJSON": "{\"csp-policies\":[{\"child-src\":[\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://www.youtube-nocookie.com\",\"https://trackertest.org\",\"https://www.surveygizmo.com\",\"https://accounts.firefox.com\",\"https://accounts.firefox.com.cn\",\"https://www.youtube.com\"],\"connect-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://accounts.firefox.com/\",\"https://accounts.firefox.com.cn/\"],\"default-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\"],\"frame-src\":[\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://www.youtube-nocookie.com\",\"https://trackertest.org\",\"https://www.surveygizmo.com\",\"https://accounts.firefox.com\",\"https://accounts.firefox.com.cn\",\"https://www.youtube.com\"],\"img-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"data:\",\"https://mozilla.org\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://adservice.google.com\",\"https://adservice.google.de\",\"https://adservice.google.dk\",\"https://creativecommons.org\",\"https://ad.doubleclick.net\"],\"report-only\":false,\"script-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"'unsafe-inline'\",\"'unsafe-eval'\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://tagmanager.google.com\",\"https://www.youtube.com\",\"https://s.ytimg.com\"],\"style-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"'unsafe-inline'\"]}]}",
},
},
];
for (let test of serializedPrincipalsFromFirefox) {
let principal = E10SUtils.deserializePrincipal(test.input);
for (let key in principal.originAttributes) {
is(
principal.originAttributes[key],
test.output.originAttributes[key],
`Ensure value of ${key} is ${test.output.originAttributes[key]}`
);
}
if ("URI" in test.output && test.output.URI === false) {
is(
principal.isContentPrincipal,
false,
"Should have not have a URI for system"
);
} else {
is(
principal.spec,
test.output.URISpec,
`Should have spec ${test.output.URISpec}`
);
}
}
});

View File

@@ -0,0 +1,161 @@
"use strict";
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*
This test file exists to ensure whenever changes to principal serialization happens,
we guarantee that the data can be restored and generated into a new principal.
The tests are written to be brittle so we encode all versions of the changes into the tests.
*/
add_task(async function test_nullPrincipal() {
const nullId = "0";
// fields
const uri = 0;
const suffix = 1;
const nullReplaceRegex =
/moz-nullprincipal:{[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}}/;
const NULL_REPLACE = "NULL_PRINCIPAL_URL";
/*
This test should NOT be resilient to changes in versioning,
however it exists purely to verify the code doesn't unintentionally change without updating versioning and migration code.
*/
let tests = [
{
input: { OA: {} },
expected: `{"${nullId}":{"${uri}":"${NULL_REPLACE}"}}`,
},
{
input: { OA: {} },
expected: `{"${nullId}":{"${uri}":"${NULL_REPLACE}"}}`,
},
{
input: { OA: { userContextId: 0 } },
expected: `{"${nullId}":{"${uri}":"${NULL_REPLACE}"}}`,
},
{
input: { OA: { userContextId: 2 } },
expected: `{"${nullId}":{"${uri}":"${NULL_REPLACE}","${suffix}":"^userContextId=2"}}`,
},
{
input: { OA: { privateBrowsingId: 1 } },
expected: `{"${nullId}":{"${uri}":"${NULL_REPLACE}","${suffix}":"^privateBrowsingId=1"}}`,
},
{
input: { OA: { privateBrowsingId: 0 } },
expected: `{"${nullId}":{"${uri}":"${NULL_REPLACE}"}}`,
},
];
for (let test of tests) {
let p = Services.scriptSecurityManager.createNullPrincipal(test.input.OA);
let sp = E10SUtils.serializePrincipal(p);
// Not sure why cppjson is adding a \n here
let spr = sp.replace(nullReplaceRegex, NULL_REPLACE);
is(
test.expected,
spr,
"Expected serialized object for " + JSON.stringify(test.input)
);
let dp = E10SUtils.deserializePrincipal(sp);
// Check all the origin attributes
for (let key in test.input.OA) {
is(
dp.originAttributes[key],
test.input.OA[key],
"Ensure value of " + key + " is " + test.input.OA[key]
);
}
}
});
add_task(async function test_contentPrincipal() {
const contentId = "1";
// fields
const content = 0;
// const domain = 1;
const suffix = 2;
// const csp = 3;
/*
This test should NOT be resilient to changes in versioning,
however it exists purely to verify the code doesn't unintentionally change without updating versioning and migration code.
*/
let tests = [
{
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
input: { uri: "http://example.com/", OA: {} },
expected: `{"${contentId}":{"${content}":"http://example.com/"}}`,
},
{
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
input: { uri: "http://mozilla1.com/", OA: {} },
expected: `{"${contentId}":{"${content}":"http://mozilla1.com/"}}`,
},
{
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
input: { uri: "http://mozilla2.com/", OA: { userContextId: 0 } },
expected: `{"${contentId}":{"${content}":"http://mozilla2.com/"}}`,
},
{
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
input: { uri: "http://mozilla3.com/", OA: { userContextId: 2 } },
expected: `{"${contentId}":{"${content}":"http://mozilla3.com/","${suffix}":"^userContextId=2"}}`,
},
{
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
input: { uri: "http://mozilla4.com/", OA: { privateBrowsingId: 1 } },
expected: `{"${contentId}":{"${content}":"http://mozilla4.com/","${suffix}":"^privateBrowsingId=1"}}`,
},
{
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
input: { uri: "http://mozilla5.com/", OA: { privateBrowsingId: 0 } },
expected: `{"${contentId}":{"${content}":"http://mozilla5.com/"}}`,
},
];
for (let test of tests) {
let uri = Services.io.newURI(test.input.uri);
let p = Services.scriptSecurityManager.createContentPrincipal(
uri,
test.input.OA
);
let sp = E10SUtils.serializePrincipal(p);
is(test.expected, sp, "Expected serialized object for " + test.input.uri);
let dp = E10SUtils.deserializePrincipal(sp);
is(dp.URI.spec, test.input.uri, "Ensure spec is the same");
// Check all the origin attributes
for (let key in test.input.OA) {
is(
dp.originAttributes[key],
test.input.OA[key],
"Ensure value of " + key + " is " + test.input.OA[key]
);
}
}
});
add_task(async function test_systemPrincipal() {
const systemId = "3";
/*
This test should NOT be resilient to changes in versioning,
however it exists purely to verify the code doesn't unintentionally change without updating versioning and migration code.
*/
const expected = `{"${systemId}":{}}`;
let p = Services.scriptSecurityManager.getSystemPrincipal();
let sp = E10SUtils.serializePrincipal(p);
is(expected, sp, "Expected serialized object for system principal");
let dp = E10SUtils.deserializePrincipal(sp);
is(
dp,
Services.scriptSecurityManager.getSystemPrincipal(),
"Deserialized the system principal"
);
});

View File

@@ -0,0 +1,159 @@
"use strict";
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*
This test file exists to ensure whenever changes to principal serialization happens,
we guarantee that the data can be restored and generated into a new principal.
The tests are written to be brittle so we encode all versions of the changes into the tests.
*/
add_task(function test_nullPrincipal() {
/*
As Null principals are designed to be non deterministic we just need to ensure that
a previous serialized version matches what it was generated as.
This test should be resilient to changes in versioning, however it should also be duplicated for a new serialization change.
*/
// Principal created with: E10SUtils.serializePrincipal(Services.scriptSecurityManager.createNullPrincipal({ }));
let p = E10SUtils.deserializePrincipal(
"vQZuXxRvRHKDMXv9BbHtkAAAAAAAAAAAwAAAAAAAAEYAAAA4bW96LW51bGxwcmluY2lwYWw6ezU2Y2FjNTQwLTg2NGQtNDdlNy04ZTI1LTE2MTRlYWI1MTU1ZX0AAAAA"
);
is(
"moz-nullprincipal:{56cac540-864d-47e7-8e25-1614eab5155e}",
p.URI.spec,
"Deserialized principal doesn't have the correct URI"
);
// Principal created with: E10SUtils.serializePrincipal(Services.scriptSecurityManager.createNullPrincipal({ userContextId: 2 }));
let p2 = E10SUtils.deserializePrincipal(
"vQZuXxRvRHKDMXv9BbHtkAAAAAAAAAAAwAAAAAAAAEYAAAA4bW96LW51bGxwcmluY2lwYWw6ezA1ZjllN2JhLWIwODMtNDJhMi1iNDdkLTZiODRmNmYwYTM3OX0AAAAQXnVzZXJDb250ZXh0SWQ9Mg=="
);
is(
"moz-nullprincipal:{05f9e7ba-b083-42a2-b47d-6b84f6f0a379}",
p2.URI.spec,
"Deserialized principal doesn't have the correct URI"
);
is(p2.originAttributes.userContextId, 2, "Expected a userContextId of 2");
});
add_task(async function test_realHistoryCheck() {
/*
This test should be resilient to changes in principal serialization, if these are failing then it's likely the code will break session storage.
To recreate this for another version, copy the function into the browser console, browse some pages and printHistory.
Generated with:
function printHistory() {
let tests = [];
let entries = SessionStore.getSessionHistory(gBrowser.selectedTab).entries.map((entry) => { return entry.triggeringPrincipal_base64 });
entries.push(E10SUtils.serializePrincipal(gBrowser.selectedTab.linkedBrowser._contentPrincipal));
for (let entry of entries) {
console.log(entry);
let testData = {};
testData.input = entry;
let principal = E10SUtils.deserializePrincipal(testData.input);
testData.output = {};
if (principal.URI === null) {
testData.output.URI = false;
} else {
testData.output.URISpec = principal.URI.spec;
}
testData.output.originAttributes = principal.originAttributes;
tests.push(testData);
}
return tests;
}
printHistory(); // Copy this into: serializedPrincipalsFromFirefox
*/
let serializedPrincipalsFromFirefox = [
{
input: "SmIS26zLEdO3ZQBgsLbOywAAAAAAAAAAwAAAAAAAAEY=",
output: {
URI: false,
originAttributes: {
firstPartyDomain: "",
inIsolatedMozBrowser: false,
privateBrowsingId: 0,
userContextId: 0,
geckoViewSessionContextId: "",
partitionKey: "",
},
},
},
{
input:
"ZT4OTT7kRfqycpfCC8AeuAAAAAAAAAAAwAAAAAAAAEYB3pRy0IA0EdOTmQAQS6D9QJIHOlRteE8wkTq4cYEyCMYAAAAC/////wAAAbsBAAAAe2h0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTLz91dG1fc291cmNlPXd3dy5tb3ppbGxhLm9yZyZ1dG1fbWVkaXVtPXJlZmVycmFsJnV0bV9jYW1wYWlnbj1uYXYmdXRtX2NvbnRlbnQ9ZGV2ZWxvcGVycwAAAAAAAAAFAAAACAAAABUAAAAA/////wAAAAD/////AAAACAAAABUAAAAdAAAAXgAAAB0AAAAHAAAAHQAAAAcAAAAkAAAAAAAAAAD/////AAAAAP////8AAAAlAAAAVgAAAAD/////AQAAAAAAAAAAAAAAAA==",
output: {
URISpec:
"https://developer.mozilla.org/en-US/?utm_source=www.mozilla.org&utm_medium=referral&utm_campaign=nav&utm_content=developers",
originAttributes: {
firstPartyDomain: "",
inIsolatedMozBrowser: false,
privateBrowsingId: 0,
userContextId: 0,
geckoViewSessionContextId: "",
partitionKey: "",
},
},
},
{
input: "SmIS26zLEdO3ZQBgsLbOywAAAAAAAAAAwAAAAAAAAEY=",
output: {
URI: false,
originAttributes: {
firstPartyDomain: "",
inIsolatedMozBrowser: false,
privateBrowsingId: 0,
userContextId: 0,
geckoViewSessionContextId: "",
partitionKey: "",
},
},
},
{
input:
"vQZuXxRvRHKDMXv9BbHtkAAAAAAAAAAAwAAAAAAAAEYAAAA4bW96LW51bGxwcmluY2lwYWw6ezA0NWNhMThkLTQzNmMtNDc0NC1iYmI2LWIxYTE1MzY2ZGY3OX0AAAAA",
output: {
URISpec: "moz-nullprincipal:{045ca18d-436c-4744-bbb6-b1a15366df79}",
originAttributes: {
firstPartyDomain: "",
inIsolatedMozBrowser: false,
privateBrowsingId: 0,
userContextId: 0,
geckoViewSessionContextId: "",
partitionKey: "",
},
},
},
];
for (let test of serializedPrincipalsFromFirefox) {
let principal = E10SUtils.deserializePrincipal(test.input);
for (let key in principal.originAttributes) {
is(
principal.originAttributes[key],
test.output.originAttributes[key],
`Ensure value of ${key} is ${test.output.originAttributes[key]}`
);
}
if ("URI" in test.output && test.output.URI === false) {
is(
principal.isContentPrincipal,
false,
"Should have not have a URI for system"
);
} else {
is(
principal.spec,
test.output.URISpec,
`Should have spec ${test.output.URISpec}`
);
}
}
});

View File

@@ -0,0 +1,11 @@
[DEFAULT]
prefs = [
"zen.window-sync.enabled=false",
]
["browser_gesture_navigation.js"]
["browser_gesture_scroll.js"]
skip-if = [
"os == 'mac' && os_version == '15.30' && arch == 'aarch64'", # Bug 1988390
]

View File

@@ -0,0 +1,231 @@
"use strict";
add_setup(async () => {
// Disable window occlusion. See bug 1733955 / bug 1779559.
if (navigator.platform.indexOf("Win") == 0) {
await SpecialPowers.pushPrefEnv({
set: [["widget.windows.window_occlusion_tracking.enabled", false]],
});
}
});
add_task(async () => {
// Open a new browser window to make sure there is no navigation history.
const newBrowser = await BrowserTestUtils.openNewBrowserWindow({});
let event = {
direction: SimpleGestureEvent.DIRECTION_LEFT,
};
ok(!newBrowser.gGestureSupport._shouldDoSwipeGesture(event));
event = {
direction: SimpleGestureEvent.DIRECTION_RIGHT,
};
ok(!newBrowser.gGestureSupport._shouldDoSwipeGesture(event));
await BrowserTestUtils.closeWindow(newBrowser);
});
function createSimpleGestureEvent(type, direction) {
let event = document.createEvent("SimpleGestureEvent");
event.initSimpleGestureEvent(
type,
false /* canBubble */,
false /* cancelableArg */,
window,
0 /* detail */,
0 /* screenX */,
0 /* screenY */,
0 /* clientX */,
0 /* clientY */,
false /* ctrlKey */,
false /* altKey */,
false /* shiftKey */,
false /* metaKey */,
0 /* button */,
null /* relatedTarget */,
0 /* allowedDirections */,
direction,
1 /* delta */
);
return event;
}
add_task(async () => {
await SpecialPowers.pushPrefEnv({
set: [["ui.swipeAnimationEnabled", false]],
});
// Open a new browser window and load two pages so that the browser can go
// back but can't go forward.
const newWindow = await BrowserTestUtils.openNewBrowserWindow({});
// gHistroySwipeAnimation gets initialized in a requestIdleCallback so we need
// to wait for the initialization.
await TestUtils.waitForCondition(() => {
return (
// There's no explicit notification for the initialization, so we wait
// until `isLTR` matches the browser locale state.
newWindow.gHistorySwipeAnimation.isLTR != Services.locale.isAppLocaleRTL
);
});
BrowserTestUtils.startLoadingURIString(
newWindow.gBrowser.selectedBrowser,
"about:mozilla"
);
await BrowserTestUtils.browserLoaded(
newWindow.gBrowser.selectedBrowser,
false,
"about:mozilla"
);
BrowserTestUtils.startLoadingURIString(
newWindow.gBrowser.selectedBrowser,
"about:about"
);
await BrowserTestUtils.browserLoaded(
newWindow.gBrowser.selectedBrowser,
false,
"about:about"
);
let event = createSimpleGestureEvent(
"MozSwipeGestureMayStart",
SimpleGestureEvent.DIRECTION_LEFT
);
newWindow.gGestureSupport._shouldDoSwipeGesture(event);
// Assuming we are on LTR environment.
is(
event.allowedDirections,
SimpleGestureEvent.DIRECTION_LEFT,
"Allows only swiping to left, i.e. backward"
);
event = createSimpleGestureEvent(
"MozSwipeGestureMayStart",
SimpleGestureEvent.DIRECTION_RIGHT
);
newWindow.gGestureSupport._shouldDoSwipeGesture(event);
is(
event.allowedDirections,
SimpleGestureEvent.DIRECTION_LEFT,
"Allows only swiping to left, i.e. backward"
);
await BrowserTestUtils.closeWindow(newWindow);
});
add_task(async () => {
await SpecialPowers.pushPrefEnv({
set: [["ui.swipeAnimationEnabled", true]],
});
// Open a new browser window and load two pages so that the browser can go
// back but can't go forward.
const newWindow = await BrowserTestUtils.openNewBrowserWindow({});
if (!newWindow.gHistorySwipeAnimation._isSupported()) {
await BrowserTestUtils.closeWindow(newWindow);
return;
}
function sendSwipeSequence(sendEnd) {
let event = createSimpleGestureEvent(
"MozSwipeGestureMayStart",
SimpleGestureEvent.DIRECTION_LEFT
);
newWindow.gGestureSupport.handleEvent(event);
event = createSimpleGestureEvent(
"MozSwipeGestureStart",
SimpleGestureEvent.DIRECTION_LEFT
);
newWindow.gGestureSupport.handleEvent(event);
event = createSimpleGestureEvent(
"MozSwipeGestureUpdate",
SimpleGestureEvent.DIRECTION_LEFT
);
newWindow.gGestureSupport.handleEvent(event);
event = createSimpleGestureEvent(
"MozSwipeGestureUpdate",
SimpleGestureEvent.DIRECTION_LEFT
);
newWindow.gGestureSupport.handleEvent(event);
if (sendEnd) {
sendSwipeEnd();
}
}
function sendSwipeEnd() {
let event = createSimpleGestureEvent(
"MozSwipeGestureEnd",
SimpleGestureEvent.DIRECTION_LEFT
);
newWindow.gGestureSupport.handleEvent(event);
}
// gHistroySwipeAnimation gets initialized in a requestIdleCallback so we need
// to wait for the initialization.
await TestUtils.waitForCondition(() => {
return (
// There's no explicit notification for the initialization, so we wait
// until `isLTR` matches the browser locale state.
newWindow.gHistorySwipeAnimation.isLTR != Services.locale.isAppLocaleRTL
);
});
BrowserTestUtils.startLoadingURIString(
newWindow.gBrowser.selectedBrowser,
"about:mozilla"
);
await BrowserTestUtils.browserLoaded(
newWindow.gBrowser.selectedBrowser,
false,
"about:mozilla"
);
BrowserTestUtils.startLoadingURIString(
newWindow.gBrowser.selectedBrowser,
"about:about"
);
await BrowserTestUtils.browserLoaded(
newWindow.gBrowser.selectedBrowser,
false,
"about:about"
);
// Start a swipe that's not enough to navigate
sendSwipeSequence(/* sendEnd = */ true);
// Wait two frames
await new Promise(r =>
window.requestAnimationFrame(() => window.requestAnimationFrame(r))
);
// The transition to fully stopped shouldn't have had enough time yet to
// become fully stopped.
ok(
newWindow.gHistorySwipeAnimation._isStoppingAnimation,
"should be stopping anim"
);
// Start another swipe.
sendSwipeSequence(/* sendEnd = */ false);
// Wait two frames
await new Promise(r =>
window.requestAnimationFrame(() => window.requestAnimationFrame(r))
);
// We should have started a new swipe, ie we shouldn't be stopping.
ok(
!newWindow.gHistorySwipeAnimation._isStoppingAnimation,
"should not be stopping anim"
);
sendSwipeEnd();
await BrowserTestUtils.closeWindow(newWindow);
});

View File

@@ -0,0 +1,43 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const URL = `data:text/html,
<!doctype html>
<meta charset="utf-8">
<div style="height: 200vh"></div>
Some content
<div style="height: 200vh"></div>
`;
function getScrollPos(browser) {
return SpecialPowers.spawn(browser, [], () => {
return {
scrollY: Math.round(content.scrollY),
scrollMaxY: Math.round(content.scrollMaxY),
};
});
}
// Bug 1964901
add_task(async function test_scroll_command() {
await BrowserTestUtils.withNewTab(URL, async function (browser) {
{
let { scrollY, scrollMaxY } = await getScrollPos(browser);
is(scrollY, 0, "Should be scrolled to the top");
isnot(scrollMaxY, 0, "Should be scrollable");
}
let scrollEvent = SpecialPowers.spawn(browser, [], async () => {
await new Promise(r => {
content.addEventListener("scrollend", r, { once: true });
});
});
goDoCommand("cmd_scrollBottom");
await scrollEvent;
{
let { scrollY, scrollMaxY } = await getScrollPos(browser);
is(scrollY, scrollMaxY, "Should be scrolled to the bottom");
}
});
});

View File

@@ -0,0 +1,12 @@
[DEFAULT]
["browser_contentAreaClick_subframe_javascript.js"]
support-files = [
"file_contentAreaClick_subframe_javascript.html"
]
["browser_javascript_links.js"]
disabled="Disabled by import_external_tests.py"
support-files = [
"file_javascript_links_subframe.html"
]

View File

@@ -0,0 +1,49 @@
const gExampleComRoot = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content/",
"https://example.com/"
);
const IFRAME_FILE = "file_contentAreaClick_subframe_javascript.html";
add_task(async function () {
await SpecialPowers.pushPrefEnv({
set: [["browser.link.alternative_click.block_javascript", false]],
});
await BrowserTestUtils.withNewTab(
`data:text/html,<iframe src="${gExampleComRoot + IFRAME_FILE}"></iframe>`,
async browser => {
let newTabPromise = BrowserTestUtils.waitForNewTab(
gBrowser,
"about:blank"
);
let javascriptRanPromise = TestUtils.topicObserved(
"contentAreaClick-javascriptRan"
);
// ctrl/cmd-click the link in the subframe. This should cause it to be
// loaded in a new tab.
info("Clicking link");
let expectedRemoteType =
browser.browsingContext.children[0].currentRemoteType;
await BrowserTestUtils.synthesizeMouseAtCenter(
"a",
{ ctrlKey: true, metaKey: true },
browser.browsingContext.children[0]
);
info("Waiting for new tab");
let newTab = await newTabPromise;
info("Waiting to be notified that the javascript ran");
await javascriptRanPromise;
is(
newTab.linkedBrowser.remoteType,
expectedRemoteType,
"new tab was loaded in expected process"
);
info("Removing the tab");
BrowserTestUtils.removeTab(newTab);
}
);
});

View File

@@ -0,0 +1,88 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TEST_PATH = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content/",
"https://example.com/"
);
const IFRAME_PATH = TEST_PATH + "file_javascript_links_subframe.html";
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [
["browser.link.alternative_click.block_javascript", true],
["browser.tabs.opentabfor.middleclick", true],
["middlemouse.paste", false],
["middlemouse.contentLoadURL", false],
["general.autoScroll", false],
],
});
});
add_task(async function () {
await BrowserTestUtils.withNewTab(
`data:text/html,<a href="javascript:alert(1);">click me`,
async browser => {
await BrowserTestUtils.synthesizeMouseAtCenter(
"a",
{ button: 0, ctrlKey: true, metaKey: true },
browser
);
is(
gBrowser.tabs.length,
2,
"Accel+click on javascript: link shouldn't open a new tab"
);
await BrowserTestUtils.synthesizeMouseAtCenter(
"a",
{ button: 1 },
browser
);
is(
gBrowser.tabs.length,
2,
"Middle click on javascript: link shouldn't open a new tab"
);
await BrowserTestUtils.synthesizeMouseAtCenter(
"a",
{ button: 0, shiftKey: true },
browser
);
// This is fragile and might miss the new window, but the test will fail
// anyway when finishing with an extra window left behind.
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(resolve => setTimeout(resolve, 200));
is(
BrowserWindowTracker.windowCount,
1,
"Shift+click on javascript: link shouldn't open a new window"
);
}
);
});
add_task(async function iframe_link() {
await BrowserTestUtils.withNewTab(
`data:text/html,<iframe src="${IFRAME_PATH}"></iframe>`,
async browser => {
// ctrl/cmd-click the link in the subframe.
await BrowserTestUtils.synthesizeMouseAtCenter(
"a",
{ ctrlKey: true, metaKey: true },
browser.browsingContext.children[0]
);
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(resolve => setTimeout(resolve, 200));
is(
gBrowser.tabs.length,
2,
"Click on javascript: link in iframe shouldn't open a new tab"
);
}
);
});

View File

@@ -0,0 +1,2 @@
<!DOCTYPE html>
<a href="javascript:SpecialPowers.notifyObserversInParentProcess(null, 'contentAreaClick-javascriptRan');">link</a>

View File

@@ -0,0 +1,2 @@
<!DOCTYPE html>
<a href="javascript:alert(1)">click me

View File

@@ -8,11 +8,18 @@
BROWSER_CHROME_MANIFESTS += [
"alerts/browser.toml",
"backforward/browser.toml",
"caps/browser.toml",
"gesture/browser.toml",
"linkHandling/browser.toml",
"readermode/browser.toml",
"safebrowsing/browser.toml",
"sessionstore/browser.toml",
"shell/browser.toml",
"tabMediaIndicator/browser.toml",
"tooltiptext/browser.toml",
"zoom/browser.toml",
]
XPCSHELL_TESTS_MANIFESTS += [
"remote-settings/xpcshell.toml",

View File

@@ -0,0 +1,197 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
BrowserTestUtils: "resource://testing-common/BrowserTestUtils.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.sys.mjs",
TestUtils: "resource://testing-common/TestUtils.sys.mjs",
});
export var SessionStoreTestUtils = {
/**
* Running this init allows helpers to access test scope helpers, like Assert
* and SimpleTest.
* Tests should call this init() before using the helpers which rely on properties assign here.
*
* @param {object} scope The global scope where tests are being run.
* @param {DOMWindow} windowGlobal The global window object, for acessing gBrowser etc.
*/
init(scope, windowGlobal) {
if (!scope) {
throw new Error(
"Must initialize SessionStoreTestUtils with a test scope"
);
}
if (!windowGlobal) {
throw new Error("this.windowGlobal must be defined when we init");
}
this._scopeRef = new WeakRef(scope);
this._windowGlobalRef = new WeakRef(windowGlobal);
},
get info() {
return this._scopeRef.deref()?.info;
},
get registerCleanupFunction() {
return this._scopeRef.deref()?.registerCleanupFunction;
},
get windowGlobal() {
return this._windowGlobalRef.deref();
},
async closeTab(tab) {
await lazy.TabStateFlusher.flush(tab.linkedBrowser);
let sessionUpdatePromise =
lazy.BrowserTestUtils.waitForSessionStoreUpdate(tab);
lazy.BrowserTestUtils.removeTab(tab);
await sessionUpdatePromise;
},
async openAndCloseTab(window, url) {
let { updatePromise } = await lazy.BrowserTestUtils.withNewTab(
{ url, gBrowser: window.gBrowser },
async browser => {
return {
updatePromise: lazy.BrowserTestUtils.waitForSessionStoreUpdate({
linkedBrowser: browser,
}),
};
}
);
await updatePromise;
return lazy.TestUtils.topicObserved("sessionstore-closed-objects-changed");
},
// This assumes that tests will at least have some state/entries
waitForBrowserState(aState, aSetStateCallback) {
if (typeof aState == "string") {
aState = JSON.parse(aState);
}
if (typeof aState != "object") {
throw new TypeError(
"Argument must be an object or a JSON representation of an object"
);
}
if (!this.windowGlobal) {
throw new Error(
"no windowGlobal defined, please call init() first with the scope and window object"
);
}
let windows = [this.windowGlobal];
let tabsRestored = 0;
let expectedTabsRestored = 0;
let expectedWindows = aState.windows.length;
let windowsOpen = 1;
let listening = false;
let windowObserving = false;
let restoreHiddenTabs = Services.prefs.getBoolPref(
"browser.sessionstore.restore_hidden_tabs"
);
// This should match the |restoreTabsLazily| value that
// SessionStore.restoreWindow() uses.
let restoreTabsLazily =
Services.prefs.getBoolPref("browser.sessionstore.restore_on_demand") &&
Services.prefs.getBoolPref("browser.sessionstore.restore_tabs_lazily");
aState.windows.forEach(function (winState) {
winState.tabs.forEach(function (tabState) {
if (!restoreTabsLazily && (restoreHiddenTabs || !tabState.hidden)) {
expectedTabsRestored++;
}
});
});
// If there are only hidden tabs and restoreHiddenTabs = false, we still
// expect one of them to be restored because it gets shown automatically.
// Otherwise if lazy tab restore there will only be one tab restored per window.
if (!expectedTabsRestored) {
expectedTabsRestored = 1;
} else if (restoreTabsLazily) {
expectedTabsRestored = aState.windows.length;
}
function onSSTabRestored() {
if (++tabsRestored == expectedTabsRestored) {
// Remove the event listener from each window
windows.forEach(function (win) {
win.gBrowser.tabContainer.removeEventListener(
"SSTabRestored",
onSSTabRestored,
true
);
});
listening = false;
SessionStoreTestUtils.info("running " + aSetStateCallback.name);
lazy.TestUtils.executeSoon(aSetStateCallback);
}
}
// Used to add our listener to further windows so we can catch SSTabRestored
// coming from them when creating a multi-window state.
function windowObserver(aSubject, aTopic) {
if (aTopic == "domwindowopened") {
let newWindow = aSubject;
newWindow.addEventListener(
"load",
function () {
if (++windowsOpen == expectedWindows) {
Services.ww.unregisterNotification(windowObserver);
windowObserving = false;
}
// Track this window so we can remove the progress listener later
windows.push(newWindow);
// Add the progress listener
newWindow.gBrowser.tabContainer.addEventListener(
"SSTabRestored",
onSSTabRestored,
true
);
},
{ once: true }
);
}
}
// We only want to register the notification if we expect more than 1 window
if (expectedWindows > 1) {
this.registerCleanupFunction(function () {
if (windowObserving) {
Services.ww.unregisterNotification(windowObserver);
}
});
windowObserving = true;
Services.ww.registerNotification(windowObserver);
}
this.registerCleanupFunction(function () {
if (listening) {
windows.forEach(function (win) {
win.gBrowser.tabContainer.removeEventListener(
"SSTabRestored",
onSSTabRestored,
true
);
});
}
});
// Add the event listener for this window as well.
listening = true;
this.windowGlobal.gBrowser.tabContainer.addEventListener(
"SSTabRestored",
onSSTabRestored,
true
);
// Ensure setBrowserState() doesn't remove the initial tab.
this.windowGlobal.gBrowser.selectedTab = this.windowGlobal.gBrowser.tabs[0];
// Finally, call setBrowserState
lazy.SessionStore.setBrowserState(JSON.stringify(aState));
},
promiseBrowserState(aState) {
return new Promise(resolve => this.waitForBrowserState(aState, resolve));
},
};

View File

@@ -0,0 +1,483 @@
[DEFAULT]
support-files = [
"head.js",
"browser_formdata_sample.html",
"browser_formdata_xpath_sample.html",
"browser_frametree_sample.html",
"browser_frametree_sample_frameset.html",
"browser_frametree_sample_iframes.html",
"browser_frame_history_index.html",
"browser_frame_history_index2.html",
"browser_frame_history_index_blank.html",
"browser_frame_history_a.html",
"browser_frame_history_b.html",
"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",
"browser_speculative_connect.html",
"coopHeaderCommon.sjs",
"restore_redirect_http.html",
"restore_redirect_http.html^headers^",
"restore_redirect_js.html",
"restore_redirect_target.html",
"empty.html",
"coop_coep.html",
"coop_coep.html^headers^",
]
# remove this after bug 1628486 is landed
prefs = [
"zen.window-sync.enabled=false",
"network.cookie.cookieBehavior=5",
"gfx.font_rendering.fallback.async=false",
"browser.sessionstore.closedTabsFromAllWindows=true",
"browser.sessionstore.closedTabsFromClosedWindows=true",
# Override the test default, allowing the session restore infobar to be shown if necessary
"browser.startup.couldRestoreSession.count=0",
]
["browser_1933485_tab_groups_history.js"]
disabled="Disabled by import_external_tests.py"
["browser_1953801_tab_groups_history_close_other.js"]
disabled="Disabled by import_external_tests.py"
["browser_aboutPrivateBrowsing.js"]
["browser_aboutRestartrequired_noRestore.js"]
["browser_aboutSessionRestore.js"]
disabled="Disabled by import_external_tests.py"
["browser_async_duplicate_tab.js"]
support-files = ["file_async_duplicate_tab.html"]
["browser_async_flushes.js"]
support-files = ["file_async_flushes.html"]
run-if = [
"crashreporter",
]
["browser_async_remove_tab.js"]
skip-if = [
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && opt", # Bug 1787024
"os == 'win' && os_version == '11.26100' && arch == 'x86_64' && asan", # Bug 1787024
"os == 'win' && os_version == '11.26200' && arch == 'x86_64' && asan", # Bug 1787024
]
["browser_async_window_flushing.js"]
https_first_disabled = true
skip-if = [
"os == 'mac' && os_version == '10.15' && arch == 'x86_64' && opt", # Bug 1775616
"os == 'win' && os_version == '11.26100' && arch == 'x86_64' && asan", # Bug 1775616
"os == 'win' && os_version == '11.26200' && arch == 'x86_64' && asan", # Bug 1775616
"os == 'win' && os_version == '11.26100' && arch == 'x86_64' && opt", # Bug 1775616
"os == 'win' && os_version == '11.26200' && arch == 'x86_64' && opt", # Bug 1775616
]
["browser_attributes.js"]
["browser_background_tab_crash.js"]
https_first_disabled = true
run-if = [
"crashreporter",
]
tags = "os_integration"
["browser_backup_recovery.js"]
https_first_disabled = true
tags = "os_integration"
["browser_bfcache_telemetry.js"]
disabled="Disabled by import_external_tests.py"
["browser_broadcast.js"]
https_first_disabled = true
["browser_capabilities.js"]
["browser_cleaner.js"]
["browser_closedId.js"]
disabled="Disabled by import_external_tests.py"
["browser_closed_objects_changed_notifications_tabs.js"]
disabled="Disabled by import_external_tests.py"
["browser_closed_objects_changed_notifications_windows.js"]
disabled="Disabled by import_external_tests.py"
["browser_closed_tabs_closed_windows.js"]
disabled="Disabled by import_external_tests.py"
["browser_closed_tabs_windows.js"]
disabled="Disabled by import_external_tests.py"
["browser_cookies.js"]
["browser_cookies_legacy.js"]
["browser_cookies_partitioned.js"]
disabled="Disabled by import_external_tests.py"
["browser_cookies_privacy.js"]
["browser_cookies_sameSite.js"]
["browser_crashedTabs.js"]
disabled="Disabled by import_external_tests.py"
https_first_disabled = true
run-if = [
"crashreporter",
]
["browser_csp_policy_container_migration.js"]
["browser_docshell_uuid_consistency.js"]
["browser_duplicate_history.js"]
["browser_duplicate_tab_in_new_window.js"]
["browser_dying_cache.js"]
disabled="Disabled by import_external_tests.py"
["browser_dynamic_frames.js"]
["browser_firefoxView_restore.js"]
disabled="Disabled by import_external_tests.py"
["browser_firefoxView_selected_restore.js"]
disabled="Disabled by import_external_tests.py"
["browser_focus_after_restore.js"]
tags = "os_integration"
["browser_forget_async_closings.js"]
["browser_forget_closed_tab_window_byId.js"]
disabled="Disabled by import_external_tests.py"
https_first_disabled = true
["browser_formdata.js"]
["browser_formdata_cc.js"]
skip-if = [
"asan", # test runs too long
]
["browser_formdata_face.js"]
["browser_formdata_format.js"]
["browser_formdata_max_size.js"]
["browser_formdata_password.js"]
support-files = ["file_formdata_password.html"]
["browser_formdata_xpath.js"]
["browser_frame_history.js"]
["browser_frametree.js"]
https_first_disabled = true
["browser_global_store.js"]
["browser_history_persist.js"]
["browser_ignore_updates_crashed_tabs.js"]
https_first_disabled = true
run-if = [
"crashreporter",
]
skip-if = [
"asan",
]
["browser_label_and_icon.js"]
https_first_disabled = true
skip-if = [
"os == 'mac' && os_version == '10.15' && arch == 'x86_64' && opt", # Bug 1638958
"os == 'mac' && os_version == '14.70' && arch == 'x86_64' && opt && socketprocess_networking", # Bug 1868915
"os == 'mac' && os_version == '15.30' && arch == 'aarch64'", # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs
"os == 'win' && os_version == '11.26100' && arch == 'x86_64' && opt", # Bug 1775605
"os == 'win' && os_version == '11.26200' && arch == 'x86_64' && opt", # Bug 1775605
]
["browser_movePendingTabToNewWindow.js"]
disabled="Disabled by import_external_tests.py"
https_first_disabled = true
tags = "os_integration"
["browser_multiple_navigateAndRestore.js"]
["browser_multiple_select_after_load.js"]
["browser_navigation_api_restore.js"]
support-files = [
"empty_frame.html",
"entry1.html",
"entry2.html",
"entry3.html",
]
["browser_newtab_userTypedValue.js"]
disabled="Disabled by import_external_tests.py"
["browser_not_collect_when_idle.js"]
["browser_page_title.js"]
["browser_parentProcessRestoreHash.js"]
https_first_disabled = true
tags = "openUILinkIn"
["browser_pending_tabs.js"]
["browser_pinned_tabs.js"]
disabled="Disabled by import_external_tests.py"
skip-if = [
"ccov", # Bug 1625525
]
["browser_policy_container_not_stored.js"]
["browser_privatetabs.js"]
["browser_purge_domaindata.js"]
disabled="Disabled by import_external_tests.py"
["browser_purge_shistory.js"]
["browser_remoteness_flip_on_restore.js"]
["browser_reopen_all_windows.js"]
disabled="Disabled by import_external_tests.py"
https_first_disabled = true
skip-if = [
"asan", # high memory
]
tags = "os_integration"
["browser_replace_load.js"]
skip-if = [
"true", # Bug 1646894
]
["browser_restoreLastActionCorrectOrder.js"]
disabled="Disabled by import_external_tests.py"
["browser_restoreLastClosedTabOrWindowOrSession.js"]
disabled="Disabled by import_external_tests.py"
["browser_restoreTabContainer.js"]
["browser_restore_container_tabs_oa.js"]
disabled="Disabled by import_external_tests.py"
["browser_restore_cookies_noOriginAttributes.js"]
["browser_restore_pageProxyState.js"]
["browser_restore_private_tab_os.js"]
["browser_restore_redirect.js"]
https_first_disabled = true
["browser_restore_reversed_z_order.js"]
skip-if = [
"true", # Bug 1455602
]
["browser_restore_srcdoc.js"]
["browser_restore_tabless_window.js"]
disabled="Disabled by import_external_tests.py"
["browser_restore_verticalPinnedTabs.js"]
disabled="Disabled by import_external_tests.py"
["browser_restored_window_features.js"]
disabled="Disabled by import_external_tests.py"
skip-if = [
"os == 'win' && os_version == '11.26200' && asan", # Bug 2019745
]
["browser_revive_crashed_bg_tabs.js"]
https_first_disabled = true
run-if = [
"crashreporter",
]
["browser_scrollPositions.js"]
disabled="Disabled by import_external_tests.py"
https_first_disabled = true
run-if = [
"fission",
]
["browser_scrollPositionsReaderMode.js"]
disabled="Disabled by import_external_tests.py"
["browser_searchModeSwitcher_restore.js"]
disabled="Disabled by import_external_tests.py"
["browser_sessionHistory.js"]
https_first_disabled = true
support-files = ["file_sessionHistory_hashchange.html"]
["browser_sessionHistory_partitionedPrincipalToInherit.js"]
["browser_sessionStorage.js"]
tags = "os_integration"
["browser_sessionStorage_size.js"]
["browser_sessionStoreContainer.js"]
["browser_should_restore_tab.js"]
skip-if = [
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && debug && socketprocess_networking", # Bug 1848488
"os == 'win' && os_version == '11.26100' && arch == 'x86' && debug && verify-standalone",
"os == 'win' && os_version == '11.26200' && arch == 'x86' && debug && verify-standalone",
"os == 'win' && os_version == '11.26100' && arch == 'x86_64' && debug", # Bug 1848488
"os == 'win' && os_version == '11.26200' && arch == 'x86_64' && debug", # Bug 1848488
]
["browser_sizemodeBeforeMinimized.js"]
skip-if = [
"os == 'win' && os_version == '11.26200' && asan", # Bug 2019745
]
["browser_speculative_connect.js"]
disabled="Disabled by import_external_tests.py"
["browser_splitview_integer_ids.js"]
disabled="Disabled by import_external_tests.py"
["browser_splitview_restore_in_closed_window.js"]
disabled="Disabled by import_external_tests.py"
["browser_splitview_string_migration.js"]
disabled="Disabled by import_external_tests.py"
["browser_swapDocShells.js"]
disabled="Disabled by import_external_tests.py"
["browser_switch_remoteness.js"]
["browser_tab_groups_closed.js"]
disabled="Disabled by import_external_tests.py"
skip-if = [
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && debug && socketprocess_networking", # Bug 1934803
"os == 'win' && os_version == '11.26100' && arch == 'x86_64' && debug", # Bug 1934803
"os == 'win' && os_version == '11.26200' && arch == 'x86_64' && debug", # Bug 1934803
]
["browser_tab_groups_closed_groups_in_closed_windows.js"]
["browser_tab_groups_empty.js"]
["browser_tab_groups_restore_closed_in_closed_window.js"]
disabled="Disabled by import_external_tests.py"
["browser_tab_groups_restore_closed_in_open_window.js"]
disabled="Disabled by import_external_tests.py"
["browser_tab_groups_restore_closed_many_tabs.js"]
disabled="Disabled by import_external_tests.py"
skip-if = [
"os == 'mac' && os_version == '15.30' && arch == 'aarch64' && opt", # Bug 1954300
]
["browser_tab_groups_restore_multiple.js"]
disabled="Disabled by import_external_tests.py"
["browser_tab_groups_restore_saved.js"]
disabled="Disabled by import_external_tests.py"
["browser_tab_groups_restore_simple.js"]
disabled="Disabled by import_external_tests.py"
["browser_tab_groups_restore_to_group.js"]
["browser_tab_groups_save_on_removeAllTabsBut.js"]
disabled="Disabled by import_external_tests.py"
["browser_tab_groups_save_on_removeTabsToTheEnd.js"]
disabled="Disabled by import_external_tests.py"
["browser_tab_groups_save_on_removeTabsToTheStart.js"]
disabled="Disabled by import_external_tests.py"
["browser_tab_groups_save_on_window_close.js"]
disabled="Disabled by import_external_tests.py"
["browser_tab_groups_saved.js"]
skip-if = [
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && opt", # Bug 1945957
]
["browser_tab_groups_state.js"]
disabled="Disabled by import_external_tests.py"
["browser_tab_groups_undo.js"]
disabled="Disabled by import_external_tests.py"
skip-if = [
"os == 'mac' && os_version == '15.30' && arch == 'aarch64'", # Bug 2023967
]
["browser_tab_label_during_restore.js"]
disabled="Disabled by import_external_tests.py"
https_first_disabled = true
["browser_tab_notes_canonicalurl.js"]
["browser_tabicon_after_bg_tab_crash.js"]
run-if = [
"crashreporter",
]
["browser_tabs_in_urlbar.js"]
https_first_disabled = true
["browser_undoCloseById.js"]
disabled="Disabled by import_external_tests.py"
skip-if = [
"debug",
]
["browser_undoCloseById_targetWindow.js"]
["browser_unrestored_crashedTabs.js"]
run-if = [
"crashreporter",
]
["browser_upgrade_backup.js"]
skip-if = [
"asan",
"tsan",
]
["browser_urlbarSearchMode.js"]
disabled="Disabled by import_external_tests.py"
["browser_userTyped_restored_after_discard.js"]
["browser_windowRestore_perwindowpb.js"]
["browser_windowStateContainer.js"]
disabled="Disabled by import_external_tests.py"
["browser_wireframe_basic.js"]
disabled="Disabled by import_external_tests.py"

View File

@@ -0,0 +1,22 @@
"use strict";
const PREF = "network.cookie.cookieBehavior";
const PAGE_URL =
"http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_1234021_page.html";
const BEHAVIOR_REJECT = 2;
add_task(async function test() {
await pushPrefs([PREF, BEHAVIOR_REJECT]);
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: PAGE_URL,
},
async function handler(aBrowser) {
await TabStateFlusher.flush(aBrowser);
ok(true, "Flush didn't time out");
}
);
});

View File

@@ -0,0 +1,6 @@
<!doctype html>
<html>
<script>
sessionStorage;
</script>
</html>

View File

@@ -0,0 +1,12 @@
<html>
<head>
<script>
window.onbeforeunload = function() {
return true;
};
</script>
</head>
<body>
TEST PAGE
</body>
</html>

View File

@@ -0,0 +1,95 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(async function test() {
await SpecialPowers.pushPrefEnv({
set: [["dom.require_user_interaction_for_beforeunload", false]],
});
let url = "about:robots";
let tab0 = gBrowser.tabs[0];
let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
const staleAttributes = [
"activemedia-blocked",
"busy",
"pendingicon",
"progress",
"soundplaying",
];
for (let attr of staleAttributes) {
tab0.toggleAttribute(attr, true);
}
gBrowser.discardBrowser(tab0);
ok(!tab0.linkedPanel, "tab0 is suspended");
for (let attr of staleAttributes) {
ok(
!tab0.hasAttribute(attr),
`discarding browser removes "${attr}" tab attribute`
);
}
await BrowserTestUtils.switchTab(gBrowser, tab0);
ok(tab0.linkedPanel, "selecting tab unsuspends it");
// Test that active tab is not able to be suspended.
gBrowser.discardBrowser(tab0);
ok(tab0.linkedPanel, "active tab is not able to be suspended");
// Test that tab that is closing is not able to be suspended.
gBrowser._beginRemoveTab(tab1);
gBrowser.discardBrowser(tab1);
ok(tab1.linkedPanel, "cannot suspend a tab that is closing");
gBrowser._endRemoveTab(tab1);
// Open tab containing a page which has a beforeunload handler which shows a prompt.
url =
"http://example.com/browser/browser/components/sessionstore/test/browser_1284886_suspend_tab.html";
tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
await BrowserTestUtils.switchTab(gBrowser, tab0);
// Test that tab with beforeunload handler which would show a prompt cannot be suspended.
gBrowser.discardBrowser(tab1);
ok(
tab1.linkedPanel,
"cannot suspend a tab with beforeunload handler which would show a prompt"
);
// Test that tab with beforeunload handler which would show a prompt will be suspended if forced.
gBrowser.discardBrowser(tab1, true);
ok(
!tab1.linkedPanel,
"force suspending a tab with beforeunload handler which would show a prompt"
);
BrowserTestUtils.removeTab(tab1);
// Open tab containing a page which has a beforeunload handler which does not show a prompt.
url =
"http://example.com/browser/browser/components/sessionstore/test/browser_1284886_suspend_tab_2.html";
tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
await BrowserTestUtils.switchTab(gBrowser, tab0);
// Test that tab with beforeunload handler which would not show a prompt can be suspended.
gBrowser.discardBrowser(tab1);
ok(
!tab1.linkedPanel,
"can suspend a tab with beforeunload handler which would not show a prompt"
);
BrowserTestUtils.removeTab(tab1);
// Test that non-remote tab is not able to be suspended.
url = "about:robots";
tab1 = BrowserTestUtils.addTab(gBrowser, url, { forceNotRemote: true });
await promiseBrowserLoaded(tab1.linkedBrowser, true, url);
await BrowserTestUtils.switchTab(gBrowser, tab1);
await BrowserTestUtils.switchTab(gBrowser, tab0);
gBrowser.discardBrowser(tab1);
ok(tab1.linkedPanel, "cannot suspend a remote tab");
BrowserTestUtils.removeTab(tab1);
});

View File

@@ -0,0 +1,11 @@
<html>
<head>
<script>
window.onbeforeunload = function() {
};
</script>
</head>
<body>
TEST PAGE
</body>
</html>

View File

@@ -0,0 +1,39 @@
add_task(async function test() {
const win = await BrowserTestUtils.openNewBrowserWindow();
async function changeSizeMode(mode) {
let promise = BrowserTestUtils.waitForEvent(win, "sizemodechange");
win[mode]();
await promise;
}
if (win.windowState != win.STATE_NORMAL) {
await changeSizeMode("restore");
}
const { outerWidth, outerHeight, screenX, screenY } = win;
function checkCurrentState(sizemode) {
let state = ss.getWindowState(win);
let winState = state.windows[0];
let msgSuffix = ` should match on ${sizemode} mode`;
is(winState.width, outerWidth, "width" + msgSuffix);
is(winState.height, outerHeight, "height" + msgSuffix);
// The position attributes seem to be affected on macOS when the
// window gets maximized, so skip checking them for now.
if (AppConstants.platform != "macosx" || sizemode == "normal") {
is(winState.screenX, screenX, "screenX" + msgSuffix);
is(winState.screenY, screenY, "screenY" + msgSuffix);
}
is(winState.sizemode, sizemode, "sizemode should match");
}
checkCurrentState("normal");
await changeSizeMode("maximize");
checkCurrentState("maximized");
await changeSizeMode("minimize");
checkCurrentState("minimized");
// Clean up.
await BrowserTestUtils.closeWindow(win);
});

View File

@@ -0,0 +1,43 @@
"use strict";
const ORIG_STATE = SessionStore.getBrowserState();
registerCleanupFunction(async () => {
await SessionStoreTestUtils.promiseBrowserState(ORIG_STATE);
});
// This test ensures that closed tab groups are immediately stored in the
// closedGroups array, even if their history was not stored in the tab state at
// the time the tab group was closed.
add_task(
async function test_browser_1933485_tabGroupImmediatelyStoredInClosedGroups() {
let win = await promiseNewWindowLoaded();
const urls = ["about:blank", "about:robots", "https://www.example.com/"];
const tabs = urls.map(url => BrowserTestUtils.addTab(win.gBrowser, url));
await Promise.all(
tabs.map((t, i) =>
BrowserTestUtils.browserLoaded(t.linkedBrowser, { wantLoad: urls[i] })
)
);
const tabGroup = win.gBrowser.addTabGroup(tabs);
let removePromise = BrowserTestUtils.waitForEvent(
tabGroup,
"TabGroupRemoved"
);
win.gBrowser.removeTabGroup(tabGroup);
await removePromise;
const closedGroupState = SessionStore.getClosedTabGroups(win);
Assert.equal(
closedGroupState.length,
1,
"Tab group is stored in closedGroups"
);
await BrowserTestUtils.closeWindow(win);
}
);

View File

@@ -0,0 +1,55 @@
"use strict";
const ORIG_STATE = SessionStore.getBrowserState();
registerCleanupFunction(async () => {
await SessionStoreTestUtils.promiseBrowserState(ORIG_STATE);
});
add_task(
async function test_browser_1953801_tabGroupImmediatelyStoredInSavedGroupsOnCloseOtherTabs() {
let savedGroupState;
let win = await promiseNewWindowLoaded();
savedGroupState = SessionStore.getSavedTabGroups(win);
Assert.equal(
savedGroupState.length,
0,
"Should start with no saved groups"
);
const tabs = [
BrowserTestUtils.addTab(win.gBrowser, "about:robots"),
BrowserTestUtils.addTab(win.gBrowser, "about:robots"),
BrowserTestUtils.addTab(win.gBrowser, "https://www.example.com"),
];
await Promise.all(tabs.map(t => promiseBrowserLoaded(t.linkedBrowser)));
const tabGroup = win.gBrowser.addTabGroup([tabs[1], tabs[2]], {
label: "group-to-save",
});
await TestUtils.waitForCondition(
() => SessionStore.getWindowState(win).windows[0].groups.length == 1,
"Waiting for group to appear in session store"
);
let removePromise = BrowserTestUtils.waitForEvent(
tabGroup,
"TabGroupRemoved"
);
win.gBrowser.removeAllTabsBut(win.gBrowser.tabs[0]);
await removePromise;
savedGroupState = SessionStore.getSavedTabGroups(win);
Assert.equal(
savedGroupState.length,
1,
"Tab group is stored in saved groups"
);
await BrowserTestUtils.closeWindow(win);
forgetClosedWindows();
SessionStore.forgetSavedTabGroup(tabGroup.id);
}
);

View File

@@ -0,0 +1,199 @@
/* 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 (B) for Bug 248970 */
waitForExplicitFinish();
let windowsToClose = [];
let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
let filePath = file.path;
let fieldList = {
"//input[@name='input']": Date.now().toString(16),
"//input[@name='spaced 1']": Math.random().toString(),
"//input[3]": "three",
"//input[@type='checkbox']": true,
"//input[@name='uncheck']": false,
"//input[@type='radio'][1]": false,
"//input[@type='radio'][2]": true,
"//input[@type='radio'][3]": false,
"//select": 2,
"//select[@multiple]": [1, 3],
"//textarea[1]": "",
"//textarea[2]": "Some text... " + Math.random(),
"//textarea[3]": "Some more text\n" + new Date(),
"//input[@type='file']": filePath,
};
registerCleanupFunction(async function () {
for (let win of windowsToClose) {
await BrowserTestUtils.closeWindow(win);
}
});
function checkNoThrow(aLambda) {
try {
return aLambda() || true;
} catch (ex) {}
return false;
}
function getElementByXPath(aTab, aQuery) {
let doc = aTab.linkedBrowser.contentDocument;
let xptype = doc.defaultView.XPathResult.FIRST_ORDERED_NODE_TYPE;
return doc.evaluate(aQuery, doc, null, xptype, null).singleNodeValue;
}
function setFormValue(aTab, aQuery, aValue) {
let node = getElementByXPath(aTab, aQuery);
if (typeof aValue == "string") {
node.value = aValue;
} else if (typeof aValue == "boolean") {
node.checked = aValue;
} else if (typeof aValue == "number") {
node.selectedIndex = aValue;
} else {
Array.prototype.forEach.call(
node.options,
(aOpt, aIx) => (aOpt.selected = aValue.indexOf(aIx) > -1)
);
}
}
function compareFormValue(aTab, aQuery, aValue) {
let node = getElementByXPath(aTab, aQuery);
if (!node) {
return false;
}
if (ChromeUtils.getClassName(node) === "HTMLInputElement") {
return (
aValue ==
(node.type == "checkbox" || node.type == "radio"
? node.checked
: node.value)
);
}
if (ChromeUtils.getClassName(node) === "HTMLTextAreaElement") {
return aValue == node.value;
}
if (!node.multiple) {
return aValue == node.selectedIndex;
}
return Array.prototype.every.call(
node.options,
(aOpt, aIx) => aValue.indexOf(aIx) > -1 == aOpt.selected
);
}
/**
* Test (B) : Session data restoration between windows
*/
let rootDir = getRootDirectory(gTestPath);
const testURL = rootDir + "browser_248970_b_sample.html";
const testURL2 =
"http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_248970_b_sample.html";
whenNewWindowLoaded({ private: false }, function (aWin) {
windowsToClose.push(aWin);
// get closed tab count
let count = ss.getClosedTabCountForWindow(aWin);
let max_tabs_undo = Services.prefs.getIntPref(
"browser.sessionstore.max_tabs_undo"
);
ok(
0 <= count && count <= max_tabs_undo,
"getClosedTabCountForWindow should return zero or at most max_tabs_undo"
);
// setup a state for tab (A) so we can check later that is restored
let value = "Value " + Math.random();
let state = { entries: [{ url: testURL }], extData: { key: value } };
// public session, add new tab: (A)
let tab_A = BrowserTestUtils.addTab(aWin.gBrowser, testURL);
ss.setTabState(tab_A, JSON.stringify(state));
promiseBrowserLoaded(tab_A.linkedBrowser).then(() => {
// make sure that the next closed tab will increase getClosedTabCountForWindow
Services.prefs.setIntPref(
"browser.sessionstore.max_tabs_undo",
max_tabs_undo + 1
);
// populate tab_A with form data
for (let i in fieldList) {
setFormValue(tab_A, i, fieldList[i]);
}
// public session, close tab: (A)
aWin.gBrowser.removeTab(tab_A);
// verify that closedTabCount increased
Assert.greater(
ss.getClosedTabCountForWindow(aWin),
count,
"getClosedTabCountForWindow has increased after closing a tab"
);
// verify tab: (A), in undo list
let tab_A_restored = checkNoThrow(() => ss.undoCloseTab(aWin, 0));
ok(tab_A_restored, "a tab is in undo list");
promiseTabRestored(tab_A_restored).then(() => {
is(
testURL,
tab_A_restored.linkedBrowser.currentURI.spec,
"it's the same tab that we expect"
);
aWin.gBrowser.removeTab(tab_A_restored);
whenNewWindowLoaded({ private: true }, function (win) {
windowsToClose.push(win);
// setup a state for tab (B) so we can check that its duplicated
// properly
let key1 = "key1";
let value1 = "Value " + Math.random();
let state1 = {
entries: [{ url: testURL2 }],
extData: { key1: value1 },
};
let tab_B = BrowserTestUtils.addTab(win.gBrowser, testURL2);
promiseTabState(tab_B, state1).then(() => {
// populate tab: (B) with different form data
for (let item in fieldList) {
setFormValue(tab_B, item, fieldList[item]);
}
// duplicate tab: (B)
let tab_C = win.gBrowser.duplicateTab(tab_B);
promiseTabRestored(tab_C).then(() => {
// verify the correctness of the duplicated tab
is(
ss.getCustomTabValue(tab_C, key1),
value1,
"tab successfully duplicated - correct state"
);
for (let item in fieldList) {
ok(
compareFormValue(tab_C, item, fieldList[item]),
'The value for "' + item + '" was correctly duplicated'
);
}
// private browsing session, close tab: (C) and (B)
win.gBrowser.removeTab(tab_C);
win.gBrowser.removeTab(tab_B);
finish();
});
});
});
});
});
});
}

View File

@@ -0,0 +1,37 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<meta charset="utf-8">
<title>Test for bug 248970</title>
<h3>Text Fields</h3>
<input type="text" name="input">
<input type="text" name="spaced 1">
<input>
<h3>Checkboxes and Radio buttons</h3>
<input type="checkbox" name="check"> Check 1
<input type="checkbox" name="uncheck" checked> Check 2
<p>
<input type="radio" name="group" value="1"> Radio 1
<input type="radio" name="group" value="some"> Radio 2
<input type="radio" name="group" checked> Radio 3
<h3>Selects</h3>
<select name="any">
<option value="1"> Select 1
<option value="some"> Select 2
<option>Select 3
</select>
<select multiple="multiple">
<option value=1> Multi-select 1
<option value=2> Multi-select 2
<option value=3> Multi-select 3
<option value=4> Multi-select 4
</select>
<h3>Text Areas</h3>
<textarea name="testarea"></textarea>
<textarea name="sized one" rows="5" cols="25"></textarea>
<textarea></textarea>
<h3>File Selector</h3>
<input type="file">

View File

@@ -0,0 +1,39 @@
/* 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() {
/** Test for Bug 339445 */
let testURL =
"http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_339445_sample.html";
let tab = BrowserTestUtils.addTab(gBrowser, testURL);
await promiseBrowserLoaded(tab.linkedBrowser, true, testURL);
await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
let doc = content.document;
is(
doc.getElementById("storageTestItem").textContent,
"PENDING",
"sessionStorage value has been set"
);
});
let tab2 = gBrowser.duplicateTab(tab);
await promiseTabRestored(tab2);
await SpecialPowers.spawn(tab2.linkedBrowser, [], function () {
let doc2 = content.document;
is(
doc2.getElementById("storageTestItem").textContent,
"SUCCESS",
"sessionStorage value has been duplicated"
);
});
// clean up
BrowserTestUtils.removeTab(tab2);
BrowserTestUtils.removeTab(tab);
});

View File

@@ -0,0 +1,18 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<meta charset="utf-8">
<title>Test for bug 339445</title>
storageTestItem = <span id="storageTestItem">FAIL</span>
<!--
storageTestItem's textContent will be one of the following:
* FAIL : sessionStorage wasn't available
* PENDING : the test value has been initialized on first load
* SUCCESS : the test value was correctly retrieved
-->
<script type="application/javascript">
document.getElementById("storageTestItem").textContent =
sessionStorage.storageTestItem || "PENDING";
sessionStorage.storageTestItem = "SUCCESS";
</script>

View File

@@ -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/. */
function test() {
/** Test for Bug 345898 */
// all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
Assert.throws(
() => ss.getWindowState({}),
/NS_ERROR_ILLEGAL_VALUE/,
"Invalid window for getWindowState throws"
);
Assert.throws(
() => ss.setWindowState({}, "", false),
/NS_ERROR_ILLEGAL_VALUE/,
"Invalid window for setWindowState throws"
);
Assert.throws(
() => ss.getTabState({}),
/NS_ERROR_ILLEGAL_VALUE/,
"Invalid tab for getTabState throws"
);
Assert.throws(
() => ss.setTabState({}, "{}"),
/NS_ERROR_ILLEGAL_VALUE/,
"Invalid tab state for setTabState throws"
);
Assert.throws(
() => ss.setTabState({}, JSON.stringify({ entries: [] })),
/NS_ERROR_ILLEGAL_VALUE/,
"Invalid tab for setTabState throws"
);
Assert.throws(
() => ss.duplicateTab({}, {}),
/NS_ERROR_ILLEGAL_VALUE/,
"Invalid tab for duplicateTab throws"
);
Assert.throws(
() => ss.duplicateTab({}, gBrowser.selectedTab),
/NS_ERROR_ILLEGAL_VALUE/,
"Invalid window for duplicateTab throws"
);
Assert.throws(
() => ss.getClosedTabDataForWindow({}),
/NS_ERROR_ILLEGAL_VALUE/,
"Invalid window for getClosedTabData throws"
);
Assert.throws(
() => ss.undoCloseTab({}, 0),
/NS_ERROR_ILLEGAL_VALUE/,
"Invalid window for undoCloseTab throws"
);
Assert.throws(
() => ss.undoCloseTab(window, -1),
/NS_ERROR_ILLEGAL_VALUE/,
"Invalid index for undoCloseTab throws"
);
Assert.throws(
() => ss.getCustomWindowValue({}, ""),
/NS_ERROR_ILLEGAL_VALUE/,
"Invalid window for getCustomWindowValue throws"
);
Assert.throws(
() => ss.setCustomWindowValue({}, "", ""),
/NS_ERROR_ILLEGAL_VALUE/,
"Invalid window for setCustomWindowValue throws"
);
}

View File

@@ -0,0 +1,136 @@
"use strict";
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [["dom.ipc.processCount", 1]],
});
});
add_task(async function () {
/** Test for Bug 350525 */
function test(aLambda) {
try {
return aLambda() || true;
} catch (ex) {}
return false;
}
/**
* setCustomWindowValue, et al.
*/
let key = "Unique name: " + Date.now();
let value = "Unique value: " + Math.random();
// test adding
ok(
test(() => ss.setCustomWindowValue(window, key, value)),
"set a window value"
);
// test retrieving
is(
ss.getCustomWindowValue(window, key),
value,
"stored window value matches original"
);
// test deleting
ok(
test(() => ss.deleteCustomWindowValue(window, key)),
"delete the window value"
);
// value should not exist post-delete
is(ss.getCustomWindowValue(window, key), "", "window value was deleted");
// test deleting a non-existent value
ok(
test(() => ss.deleteCustomWindowValue(window, key)),
"delete non-existent window value"
);
/**
* setCustomTabValue, et al.
*/
key = "Unique name: " + Math.random();
value = "Unique value: " + Date.now();
let tab = BrowserTestUtils.addTab(gBrowser);
tab.linkedBrowser.stop();
// test adding
ok(
test(() => ss.setCustomTabValue(tab, key, value)),
"store a tab value"
);
// test retrieving
is(ss.getCustomTabValue(tab, key), value, "stored tab value match original");
// test deleting
ok(
test(() => ss.deleteCustomTabValue(tab, key)),
"delete the tab value"
);
// value should not exist post-delete
is(ss.getCustomTabValue(tab, key), "", "tab value was deleted");
// test deleting a non-existent value
ok(
test(() => ss.deleteCustomTabValue(tab, key)),
"delete non-existent tab value"
);
// clean up
await promiseRemoveTabAndSessionState(tab);
/**
* getClosedTabCountForWindow, undoCloseTab
*/
// get closed tab count
let count = ss.getClosedTabCountForWindow(window);
let max_tabs_undo = Services.prefs.getIntPref(
"browser.sessionstore.max_tabs_undo"
);
ok(
0 <= count && count <= max_tabs_undo,
"getClosedTabCountForWindow returns zero or at most max_tabs_undo"
);
// create a new tab
let testURL = "about:mozilla";
tab = BrowserTestUtils.addTab(gBrowser, testURL);
await promiseBrowserLoaded(tab.linkedBrowser);
// make sure that the next closed tab will increase getClosedTabCountForWindow
Services.prefs.setIntPref(
"browser.sessionstore.max_tabs_undo",
max_tabs_undo + 1
);
registerCleanupFunction(() =>
Services.prefs.clearUserPref("browser.sessionstore.max_tabs_undo")
);
// remove tab
await promiseRemoveTabAndSessionState(tab);
// getClosedTabCountForWindow
let newcount = ss.getClosedTabCountForWindow(window);
Assert.greater(
newcount,
count,
"after closing a tab, getClosedTabCountForWindow has been incremented"
);
// undoCloseTab
tab = test(() => ss.undoCloseTab(window, 0));
ok(tab, "undoCloseTab doesn't throw");
await promiseTabRestored(tab);
is(tab.linkedBrowser.currentURI.spec, testURL, "correct tab was reopened");
// clean up
gBrowser.removeTab(tab);
});

View File

@@ -0,0 +1,490 @@
/* 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";
/**
* Checks that restoring the last browser window in session is actually
* working.
*
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=354894
* Note: It is implicitly tested that restoring the last window works when
* non-browser windows are around. The "Run Tests" window as well as the main
* browser window (wherein the test code gets executed) won't be considered
* browser windows. To achiveve this said main browser window has its windowtype
* attribute modified so that it's not considered a browser window any longer.
* This is crucial, because otherwise there would be two browser windows around,
* said main test window and the one opened by the tests, and hence the new
* logic wouldn't be executed at all.
* Note: Mac only tests the new notifications, as restoring the last window is
* not enabled on that platform (platform shim; the application is kept running
* although there are no windows left)
* Note: There is a difference when closing a browser window with
* BrowserCommands.tryToCloseWindow() as opposed to close(). The former will make
* nsSessionStore restore a window next time it gets a chance and will post
* notifications. The latter won't.
*/
// The rejection "BrowserWindowTracker.getTopWindow(...) is null" is left
// unhandled in some cases. This bug should be fixed, but for the moment this
// file allows a class of rejections.
//
// NOTE: Allowing a whole class of rejections should be avoided. Normally you
// should use "expectUncaughtRejection" to flag individual failures.
const { PromiseTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/PromiseTestUtils.sys.mjs"
);
PromiseTestUtils.allowMatchingRejectionsGlobally(/getTopWindow/);
// Some urls that might be opened in tabs and/or popups
// Do not use about:blank:
// That one is reserved for special purposes in the tests
const TEST_URLS = ["about:mozilla", "about:buildconfig"];
// Number of -request notifications to except
// remember to adjust when adding new tests
const NOTIFICATIONS_EXPECTED = 6;
// Window features of popup windows
const POPUP_FEATURES = "toolbar=no,resizable=no,status=no";
// Window features of browser windows
const CHROME_FEATURES = "chrome,all,dialog=no";
const IS_MAC = navigator.platform.match(/Mac/);
/**
* Returns an Object with two properties:
* open (int):
* A count of how many non-closed navigator:browser windows there are.
* winstates (int):
* A count of how many windows there are in the SessionStore state.
*/
function getBrowserWindowsCount() {
let open = 0;
for (let win of Services.wm.getEnumerator("navigator:browser")) {
if (!win.closed) {
++open;
}
}
let winstates = JSON.parse(ss.getBrowserState()).windows.length;
return { open, winstates };
}
add_setup(async function () {
// Make sure we've only got one browser window to start with
let { open, winstates } = getBrowserWindowsCount();
is(open, 1, "Should only be one open window");
is(winstates, 1, "Should only be one window state in SessionStore");
// This test takes some time to run, and it could timeout randomly.
// So we require a longer timeout. See bug 528219.
requestLongerTimeout(3);
// Make the main test window not count as a browser window any longer
let oldWinType = document.documentElement.getAttribute("windowtype");
document.documentElement.setAttribute("windowtype", "navigator:testrunner");
registerCleanupFunction(() => {
document.documentElement.setAttribute("windowtype", oldWinType);
});
});
/**
* Sets up one of our tests by setting the right preferences, and
* then opening up a browser window preloaded with some tabs.
*
* @param options (Object)
* An object that can contain the following properties:
*
* private:
* Whether or not the opened window should be private.
*
* denyFirst:
* Whether or not the first window that attempts to close
* via closeWindowForRestoration should be denied.
*
* @param testFunction (Function*)
* A generator function that yields Promises to be run
* once the test has been set up.
*
* @returns Promise
* Resolves once the test has been cleaned up.
*/
let setupTest = async function (options, testFunction) {
await pushPrefs(
["browser.startup.page", 3],
["browser.tabs.warnOnClose", false]
);
// SessionStartup caches pref values, but as this test tries to simulate a
// startup scenario, we'll reset them here.
SessionStartup.resetForTest();
// Observe these, and also use to count the number of hits
let observing = {
"browser-lastwindow-close-requested": 0,
"browser-lastwindow-close-granted": 0,
};
/**
* Helper: Will observe and handle the notifications for us
*/
let hitCount = 0;
function observer(aCancel, aTopic) {
// count so that we later may compare
observing[aTopic]++;
// handle some tests
if (options.denyFirst && ++hitCount == 1) {
aCancel.QueryInterface(Ci.nsISupportsPRBool).data = true;
}
}
for (let o in observing) {
Services.obs.addObserver(observer, o);
}
let newWin = await promiseNewWindowLoaded({
private: options.private || false,
});
await injectTestTabs(newWin);
await testFunction(newWin, observing);
let count = getBrowserWindowsCount();
is(count.open, 0, "Got right number of open windows");
is(count.winstates, 1, "Got right number of stored window states");
for (let o in observing) {
Services.obs.removeObserver(observer, o);
}
await popPrefs();
// Act like nothing ever happened.
SessionStartup.resetForTest();
};
/**
* Loads a TEST_URLS into a browser window.
*
* @param win (Window)
* The browser window to load the tabs in
*/
function injectTestTabs(win) {
let promises = TEST_URLS.map(url =>
BrowserTestUtils.addTab(win.gBrowser, url)
).map(tab => BrowserTestUtils.browserLoaded(tab.linkedBrowser));
return Promise.all(promises);
}
/**
* Attempts to close a window via BrowserCommands.tryToCloseWindow so that
* we get the browser-lastwindow-close-requested and
* browser-lastwindow-close-granted observer notifications.
*
* @param win (Window)
* The window to try to close
* @returns Promise
* Resolves to true if the window closed, or false if the window
* was denied the ability to close.
*/
function closeWindowForRestoration(win) {
return new Promise(resolve => {
let closePromise = BrowserTestUtils.windowClosed(win);
win.BrowserCommands.tryToCloseWindow();
if (!win.closed) {
resolve(false);
return;
}
closePromise.then(() => {
resolve(true);
});
});
}
/**
* Normal in-session restore
*
* Note: Non-Mac only
*
* Should do the following:
* 1. Open a new browser window
* 2. Add some tabs
* 3. Close that window
* 4. Opening another window
* 5. Checks that state is restored
*/
add_task(async function test_open_close_normal() {
if (IS_MAC) {
return;
}
await setupTest({ denyFirst: true }, async function (newWin, obs) {
let closed = await closeWindowForRestoration(newWin);
ok(!closed, "First close request should have been denied");
closed = await closeWindowForRestoration(newWin);
ok(closed, "Second close request should be accepted");
newWin = await promiseNewWindowLoaded();
is(
newWin.gBrowser.browsers.length,
TEST_URLS.length + 2,
"Restored window in-session with otherpopup windows around"
);
// Note that this will not result in the the browser-lastwindow-close
// notifications firing for this other newWin.
await BrowserTestUtils.closeWindow(newWin);
// setupTest gave us a window which was denied for closing once, and then
// closed.
is(
obs["browser-lastwindow-close-requested"],
2,
"Got expected browser-lastwindow-close-requested notifications"
);
is(
obs["browser-lastwindow-close-granted"],
1,
"Got expected browser-lastwindow-close-granted notifications"
);
});
});
/**
* PrivateBrowsing in-session restore
*
* Note: Non-Mac only
*
* Should do the following:
* 1. Open a new browser window A
* 2. Add some tabs
* 3. Close the window A as the last window
* 4. Open a private browsing window B
* 5. Make sure that B didn't restore the tabs from A
* 6. Close private browsing window B
* 7. Open a new window C
* 8. Make sure that new window C has restored tabs from A
*/
add_task(async function test_open_close_private_browsing() {
if (IS_MAC) {
return;
}
await setupTest({}, async function (newWin, obs) {
let closed = await closeWindowForRestoration(newWin);
ok(closed, "Should be able to close the window");
newWin = await promiseNewWindowLoaded({ private: true });
is(
newWin.gBrowser.browsers.length,
1,
"Did not restore in private browsing mode"
);
closed = await closeWindowForRestoration(newWin);
ok(closed, "Should be able to close the window");
newWin = await promiseNewWindowLoaded();
is(
newWin.gBrowser.browsers.length,
TEST_URLS.length + 2,
"Restored tabs in a new non-private window"
);
// Note that this will not result in the the browser-lastwindow-close
// notifications firing for this other newWin.
await BrowserTestUtils.closeWindow(newWin);
// We closed two windows with closeWindowForRestoration, and both
// should have been successful.
is(
obs["browser-lastwindow-close-requested"],
2,
"Got expected browser-lastwindow-close-requested notifications"
);
is(
obs["browser-lastwindow-close-granted"],
2,
"Got expected browser-lastwindow-close-granted notifications"
);
});
});
/**
* Open some popup window to check it isn't restored. Instead nothing at all
* should be restored
*
* Note: Non-Mac only
*
* Should do the following:
* 1. Open a popup
* 2. Add another tab to the popup (so that it gets stored) and close it again
* 3. Open a window
* 4. Check that nothing at all is restored
* 5. Open two browser windows and close them again
* 6. undoCloseWindow() one
* 7. Open another browser window
* 8. Check that nothing at all is restored
*/
add_task(async function test_open_close_only_popup() {
if (IS_MAC) {
return;
}
await setupTest({}, async function (newWin, obs) {
// We actually don't care about the initial window in this test.
await BrowserTestUtils.closeWindow(newWin);
// This will cause nsSessionStore to restore a window the next time it
// gets a chance.
let popupPromise = BrowserTestUtils.waitForNewWindow();
openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]);
let popup = await popupPromise;
is(
popup.gBrowser.browsers.length,
1,
"Did not restore the popup window (1)"
);
let closed = await closeWindowForRestoration(popup);
ok(closed, "Should be able to close the window");
popupPromise = BrowserTestUtils.waitForNewWindow();
openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]);
popup = await popupPromise;
BrowserTestUtils.addTab(popup.gBrowser, TEST_URLS[0]);
is(
popup.gBrowser.browsers.length,
2,
"Did not restore to the popup window (2)"
);
await BrowserTestUtils.closeWindow(popup);
newWin = await promiseNewWindowLoaded();
isnot(
newWin.gBrowser.browsers.length,
2,
"Did not restore the popup window"
);
is(
TEST_URLS.indexOf(newWin.gBrowser.browsers[0].currentURI.spec),
-1,
"Did not restore the popup window (2)"
);
await BrowserTestUtils.closeWindow(newWin);
// We closed one popup window with closeWindowForRestoration, and popup
// windows should never fire the browser-lastwindow notifications.
is(
obs["browser-lastwindow-close-requested"],
0,
"Got expected browser-lastwindow-close-requested notifications"
);
is(
obs["browser-lastwindow-close-granted"],
0,
"Got expected browser-lastwindow-close-granted notifications"
);
});
});
/**
* Open some windows and do undoCloseWindow. This should prevent any
* restoring later in the test
*
* Note: Non-Mac only
*
* Should do the following:
* 1. Open two browser windows and close them again
* 2. undoCloseWindow() one
* 3. Open another browser window
* 4. Make sure nothing at all is restored
*/
add_task(async function test_open_close_restore_from_popup() {
if (IS_MAC) {
return;
}
await setupTest({}, async function (newWin) {
let newWin2 = await promiseNewWindowLoaded();
await injectTestTabs(newWin2);
let closed = await closeWindowForRestoration(newWin);
ok(closed, "Should be able to close the window");
closed = await closeWindowForRestoration(newWin2);
ok(closed, "Should be able to close the window");
let counts = getBrowserWindowsCount();
is(counts.open, 0, "Got right number of open windows");
is(counts.winstates, 1, "Got right number of window states");
newWin = SessionWindowUI.undoCloseWindow(0);
await BrowserTestUtils.waitForEvent(newWin, "load");
// Make sure we wait until this window is restored.
await BrowserTestUtils.waitForEvent(
newWin.gBrowser.tabContainer,
"SSTabRestored"
);
newWin2 = await promiseNewWindowLoaded();
is(
TEST_URLS.indexOf(newWin2.gBrowser.browsers[0].currentURI.spec),
-1,
"Did not restore, as undoCloseWindow() was last called (2)"
);
counts = getBrowserWindowsCount();
is(counts.open, 2, "Got right number of open windows");
is(counts.winstates, 3, "Got right number of window states");
await BrowserTestUtils.closeWindow(newWin);
await BrowserTestUtils.closeWindow(newWin2);
counts = getBrowserWindowsCount();
is(counts.open, 0, "Got right number of open windows");
is(counts.winstates, 1, "Got right number of window states");
});
});
/**
* Test if closing can be denied on Mac.
*
* Note: Mac only
*/
add_task(async function test_mac_notifications() {
if (!IS_MAC) {
return;
}
await setupTest({ denyFirst: true }, async function (newWin, obs) {
let closed = await closeWindowForRestoration(newWin);
ok(!closed, "First close attempt should be denied");
closed = await closeWindowForRestoration(newWin);
ok(closed, "Second close attempt should be granted");
// We tried closing once, and got denied. Then we tried again and
// succeeded. That means 2 close requests, and 1 close granted.
is(
obs["browser-lastwindow-close-requested"],
2,
"Got expected browser-lastwindow-close-requested notifications"
);
is(
obs["browser-lastwindow-close-granted"],
1,
"Got expected browser-lastwindow-close-granted notifications"
);
});
});

View File

@@ -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/. */
"use strict";
add_task(async function () {
// make sure that the next closed tab will increase getClosedTabCountForWindow
let max_tabs_undo = Services.prefs.getIntPref(
"browser.sessionstore.max_tabs_undo"
);
Services.prefs.setIntPref(
"browser.sessionstore.max_tabs_undo",
max_tabs_undo + 1
);
registerCleanupFunction(() =>
Services.prefs.clearUserPref("browser.sessionstore.max_tabs_undo")
);
forgetClosedTabs(window);
// restore a blank tab
let tab = BrowserTestUtils.addTab(gBrowser, "about:mozilla");
await promiseBrowserLoaded(tab.linkedBrowser);
let count = await promiseSHistoryCount(tab.linkedBrowser);
Assert.greaterOrEqual(
count,
1,
"the new tab does have at least one history entry"
);
await promiseTabState(tab, { entries: [] });
// We may have a different sessionHistory object if the tab
// switched from non-remote to remote.
count = await promiseSHistoryCount(tab.linkedBrowser);
is(count, 0, "the tab was restored without any history whatsoever");
await promiseRemoveTabAndSessionState(tab);
is(
ss.getClosedTabCountForWindow(window),
0,
"The closed blank tab wasn't added to Recently Closed Tabs"
);
});
function promiseSHistoryCount(browser) {
return SpecialPowers.spawn(browser, [], async function () {
return docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory.count;
});
}

View File

@@ -0,0 +1,103 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const URL = "about:config";
add_setup(async function () {
// Make sure that the field of which we restore the state is visible on load.
await SpecialPowers.pushPrefEnv({
set: [["browser.aboutConfig.showWarning", false]],
});
});
/**
* Bug 393716 - Basic tests for getTabState(), setTabState(), and duplicateTab().
*/
add_task(async function test_set_tabstate() {
let key = "Unique key: " + Date.now();
let value = "Unique value: " + Math.random();
// create a new tab
let tab = BrowserTestUtils.addTab(gBrowser, URL);
ss.setCustomTabValue(tab, key, value);
await promiseBrowserLoaded(tab.linkedBrowser);
// get the tab's state
await TabStateFlusher.flush(tab.linkedBrowser);
let state = ss.getTabState(tab);
ok(state, "get the tab's state");
// verify the tab state's integrity
state = JSON.parse(state);
ok(
state instanceof Object &&
state.entries instanceof Array &&
!!state.entries.length,
"state object seems valid"
);
ok(
state.entries.length == 1 && state.entries[0].url == URL,
"Got the expected state object (test URL)"
);
ok(
state.extData && state.extData[key] == value,
"Got the expected state object (test manually set tab value)"
);
// clean up
gBrowser.removeTab(tab);
});
add_task(async function test_set_tabstate_and_duplicate() {
let key2 = "key2";
let value2 = "Value " + Math.random();
let value3 = "Another value: " + Date.now();
let state = {
entries: [{ url: URL, triggeringPrincipal_base64 }],
extData: { key2: value2 },
};
// create a new tab
let tab = BrowserTestUtils.addTab(gBrowser);
// set the tab's state
ss.setTabState(tab, JSON.stringify(state));
await promiseBrowserLoaded(tab.linkedBrowser);
// verify the correctness of the restored tab
ok(
ss.getCustomTabValue(tab, key2) == value2 &&
tab.linkedBrowser.currentURI.spec == URL,
"the tab's state was correctly restored"
);
// add text data
await setPropertyOfFormField(
tab.linkedBrowser,
"#about-config-search",
"value",
value3
);
// duplicate the tab
let tab2 = ss.duplicateTab(window, tab);
await promiseTabRestored(tab2);
// verify the correctness of the duplicated tab
ok(
ss.getCustomTabValue(tab2, key2) == value2 &&
tab2.linkedBrowser.currentURI.spec == URL,
"correctly duplicated the tab's state"
);
let textbox = await getPropertyOfFormField(
tab2.linkedBrowser,
"#about-config-search",
"value"
);
is(textbox, value3, "also duplicated text data");
// clean up
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
});

View File

@@ -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/. */
"use strict";
const TEST_URL =
"data:text/html;charset=utf-8,<input%20id=txt>" +
"<input%20type=checkbox%20id=chk>";
/**
* This test ensures that closing a window is a reversible action. We will
* close the the window, restore it and check that all data has been restored.
* This includes window-specific data as well as form data for tabs.
*/
function test() {
waitForExplicitFinish();
let uniqueKey = "bug 394759";
let uniqueValue = "unik" + Date.now();
let uniqueText = "pi != " + Math.random();
// Clear the list of closed windows.
forgetClosedWindows();
provideWindow(function onTestURLLoaded(newWin) {
BrowserTestUtils.addTab(newWin.gBrowser).linkedBrowser.stop();
// Mark the window with some unique data to be restored later on.
ss.setCustomWindowValue(newWin, uniqueKey, uniqueValue);
let [txt] = newWin.content.document.querySelectorAll("#txt");
txt.value = uniqueText;
let browser = newWin.gBrowser.selectedBrowser;
setPropertyOfFormField(browser, "#chk", "checked", true).then(() => {
BrowserTestUtils.closeWindow(newWin).then(() => {
is(
ss.getClosedWindowCount(),
1,
"The closed window was added to Recently Closed Windows"
);
let data = SessionStore.getClosedWindowData();
// Verify that non JSON serialized data is the same as JSON serialized data.
is(
JSON.stringify(data),
ss.getClosedWindowData(),
"Non-serialized data is the same as serialized data"
);
ok(
data[0].title == TEST_URL &&
JSON.stringify(data[0]).indexOf(uniqueText) > -1,
"The closed window data was stored correctly"
);
// Reopen the closed window and ensure its integrity.
let newWin2 = ss.undoCloseWindow(0);
ok(
newWin2.isChromeWindow,
"undoCloseWindow actually returned a window"
);
is(
ss.getClosedWindowCount(),
0,
"The reopened window was removed from Recently Closed Windows"
);
// SSTabRestored will fire more than once, so we need to make sure we count them.
let restoredTabs = 0;
let expectedTabs = data[0].tabs.length;
newWin2.addEventListener(
"SSTabRestored",
function sstabrestoredListener() {
++restoredTabs;
info("Restored tab " + restoredTabs + "/" + expectedTabs);
if (restoredTabs < expectedTabs) {
return;
}
is(restoredTabs, expectedTabs, "Correct number of tabs restored");
newWin2.removeEventListener(
"SSTabRestored",
sstabrestoredListener,
true
);
is(
newWin2.gBrowser.tabs.length,
2,
"The window correctly restored 2 tabs"
);
is(
newWin2.gBrowser.currentURI.spec,
TEST_URL,
"The window correctly restored the URL"
);
let chk;
[txt, chk] =
newWin2.content.document.querySelectorAll("#txt, #chk");
ok(
txt.value == uniqueText && chk.checked,
"The window correctly restored the form"
);
is(
ss.getCustomWindowValue(newWin2, uniqueKey),
uniqueValue,
"The window correctly restored the data associated with it"
);
// Clean up.
BrowserTestUtils.closeWindow(newWin2).then(finish);
},
true
);
});
});
}, TEST_URL);
}

View File

@@ -0,0 +1,93 @@
/**
* Test helper function that opens a series of windows, closes them
* and then checks the closed window data from SessionStore against
* expected results.
*
* @param windowsToOpen (Array)
* An array of Objects, where each object must define a single
* property "isPopup" for whether or not the opened window should
* be a popup.
* @param expectedResults (Array)
* An Object with two properies: mac and other, where each points
* at yet another Object, with the following properties:
*
* popup (int):
* The number of popup windows we expect to be in the closed window
* data.
* normal (int):
* The number of normal windows we expect to be in the closed window
* data.
* @returns Promise
*/
function testWindows(windowsToOpen, expectedResults) {
return (async function () {
let num = 0;
for (let winData of windowsToOpen) {
let features = "chrome,dialog=no," + (winData.isPopup ? "all=no" : "all");
let url = "http://example.com/?window=" + num;
num = num + 1;
let openWindowPromise = BrowserTestUtils.waitForNewWindow({ url });
openDialog(AppConstants.BROWSER_CHROME_URL, "", features, url);
let win = await openWindowPromise;
await BrowserTestUtils.closeWindow(win);
}
let closedWindowData = ss.getClosedWindowData();
let numPopups = closedWindowData.filter(function (el) {
return el.isPopup;
}).length;
let numNormal = ss.getClosedWindowCount() - numPopups;
// #ifdef doesn't work in browser-chrome tests, so do a simple regex on platform
let oResults = navigator.platform.match(/Mac/)
? expectedResults.mac
: expectedResults.other;
is(
numPopups,
oResults.popup,
"There were " + oResults.popup + " popup windows to reopen"
);
is(
numNormal,
oResults.normal,
"There were " + oResults.normal + " normal windows to reopen"
);
})();
}
add_task(async function test_closed_window_states() {
// This test takes quite some time, and timeouts frequently, so we require
// more time to run.
// See Bug 518970.
requestLongerTimeout(2);
let windowsToOpen = [
{ isPopup: false },
{ isPopup: true },
{ isPopup: true },
{ isPopup: true },
{ isPopup: true },
{ isPopup: true },
];
let expectedResults = {
mac: { popup: 5, normal: 0 },
other: { popup: 5, normal: 1 },
};
await testWindows(windowsToOpen, expectedResults);
let windowsToOpen2 = [
{ isPopup: false },
{ isPopup: false },
{ isPopup: false },
{ isPopup: false },
{ isPopup: false },
{ isPopup: false },
];
let expectedResults2 = {
mac: { popup: 0, normal: 5 },
other: { popup: 0, normal: 5 },
};
await testWindows(windowsToOpen2, expectedResults2);
});

View File

@@ -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/. */
"use strict";
const TESTS = [
{ url: "about:config", key: "bug 394759 Non-PB", value: "uniq" + r() },
{ url: "about:mozilla", key: "bug 394759 PB", value: "uniq" + r() },
];
function promiseTestOpenCloseWindow(aIsPrivate, aTest) {
return (async function () {
let win = await BrowserTestUtils.openNewBrowserWindow({
private: aIsPrivate,
});
BrowserTestUtils.startLoadingURIString(
win.gBrowser.selectedBrowser,
aTest.url
);
await promiseBrowserLoaded(win.gBrowser.selectedBrowser, true, aTest.url);
// Mark the window with some unique data to be restored later on.
ss.setCustomWindowValue(win, aTest.key, aTest.value);
await TabStateFlusher.flushWindow(win);
// Close.
await BrowserTestUtils.closeWindow(win);
})();
}
function promiseTestOnWindow(aIsPrivate, aValue) {
return (async function () {
let win = await BrowserTestUtils.openNewBrowserWindow({
private: aIsPrivate,
});
await TabStateFlusher.flushWindow(win);
let data = ss.getClosedWindowData()[0];
is(
ss.getClosedWindowCount(),
1,
"Check that the closed window count hasn't changed"
);
Assert.greater(
JSON.stringify(data).indexOf(aValue),
-1,
"Check the closed window data was stored correctly"
);
registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
})();
}
add_setup(async function () {
forgetClosedWindows();
forgetClosedTabs(window);
});
add_task(async function main() {
await promiseTestOpenCloseWindow(false, TESTS[0]);
await promiseTestOpenCloseWindow(true, TESTS[1]);
await promiseTestOnWindow(false, TESTS[0].value);
await promiseTestOnWindow(true, TESTS[0].value);
});

View File

@@ -0,0 +1,250 @@
/* 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/. */
let { ForgetAboutSite } = ChromeUtils.importESModule(
"moz-src:///toolkit/components/forgetaboutsite/ForgetAboutSite.sys.mjs"
);
function promiseClearHistory() {
return new Promise(resolve => {
let observer = {
observe() {
Services.obs.removeObserver(
this,
"browser:purge-session-history-for-domain"
);
resolve();
},
};
Services.obs.addObserver(
observer,
"browser:purge-session-history-for-domain"
);
});
}
add_task(async function () {
// utility functions
function countClosedTabsByTitle(aClosedTabList, aTitle) {
return aClosedTabList.filter(aData => aData.title == aTitle).length;
}
function countOpenTabsByTitle(aOpenTabList, aTitle) {
return aOpenTabList.filter(aData =>
aData.entries.some(aEntry => aEntry.title == aTitle)
).length;
}
// backup old state
let oldState = ss.getBrowserState();
let oldState_wins = JSON.parse(oldState).windows.length;
if (oldState_wins != 1) {
ok(
false,
"oldState in test_purge has " + oldState_wins + " windows instead of 1"
);
}
// create a new state for testing
const REMEMBER = Date.now(),
FORGET = Math.random();
let testState = {
windows: [
{
tabs: [
{
entries: [
{ url: "http://example.com/", triggeringPrincipal_base64 },
],
},
],
selected: 1,
},
],
_closedWindows: [
// _closedWindows[0]
{
tabs: [
{
entries: [
{
url: "http://example.com/",
triggeringPrincipal_base64,
title: REMEMBER,
},
],
},
{
entries: [
{
url: "http://mozilla.org/",
triggeringPrincipal_base64,
title: FORGET,
},
],
},
],
selected: 2,
title: "mozilla.org",
_closedTabs: [],
closedGroups: [],
},
// _closedWindows[1]
{
tabs: [
{
entries: [
{
url: "http://mozilla.org/",
triggeringPrincipal_base64,
title: FORGET,
},
],
},
{
entries: [
{
url: "http://example.com/",
triggeringPrincipal_base64,
title: REMEMBER,
},
],
},
{
entries: [
{
url: "http://example.com/",
triggeringPrincipal_base64,
title: REMEMBER,
},
],
},
{
entries: [
{
url: "http://mozilla.org/",
triggeringPrincipal_base64,
title: FORGET,
},
],
},
{
entries: [
{
url: "http://example.com/",
triggeringPrincipal_base64,
title: REMEMBER,
},
],
},
],
selected: 5,
_closedTabs: [],
closedGroups: [],
},
// _closedWindows[2]
{
tabs: [
{
entries: [
{
url: "http://example.com/",
triggeringPrincipal_base64,
title: REMEMBER,
},
],
},
],
selected: 1,
_closedTabs: [
{
state: {
entries: [
{
url: "http://mozilla.org/",
triggeringPrincipal_base64,
title: FORGET,
},
{
url: "http://mozilla.org/again",
triggeringPrincipal_base64,
title: "doesn't matter",
},
],
},
pos: 1,
title: FORGET,
},
{
state: {
entries: [
{
url: "http://example.com",
triggeringPrincipal_base64,
title: REMEMBER,
},
],
},
title: REMEMBER,
},
],
closedGroups: [],
},
],
};
// set browser to test state
ss.setBrowserState(JSON.stringify(testState));
// purge domain & check that we purged correctly for closed windows
let clearHistoryPromise = promiseClearHistory();
await ForgetAboutSite.removeDataFromBaseDomain("mozilla.org");
await clearHistoryPromise;
let closedWindowData = ss.getClosedWindowData();
// First set of tests for _closedWindows[0] - tests basics
let win = closedWindowData[0];
is(win.tabs.length, 1, "1 tab was removed");
is(countOpenTabsByTitle(win.tabs, FORGET), 0, "The correct tab was removed");
is(
countOpenTabsByTitle(win.tabs, REMEMBER),
1,
"The correct tab was remembered"
);
is(win.selected, 1, "Selected tab has changed");
is(win.title, REMEMBER, "The window title was correctly updated");
// Test more complicated case
win = closedWindowData[1];
is(win.tabs.length, 3, "2 tabs were removed");
is(
countOpenTabsByTitle(win.tabs, FORGET),
0,
"The correct tabs were removed"
);
is(
countOpenTabsByTitle(win.tabs, REMEMBER),
3,
"The correct tabs were remembered"
);
is(win.selected, 3, "Selected tab has changed");
is(win.title, REMEMBER, "The window title was correctly updated");
// Tests handling of _closedTabs
win = closedWindowData[2];
is(
countClosedTabsByTitle(win._closedTabs, REMEMBER),
1,
"The correct number of tabs were removed, and the correct ones"
);
is(
countClosedTabsByTitle(win._closedTabs, FORGET),
0,
"All tabs to be forgotten were indeed removed"
);
// restore pre-test state
ss.setBrowserState(oldState);
});

View File

@@ -0,0 +1,52 @@
"use strict";
/**
* Tests that cookies are stored and restored correctly
* by sessionstore (bug 423132).
*/
add_task(async function () {
const testURL =
"http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_423132_sample.html";
Services.cookies.removeAll();
// make sure that sessionstore.js can be forced to be created by setting
// the interval pref to 0
await SpecialPowers.pushPrefEnv({
set: [["browser.sessionstore.interval", 0]],
});
let tab = BrowserTestUtils.addTab(gBrowser, testURL);
await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
await TabStateFlusher.flush(tab.linkedBrowser);
// get the sessionstore state for the window
let state = ss.getBrowserState();
// verify our cookie got set during pageload
let i = 0;
for (var cookie of Services.cookies.cookies) {
i++;
}
Assert.equal(i, 1, "expected one cookie");
// remove the cookie
Services.cookies.removeAll();
// restore the window state
await setBrowserState(state);
// at this point, the cookie should be restored...
for (var cookie2 of Services.cookies.cookies) {
if (cookie.name == cookie2.name) {
break;
}
}
is(cookie.name, cookie2.name, "cookie name successfully restored");
is(cookie.value, cookie2.value, "cookie value successfully restored");
is(cookie.path, cookie2.path, "cookie path successfully restored");
// clean up
Services.cookies.removeAll();
BrowserTestUtils.removeTab(gBrowser.tabs[1]);
});

View File

@@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript">
// generate an enormous random number...
var r = Math.floor(Math.random() * Math.pow(2, 62)).toString();
// ... and use it to set a randomly named cookie
document.cookie = r + "=value; path=/ohai";
</script>
<body>
</body>
</html>

View File

@@ -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 447951 */
waitForExplicitFinish();
const baseURL =
"http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_447951_sample.html#";
// Make sure the functionality added in bug 943339 doesn't affect the results
Services.prefs.setIntPref("browser.sessionstore.max_serialize_back", -1);
Services.prefs.setIntPref("browser.sessionstore.max_serialize_forward", -1);
registerCleanupFunction(function () {
Services.prefs.clearUserPref("browser.sessionstore.max_serialize_back");
Services.prefs.clearUserPref("browser.sessionstore.max_serialize_forward");
});
let tab = BrowserTestUtils.addTab(gBrowser);
BrowserTestUtils.browserLoaded(tab.linkedBrowser, {
wantLoad: "about:blank",
}).then(() => {
let tabState = { entries: [] };
let max_entries = Services.prefs.getIntPref(
"browser.sessionhistory.max_entries"
);
for (let i = 0; i < max_entries; i++) {
tabState.entries.push({ url: baseURL + i, triggeringPrincipal_base64 });
}
promiseTabState(tab, tabState)
.then(() => {
return TabStateFlusher.flush(tab.linkedBrowser);
})
.then(() => {
tabState = JSON.parse(ss.getTabState(tab));
is(
tabState.entries.length,
max_entries,
"session history filled to the limit"
);
is(tabState.entries[0].url, baseURL + 0, "... but not more");
// visit yet another anchor (appending it to session history)
SpecialPowers.spawn(tab.linkedBrowser, [], function () {
content.window.document.querySelector("a").click();
}).then(flushAndCheck);
function flushAndCheck() {
TabStateFlusher.flush(tab.linkedBrowser).then(check);
}
function check() {
tabState = JSON.parse(ss.getTabState(tab));
if (tab.linkedBrowser.currentURI.spec != baseURL + "end") {
// It may take a few passes through the event loop before we
// get the right URL.
executeSoon(flushAndCheck);
return;
}
is(
tab.linkedBrowser.currentURI.spec,
baseURL + "end",
"the new anchor was loaded"
);
is(
tabState.entries[tabState.entries.length - 1].url,
baseURL + "end",
"... and ignored"
);
is(
tabState.entries[0].url,
baseURL + 1,
"... and the first item was removed"
);
// clean up
gBrowser.removeTab(tab);
finish();
}
});
});
}

View File

@@ -0,0 +1,5 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Testcase for bug 447951</title>
<a href="#end">click me</a>

View File

@@ -0,0 +1,65 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
if (gFissionBrowser) {
addCoopTask(
"browser_454908_sample.html",
test_dont_save_passwords,
HTTPSROOT
);
}
addNonCoopTask("browser_454908_sample.html", test_dont_save_passwords, ROOT);
addNonCoopTask(
"browser_454908_sample.html",
test_dont_save_passwords,
HTTPROOT
);
addNonCoopTask(
"browser_454908_sample.html",
test_dont_save_passwords,
HTTPSROOT
);
const PASS = "pwd-" + Math.random();
/**
* Bug 454908 - Don't save/restore values of password fields.
*/
async function test_dont_save_passwords(aURL) {
// Make sure we do save form data.
Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
// Add a tab with a password field.
let tab = BrowserTestUtils.addTab(gBrowser, aURL);
let browser = tab.linkedBrowser;
await promiseBrowserLoaded(browser);
// Fill in some values.
let usernameValue = "User " + Math.random();
await setPropertyOfFormField(browser, "#username", "value", usernameValue);
await setPropertyOfFormField(browser, "#passwd", "value", PASS);
// Close and restore the tab.
await promiseRemoveTabAndSessionState(tab);
tab = ss.undoCloseTab(window, 0);
browser = tab.linkedBrowser;
await promiseTabRestored(tab);
// Check that password fields aren't saved/restored.
let username = await getPropertyOfFormField(browser, "#username", "value");
is(username, usernameValue, "username was saved/restored");
let passwd = await getPropertyOfFormField(browser, "#passwd", "value");
is(passwd, "", "password wasn't saved/restored");
// Write to disk and read our file.
await forceSaveState();
await promiseForEachSessionRestoreFile((state, key) =>
// Ensure that we have not saved our password.
ok(!state.includes(PASS), "password has not been written to file " + key)
);
// Cleanup.
gBrowser.removeTab(tab);
}

View File

@@ -0,0 +1,8 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<title>Test for bug 454908</title>
<h3>Dummy Login</h3>
<form>
<p>Username: <input type="text" id="username">
<p>Password: <input type="password" id="passwd">
</form>

View File

@@ -0,0 +1,90 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
addCoopTask(
"browser_456342_sample.xhtml",
test_restore_nonstandard_input_values,
HTTPSROOT
);
addNonCoopTask(
"browser_456342_sample.xhtml",
test_restore_nonstandard_input_values,
ROOT
);
addNonCoopTask(
"browser_456342_sample.xhtml",
test_restore_nonstandard_input_values,
HTTPROOT
);
addNonCoopTask(
"browser_456342_sample.xhtml",
test_restore_nonstandard_input_values,
HTTPSROOT
);
const EXPECTED_IDS = new Set(["searchTerm"]);
const EXPECTED_XPATHS = new Set([
"/xhtml:html/xhtml:body/xhtml:form/xhtml:p[2]/xhtml:input",
"/xhtml:html/xhtml:body/xhtml:form/xhtml:p[3]/xhtml:input[@name='fill-in']",
"/xhtml:html/xhtml:body/xhtml:form/xhtml:p[4]/xhtml:input[@name='mistyped']",
"/xhtml:html/xhtml:body/xhtml:form/xhtml:p[5]/xhtml:textarea[@name='textarea_pass']",
]);
/**
* Bug 456342 - Restore values from non-standard input field types.
*/
async function test_restore_nonstandard_input_values(aURL) {
// Add tab with various non-standard input field types.
let tab = BrowserTestUtils.addTab(gBrowser, aURL);
let browser = tab.linkedBrowser;
await promiseBrowserLoaded(browser);
// Fill in form values.
let expectedValue = Math.random();
await SpecialPowers.spawn(browser, [expectedValue], valueChild => {
for (let elem of content.document.forms[0].elements) {
elem.value = valueChild;
let event = elem.ownerDocument.createEvent("UIEvents");
event.initUIEvent("input", true, true, elem.documentGlobal, 0);
elem.dispatchEvent(event);
}
});
// Remove tab and check collected form data.
await promiseRemoveTabAndSessionState(tab);
let undoItems = ss.getClosedTabDataForWindow(window);
let savedFormData = undoItems[0].state.formdata;
let foundIds = 0;
for (let id of Object.keys(savedFormData.id)) {
ok(EXPECTED_IDS.has(id), `Check saved ID "${id}" was expected`);
is(
savedFormData.id[id],
"" + expectedValue,
`Check saved value for #${id}`
);
foundIds++;
}
let foundXpaths = 0;
for (let exp of Object.keys(savedFormData.xpath)) {
ok(EXPECTED_XPATHS.has(exp), `Check saved xpath "${exp}" was expected`);
is(
savedFormData.xpath[exp],
"" + expectedValue,
`Check saved value for ${exp}`
);
foundXpaths++;
}
is(foundIds, EXPECTED_IDS.size, "Check number of fields saved by ID");
is(
foundXpaths,
EXPECTED_XPATHS.size,
"Check number of fields saved by xpath"
);
}

View File

@@ -0,0 +1,46 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>Test for bug 456342</title></head>
<body>
<form>
<h3>Non-standard &lt;input&gt;s</h3>
<p>Search <input type="search" id="searchTerm"/></p>
<p>Image Search: <input type="image search" /></p>
<p>Autocomplete: <input type="autocomplete" name="fill-in"/></p>
<p>Mistyped: <input type="txet" name="mistyped"/></p>
<p>Invalid attr: <textarea type="password" name="textarea_pass"/></p>
<h3>Ignored types</h3>
<input type="hidden" name="hideme"/>
<input type="HIDDEN" name="hideme2"/>
<input type="submit" name="submit"/>
<input type="reset" name="reset"/>
<input type="image" name="image"/>
<input type="button" name="button"/>
<input type="password" name="password"/>
<input type="PassWord" name="password2"/>
<input type="PASSWORD" name="password3"/>
<input autocomplete="off" name="auto1"/>
<input type="text" autocomplete="OFF" name="auto2"/>
<input type="text" autocomplete=" OFF " name="auto5"/>
<input autocomplete=" off " name="auto6"/>
<input autocomplete=" cc-CSC " name="auto7"/>
<input autocomplete=" NEW-password " name="auto8"/>
<textarea autocomplete="off" name="auto3"/>
<select autocomplete="off" name="auto4">
<option value="1" selected="true"/>
<option value="2"/>
<option value="3"/>
</select>
<select autocomplete="cc-CSC" name="CSC">
<option value="123" selected="true"/>
<option value="234"/>
<option value="345"/>
</select>
</form>
</body>
</html>

View File

@@ -0,0 +1,78 @@
/* 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/. */
/* eslint-disable mozilla/no-arbitrary-setTimeout */
function test() {
/** Test for Bug 459906 */
waitForExplicitFinish();
let testURL =
"http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_459906_sample.html";
let uniqueValue = "<b>Unique:</b> " + Date.now();
var frameCount = 0;
let tab = BrowserTestUtils.addTab(gBrowser, testURL);
tab.linkedBrowser.addEventListener(
"load",
function listener() {
// wait for all frames to load completely
if (frameCount++ < 2) {
return;
}
tab.linkedBrowser.removeEventListener("load", listener, true);
let iframes = tab.linkedBrowser.contentWindow.frames;
iframes[1].document.body.innerHTML = uniqueValue;
frameCount = 0;
let tab2 = gBrowser.duplicateTab(tab);
tab2.linkedBrowser.addEventListener(
"load",
function loadListener() {
// wait for all frames to load (and reload!) completely
if (frameCount++ < 2) {
return;
}
tab2.linkedBrowser.removeEventListener("load", loadListener, true);
executeSoon(function innerHTMLPoller() {
let iframesTab2 = tab2.linkedBrowser.contentWindow.frames;
if (iframesTab2[1].document.body.innerHTML !== uniqueValue) {
// Poll again the value, since we can't ensure to run
// after SessionStore has injected innerHTML value.
// See bug 521802.
info("Polling for innerHTML value");
setTimeout(innerHTMLPoller, 100);
return;
}
is(
iframesTab2[1].document.body.innerHTML,
uniqueValue,
"rich textarea's content correctly duplicated"
);
let innerDomain = null;
try {
innerDomain = iframesTab2[0].document.domain;
} catch (ex) {
/* throws for chrome: documents */
}
is(innerDomain, "mochi.test", "XSS exploit prevented!");
// clean up
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
finish();
});
},
true
);
},
true
);
}

View File

@@ -0,0 +1,3 @@
<title>Cross Domain File for bug 459906</title>
cheers from localhost

View File

@@ -0,0 +1,41 @@
<!-- Testcase originally by David Bloom <bloom@google.com> -->
<!DOCTYPE html>
<title>Test for bug 459906</title>
<body>
<iframe src="data:text/html;charset=utf-8,not_on_localhost"></iframe>
<iframe></iframe>
<script type="application/javascript">
var loadCount = 0;
frames[0].addEventListener("DOMContentLoaded", handleLoad);
frames[1].addEventListener("DOMContentLoaded", handleLoad);
function handleLoad() {
if (++loadCount < 2)
return;
frames[0].removeEventListener("DOMContentLoaded", handleLoad);
frames[1].removeEventListener("DOMContentLoaded", handleLoad);
frames[0].document.designMode = "on";
frames[0].document.__defineGetter__("designMode", function() {
// inject a cross domain file ...
var documentInjected = false;
document.getElementsByTagName("iframe")[0].onload =
function() { documentInjected = true; };
frames[0].location = "browser_459906_empty.html";
// ... and ensure that it has time to load
for (var c = 0; !documentInjected && c < 20; c++) {
var r = new XMLHttpRequest();
r.open("GET", location.href, false);
r.overrideMimeType("text/plain");
r.send(null);
}
return "on";
});
frames[1].document.designMode = "on";
}
</script>
</body>

View File

@@ -0,0 +1,133 @@
/* 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 testClosedTabData() {
/** Test for Bug 461634 */
const REMEMBER = Date.now(),
FORGET = Math.random();
let test_state = {
windows: [
{
tabs: [{ entries: [] }],
_closedTabs: [
{
state: { entries: [{ url: "http://www.example.net/" }] },
title: FORGET,
},
{
state: { entries: [{ url: "http://www.example.net/" }] },
title: REMEMBER,
},
{
state: { entries: [{ url: "http://www.example.net/" }] },
title: FORGET,
},
{
state: { entries: [{ url: "http://www.example.net/" }] },
title: REMEMBER,
},
],
},
],
};
let remember_count = 2;
function countByTitle(aClosedTabList, aTitle) {
return aClosedTabList.filter(aData => aData.title == aTitle).length;
}
function testForError(aFunction) {
try {
aFunction();
return false;
} catch (ex) {
return ex.name == "NS_ERROR_ILLEGAL_VALUE";
}
}
// Open a window and add the above closed tab list.
let newWin = openDialog(location, "", "chrome,all,dialog=no");
await promiseWindowLoaded(newWin);
Services.prefs.setIntPref(
"browser.sessionstore.max_tabs_undo",
test_state.windows[0]._closedTabs.length
);
await setWindowState(newWin, test_state, true);
let closedTabs = SessionStore.getClosedTabDataForWindow(newWin);
// Verify that non JSON serialized data is the same as JSON serialized data.
is(
JSON.stringify(closedTabs),
JSON.stringify(SessionStore.getClosedTabDataForWindow(newWin)),
"Non-serialized data is the same as serialized data"
);
is(
closedTabs.length,
test_state.windows[0]._closedTabs.length,
"Closed tab list has the expected length"
);
is(
countByTitle(closedTabs, FORGET),
test_state.windows[0]._closedTabs.length - remember_count,
"The correct amout of tabs are to be forgotten"
);
is(
countByTitle(closedTabs, REMEMBER),
remember_count,
"Everything is set up"
);
// All of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE.
ok(
testForError(() => ss.forgetClosedTab({}, 0)),
"Invalid window for forgetClosedTab throws"
);
ok(
testForError(() => ss.forgetClosedTab(newWin, -1)),
"Invalid tab for forgetClosedTab throws"
);
ok(
testForError(() =>
ss.forgetClosedTab(newWin, test_state.windows[0]._closedTabs.length + 1)
),
"Invalid tab for forgetClosedTab throws"
);
// Remove third tab, then first tab.
ss.forgetClosedTab(newWin, 2);
ss.forgetClosedTab(newWin, null);
closedTabs = SessionStore.getClosedTabDataForWindow(newWin);
// Verify that non JSON serialized data is the same as JSON serialized data.
is(
JSON.stringify(closedTabs),
JSON.stringify(SessionStore.getClosedTabDataForWindow(newWin)),
"Non-serialized data is the same as serialized data"
);
is(
closedTabs.length,
remember_count,
"The correct amout of tabs was removed"
);
is(
countByTitle(closedTabs, FORGET),
0,
"All tabs specifically forgotten were indeed removed"
);
is(
countByTitle(closedTabs, REMEMBER),
remember_count,
"... and tabs not specifically forgetten weren't"
);
// Clean up.
Services.prefs.clearUserPref("browser.sessionstore.max_tabs_undo");
await BrowserTestUtils.closeWindow(newWin);
});

View File

@@ -0,0 +1,53 @@
/* 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 461743 */
waitForExplicitFinish();
let testURL =
"http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_461743_sample.html";
let frameCount = 0;
let tab = BrowserTestUtils.addTab(gBrowser, testURL);
tab.linkedBrowser.addEventListener(
"load",
function loadListener(aEvent) {
// Wait for all frames to load completely.
if (frameCount++ < 2) {
return;
}
tab.linkedBrowser.removeEventListener("load", loadListener, true);
let tab2 = gBrowser.duplicateTab(tab);
tab2.linkedBrowser.addEventListener(
"461743",
function listener() {
tab2.linkedBrowser.removeEventListener("461743", listener, true);
is(aEvent.data, "done", "XSS injection was attempted");
executeSoon(function () {
let iframes = tab2.linkedBrowser.contentWindow.frames;
let innerHTML = iframes[1].document.body.innerHTML;
isnot(
innerHTML,
Cu.reportError.toString(),
"chrome access denied!"
);
// Clean up.
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
finish();
});
},
true,
true
);
},
true
);
}

View File

@@ -0,0 +1,56 @@
<!-- Testcase originally by <moz_bug_r_a4@yahoo.com> -->
<!DOCTYPE html>
<title>Test for bug 461743</title>
<body>
<iframe src="data:text/html;charset=utf-8,empty"></iframe>
<iframe></iframe>
<script type="application/javascript">
var chromeUrl = "chrome://global/content/mozilla.html";
var exploitUrl = "javascript:try { document.body.innerHTML = Components.utils.reportError; } catch (ex) { }";
var loadCount = 0;
frames[0].addEventListener("DOMContentLoaded", handleLoad);
frames[1].addEventListener("DOMContentLoaded", handleLoad);
function handleLoad() {
if (++loadCount < 2)
return;
frames[0].removeEventListener("DOMContentLoaded", handleLoad);
frames[1].removeEventListener("DOMContentLoaded", handleLoad);
var flip = 0;
MutationEvent.prototype.toString = function() {
return flip++ == 0 ? chromeUrl : exploitUrl;
};
var href = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(frames[1].location), "href").get;
var loadChrome = { handleEvent: href };
var loadExploit = { handleEvent: href };
function delay() {
var xhr = new XMLHttpRequest();
xhr.open("GET", location.href, false);
xhr.send(null);
}
function done() {
var event = new MessageEvent("461743", { bubbles: true, cancelable: false,
data: "done", origin: location.href,
source: window });
document.dispatchEvent(event);
frames[0].document.removeEventListener("DOMNodeInserted", loadChrome, true);
frames[0].document.removeEventListener("DOMNodeInserted", delay, true);
frames[0].document.removeEventListener("DOMNodeInserted", loadExploit, true);
frames[0].document.removeEventListener("DOMNodeInserted", done, true);
}
frames[0].document.addEventListener("DOMNodeInserted", loadChrome, true);
frames[0].document.addEventListener("DOMNodeInserted", delay, true);
frames[0].document.addEventListener("DOMNodeInserted", loadExploit, true);
frames[0].document.addEventListener("DOMNodeInserted", done, true);
frames[0].document.designMode = "on";
}
</script>
</body>

View File

@@ -0,0 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const URL = ROOT + "browser_463205_sample.html";
/**
* Bug 463205 - Check URLs before restoring form data to make sure a malicious
* website can't modify frame URLs and make us inject form data into the wrong
* web pages.
*/
add_task(async function test_check_urls_before_restoring() {
// Add a blank tab.
let tab = BrowserTestUtils.addTab(gBrowser, "about:blank");
let browser = tab.linkedBrowser;
await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" });
// Restore form data with a valid URL.
await promiseTabState(tab, getState(URL));
let value = await getPropertyOfFormField(browser, "#text", "value");
is(value, "foobar", "value was restored");
// Restore form data with an invalid URL.
await promiseTabState(tab, getState("http://example.com/"));
value = await getPropertyOfFormField(browser, "#text", "value");
is(value, "", "value was not restored");
// Cleanup.
gBrowser.removeTab(tab);
});
function getState(url) {
return JSON.stringify({
entries: [{ url: URL, triggeringPrincipal_base64 }],
formdata: { url, id: { text: "foobar" } },
});
}

View File

@@ -0,0 +1,7 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>bug 463205</title>
<body>
<input type="text" id="text" />
</body>

View File

@@ -0,0 +1,120 @@
/* 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";
const MOCHI_ROOT = ROOT.replace(
"chrome://mochitests/content/",
"http://mochi.test:8888/"
);
if (gFissionBrowser) {
addCoopTask(
"browser_463206_sample.html",
test_restore_text_data_subframes,
HTTPSROOT
);
}
addNonCoopTask(
"browser_463206_sample.html",
test_restore_text_data_subframes,
HTTPSROOT
);
addNonCoopTask(
"browser_463206_sample.html",
test_restore_text_data_subframes,
HTTPROOT
);
addNonCoopTask(
"browser_463206_sample.html",
test_restore_text_data_subframes,
MOCHI_ROOT
);
async function test_restore_text_data_subframes(aURL) {
// Add a new tab.
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, aURL);
await setPropertyOfFormField(
tab.linkedBrowser,
"#out1",
"value",
Date.now().toString(16)
);
await setPropertyOfFormField(
tab.linkedBrowser,
"input[name='1|#out2']",
"value",
Math.random()
);
await setPropertyOfFormField(
tab.linkedBrowser.browsingContext.children[0].children[1],
"#in1",
"value",
new Date()
);
// Duplicate the tab.
let tab2 = gBrowser.duplicateTab(tab);
let browser2 = tab2.linkedBrowser;
await promiseTabRestored(tab2);
isnot(
await getPropertyOfFormField(browser2, "#out1", "value"),
await getPropertyOfFormField(
browser2.browsingContext.children[1],
"#out1",
"value"
),
"text isn't reused for frames"
);
isnot(
await getPropertyOfFormField(browser2, "input[name='1|#out2']", "value"),
"",
"text containing | and # is correctly restored"
);
is(
await getPropertyOfFormField(
browser2.browsingContext.children[1],
"#out2",
"value"
),
"",
"id prefixes can't be faked"
);
// Query a few values from the top and its child frames.
await SpecialPowers.spawn(tab2.linkedBrowser, [], async function () {
// Bug 588077
// XXX(farre): disabling this, because it started passing more heavily on Windows.
/*
let in1ValFrame0_1 = await SpecialPowers.spawn(
content.frames[0],
[],
async function() {
return SpecialPowers.spawn(content.frames[1], [], async function() {
return content.document.getElementById("in1").value;
});
}
);
todo_is(in1ValFrame0_1, "", "id prefixes aren't mixed up");
*/
});
is(
await getPropertyOfFormField(
browser2.browsingContext.children[1].children[0],
"#in1",
"value"
),
"",
"id prefixes aren't mixed up"
);
// Cleanup.
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
}

View File

@@ -0,0 +1,11 @@
<!-- Testcase originally by <moz_bug_r_a4@yahoo.com> -->
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test for bug 463206</title>
<iframe src="data:text/html;charset=utf-8,<iframe></iframe><iframe%20src='data:text/html;charset=utf-8,<input%2520id=%2522in1%2522>'></iframe>"></iframe>
<iframe src="data:text/html;charset=utf-8,<input%20id='out1'><input%20id='out2'><iframe%20src='data:text/html;charset=utf-8,<input%2520id=%2522in1%2522>'>"></iframe>
<input id="out1">
<input name="1|#out2">

View File

@@ -0,0 +1,176 @@
/* 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/. */
let { ForgetAboutSite } = ChromeUtils.importESModule(
"moz-src:///toolkit/components/forgetaboutsite/ForgetAboutSite.sys.mjs"
);
function promiseClearHistory() {
return new Promise(resolve => {
let observer = {
observe() {
Services.obs.removeObserver(
this,
"browser:purge-session-history-for-domain"
);
resolve();
},
};
Services.obs.addObserver(
observer,
"browser:purge-session-history-for-domain"
);
});
}
add_task(async function () {
/** Test for Bug 464199 */
const REMEMBER = Date.now(),
FORGET = Math.random();
let test_state = {
windows: [
{
tabs: [{ entries: [] }],
_closedTabs: [
{
state: { entries: [{ url: "http://www.example.net/" }] },
title: FORGET,
},
{
state: { entries: [{ url: "http://www.example.org/" }] },
title: REMEMBER,
},
{
state: {
entries: [
{ url: "http://www.example.net/" },
{ url: "http://www.example.org/" },
],
},
title: FORGET,
},
{
state: { entries: [{ url: "http://example.net/" }] },
title: FORGET,
},
{
state: { entries: [{ url: "http://sub.example.net/" }] },
title: FORGET,
},
{
state: { entries: [{ url: "http://www.example.net:8080/" }] },
title: FORGET,
},
{ state: { entries: [{ url: "about:license" }] }, title: REMEMBER },
{
state: {
entries: [
{
url: "http://www.example.org/frameset",
children: [
{ url: "http://www.example.org/frame" },
{ url: "http://www.example.org:8080/frame2" },
],
},
],
},
title: REMEMBER,
},
{
state: {
entries: [
{
url: "http://www.example.org/frameset",
children: [
{ url: "http://www.example.org/frame" },
{ url: "http://www.example.net/frame" },
],
},
],
},
title: FORGET,
},
{
state: {
entries: [
{
url: "http://www.example.org/form",
formdata: { id: { url: "http://www.example.net/" } },
},
],
},
title: REMEMBER,
},
{
state: {
entries: [{ url: "http://www.example.org/form" }],
extData: { setCustomTabValue: "http://example.net:80" },
},
title: REMEMBER,
},
],
},
],
};
let remember_count = 5;
function countByTitle(aClosedTabList, aTitle) {
return aClosedTabList.filter(aData => aData.title == aTitle).length;
}
// open a window and add the above closed tab list
let newWin = openDialog(location, "", "chrome,all,dialog=no");
await promiseWindowLoaded(newWin);
Services.prefs.setIntPref(
"browser.sessionstore.max_tabs_undo",
test_state.windows[0]._closedTabs.length
);
let restoring = promiseWindowRestoring(newWin);
let restored = promiseWindowRestored(newWin);
ss.setWindowState(newWin, JSON.stringify(test_state), true);
await restoring;
await restored;
let closedTabs = ss.getClosedTabDataForWindow(newWin);
is(
closedTabs.length,
test_state.windows[0]._closedTabs.length,
"Closed tab list has the expected length"
);
is(
countByTitle(closedTabs, FORGET),
test_state.windows[0]._closedTabs.length - remember_count,
"The correct amout of tabs are to be forgotten"
);
is(
countByTitle(closedTabs, REMEMBER),
remember_count,
"Everything is set up."
);
let promise = promiseClearHistory();
await ForgetAboutSite.removeDataFromBaseDomain("example.net");
await promise;
closedTabs = ss.getClosedTabDataForWindow(newWin);
is(
closedTabs.length,
remember_count,
"The correct amout of tabs was removed"
);
is(
countByTitle(closedTabs, FORGET),
0,
"All tabs to be forgotten were indeed removed"
);
is(
countByTitle(closedTabs, REMEMBER),
remember_count,
"... and tabs to be remembered weren't."
);
// clean up
Services.prefs.clearUserPref("browser.sessionstore.max_tabs_undo");
await BrowserTestUtils.closeWindow(newWin);
});

View File

@@ -0,0 +1,54 @@
<!-- Testcase originally by <moz_bug_r_a4@yahoo.com> -->
<title>Test for bug 464620 (injection on input)</title>
<iframe></iframe>
<iframe onload="setup()"></iframe>
<script>
var targetUrl = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_464620_xd.html";
var firstPass;
function setup() {
if (firstPass !== undefined)
return;
firstPass = frames[1].location.href == "about:blank";
if (firstPass) {
frames[0].location = 'data:text/html;charset=utf-8,<body onload="if (parent.firstPass) parent.step();"><input id="x" oninput="parent.xss()">XXX</body>';
}
frames[1].location = targetUrl;
}
function step() {
var x = frames[0].document.getElementById("x");
if (x.value == "")
x.value = "ready";
x.style.display = "none";
frames[0].document.designMode = "on";
}
function xss() {
step();
var documentInjected = false;
document.getElementsByTagName("iframe")[0].onload =
function() { documentInjected = true; };
frames[0].location = targetUrl;
for (var c = 0; !documentInjected && c < 20; c++) {
var r = new XMLHttpRequest();
r.open("GET", location.href, false);
r.overrideMimeType("text/plain");
r.send(null);
}
document.getElementById("state").textContent = "done";
var event = new MessageEvent("464620_a", { bubbles: true, cancelable: false,
data: "done", origin: location.href,
source: window });
document.dispatchEvent(event);
}
</script>
<p id="state">pending</p>

View File

@@ -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
);
}

View File

@@ -0,0 +1,57 @@
<!-- Testcase originally by <moz_bug_r_a4@yahoo.com> -->
<title>Test for bug 464620 (injection on DOM node insertion)</title>
<iframe></iframe>
<iframe></iframe>
<iframe onload="setup()"></iframe>
<script>
var targetUrl = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_464620_xd.html";
var firstPass;
function setup() {
if (firstPass !== undefined)
return;
firstPass = frames[2].location.href == "about:blank";
if (firstPass) {
frames[0].location = 'data:text/html;charset=utf-8,<body onload="parent.step()">a</body>';
frames[1].location = 'data:text/html;charset=utf-8,<body onload="document.designMode=\'on\';">XXX</body>';
}
frames[2].location = targetUrl;
}
function step() {
frames[0].document.designMode = "on";
if (firstPass)
return;
var body = frames[0].document.body;
body.addEventListener("DOMNodeInserted", function() {
xss();
}, {capture: true, once: true});
}
function xss() {
var documentInjected = false;
document.getElementsByTagName("iframe")[1].onload =
function() { documentInjected = true; };
frames[1].location = targetUrl;
for (var c = 0; !documentInjected && c < 20; c++) {
var r = new XMLHttpRequest();
r.open("GET", location.href, false);
r.overrideMimeType("text/plain");
r.send(null);
}
document.getElementById("state").textContent = "done";
var event = new MessageEvent("464620_b", { bubbles: true, cancelable: false,
data: "done", origin: location.href,
source: window });
document.dispatchEvent(event);
}
</script>
<p id="state">pending</p>

View File

@@ -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
);
}

View File

@@ -0,0 +1,5 @@
<title>Cross Document File for bug 464620</title>
<body onload="document.designMode='on';" bgcolor="red">
This document is editable.
</body>

View File

@@ -0,0 +1,38 @@
"use strict";
var uniqueName = "bug 465215";
var uniqueValue1 = "as good as unique: " + Date.now();
var uniqueValue2 = "as good as unique: " + Math.random();
add_task(async function () {
// set a unique value on a new, blank tab
let tab1 = BrowserTestUtils.addTab(gBrowser, "about:blank");
await BrowserTestUtils.browserLoaded(tab1.linkedBrowser, {
wantLoad: "about:blank",
});
ss.setCustomTabValue(tab1, uniqueName, uniqueValue1);
// duplicate the tab with that value
let tab2 = ss.duplicateTab(window, tab1);
await promiseTabRestored(tab2);
is(
ss.getCustomTabValue(tab2, uniqueName),
uniqueValue1,
"tab value was duplicated"
);
ss.setCustomTabValue(tab2, uniqueName, uniqueValue2);
isnot(
ss.getCustomTabValue(tab1, uniqueName),
uniqueValue2,
"tab values aren't sync'd"
);
// overwrite the tab with the value which should remove it
await promiseTabState(tab1, { entries: [] });
is(ss.getCustomTabValue(tab1, uniqueName), "", "tab value was cleared");
// clean up
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab1);
});

View File

@@ -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/. */
add_task(async function test_clearWindowValues() {
/** Test for Bug 465223 */
let uniqueKey1 = "bug 465223.1";
let uniqueKey2 = "bug 465223.2";
let uniqueValue1 = "unik" + Date.now();
let uniqueValue2 = "pi != " + Math.random();
// open a window and set a value on it
let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
await promiseWindowLoaded(newWin);
ss.setCustomWindowValue(newWin, uniqueKey1, uniqueValue1);
let newState = { windows: [{ tabs: [{ entries: [] }], extData: {} }] };
newState.windows[0].extData[uniqueKey2] = uniqueValue2;
await setWindowState(newWin, newState);
is(newWin.gBrowser.tabs.length, 2, "original tab wasn't overwritten");
is(
ss.getCustomWindowValue(newWin, uniqueKey1),
uniqueValue1,
"window value wasn't overwritten when the tabs weren't"
);
is(
ss.getCustomWindowValue(newWin, uniqueKey2),
uniqueValue2,
"new window value was correctly added"
);
newState.windows[0].extData[uniqueKey2] = uniqueValue1;
await setWindowState(newWin, newState, true);
is(newWin.gBrowser.tabs.length, 1, "original tabs were overwritten");
is(
ss.getCustomWindowValue(newWin, uniqueKey1),
"",
"window value was cleared"
);
is(
ss.getCustomWindowValue(newWin, uniqueKey2),
uniqueValue1,
"window value was correctly overwritten"
);
// clean up
await BrowserTestUtils.closeWindow(newWin);
});

View File

@@ -0,0 +1,51 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const URL = ROOT + "browser_466937_sample.html";
/**
* Bug 466937 - Prevent file stealing with sessionstore.
*/
add_task(async function test_prevent_file_stealing() {
// Add a tab with some file input fields.
let tab = BrowserTestUtils.addTab(gBrowser, URL);
let browser = tab.linkedBrowser;
await promiseBrowserLoaded(browser);
// Generate a path to a 'secret' file.
let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
file.append("466937_test.file");
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
let testPath = file.path;
// Fill in form values.
await setPropertyOfFormField(
browser,
"#reverse_thief",
"value",
"/home/user/secret2"
);
await setPropertyOfFormField(browser, "#bystander", "value", testPath);
// Duplicate and check form values.
let tab2 = gBrowser.duplicateTab(tab);
let browser2 = tab2.linkedBrowser;
await promiseTabRestored(tab2);
let thief = await getPropertyOfFormField(browser2, "#thief", "value");
is(thief, "", "file path wasn't set to text field value");
let reverse_thief = await getPropertyOfFormField(
browser2,
"#reverse_thief",
"value"
);
is(reverse_thief, "", "text field value wasn't set to full file path");
let bystander = await getPropertyOfFormField(browser2, "#bystander", "value");
is(bystander, testPath, "normal case: file path was correctly preserved");
// Cleanup.
gBrowser.removeTab(tab);
gBrowser.removeTab(tab2);
});

View File

@@ -0,0 +1,20 @@
<!-- Testcase originally by <moz_bug_r_a4@yahoo.com> -->
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test for bug 466937</title>
<input id="thief" value="/home/user/secret">
<input type="file" id="reverse_thief">
<input type="file" id="bystander">
<script>
window.addEventListener("DOMContentLoaded", function() {
if (!document.location.hash) {
document.location.hash = "#ready";
} else {
document.getElementById("thief").type = "file";
document.getElementById("reverse_thief").type = "text";
}
}, {once: true});
</script>

View File

@@ -0,0 +1,88 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test Summary:
// 1. Open about:sessionrestore where formdata is a JS object, not a string
// 1a. Check that #sessionData on the page is readable after JSON.parse (skipped, checking formdata is sufficient)
// 1b. Check that there are no backslashes in the formdata
// 1c. Check that formdata doesn't require JSON.parse
//
// 2. Use the current state (currently about:sessionrestore with data) and then open that in a new instance of about:sessionrestore
// 2a. Check that there are no backslashes in the formdata
// 2b. Check that formdata doesn't require JSON.parse
//
// 3. [backwards compat] Use a stringified state as formdata when opening about:sessionrestore
// 3a. Make sure there are nodes in the tree on about:sessionrestore (skipped, checking formdata is sufficient)
// 3b. Check that there are no backslashes in the formdata
// 3c. Check that formdata doesn't require JSON.parse
const CRASH_STATE = {
windows: [
{
tabs: [
{ entries: [{ url: "about:mozilla", triggeringPrincipal_base64 }] },
],
},
],
};
const STATE = createEntries(CRASH_STATE);
const STATE2 = createEntries({ windows: [{ tabs: [STATE] }] });
const STATE3 = createEntries(JSON.stringify(CRASH_STATE));
function createEntries(sessionData) {
return {
entries: [{ url: "about:sessionrestore", triggeringPrincipal_base64 }],
formdata: { id: { sessionData }, url: "about:sessionrestore" },
};
}
add_task(async function test_nested_about_sessionrestore() {
// Prepare a blank tab.
let tab = BrowserTestUtils.addTab(gBrowser, "about:blank");
let browser = tab.linkedBrowser;
await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" });
// test 1
await promiseTabState(tab, STATE);
await checkState("test1", tab);
// test 2
await promiseTabState(tab, STATE2);
await checkState("test2", tab);
// test 3
await promiseTabState(tab, STATE3);
await checkState("test3", tab);
// Cleanup.
gBrowser.removeTab(tab);
});
async function checkState(prefix, tab) {
// Flush and query tab state.
await TabStateFlusher.flush(tab.linkedBrowser);
let { formdata } = JSON.parse(ss.getTabState(tab));
ok(
formdata.id.sessionData,
prefix + ": we have form data for about:sessionrestore"
);
let sessionData_raw = JSON.stringify(formdata.id.sessionData);
ok(
!/\\/.test(sessionData_raw),
prefix + ": #sessionData contains no backslashes"
);
info(sessionData_raw);
let gotError = false;
try {
JSON.parse(formdata.id.sessionData);
} catch (e) {
info(prefix + ": got error: " + e);
gotError = true;
}
ok(gotError, prefix + ": attempting to JSON.parse form data threw error");
}

View File

@@ -0,0 +1,80 @@
/* 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_sizemodeDefaults() {
/** Test for Bug 477657 */
let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
await promiseWindowLoaded(newWin);
let newState = {
windows: [
{
tabs: [{ entries: [] }],
_closedTabs: [
{
state: { entries: [{ url: "about:" }] },
title: "About:",
},
],
sizemode: "maximized",
},
],
};
let uniqueKey = "bug 477657";
let uniqueValue = "unik" + Date.now();
ss.setCustomWindowValue(newWin, uniqueKey, uniqueValue);
is(
ss.getCustomWindowValue(newWin, uniqueKey),
uniqueValue,
"window value was set before the window was overwritten"
);
await setWindowState(newWin, newState, true);
// use newWin.setTimeout(..., 0) to mirror sss_restoreWindowFeatures
await new Promise(resolve => newWin.setTimeout(resolve, 0));
is(
ss.getCustomWindowValue(newWin, uniqueKey),
"",
"window value was implicitly cleared"
);
is(newWin.windowState, newWin.STATE_MAXIMIZED, "the window was maximized");
is(
ss.getClosedTabDataForWindow(newWin).length,
1,
"the closed tab was added before the window was overwritten"
);
delete newState.windows[0]._closedTabs;
delete newState.windows[0].sizemode;
await setWindowState(newWin, newState, true);
await new Promise(resolve => newWin.setTimeout(resolve, 0));
is(
ss.getClosedTabDataForWindow(newWin).length,
0,
"closed tabs were implicitly cleared"
);
is(
newWin.windowState,
newWin.STATE_MAXIMIZED,
"the window remains maximized"
);
newState.windows[0].sizemode = "normal";
await setWindowState(newWin, newState, true);
await new Promise(resolve => newWin.setTimeout(resolve, 0));
isnot(
newWin.windowState,
newWin.STATE_MAXIMIZED,
"the window was explicitly unmaximized"
);
await BrowserTestUtils.closeWindow(newWin);
});

View File

@@ -0,0 +1,45 @@
"use strict";
/**
* Tests that we get sent to the right page when the user clicks
* the "Close" button in about:sessionrestore
*/
add_task(async function () {
await SpecialPowers.pushPrefEnv({
set: [["browser.startup.page", 0]],
});
let tab = BrowserTestUtils.addTab(gBrowser, "about:sessionrestore");
gBrowser.selectedTab = tab;
let browser = tab.linkedBrowser;
await BrowserTestUtils.browserLoaded(browser, false, "about:sessionrestore");
let doc = browser.contentDocument;
// Click on the "Close" button after about:sessionrestore is loaded.
doc.getElementById("errorCancel").click();
await BrowserTestUtils.browserLoaded(browser, false, "about:blank");
// Test that starting a new session loads the homepage (set to http://mochi.test:8888)
// if Firefox is configured to display a homepage at startup (browser.startup.page = 1)
let homepage = "http://mochi.test:8888/";
await SpecialPowers.pushPrefEnv({
set: [
["browser.startup.homepage", homepage],
["browser.startup.page", 1],
],
});
BrowserTestUtils.startLoadingURIString(browser, "about:sessionrestore");
await BrowserTestUtils.browserLoaded(browser, false, "about:sessionrestore");
doc = browser.contentDocument;
// Click on the "Close" button after about:sessionrestore is loaded.
doc.getElementById("errorCancel").click();
await BrowserTestUtils.browserLoaded(browser);
is(browser.currentURI.spec, homepage, "loaded page is the homepage");
BrowserTestUtils.removeTab(tab);
});

View File

@@ -0,0 +1,76 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
if (gFissionBrowser) {
addCoopTask(
"browser_485482_sample.html",
test_xpath_exp_for_strange_documents,
HTTPSROOT
);
}
addNonCoopTask(
"browser_485482_sample.html",
test_xpath_exp_for_strange_documents,
ROOT
);
addNonCoopTask(
"browser_485482_sample.html",
test_xpath_exp_for_strange_documents,
HTTPSROOT
);
addNonCoopTask(
"browser_485482_sample.html",
test_xpath_exp_for_strange_documents,
HTTPROOT
);
/**
* Bug 485482 - Make sure that we produce valid XPath expressions even for very
* weird HTML documents.
*/
async function test_xpath_exp_for_strange_documents(aURL) {
// Load a page with weird tag names.
let tab = BrowserTestUtils.addTab(gBrowser, aURL);
let browser = tab.linkedBrowser;
await promiseBrowserLoaded(browser);
// Fill in some values.
let uniqueValue = Math.random();
await setPropertyOfFormField(
browser,
"input[type=text]",
"value",
uniqueValue
);
await setPropertyOfFormField(
browser,
"input[type=checkbox]",
"checked",
true
);
// Duplicate the tab.
let tab2 = gBrowser.duplicateTab(tab);
let browser2 = tab2.linkedBrowser;
await promiseTabRestored(tab2);
// Check that we generated valid XPath expressions to restore form values.
let text = await getPropertyOfFormField(
browser2,
"input[type=text]",
"value"
);
is("" + text, "" + uniqueValue, "generated XPath expression was valid");
let checkbox = await getPropertyOfFormField(
browser2,
"input[type=checkbox]",
"checked"
);
ok(checkbox, "generated XPath expression was valid");
// Cleanup.
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab);
}

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<title>Test for bug 485482</title>
<bad=name>
<input type="text">
</bad=name>
<worse=name>
<l0c@l+na~e"'<27>>
<input type="checkbox" name="check"> Check
</l0c@l+na~e"'<27>>
</worse=name>

View File

@@ -0,0 +1,35 @@
/* 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 485563 */
waitForExplicitFinish();
let uniqueValue =
Math.random() + "\u2028Second line\u2029Second paragraph\u2027";
let tab = BrowserTestUtils.addTab(gBrowser);
BrowserTestUtils.browserLoaded(tab.linkedBrowser, {
wantLoad: "about:blank",
}).then(() => {
ss.setCustomTabValue(tab, "bug485563", uniqueValue);
let tabState = JSON.parse(ss.getTabState(tab));
is(
tabState.extData.bug485563,
uniqueValue,
"unicode line separator wasn't over-encoded"
);
ss.deleteCustomTabValue(tab, "bug485563");
ss.setTabState(tab, JSON.stringify(tabState));
is(
ss.getCustomTabValue(tab, "bug485563"),
uniqueValue,
"unicode line separator was correctly preserved"
);
gBrowser.removeTab(tab);
finish();
});
}

View File

@@ -0,0 +1,107 @@
/* 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/. */
// Only windows with open tabs are restorable. Windows where a lone tab is
// detached may have _closedTabs, but is left with just an empty tab.
const STATES = [
{
shouldBeAdded: true,
windowState: {
windows: [
{
tabs: [
{
entries: [
{
url: "http://example.com",
triggeringPrincipal_base64,
title: "example.com",
},
],
},
],
selected: 1,
_closedTabs: [],
},
],
},
},
{
shouldBeAdded: false,
windowState: {
windows: [
{
tabs: [{ entries: [] }],
_closedTabs: [],
},
],
},
},
{
shouldBeAdded: false,
windowState: {
windows: [
{
tabs: [{ entries: [] }],
_closedTabs: [
{
state: {
entries: [
{
url: "http://example.com",
triggeringPrincipal_base64,
index: 1,
},
],
},
},
],
},
],
},
},
{
shouldBeAdded: false,
windowState: {
windows: [
{
tabs: [{ entries: [] }],
_closedTabs: [],
extData: { keyname: "pi != " + Math.random() },
},
],
},
},
];
add_task(async function test_bug_490040() {
for (let state of STATES) {
// Ensure we can store the window if needed.
let startingClosedWindowCount = ss.getClosedWindowCount();
await pushPrefs([
"browser.sessionstore.max_windows_undo",
startingClosedWindowCount + 1,
]);
let curClosedWindowCount = ss.getClosedWindowCount();
let win = await BrowserTestUtils.openNewBrowserWindow();
await setWindowState(win, state.windowState, true);
if (state.windowState.windows[0].tabs.length) {
await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser, {
wantLoad: () => true,
});
}
await BrowserTestUtils.closeWindow(win);
is(
ss.getClosedWindowCount(),
curClosedWindowCount + (state.shouldBeAdded ? 1 : 0),
"That window should " +
(state.shouldBeAdded ? "" : "not ") +
"be restorable"
);
}
});

View File

@@ -0,0 +1,113 @@
"use strict";
const REFERRER1 = "http://example.org/?" + Date.now();
const REFERRER2 = "http://example.org/?" + Math.random();
const REFERRER3 = "http://example.org/?" + Math.random();
add_task(async function () {
function getExpectedReferrer(referrer) {
let defaultPolicy = Services.prefs.getIntPref(
"network.http.referer.defaultPolicy"
);
Assert.greater(
[2, 3].indexOf(defaultPolicy),
-1,
"default referrer policy should be either strict-origin-when-cross-origin(2) or no-referrer-when-downgrade(3)"
);
if (defaultPolicy == 2) {
return referrer.match(/https?:\/\/[^\/]+\/?/i)[0];
}
return referrer;
}
async function checkDocumentReferrer(referrer, msg) {
await SpecialPowers.spawn(
gBrowser.selectedBrowser,
[{ referrer, msg }],
async function (args) {
Assert.equal(content.document.referrer, args.referrer, args.msg);
}
);
}
let ReferrerInfo = Components.Constructor(
"@mozilla.org/referrer-info;1",
"nsIReferrerInfo",
"init"
);
// Add a new tab.
let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(
gBrowser,
"about:blank"
));
let browser = tab.linkedBrowser;
await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" });
// Load a new URI with a specific referrer.
let referrerInfo1 = new ReferrerInfo(
Ci.nsIReferrerInfo.EMPTY,
true,
Services.io.newURI(REFERRER1)
);
browser.loadURI(Services.io.newURI("http://example.org"), {
referrerInfo: referrerInfo1,
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
});
await promiseBrowserLoaded(browser);
await TabStateFlusher.flush(browser);
let tabState = JSON.parse(ss.getTabState(tab));
let actualReferrerInfo = E10SUtils.deserializeReferrerInfo(
tabState.entries[0].referrerInfo
);
is(
actualReferrerInfo.originalReferrer.spec,
REFERRER1,
"Referrer retrieved via getTabState matches referrer set via loadURI."
);
let referrerInfo2 = new ReferrerInfo(
Ci.nsIReferrerInfo.EMPTY,
true,
Services.io.newURI(REFERRER2)
);
tabState.entries[0].referrerInfo =
E10SUtils.serializeReferrerInfo(referrerInfo2);
await promiseTabState(tab, tabState);
await checkDocumentReferrer(
getExpectedReferrer(REFERRER2),
"document.referrer matches referrer set via setTabState using referrerInfo."
);
gBrowser.removeCurrentTab();
// Restore the closed tab.
tab = ss.undoCloseTab(window, 0);
await promiseTabRestored(tab);
await checkDocumentReferrer(
getExpectedReferrer(REFERRER2),
"document.referrer is still correct after closing and reopening the tab."
);
tabState.entries[0].referrerInfo = null;
tabState.entries[0].referrer = REFERRER3;
await promiseTabState(tab, tabState);
await checkDocumentReferrer(
getExpectedReferrer(REFERRER3),
"document.referrer matches referrer set via setTabState using referrer."
);
gBrowser.removeCurrentTab();
// Restore the closed tab.
tab = ss.undoCloseTab(window, 0);
await promiseTabRestored(tab);
await checkDocumentReferrer(
getExpectedReferrer(REFERRER3),
"document.referrer is still correct after closing and reopening the tab."
);
gBrowser.removeCurrentTab();
});

View File

@@ -0,0 +1,212 @@
/* 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_deleteClosedWindow() {
/** Test for Bug 491577 */
const REMEMBER = Date.now(),
FORGET = Math.random();
let test_state = {
windows: [
{
tabs: [
{
entries: [
{ url: "http://example.com/", triggeringPrincipal_base64 },
],
},
],
selected: 1,
},
],
_closedWindows: [
// _closedWindows[0]
{
tabs: [
{
entries: [
{
url: "http://example.com/",
triggeringPrincipal_base64,
title: "title",
},
],
},
{
entries: [
{
url: "http://mozilla.org/",
triggeringPrincipal_base64,
title: "title",
},
],
},
],
selected: 2,
title: FORGET,
_closedTabs: [],
},
// _closedWindows[1]
{
tabs: [
{
entries: [
{
url: "http://mozilla.org/",
triggeringPrincipal_base64,
title: "title",
},
],
},
{
entries: [
{
url: "http://example.com/",
triggeringPrincipal_base64,
title: "title",
},
],
},
{
entries: [
{
url: "http://mozilla.org/",
triggeringPrincipal_base64,
title: "title",
},
],
},
],
selected: 3,
title: REMEMBER,
_closedTabs: [],
},
// _closedWindows[2]
{
tabs: [
{
entries: [
{
url: "http://example.com/",
triggeringPrincipal_base64,
title: "title",
},
],
},
],
selected: 1,
title: FORGET,
_closedTabs: [
{
state: {
entries: [
{
url: "http://mozilla.org/",
triggeringPrincipal_base64,
title: "title",
},
{
url: "http://mozilla.org/again",
triggeringPrincipal_base64,
title: "title",
},
],
},
pos: 1,
title: "title",
},
{
state: {
entries: [
{
url: "http://example.com",
triggeringPrincipal_base64,
title: "title",
},
],
},
title: "title",
},
],
},
],
};
let remember_count = 1;
function countByTitle(aClosedWindowList, aTitle) {
return aClosedWindowList.filter(aData => aData.title == aTitle).length;
}
function testForError(aFunction) {
try {
aFunction();
return false;
} catch (ex) {
return ex.name == "NS_ERROR_ILLEGAL_VALUE";
}
}
// open a window and add the above closed window list
let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
await promiseWindowLoaded(newWin);
Services.prefs.setIntPref(
"browser.sessionstore.max_windows_undo",
test_state._closedWindows.length
);
await setWindowState(newWin, test_state, true);
let closedWindows = ss.getClosedWindowData();
is(
closedWindows.length,
test_state._closedWindows.length,
"Closed window list has the expected length"
);
is(
countByTitle(closedWindows, FORGET),
test_state._closedWindows.length - remember_count,
"The correct amount of windows are to be forgotten"
);
is(
countByTitle(closedWindows, REMEMBER),
remember_count,
"Everything is set up."
);
// all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
ok(
testForError(() => ss.forgetClosedWindow(-1)),
"Invalid window for forgetClosedWindow throws"
);
ok(
testForError(() =>
ss.forgetClosedWindow(test_state._closedWindows.length + 1)
),
"Invalid window for forgetClosedWindow throws"
);
// Remove third window, then first window
ss.forgetClosedWindow(2);
ss.forgetClosedWindow(null);
closedWindows = ss.getClosedWindowData();
is(
closedWindows.length,
remember_count,
"The correct amount of windows were removed"
);
is(
countByTitle(closedWindows, FORGET),
0,
"All windows specifically forgotten were indeed removed"
);
is(
countByTitle(closedWindows, REMEMBER),
remember_count,
"... and windows not specifically forgetten weren't."
);
// clean up
Services.prefs.clearUserPref("browser.sessionstore.max_windows_undo");
await BrowserTestUtils.closeWindow(newWin);
});

View File

@@ -0,0 +1,47 @@
/* 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_urlbarFocus() {
/** Test for Bug 495495 */
let newWin = openDialog(
location,
"_blank",
"chrome,all,dialog=no,toolbar=yes"
);
await promiseWindowLoaded(newWin);
let state1 = ss.getWindowState(newWin);
await BrowserTestUtils.closeWindow(newWin);
newWin = openDialog(
location,
"_blank",
"chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar=no,location,personal,directories,dialog=no"
);
await promiseWindowLoaded(newWin);
let state2 = ss.getWindowState(newWin);
async function testState(state, expected) {
let win = openDialog(location, "_blank", "chrome,all,dialog=no");
await promiseWindowLoaded(win);
is(
win.gURLBar.readOnly,
false,
"URL bar should not be read-only before setting the state"
);
await setWindowState(win, state, true);
is(
win.gURLBar.readOnly,
expected.readOnly,
"URL bar read-only state should be restored correctly"
);
await BrowserTestUtils.closeWindow(win);
}
await BrowserTestUtils.closeWindow(newWin);
await testState(state1, { readOnly: false });
await testState(state2, { readOnly: true });
});

View File

@@ -0,0 +1,132 @@
/* 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/. */
async function checkState(browser) {
await SpecialPowers.spawn(browser, [], () => {
// Go back and then forward, and make sure that the state objects received
// from the popState event are as we expect them to be.
//
// We also add a node to the document's body when after going back and make
// sure it's still there after we go forward -- this is to test that the two
// history entries correspond to the same document.
// Set some state in the page's window. When we go back(), the page should
// be retrieved from bfcache, and this state should still be there.
content.testState = "foo";
});
// Now go back. This should trigger the popstate event handler.
let popstatePromise = SpecialPowers.spawn(browser, [], async () => {
let event = await ContentTaskUtils.waitForEvent(content, "popstate", true);
ok(event.state, "Event should have a state property.");
is(content.testState, "foo", "testState after going back");
is(
JSON.stringify(content.history.state),
JSON.stringify({ obj1: 1 }),
"first popstate object."
);
// Add a node with id "new-elem" to the document.
let doc = content.document;
ok(
!doc.getElementById("new-elem"),
"doc shouldn't contain new-elem before we add it."
);
let elem = doc.createElement("div");
elem.id = "new-elem";
doc.body.appendChild(elem);
});
// Ensure that the message manager has processed the previous task before
// going back to prevent racing with it in non-e10s mode.
await SpecialPowers.spawn(browser, [], () => {});
browser.goBack();
await popstatePromise;
popstatePromise = SpecialPowers.spawn(browser, [], async () => {
let event = await ContentTaskUtils.waitForEvent(content, "popstate", true);
// When content fires a PopStateEvent and we observe it from a chrome event
// listener (as we do here, and, thankfully, nowhere else in the tree), the
// state object will be a cross-compartment wrapper to an object that was
// deserialized in the content scope. And in this case, since RegExps are
// not currently Xrayable (see bug 1014991), trying to pull |obj3| (a RegExp)
// off of an Xrayed Object won't work. So we need to waive.
Assert.equal(
Cu.waiveXrays(event.state).obj3.toString(),
"/^a$/",
"second popstate object."
);
// Make sure that the new-elem node is present in the document. If it's
// not, then this history entry has a different doc identifier than the
// previous entry, which is bad.
let doc = content.document;
let newElem = doc.getElementById("new-elem");
ok(newElem, "doc should contain new-elem.");
newElem.remove();
ok(!doc.getElementById("new-elem"), "new-elem should be removed.");
});
// Ensure that the message manager has processed the previous task before
// going forward to prevent racing with it in non-e10s mode.
await SpecialPowers.spawn(browser, [], () => {});
browser.goForward();
await popstatePromise;
}
add_task(async function test() {
await SpecialPowers.pushPrefEnv({
set: [["browser.navigation.requireUserInteraction", false]],
});
// Tests session restore functionality of history.pushState and
// history.replaceState(). (Bug 500328)
// We open a new blank window, let it load, and then load in
// http://example.com. We need to load the blank window first, otherwise the
// docshell gets confused and doesn't have a current history entry.
let state;
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:blank" },
async function (browser) {
BrowserTestUtils.startLoadingURIString(browser, "http://example.com");
await BrowserTestUtils.browserLoaded(browser);
// After these push/replaceState calls, the window should have three
// history entries:
// testURL (state object: null) <-- oldest
// testURL (state object: {obj1:1})
// testURL?page2 (state object: {obj3:/^a$/}) <-- newest
function contentTest() {
let history = content.window.history;
history.pushState({ obj1: 1 }, "title-obj1");
history.pushState({ obj2: 2 }, "title-obj2", "?page2");
history.replaceState({ obj3: /^a$/ }, "title-obj3");
}
await SpecialPowers.spawn(browser, [], contentTest);
await TabStateFlusher.flush(browser);
state = ss.getTabState(gBrowser.getTabForBrowser(browser));
}
);
// Restore the state into a new tab. Things don't work well when we
// restore into the old tab, but that's not a real use case anyway.
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:blank" },
async function (browser) {
let tab2 = gBrowser.getTabForBrowser(browser);
let tabRestoredPromise = promiseTabRestored(tab2);
ss.setTabState(tab2, state, true);
// Run checkState() once the tab finishes loading its restored state.
await tabRestoredPromise;
await checkState(browser);
}
);
});

View File

@@ -0,0 +1,78 @@
/* 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/. */
/* eslint-disable mozilla/no-arbitrary-setTimeout */
function test() {
/** Test for Bug 506482 */
// test setup
waitForExplicitFinish();
// read the sessionstore.js mtime (picked from browser_248970_a.js)
let profilePath = Services.dirsvc.get("ProfD", Ci.nsIFile);
function getSessionstoreFile() {
let sessionStoreJS = profilePath.clone();
sessionStoreJS.append("sessionstore.jsonlz4");
return sessionStoreJS;
}
function getSessionstorejsModificationTime() {
let file = getSessionstoreFile();
if (file.exists()) {
return file.lastModifiedTime;
}
return -1;
}
// delete existing sessionstore.js, to make sure we're not reading
// the mtime of an old one initially.
let sessionStoreJS = getSessionstoreFile();
if (sessionStoreJS.exists()) {
sessionStoreJS.remove(false);
}
// test content URL
const TEST_URL =
"data:text/html;charset=utf-8," +
"<body style='width: 100000px; height: 100000px;'><p>top</p></body>";
// 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()
});
}

View File

@@ -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);
});

View File

@@ -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)
);
}

View File

@@ -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();
}

Some files were not shown because too many files have changed in this diff Show More