mirror of
https://github.com/zen-browser/desktop.git
synced 2026-06-14 15:33:42 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3aba894bb |
@@ -60,7 +60,7 @@ jobs:
|
|||||||
|
|
||||||
brew install watchman
|
brew install watchman
|
||||||
|
|
||||||
cargo install apple-codesign --locked --force
|
cargo install apple-codesign
|
||||||
|
|
||||||
- name: Force usage of gnu-tar
|
- name: Force usage of gnu-tar
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ Zen is a firefox-based browser with the aim of pushing your productivity to a ne
|
|||||||
|
|
||||||
### Firefox Versions
|
### Firefox Versions
|
||||||
|
|
||||||
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `151.0.4`! 🚀
|
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `151.0.3`! 🚀
|
||||||
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 151.0.4`!
|
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 151.0.3`!
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
9a6aa4c359d1fb6ac60decc82402f82d49a17cea
|
5c4d14a559bf26eb4ab3e136d2084310ebe51ac0
|
||||||
@@ -94,8 +94,3 @@
|
|||||||
# See gh-12985 for details on the following preferences
|
# See gh-12985 for details on the following preferences
|
||||||
- name: browser.search.widget.new
|
- name: browser.search.widget.new
|
||||||
value: true
|
value: true
|
||||||
|
|
||||||
# Disabled from https://searchfox.org/firefox-main/rev/d6bfff43852356ca98af848b4705d37f8d41856f/modules/libpref/init/StaticPrefList.yaml#2008
|
|
||||||
# Only enabled for windows, doesn't really fit inside Zen.
|
|
||||||
- name: browser.startup.preXulSkeletonUI
|
|
||||||
value: false
|
|
||||||
|
|||||||
@@ -945,7 +945,6 @@ var gZenCKSSettings = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
input.addEventListener("blur", (event) => {
|
input.addEventListener("blur", (event) => {
|
||||||
this._currentActionID = null;
|
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
target.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-editing`);
|
target.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-editing`);
|
||||||
if (!this._hasSafed) {
|
if (!this._hasSafed) {
|
||||||
@@ -1050,7 +1049,6 @@ var gZenCKSSettings = {
|
|||||||
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-not-set`);
|
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-not-set`);
|
||||||
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-editing`);
|
input.classList.remove(`${ZEN_CKS_INPUT_FIELD_CLASS}-editing`);
|
||||||
this._latestValidKey = null;
|
this._latestValidKey = null;
|
||||||
this._currentActionID = null;
|
|
||||||
return;
|
return;
|
||||||
} else if (shortcut == "Escape" && !modifiersActive) {
|
} else if (shortcut == "Escape" && !modifiersActive) {
|
||||||
const { hasConflicts, conflictShortcut } = gZenKeyboardShortcutsManager.checkForConflicts(
|
const { hasConflicts, conflictShortcut } = gZenKeyboardShortcutsManager.checkForConflicts(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
|
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
|
||||||
index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008ede72076 100644
|
index 43fb79a3060e20f671ae6ffc26350c7abf497702..2da16e06541438ece4a3ae3a1663a1559780fe22 100644
|
||||||
--- a/browser/components/tabbrowser/content/tabbrowser.js
|
--- a/browser/components/tabbrowser/content/tabbrowser.js
|
||||||
+++ b/browser/components/tabbrowser/content/tabbrowser.js
|
+++ b/browser/components/tabbrowser/content/tabbrowser.js
|
||||||
@@ -502,6 +502,7 @@
|
@@ -502,6 +502,7 @@
|
||||||
@@ -79,17 +79,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
set selectedTab(val) {
|
set selectedTab(val) {
|
||||||
if (
|
if (
|
||||||
gSharedTabWarning.willShowSharedTabWarning(val) ||
|
gSharedTabWarning.willShowSharedTabWarning(val) ||
|
||||||
@@ -592,6 +644,9 @@
|
@@ -659,6 +711,10 @@
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
+ if (gZenWorkspaces.onBeforeTabSelect(val)) {
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
// Update the tab
|
|
||||||
this.tabbox.selectedTab = val;
|
|
||||||
}
|
|
||||||
@@ -659,6 +714,10 @@
|
|
||||||
userContextId = parseInt(tabArgument.getAttribute("usercontextid"), 10);
|
userContextId = parseInt(tabArgument.getAttribute("usercontextid"), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +90,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
if (tabArgument && tabArgument.linkedBrowser) {
|
if (tabArgument && tabArgument.linkedBrowser) {
|
||||||
remoteType = tabArgument.linkedBrowser.remoteType;
|
remoteType = tabArgument.linkedBrowser.remoteType;
|
||||||
initialBrowsingContextGroupId =
|
initialBrowsingContextGroupId =
|
||||||
@@ -751,6 +810,8 @@
|
@@ -751,6 +807,8 @@
|
||||||
this.tabpanels.appendChild(panel);
|
this.tabpanels.appendChild(panel);
|
||||||
|
|
||||||
let tab = this.tabs[0];
|
let tab = this.tabs[0];
|
||||||
@@ -109,7 +99,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
tab.linkedPanel = uniqueId;
|
tab.linkedPanel = uniqueId;
|
||||||
this._selectedTab = tab;
|
this._selectedTab = tab;
|
||||||
this._selectedBrowser = browser;
|
this._selectedBrowser = browser;
|
||||||
@@ -1121,13 +1182,18 @@
|
@@ -1121,13 +1179,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showTab(aTab);
|
this.showTab(aTab);
|
||||||
@@ -129,7 +119,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
aTab.setAttribute("pinned", "true");
|
aTab.setAttribute("pinned", "true");
|
||||||
this._updateTabBarForPinnedTabs();
|
this._updateTabBarForPinnedTabs();
|
||||||
@@ -1140,11 +1206,19 @@
|
@@ -1140,11 +1203,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#handleTabMove(aTab, () => {
|
this.#handleTabMove(aTab, () => {
|
||||||
@@ -150,7 +140,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
});
|
});
|
||||||
|
|
||||||
aTab.style.marginInlineStart = "";
|
aTab.style.marginInlineStart = "";
|
||||||
@@ -1321,6 +1395,9 @@
|
@@ -1321,6 +1392,9 @@
|
||||||
|
|
||||||
let LOCAL_PROTOCOLS = ["chrome:", "about:", "resource:", "data:"];
|
let LOCAL_PROTOCOLS = ["chrome:", "about:", "resource:", "data:"];
|
||||||
|
|
||||||
@@ -160,7 +150,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
if (
|
if (
|
||||||
aIconURL &&
|
aIconURL &&
|
||||||
!LOCAL_PROTOCOLS.some(protocol => aIconURL.startsWith(protocol))
|
!LOCAL_PROTOCOLS.some(protocol => aIconURL.startsWith(protocol))
|
||||||
@@ -1330,6 +1407,9 @@
|
@@ -1330,6 +1404,9 @@
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -170,7 +160,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
let browser = this.getBrowserForTab(aTab);
|
let browser = this.getBrowserForTab(aTab);
|
||||||
browser.mIconURL = aIconURL;
|
browser.mIconURL = aIconURL;
|
||||||
@@ -1652,7 +1732,6 @@
|
@@ -1652,7 +1729,6 @@
|
||||||
|
|
||||||
// Preview mode should not reset the owner
|
// Preview mode should not reset the owner
|
||||||
if (!this._previewMode && !oldTab.selected) {
|
if (!this._previewMode && !oldTab.selected) {
|
||||||
@@ -178,7 +168,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}
|
}
|
||||||
|
|
||||||
let lastRelatedTab = this._lastRelatedTabMap.get(oldTab);
|
let lastRelatedTab = this._lastRelatedTabMap.get(oldTab);
|
||||||
@@ -1743,6 +1822,7 @@
|
@@ -1743,6 +1819,7 @@
|
||||||
if (!this._previewMode) {
|
if (!this._previewMode) {
|
||||||
newTab.recordTimeFromUnloadToReload();
|
newTab.recordTimeFromUnloadToReload();
|
||||||
newTab.updateLastAccessed();
|
newTab.updateLastAccessed();
|
||||||
@@ -186,7 +176,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
oldTab.updateLastAccessed();
|
oldTab.updateLastAccessed();
|
||||||
// if this is the foreground window, update the last-seen timestamps.
|
// if this is the foreground window, update the last-seen timestamps.
|
||||||
if (this.ownerGlobal == BrowserWindowTracker.getTopWindow()) {
|
if (this.ownerGlobal == BrowserWindowTracker.getTopWindow()) {
|
||||||
@@ -1957,6 +2037,9 @@
|
@@ -1957,6 +2034,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let activeEl = document.activeElement;
|
let activeEl = document.activeElement;
|
||||||
@@ -196,7 +186,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
// If focus is on the old tab, move it to the new tab.
|
// If focus is on the old tab, move it to the new tab.
|
||||||
if (activeEl == oldTab) {
|
if (activeEl == oldTab) {
|
||||||
newTab.focus();
|
newTab.focus();
|
||||||
@@ -1995,7 +2078,7 @@
|
@@ -1995,7 +2075,7 @@
|
||||||
// Focus the location bar if it was previously focused for that tab.
|
// Focus the location bar if it was previously focused for that tab.
|
||||||
// In full screen mode, only bother making the location bar visible
|
// In full screen mode, only bother making the location bar visible
|
||||||
// if the tab is a blank one.
|
// if the tab is a blank one.
|
||||||
@@ -205,7 +195,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
let selectURL = () => {
|
let selectURL = () => {
|
||||||
if (this._asyncTabSwitching) {
|
if (this._asyncTabSwitching) {
|
||||||
// Set _awaitingSetURI flag to suppress popup notification
|
// Set _awaitingSetURI flag to suppress popup notification
|
||||||
@@ -2283,7 +2366,12 @@
|
@@ -2283,7 +2363,12 @@
|
||||||
return this._setTabLabel(aTab, aLabel);
|
return this._setTabLabel(aTab, aLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +209,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
if (!aLabel || (isURL && /^about:reader\?url=/.test(aLabel))) {
|
if (!aLabel || (isURL && /^about:reader\?url=/.test(aLabel))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2408,7 +2496,7 @@
|
@@ -2408,7 +2493,7 @@
|
||||||
newIndex = this.selectedTab._tPos + 1;
|
newIndex = this.selectedTab._tPos + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +218,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
if (this.isTabGroupLabel(targetTab)) {
|
if (this.isTabGroupLabel(targetTab)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Replacing a tab group label with a tab is not supported"
|
"Replacing a tab group label with a tab is not supported"
|
||||||
@@ -2685,6 +2773,7 @@
|
@@ -2685,6 +2770,7 @@
|
||||||
uriIsAboutBlank,
|
uriIsAboutBlank,
|
||||||
userContextId,
|
userContextId,
|
||||||
skipLoad,
|
skipLoad,
|
||||||
@@ -236,7 +226,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
} = {}) {
|
} = {}) {
|
||||||
let b = document.createXULElement("browser");
|
let b = document.createXULElement("browser");
|
||||||
// Use the JSM global to create the permanentKey, so that if the
|
// Use the JSM global to create the permanentKey, so that if the
|
||||||
@@ -2758,8 +2847,7 @@
|
@@ -2758,8 +2844,7 @@
|
||||||
// we use a different attribute name for this?
|
// we use a different attribute name for this?
|
||||||
b.setAttribute("name", name);
|
b.setAttribute("name", name);
|
||||||
}
|
}
|
||||||
@@ -246,7 +236,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
b.setAttribute("transparent", "true");
|
b.setAttribute("transparent", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2929,7 +3017,7 @@
|
@@ -2929,7 +3014,7 @@
|
||||||
|
|
||||||
let panel = this.getPanel(browser);
|
let panel = this.getPanel(browser);
|
||||||
let uniqueId = this._generateUniquePanelID();
|
let uniqueId = this._generateUniquePanelID();
|
||||||
@@ -255,7 +245,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
aTab.linkedPanel = uniqueId;
|
aTab.linkedPanel = uniqueId;
|
||||||
|
|
||||||
// Inject the <browser> into the DOM if necessary.
|
// Inject the <browser> into the DOM if necessary.
|
||||||
@@ -2989,8 +3077,8 @@
|
@@ -2989,8 +3074,8 @@
|
||||||
// If we transitioned from one browser to two browsers, we need to set
|
// If we transitioned from one browser to two browsers, we need to set
|
||||||
// hasSiblings=false on both the existing browser and the new browser.
|
// hasSiblings=false on both the existing browser and the new browser.
|
||||||
if (this.tabs.length == 2) {
|
if (this.tabs.length == 2) {
|
||||||
@@ -266,7 +256,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
} else {
|
} else {
|
||||||
aTab.linkedBrowser.browsingContext.hasSiblings = this.tabs.length > 1;
|
aTab.linkedBrowser.browsingContext.hasSiblings = this.tabs.length > 1;
|
||||||
}
|
}
|
||||||
@@ -3175,7 +3263,6 @@
|
@@ -3175,7 +3260,6 @@
|
||||||
this.selectedTab = this.addTrustedTab(BROWSER_NEW_TAB_URL, {
|
this.selectedTab = this.addTrustedTab(BROWSER_NEW_TAB_URL, {
|
||||||
tabIndex: tab._tPos + 1,
|
tabIndex: tab._tPos + 1,
|
||||||
userContextId: tab.userContextId,
|
userContextId: tab.userContextId,
|
||||||
@@ -274,7 +264,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
focusUrlBar: true,
|
focusUrlBar: true,
|
||||||
});
|
});
|
||||||
resolve(this.selectedBrowser);
|
resolve(this.selectedBrowser);
|
||||||
@@ -3285,6 +3372,10 @@
|
@@ -3285,6 +3369,10 @@
|
||||||
schemelessInput,
|
schemelessInput,
|
||||||
hasValidUserGestureActivation = false,
|
hasValidUserGestureActivation = false,
|
||||||
textDirectiveUserActivation = false,
|
textDirectiveUserActivation = false,
|
||||||
@@ -285,7 +275,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
// all callers of addTab that pass a params object need to pass
|
// all callers of addTab that pass a params object need to pass
|
||||||
@@ -3295,10 +3386,25 @@
|
@@ -3295,10 +3383,25 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +286,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
+
|
+
|
||||||
+ let hasZenDefaultUserContextId = false;
|
+ let hasZenDefaultUserContextId = false;
|
||||||
+ let zenForcedWorkspaceId = undefined;
|
+ let zenForcedWorkspaceId = undefined;
|
||||||
+ if (beforeRouteResult.isRouteFound) {
|
+ if (beforeRouteResult.userContextId) {
|
||||||
+ userContextId = beforeRouteResult.userContextId;
|
+ userContextId = beforeRouteResult.userContextId;
|
||||||
+ hasZenDefaultUserContextId = true;
|
+ hasZenDefaultUserContextId = true;
|
||||||
+ } else if (typeof gZenWorkspaces !== "undefined" && !_forZenEmptyTab) {
|
+ } else if (typeof gZenWorkspaces !== "undefined" && !_forZenEmptyTab) {
|
||||||
@@ -311,7 +301,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
// If we're opening a foreground tab, set the owner by default.
|
// If we're opening a foreground tab, set the owner by default.
|
||||||
ownerTab ??= inBackground ? null : this.selectedTab;
|
ownerTab ??= inBackground ? null : this.selectedTab;
|
||||||
|
|
||||||
@@ -3306,6 +3412,7 @@
|
@@ -3306,6 +3409,7 @@
|
||||||
if (this.selectedTab.owner) {
|
if (this.selectedTab.owner) {
|
||||||
this.selectedTab.owner = null;
|
this.selectedTab.owner = null;
|
||||||
}
|
}
|
||||||
@@ -319,7 +309,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
// Find the tab that opened this one, if any. This is used for
|
// Find the tab that opened this one, if any. This is used for
|
||||||
// determining positioning, and inherited attributes such as the
|
// determining positioning, and inherited attributes such as the
|
||||||
@@ -3358,6 +3465,22 @@
|
@@ -3358,6 +3462,22 @@
|
||||||
noInitialLabel,
|
noInitialLabel,
|
||||||
skipBackgroundNotify,
|
skipBackgroundNotify,
|
||||||
});
|
});
|
||||||
@@ -342,7 +332,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
if (insertTab) {
|
if (insertTab) {
|
||||||
// Insert the tab into the tab container in the correct position.
|
// Insert the tab into the tab container in the correct position.
|
||||||
this.#insertTabAtIndex(t, {
|
this.#insertTabAtIndex(t, {
|
||||||
@@ -3366,6 +3489,7 @@
|
@@ -3366,6 +3486,7 @@
|
||||||
ownerTab,
|
ownerTab,
|
||||||
openerTab,
|
openerTab,
|
||||||
pinned,
|
pinned,
|
||||||
@@ -350,7 +340,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
bulkOrderedOpen,
|
bulkOrderedOpen,
|
||||||
tabGroup: tabGroup ?? openerTab?.group,
|
tabGroup: tabGroup ?? openerTab?.group,
|
||||||
});
|
});
|
||||||
@@ -3384,6 +3508,7 @@
|
@@ -3384,6 +3505,7 @@
|
||||||
openWindowInfo,
|
openWindowInfo,
|
||||||
skipLoad,
|
skipLoad,
|
||||||
triggeringRemoteType,
|
triggeringRemoteType,
|
||||||
@@ -358,7 +348,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
if (focusUrlBar) {
|
if (focusUrlBar) {
|
||||||
@@ -3508,6 +3633,12 @@
|
@@ -3508,6 +3630,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,7 +361,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
// Additionally send pinned tab events
|
// Additionally send pinned tab events
|
||||||
if (pinned) {
|
if (pinned) {
|
||||||
this.#notifyPinnedStatus(t);
|
this.#notifyPinnedStatus(t);
|
||||||
@@ -3518,6 +3649,9 @@
|
@@ -3518,6 +3646,9 @@
|
||||||
if (!inBackground) {
|
if (!inBackground) {
|
||||||
this.selectedTab = t;
|
this.selectedTab = t;
|
||||||
}
|
}
|
||||||
@@ -381,7 +371,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3750,6 +3884,7 @@
|
@@ -3750,6 +3881,7 @@
|
||||||
isAdoptingGroup = false,
|
isAdoptingGroup = false,
|
||||||
isUserTriggered = false,
|
isUserTriggered = false,
|
||||||
telemetryUserCreateSource = "unknown",
|
telemetryUserCreateSource = "unknown",
|
||||||
@@ -389,7 +379,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
@@ -3760,9 +3895,6 @@
|
@@ -3760,9 +3892,6 @@
|
||||||
!this.isSplitViewWrapper(tabOrSplitView)
|
!this.isSplitViewWrapper(tabOrSplitView)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -399,7 +389,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!color) {
|
if (!color) {
|
||||||
@@ -3783,9 +3915,14 @@
|
@@ -3783,9 +3912,14 @@
|
||||||
label,
|
label,
|
||||||
isAdoptingGroup
|
isAdoptingGroup
|
||||||
);
|
);
|
||||||
@@ -416,7 +406,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
);
|
);
|
||||||
group.addTabs(tabsAndSplitViews);
|
group.addTabs(tabsAndSplitViews);
|
||||||
|
|
||||||
@@ -3906,7 +4043,7 @@
|
@@ -3906,7 +4040,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#handleTabMove(tab, () =>
|
this.#handleTabMove(tab, () =>
|
||||||
@@ -425,7 +415,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3990,6 +4127,7 @@
|
@@ -3990,6 +4124,7 @@
|
||||||
color: group.color,
|
color: group.color,
|
||||||
insertBefore: newTabs[0],
|
insertBefore: newTabs[0],
|
||||||
isAdoptingGroup: true,
|
isAdoptingGroup: true,
|
||||||
@@ -433,7 +423,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4200,6 +4338,7 @@
|
@@ -4200,6 +4335,7 @@
|
||||||
openWindowInfo,
|
openWindowInfo,
|
||||||
skipLoad,
|
skipLoad,
|
||||||
triggeringRemoteType,
|
triggeringRemoteType,
|
||||||
@@ -441,7 +431,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
// If we don't have a preferred remote type (or it is `NOT_REMOTE`), and
|
// If we don't have a preferred remote type (or it is `NOT_REMOTE`), and
|
||||||
@@ -4269,6 +4408,7 @@
|
@@ -4269,6 +4405,7 @@
|
||||||
openWindowInfo,
|
openWindowInfo,
|
||||||
name,
|
name,
|
||||||
skipLoad,
|
skipLoad,
|
||||||
@@ -449,7 +439,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4482,9 +4622,9 @@
|
@@ -4482,9 +4619,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new tab if needed.
|
// Add a new tab if needed.
|
||||||
@@ -461,7 +451,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
let url = "about:blank";
|
let url = "about:blank";
|
||||||
if (tabData.entries?.length) {
|
if (tabData.entries?.length) {
|
||||||
@@ -4521,8 +4661,10 @@
|
@@ -4521,8 +4658,10 @@
|
||||||
insertTab: false,
|
insertTab: false,
|
||||||
skipLoad: true,
|
skipLoad: true,
|
||||||
preferredRemoteType,
|
preferredRemoteType,
|
||||||
@@ -473,7 +463,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
if (select) {
|
if (select) {
|
||||||
tabToSelect = tab;
|
tabToSelect = tab;
|
||||||
}
|
}
|
||||||
@@ -4544,7 +4686,8 @@
|
@@ -4544,7 +4683,8 @@
|
||||||
this.pinTab(tab);
|
this.pinTab(tab);
|
||||||
// Then ensure all the tab open/pinning information is sent.
|
// Then ensure all the tab open/pinning information is sent.
|
||||||
this._fireTabOpen(tab, {});
|
this._fireTabOpen(tab, {});
|
||||||
@@ -483,7 +473,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
let { groupId } = tabData;
|
let { groupId } = tabData;
|
||||||
const tabGroup = tabGroupWorkingData.get(groupId);
|
const tabGroup = tabGroupWorkingData.get(groupId);
|
||||||
// if a tab refers to a tab group we don't know, skip any group
|
// if a tab refers to a tab group we don't know, skip any group
|
||||||
@@ -4564,7 +4707,10 @@
|
@@ -4564,7 +4704,10 @@
|
||||||
tabGroup.stateData.id,
|
tabGroup.stateData.id,
|
||||||
tabGroup.stateData.color,
|
tabGroup.stateData.color,
|
||||||
tabGroup.stateData.collapsed,
|
tabGroup.stateData.collapsed,
|
||||||
@@ -495,7 +485,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
);
|
);
|
||||||
tabsFragment.appendChild(tabGroup.node);
|
tabsFragment.appendChild(tabGroup.node);
|
||||||
}
|
}
|
||||||
@@ -4619,9 +4765,21 @@
|
@@ -4619,9 +4762,21 @@
|
||||||
// to remove the old selected tab.
|
// to remove the old selected tab.
|
||||||
if (tabToSelect) {
|
if (tabToSelect) {
|
||||||
let leftoverTab = this.selectedTab;
|
let leftoverTab = this.selectedTab;
|
||||||
@@ -517,7 +507,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
if (tabs.length > 1 || !tabs[0].selected) {
|
if (tabs.length > 1 || !tabs[0].selected) {
|
||||||
this._updateTabsAfterInsert();
|
this._updateTabsAfterInsert();
|
||||||
@@ -4812,11 +4970,14 @@
|
@@ -4812,11 +4967,14 @@
|
||||||
if (ownerTab) {
|
if (ownerTab) {
|
||||||
tab.owner = ownerTab;
|
tab.owner = ownerTab;
|
||||||
}
|
}
|
||||||
@@ -533,7 +523,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
if (
|
if (
|
||||||
!bulkOrderedOpen &&
|
!bulkOrderedOpen &&
|
||||||
((openerTab &&
|
((openerTab &&
|
||||||
@@ -4828,7 +4989,7 @@
|
@@ -4828,7 +4986,7 @@
|
||||||
let lastRelatedTab =
|
let lastRelatedTab =
|
||||||
openerTab && this._lastRelatedTabMap.get(openerTab);
|
openerTab && this._lastRelatedTabMap.get(openerTab);
|
||||||
let previousTab = lastRelatedTab || openerTab || this.selectedTab;
|
let previousTab = lastRelatedTab || openerTab || this.selectedTab;
|
||||||
@@ -542,7 +532,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
tabGroup = previousTab.group;
|
tabGroup = previousTab.group;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -4844,7 +5005,7 @@
|
@@ -4844,7 +5002,7 @@
|
||||||
previousTab.splitview
|
previousTab.splitview
|
||||||
) + 1;
|
) + 1;
|
||||||
} else if (previousTab.visible) {
|
} else if (previousTab.visible) {
|
||||||
@@ -551,7 +541,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
} else if (previousTab == FirefoxViewHandler.tab) {
|
} else if (previousTab == FirefoxViewHandler.tab) {
|
||||||
elementIndex = 0;
|
elementIndex = 0;
|
||||||
}
|
}
|
||||||
@@ -4872,14 +5033,14 @@
|
@@ -4872,14 +5030,14 @@
|
||||||
}
|
}
|
||||||
// Ensure index is within bounds.
|
// Ensure index is within bounds.
|
||||||
if (tab.pinned) {
|
if (tab.pinned) {
|
||||||
@@ -570,7 +560,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
if (pinned && !itemAfter?.pinned) {
|
if (pinned && !itemAfter?.pinned) {
|
||||||
itemAfter = null;
|
itemAfter = null;
|
||||||
@@ -4896,7 +5057,7 @@
|
@@ -4896,7 +5054,7 @@
|
||||||
|
|
||||||
this.tabContainer._invalidateCachedTabs();
|
this.tabContainer._invalidateCachedTabs();
|
||||||
|
|
||||||
@@ -579,7 +569,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
if (
|
if (
|
||||||
(this.isTab(itemAfter) && itemAfter.group == tabGroup) ||
|
(this.isTab(itemAfter) && itemAfter.group == tabGroup) ||
|
||||||
this.isSplitViewWrapper(itemAfter)
|
this.isSplitViewWrapper(itemAfter)
|
||||||
@@ -4927,7 +5088,11 @@
|
@@ -4927,7 +5085,11 @@
|
||||||
const tabContainer = pinned
|
const tabContainer = pinned
|
||||||
? this.tabContainer.pinnedTabsContainer
|
? this.tabContainer.pinnedTabsContainer
|
||||||
: this.tabContainer;
|
: this.tabContainer;
|
||||||
@@ -591,7 +581,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tab.group?.collapsed) {
|
if (tab.group?.collapsed) {
|
||||||
@@ -4942,6 +5107,7 @@
|
@@ -4942,6 +5104,7 @@
|
||||||
if (pinned) {
|
if (pinned) {
|
||||||
this._updateTabBarForPinnedTabs();
|
this._updateTabBarForPinnedTabs();
|
||||||
}
|
}
|
||||||
@@ -599,7 +589,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
TabBarVisibility.update();
|
TabBarVisibility.update();
|
||||||
}
|
}
|
||||||
@@ -5490,6 +5656,7 @@
|
@@ -5490,6 +5653,7 @@
|
||||||
telemetrySource,
|
telemetrySource,
|
||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
@@ -607,7 +597,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
// When 'closeWindowWithLastTab' pref is enabled, closing all tabs
|
// When 'closeWindowWithLastTab' pref is enabled, closing all tabs
|
||||||
// can be considered equivalent to closing the window.
|
// can be considered equivalent to closing the window.
|
||||||
if (
|
if (
|
||||||
@@ -5579,6 +5746,7 @@
|
@@ -5579,6 +5743,7 @@
|
||||||
if (lastToClose) {
|
if (lastToClose) {
|
||||||
this.removeTab(lastToClose, aParams);
|
this.removeTab(lastToClose, aParams);
|
||||||
}
|
}
|
||||||
@@ -615,7 +605,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
@@ -5624,6 +5792,14 @@
|
@@ -5624,6 +5789,14 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,7 +620,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
let isVisibleTab = aTab.visible;
|
let isVisibleTab = aTab.visible;
|
||||||
// We have to sample the tab width now, since _beginRemoveTab might
|
// We have to sample the tab width now, since _beginRemoveTab might
|
||||||
// end up modifying the DOM in such a way that aTab gets a new
|
// end up modifying the DOM in such a way that aTab gets a new
|
||||||
@@ -5631,6 +5807,9 @@
|
@@ -5631,6 +5804,9 @@
|
||||||
// state).
|
// state).
|
||||||
let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width;
|
let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width;
|
||||||
let isLastTab = this.#isLastTabInWindow(aTab);
|
let isLastTab = this.#isLastTabInWindow(aTab);
|
||||||
@@ -640,7 +630,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
if (
|
if (
|
||||||
!this._beginRemoveTab(aTab, {
|
!this._beginRemoveTab(aTab, {
|
||||||
closeWindowFastpath: true,
|
closeWindowFastpath: true,
|
||||||
@@ -5642,13 +5821,14 @@
|
@@ -5642,13 +5818,14 @@
|
||||||
telemetrySource,
|
telemetrySource,
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
@@ -656,7 +646,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
let lockTabSizing =
|
let lockTabSizing =
|
||||||
!this.tabContainer.verticalMode &&
|
!this.tabContainer.verticalMode &&
|
||||||
!aTab.pinned &&
|
!aTab.pinned &&
|
||||||
@@ -5679,7 +5859,13 @@
|
@@ -5679,7 +5856,13 @@
|
||||||
// We're not animating, so we can cancel the animation stopwatch.
|
// We're not animating, so we can cancel the animation stopwatch.
|
||||||
Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId);
|
Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId);
|
||||||
aTab._closeTimeAnimTimerId = null;
|
aTab._closeTimeAnimTimerId = null;
|
||||||
@@ -671,7 +661,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5813,7 +5999,7 @@
|
@@ -5813,7 +5996,7 @@
|
||||||
closeWindowWithLastTab != null
|
closeWindowWithLastTab != null
|
||||||
? closeWindowWithLastTab
|
? closeWindowWithLastTab
|
||||||
: !window.toolbar.visible ||
|
: !window.toolbar.visible ||
|
||||||
@@ -680,7 +670,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
if (closeWindow) {
|
if (closeWindow) {
|
||||||
// We've already called beforeunload on all the relevant tabs if we get here,
|
// We've already called beforeunload on all the relevant tabs if we get here,
|
||||||
@@ -5837,6 +6023,7 @@
|
@@ -5837,6 +6020,7 @@
|
||||||
|
|
||||||
newTab = true;
|
newTab = true;
|
||||||
}
|
}
|
||||||
@@ -688,7 +678,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
aTab._endRemoveArgs = [closeWindow, newTab];
|
aTab._endRemoveArgs = [closeWindow, newTab];
|
||||||
|
|
||||||
// swapBrowsersAndCloseOther will take care of closing the window without animation.
|
// swapBrowsersAndCloseOther will take care of closing the window without animation.
|
||||||
@@ -5877,13 +6064,7 @@
|
@@ -5877,13 +6061,7 @@
|
||||||
aTab._mouseleave();
|
aTab._mouseleave();
|
||||||
|
|
||||||
if (newTab) {
|
if (newTab) {
|
||||||
@@ -703,7 +693,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
} else {
|
} else {
|
||||||
TabBarVisibility.update();
|
TabBarVisibility.update();
|
||||||
}
|
}
|
||||||
@@ -6016,6 +6197,7 @@
|
@@ -6016,6 +6194,7 @@
|
||||||
this.tabs[i]._tPos = i;
|
this.tabs[i]._tPos = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,7 +701,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
if (!this._windowIsClosing) {
|
if (!this._windowIsClosing) {
|
||||||
// update tab close buttons state
|
// update tab close buttons state
|
||||||
this.tabContainer._updateCloseButtons();
|
this.tabContainer._updateCloseButtons();
|
||||||
@@ -6201,6 +6383,7 @@
|
@@ -6201,6 +6380,7 @@
|
||||||
memory_after: await getTotalMemoryUsage(),
|
memory_after: await getTotalMemoryUsage(),
|
||||||
time_to_unload_in_ms: timeElapsed,
|
time_to_unload_in_ms: timeElapsed,
|
||||||
});
|
});
|
||||||
@@ -719,7 +709,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -6246,6 +6429,7 @@
|
@@ -6246,6 +6426,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let excludeTabs = new Set(aExcludeTabs);
|
let excludeTabs = new Set(aExcludeTabs);
|
||||||
@@ -727,7 +717,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
// If this tab has a successor, it should be selectable, since
|
// If this tab has a successor, it should be selectable, since
|
||||||
// hiding or closing a tab removes that tab as a successor.
|
// hiding or closing a tab removes that tab as a successor.
|
||||||
@@ -6258,15 +6442,22 @@
|
@@ -6258,15 +6439,22 @@
|
||||||
!excludeTabs.has(aTab.owner) &&
|
!excludeTabs.has(aTab.owner) &&
|
||||||
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
|
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
|
||||||
) {
|
) {
|
||||||
@@ -752,7 +742,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
let tab = this.tabContainer.findNextTab(aTab, {
|
let tab = this.tabContainer.findNextTab(aTab, {
|
||||||
direction: 1,
|
direction: 1,
|
||||||
filter: _tab => remainingTabs.includes(_tab),
|
filter: _tab => remainingTabs.includes(_tab),
|
||||||
@@ -6280,7 +6471,7 @@
|
@@ -6280,7 +6468,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab) {
|
if (tab) {
|
||||||
@@ -761,7 +751,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no qualifying visible tab was found, see if there is a tab in
|
// If no qualifying visible tab was found, see if there is a tab in
|
||||||
@@ -6301,7 +6492,7 @@
|
@@ -6301,7 +6489,7 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,7 +760,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}
|
}
|
||||||
|
|
||||||
_blurTab(aTab) {
|
_blurTab(aTab) {
|
||||||
@@ -6312,7 +6503,7 @@
|
@@ -6312,7 +6500,7 @@
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
* False if swapping isn't permitted, true otherwise.
|
* False if swapping isn't permitted, true otherwise.
|
||||||
*/
|
*/
|
||||||
@@ -779,7 +769,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
// Do not allow transfering a private tab to a non-private window
|
// Do not allow transfering a private tab to a non-private window
|
||||||
// and vice versa.
|
// and vice versa.
|
||||||
if (
|
if (
|
||||||
@@ -6366,6 +6557,7 @@
|
@@ -6366,6 +6554,7 @@
|
||||||
// fire the beforeunload event in the process. Close the other
|
// fire the beforeunload event in the process. Close the other
|
||||||
// window if this was its last tab.
|
// window if this was its last tab.
|
||||||
if (
|
if (
|
||||||
@@ -787,7 +777,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
!remoteBrowser._beginRemoveTab(aOtherTab, {
|
!remoteBrowser._beginRemoveTab(aOtherTab, {
|
||||||
adoptedByTab: aOurTab,
|
adoptedByTab: aOurTab,
|
||||||
closeWindowWithLastTab: true,
|
closeWindowWithLastTab: true,
|
||||||
@@ -6377,7 +6569,7 @@
|
@@ -6377,7 +6566,7 @@
|
||||||
// If this is the last tab of the window, hide the window
|
// If this is the last tab of the window, hide the window
|
||||||
// immediately without animation before the docshell swap, to avoid
|
// immediately without animation before the docshell swap, to avoid
|
||||||
// about:blank being painted.
|
// about:blank being painted.
|
||||||
@@ -796,7 +786,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
if (closeWindow) {
|
if (closeWindow) {
|
||||||
let win = aOtherTab.ownerGlobal;
|
let win = aOtherTab.ownerGlobal;
|
||||||
win.windowUtils.suppressAnimation(true);
|
win.windowUtils.suppressAnimation(true);
|
||||||
@@ -6511,11 +6703,13 @@
|
@@ -6511,11 +6700,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finish tearing down the tab that's going away.
|
// Finish tearing down the tab that's going away.
|
||||||
@@ -810,7 +800,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
this.setTabTitle(aOurTab);
|
this.setTabTitle(aOurTab);
|
||||||
|
|
||||||
@@ -6717,10 +6911,10 @@
|
@@ -6717,10 +6908,10 @@
|
||||||
SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
|
SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -823,7 +813,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
aTab.selected ||
|
aTab.selected ||
|
||||||
aTab.closing ||
|
aTab.closing ||
|
||||||
// Tabs that are sharing the screen, microphone or camera cannot be hidden.
|
// Tabs that are sharing the screen, microphone or camera cannot be hidden.
|
||||||
@@ -6780,7 +6974,8 @@
|
@@ -6780,7 +6971,8 @@
|
||||||
* @param {object} [aOptions={}]
|
* @param {object} [aOptions={}]
|
||||||
* Key-value pairs that will be serialized into the features string.
|
* Key-value pairs that will be serialized into the features string.
|
||||||
*/
|
*/
|
||||||
@@ -833,7 +823,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
if (this.tabs.length == 1) {
|
if (this.tabs.length == 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -6797,7 +6992,7 @@
|
@@ -6797,7 +6989,7 @@
|
||||||
// tell a new window to take the "dropped" tab
|
// tell a new window to take the "dropped" tab
|
||||||
let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||||
args.appendElement(aTab.splitview ?? aTab);
|
args.appendElement(aTab.splitview ?? aTab);
|
||||||
@@ -842,7 +832,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
private: PrivateBrowsingUtils.isWindowPrivate(window),
|
private: PrivateBrowsingUtils.isWindowPrivate(window),
|
||||||
features: Object.entries(aOptions)
|
features: Object.entries(aOptions)
|
||||||
.map(([key, value]) => `${key}=${value}`)
|
.map(([key, value]) => `${key}=${value}`)
|
||||||
@@ -6805,6 +7000,8 @@
|
@@ -6805,6 +6997,8 @@
|
||||||
openerWindow: window,
|
openerWindow: window,
|
||||||
args,
|
args,
|
||||||
});
|
});
|
||||||
@@ -851,7 +841,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -6917,7 +7114,7 @@
|
@@ -6917,7 +7111,7 @@
|
||||||
* `true` if element is a `<tab-group>`
|
* `true` if element is a `<tab-group>`
|
||||||
*/
|
*/
|
||||||
isTabGroup(element) {
|
isTabGroup(element) {
|
||||||
@@ -860,7 +850,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7002,8 +7199,8 @@
|
@@ -7002,8 +7196,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow mixing pinned and unpinned tabs.
|
// Don't allow mixing pinned and unpinned tabs.
|
||||||
@@ -871,7 +861,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
} else {
|
} else {
|
||||||
tabIndex = Math.max(tabIndex, this.pinnedTabCount);
|
tabIndex = Math.max(tabIndex, this.pinnedTabCount);
|
||||||
}
|
}
|
||||||
@@ -7049,8 +7246,8 @@
|
@@ -7049,8 +7243,8 @@
|
||||||
this.#handleTabMove(
|
this.#handleTabMove(
|
||||||
element,
|
element,
|
||||||
() => {
|
() => {
|
||||||
@@ -882,7 +872,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
neighbor = neighbor.group;
|
neighbor = neighbor.group;
|
||||||
}
|
}
|
||||||
if (neighbor?.splitview) {
|
if (neighbor?.splitview) {
|
||||||
@@ -7061,6 +7258,12 @@
|
@@ -7061,6 +7255,12 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -895,7 +885,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
if (movingForwards && neighbor) {
|
if (movingForwards && neighbor) {
|
||||||
neighbor.after(element);
|
neighbor.after(element);
|
||||||
@@ -7119,23 +7322,31 @@
|
@@ -7119,23 +7319,31 @@
|
||||||
#moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) {
|
#moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) {
|
||||||
if (this.isTabGroupLabel(targetElement)) {
|
if (this.isTabGroupLabel(targetElement)) {
|
||||||
targetElement = targetElement.group;
|
targetElement = targetElement.group;
|
||||||
@@ -933,7 +923,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
} else if (!element.pinned && targetElement && targetElement.pinned) {
|
} else if (!element.pinned && targetElement && targetElement.pinned) {
|
||||||
// If the caller asks to move an unpinned element next to a pinned
|
// If the caller asks to move an unpinned element next to a pinned
|
||||||
// tab, move the unpinned element to be the first unpinned element
|
// tab, move the unpinned element to be the first unpinned element
|
||||||
@@ -7148,12 +7359,35 @@
|
@@ -7148,12 +7356,35 @@
|
||||||
// move the tab group right before the first unpinned tab.
|
// move the tab group right before the first unpinned tab.
|
||||||
// 4. Moving a tab group and the first unpinned tab is grouped:
|
// 4. Moving a tab group and the first unpinned tab is grouped:
|
||||||
// move the tab group right before the first unpinned tab's tab group.
|
// move the tab group right before the first unpinned tab's tab group.
|
||||||
@@ -970,7 +960,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
// We want to include the splitview wrapper if it's the targetElement, but
|
// We want to include the splitview wrapper if it's the targetElement, but
|
||||||
// not in the case where we want to reverse tabs within the same splitview.
|
// not in the case where we want to reverse tabs within the same splitview.
|
||||||
@@ -7162,6 +7396,7 @@
|
@@ -7162,6 +7393,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let getContainer = () =>
|
let getContainer = () =>
|
||||||
@@ -978,7 +968,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
element.pinned
|
element.pinned
|
||||||
? this.tabContainer.pinnedTabsContainer
|
? this.tabContainer.pinnedTabsContainer
|
||||||
: this.tabContainer;
|
: this.tabContainer;
|
||||||
@@ -7170,11 +7405,15 @@
|
@@ -7170,11 +7402,15 @@
|
||||||
element,
|
element,
|
||||||
() => {
|
() => {
|
||||||
if (moveBefore) {
|
if (moveBefore) {
|
||||||
@@ -995,7 +985,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
metricsContext
|
metricsContext
|
||||||
@@ -7248,11 +7487,15 @@
|
@@ -7248,11 +7484,15 @@
|
||||||
* @param {TabMetricsContext} [metricsContext]
|
* @param {TabMetricsContext} [metricsContext]
|
||||||
*/
|
*/
|
||||||
moveTabToExistingGroup(aTab, aGroup, metricsContext) {
|
moveTabToExistingGroup(aTab, aGroup, metricsContext) {
|
||||||
@@ -1014,7 +1004,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}
|
}
|
||||||
if (aTab.group && aTab.group.id === aGroup.id) {
|
if (aTab.group && aTab.group.id === aGroup.id) {
|
||||||
return;
|
return;
|
||||||
@@ -7324,6 +7567,7 @@
|
@@ -7324,6 +7564,7 @@
|
||||||
|
|
||||||
let state = {
|
let state = {
|
||||||
tabIndex: tab._tPos,
|
tabIndex: tab._tPos,
|
||||||
@@ -1022,7 +1012,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
};
|
};
|
||||||
if (tab.visible) {
|
if (tab.visible) {
|
||||||
state.elementIndex = tab.elementIndex;
|
state.elementIndex = tab.elementIndex;
|
||||||
@@ -7355,7 +7599,7 @@
|
@@ -7355,7 +7596,7 @@
|
||||||
let changedSplitView =
|
let changedSplitView =
|
||||||
previousTabState.splitViewId != currentTabState.splitViewId;
|
previousTabState.splitViewId != currentTabState.splitViewId;
|
||||||
|
|
||||||
@@ -1031,7 +1021,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
tab.dispatchEvent(
|
tab.dispatchEvent(
|
||||||
new CustomEvent("TabMove", {
|
new CustomEvent("TabMove", {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
@@ -7402,6 +7646,10 @@
|
@@ -7402,6 +7643,10 @@
|
||||||
|
|
||||||
moveActionCallback();
|
moveActionCallback();
|
||||||
|
|
||||||
@@ -1042,7 +1032,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
// Clear tabs cache after moving nodes because the order of tabs may have
|
// Clear tabs cache after moving nodes because the order of tabs may have
|
||||||
// changed.
|
// changed.
|
||||||
this.tabContainer._invalidateCachedTabs();
|
this.tabContainer._invalidateCachedTabs();
|
||||||
@@ -7452,7 +7700,22 @@
|
@@ -7452,7 +7697,22 @@
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
* The new tab in the current window, null if the tab couldn't be adopted.
|
* The new tab in the current window, null if the tab couldn't be adopted.
|
||||||
*/
|
*/
|
||||||
@@ -1066,7 +1056,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
// Swap the dropped tab with a new one we create and then close
|
// Swap the dropped tab with a new one we create and then close
|
||||||
// it in the other window (making it seem to have moved between
|
// it in the other window (making it seem to have moved between
|
||||||
// windows). We also ensure that the tab we create to swap into has
|
// windows). We also ensure that the tab we create to swap into has
|
||||||
@@ -7495,6 +7758,8 @@
|
@@ -7495,6 +7755,8 @@
|
||||||
}
|
}
|
||||||
params.skipLoad = true;
|
params.skipLoad = true;
|
||||||
let newTab = this.addWebTab("about:blank", params);
|
let newTab = this.addWebTab("about:blank", params);
|
||||||
@@ -1075,7 +1065,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
aTab.container.tabDragAndDrop.finishAnimateTabMove();
|
aTab.container.tabDragAndDrop.finishAnimateTabMove();
|
||||||
|
|
||||||
@@ -8205,7 +8470,7 @@
|
@@ -8205,7 +8467,7 @@
|
||||||
// preventDefault(). It will still raise the window if appropriate.
|
// preventDefault(). It will still raise the window if appropriate.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1084,7 +1074,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
window.focus();
|
window.focus();
|
||||||
aEvent.preventDefault();
|
aEvent.preventDefault();
|
||||||
}
|
}
|
||||||
@@ -8222,7 +8487,6 @@
|
@@ -8222,7 +8484,6 @@
|
||||||
|
|
||||||
on_TabGroupCollapse(aEvent) {
|
on_TabGroupCollapse(aEvent) {
|
||||||
aEvent.target.tabs.forEach(tab => {
|
aEvent.target.tabs.forEach(tab => {
|
||||||
@@ -1092,7 +1082,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8556,7 +8820,9 @@
|
@@ -8556,7 +8817,9 @@
|
||||||
|
|
||||||
let filter = this._tabFilters.get(tab);
|
let filter = this._tabFilters.get(tab);
|
||||||
if (filter) {
|
if (filter) {
|
||||||
@@ -1102,7 +1092,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
|
|
||||||
let listener = this._tabListeners.get(tab);
|
let listener = this._tabListeners.get(tab);
|
||||||
if (listener) {
|
if (listener) {
|
||||||
@@ -9359,6 +9625,7 @@
|
@@ -9359,6 +9622,7 @@
|
||||||
aWebProgress.isTopLevel
|
aWebProgress.isTopLevel
|
||||||
) {
|
) {
|
||||||
this.mTab.setAttribute("busy", "true");
|
this.mTab.setAttribute("busy", "true");
|
||||||
@@ -1110,7 +1100,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
gBrowser._tabAttrModified(this.mTab, ["busy"]);
|
gBrowser._tabAttrModified(this.mTab, ["busy"]);
|
||||||
this.mTab._notselectedsinceload = !this.mTab.selected;
|
this.mTab._notselectedsinceload = !this.mTab.selected;
|
||||||
}
|
}
|
||||||
@@ -9439,6 +9706,7 @@
|
@@ -9439,6 +9703,7 @@
|
||||||
// known defaults. Note we use the original URL since about:newtab
|
// known defaults. Note we use the original URL since about:newtab
|
||||||
// redirects to a prerendered page.
|
// redirects to a prerendered page.
|
||||||
const shouldRemoveFavicon =
|
const shouldRemoveFavicon =
|
||||||
@@ -1118,7 +1108,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
!this.mBrowser.mIconURL &&
|
!this.mBrowser.mIconURL &&
|
||||||
!ignoreBlank &&
|
!ignoreBlank &&
|
||||||
!(originalLocation.spec in FAVICON_DEFAULTS);
|
!(originalLocation.spec in FAVICON_DEFAULTS);
|
||||||
@@ -9613,13 +9881,6 @@
|
@@ -9613,13 +9878,6 @@
|
||||||
this.mBrowser.originalURI = aRequest.originalURI;
|
this.mBrowser.originalURI = aRequest.originalURI;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1132,7 +1122,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..68a037d5a0e3416f31ffcb163592f008
|
|||||||
}
|
}
|
||||||
|
|
||||||
let userContextId = this.mBrowser.getAttribute("usercontextid") || 0;
|
let userContextId = this.mBrowser.getAttribute("usercontextid") || 0;
|
||||||
@@ -10507,7 +10768,8 @@ var TabContextMenu = {
|
@@ -10507,7 +10765,8 @@ var TabContextMenu = {
|
||||||
);
|
);
|
||||||
contextUnpinSelectedTabs.hidden =
|
contextUnpinSelectedTabs.hidden =
|
||||||
!this.contextTab.pinned || !this.multiselected;
|
!this.contextTab.pinned || !this.multiselected;
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
diff --git a/gfx/webrender_bindings/WebRenderAPI.cpp b/gfx/webrender_bindings/WebRenderAPI.cpp
|
||||||
|
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
|
||||||
|
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
|
||||||
|
@@ -298,11 +298,13 @@
|
||||||
|
panic_on_gl_error, picTileWidth, picTileHeight,
|
||||||
|
gfx::gfxVars::WebRenderRequiresHardwareDriver(),
|
||||||
|
StaticPrefs::gfx_webrender_low_quality_pinch_zoom_AtStartup(),
|
||||||
|
StaticPrefs::gfx_webrender_max_shared_surface_size_AtStartup(),
|
||||||
|
StaticPrefs::gfx_webrender_enable_subpixel_aa_AtStartup(),
|
||||||
|
- compositor->ShouldUseLayerCompositor())) {
|
||||||
|
+ compositor->ShouldUseLayerCompositor(),
|
||||||
|
+ StaticPrefs::
|
||||||
|
+ gfx_webrender_opaque_backdrop_fallback_AtStartup())) {
|
||||||
|
// wr_window_new puts a message into gfxCriticalNote if it returns
|
||||||
|
// false
|
||||||
|
MOZ_ASSERT(errorMessage);
|
||||||
|
error.AssignASCII(errorMessage);
|
||||||
|
wr_api_free_error_msg(errorMessage);
|
||||||
|
diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs
|
||||||
|
--- a/gfx/webrender_bindings/src/bindings.rs
|
||||||
|
+++ b/gfx/webrender_bindings/src/bindings.rs
|
||||||
|
@@ -1998,10 +1998,11 @@
|
||||||
|
reject_software_rasterizer: bool,
|
||||||
|
low_quality_pinch_zoom: bool,
|
||||||
|
max_shared_surface_size: i32,
|
||||||
|
enable_subpixel_aa: bool,
|
||||||
|
use_layer_compositor: bool,
|
||||||
|
+ opaque_backdrop_fallback: bool,
|
||||||
|
) -> bool {
|
||||||
|
assert!(unsafe { is_in_render_thread() });
|
||||||
|
|
||||||
|
// Ensure the WR profiler callbacks are hooked up to the Gecko profiler.
|
||||||
|
set_profiler_hooks(Some(&PROFILER_HOOKS));
|
||||||
|
@@ -2164,10 +2165,11 @@
|
||||||
|
texture_cache_config,
|
||||||
|
reject_software_rasterizer,
|
||||||
|
low_quality_pinch_zoom,
|
||||||
|
max_shared_surface_size,
|
||||||
|
enable_dithering,
|
||||||
|
+ opaque_backdrop_fallback,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let window_size = DeviceIntSize::new(window_width, window_height);
|
||||||
|
let notifier = Box::new(CppNotifier { window_id });
|
||||||
|
diff --git a/gfx/wr/webrender/src/device/gl.rs b/gfx/wr/webrender/src/device/gl.rs
|
||||||
|
--- a/gfx/wr/webrender/src/device/gl.rs
|
||||||
|
+++ b/gfx/wr/webrender/src/device/gl.rs
|
||||||
|
@@ -3982,10 +3982,14 @@
|
||||||
|
|
||||||
|
pub fn disable_color_write(&self) {
|
||||||
|
self.gl.color_mask(false, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ pub fn set_color_mask(&self, r: bool, g: bool, b: bool, a: bool) {
|
||||||
|
+ self.gl.color_mask(r, g, b, a);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
pub fn set_blend(&mut self, enable: bool) {
|
||||||
|
if enable {
|
||||||
|
self.gl.enable(gl::BLEND);
|
||||||
|
} else {
|
||||||
|
self.gl.disable(gl::BLEND);
|
||||||
|
diff --git a/gfx/wr/webrender/src/renderer/init.rs b/gfx/wr/webrender/src/renderer/init.rs
|
||||||
|
--- a/gfx/wr/webrender/src/renderer/init.rs
|
||||||
|
+++ b/gfx/wr/webrender/src/renderer/init.rs
|
||||||
|
@@ -204,10 +204,12 @@
|
||||||
|
pub low_quality_pinch_zoom: bool,
|
||||||
|
pub max_shared_surface_size: i32,
|
||||||
|
/// If true, open a debug socket to listen for remote debugger.
|
||||||
|
/// Relies on `debugger` cargo feature being enabled.
|
||||||
|
pub enable_debugger: bool,
|
||||||
|
+ /// See explanation of `gfx.webrender.opaque-backdrop-fallback`.
|
||||||
|
+ pub opaque_backdrop_fallback: bool,
|
||||||
|
|
||||||
|
/// Use the new quad primitive path for box-shadow blur rendering.
|
||||||
|
pub use_quad_box_shadow: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -277,10 +279,11 @@
|
||||||
|
enable_instancing: true,
|
||||||
|
reject_software_rasterizer: false,
|
||||||
|
low_quality_pinch_zoom: false,
|
||||||
|
max_shared_surface_size: 2048,
|
||||||
|
enable_debugger: true,
|
||||||
|
+ opaque_backdrop_fallback: false,
|
||||||
|
use_quad_box_shadow: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -802,10 +805,11 @@
|
||||||
|
allocated_native_surfaces: FastHashSet::default(),
|
||||||
|
debug_overlay_state: DebugOverlayState::new(),
|
||||||
|
buffer_damage_tracker: BufferDamageTracker::default(),
|
||||||
|
max_primitive_instance_count,
|
||||||
|
enable_instancing: options.enable_instancing,
|
||||||
|
+ opaque_backdrop_fallback: options.opaque_backdrop_fallback,
|
||||||
|
consecutive_oom_frames: 0,
|
||||||
|
target_frame_publish_id: None,
|
||||||
|
pending_result_msg: None,
|
||||||
|
layer_compositor_frame_state_in_prev_frame: None,
|
||||||
|
external_composite_debug_items: Vec::new(),
|
||||||
|
diff --git a/gfx/wr/webrender/src/renderer/mod.rs b/gfx/wr/webrender/src/renderer/mod.rs
|
||||||
|
--- a/gfx/wr/webrender/src/renderer/mod.rs
|
||||||
|
+++ b/gfx/wr/webrender/src/renderer/mod.rs
|
||||||
|
@@ -867,10 +867,12 @@
|
||||||
|
buffer_damage_tracker: BufferDamageTracker,
|
||||||
|
|
||||||
|
max_primitive_instance_count: usize,
|
||||||
|
enable_instancing: bool,
|
||||||
|
|
||||||
|
+ opaque_backdrop_fallback: bool,
|
||||||
|
+
|
||||||
|
/// Count consecutive oom frames to detectif we are stuck unable to render
|
||||||
|
/// in a loop.
|
||||||
|
consecutive_oom_frames: u32,
|
||||||
|
|
||||||
|
/// update() defers processing of ResultMsg, if frame_publish_id of
|
||||||
|
@@ -2787,18 +2789,29 @@
|
||||||
|
let read_target = ReadTarget::from_texture(cache_texture);
|
||||||
|
|
||||||
|
// Should always be drawing to picture cache tiles or off-screen surface!
|
||||||
|
debug_assert!(!draw_target.is_default());
|
||||||
|
let device_to_framebuffer = Scale::new(1i32);
|
||||||
|
+ let dest_fb_rect = dest * device_to_framebuffer;
|
||||||
|
|
||||||
|
self.device.blit_render_target(
|
||||||
|
read_target,
|
||||||
|
src * device_to_framebuffer,
|
||||||
|
draw_target,
|
||||||
|
- dest * device_to_framebuffer,
|
||||||
|
+ dest_fb_rect,
|
||||||
|
TextureFilter::Linear,
|
||||||
|
);
|
||||||
|
+
|
||||||
|
+ if self.opaque_backdrop_fallback {
|
||||||
|
+ self.device.set_color_mask(false, false, false, true);
|
||||||
|
+ self.device.clear_target(
|
||||||
|
+ Some([0.0, 0.0, 0.0, 1.0]),
|
||||||
|
+ None,
|
||||||
|
+ Some(dest_fb_rect),
|
||||||
|
+ );
|
||||||
|
+ self.device.set_color_mask(true, true, true, true);
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_picture_cache_target(
|
||||||
|
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
|
||||||
|
--- a/modules/libpref/init/StaticPrefList.yaml
|
||||||
|
+++ b/modules/libpref/init/StaticPrefList.yaml
|
||||||
|
@@ -8439,10 +8439,17 @@
|
||||||
|
#else
|
||||||
|
value: false
|
||||||
|
#endif
|
||||||
|
mirror: once
|
||||||
|
|
||||||
|
+# Make backdrop-filter treat its captured backdrop as if it had been
|
||||||
|
+# composited over an opaque-black background. (See bug 2036640)
|
||||||
|
+- name: gfx.webrender.opaque-backdrop-fallback
|
||||||
|
+ type: bool
|
||||||
|
+ value: true
|
||||||
|
+ mirror: once
|
||||||
|
+
|
||||||
|
# Disable wait of GPU execution completion
|
||||||
|
- name: gfx.webrender.wait-gpu-finished.disabled
|
||||||
|
type: bool
|
||||||
|
value: false
|
||||||
|
mirror: once
|
||||||
|
|
||||||
14
src/widget/cocoa/nsCocoaWindow-mm.patch
Normal file
14
src/widget/cocoa/nsCocoaWindow-mm.patch
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
|
||||||
|
index 3e58a40d747fb3e05bd67518b41b6467725cfe42..995419b6390e8abe663f71f8fb26281eae56aa84 100644
|
||||||
|
--- a/widget/cocoa/nsCocoaWindow.mm
|
||||||
|
+++ b/widget/cocoa/nsCocoaWindow.mm
|
||||||
|
@@ -5329,6 +5329,9 @@ static unsigned int WindowMaskForBorderStyle(BorderStyle aBorderStyle) {
|
||||||
|
case nsIWidget::eDocumentWindowAnimation:
|
||||||
|
behavior = NSWindowAnimationBehaviorDocumentWindow;
|
||||||
|
break;
|
||||||
|
+ case nsIWidget::eZenUtilityWindowAnimation:
|
||||||
|
+ behavior = NSWindowAnimationBehaviorUtilityWindow;
|
||||||
|
+ break;
|
||||||
|
default:
|
||||||
|
MOZ_FALLTHROUGH_ASSERT("unexpected mAnimationType value");
|
||||||
|
case nsIWidget::eGenericWindowAnimation:
|
||||||
12
src/widget/nsIWidget-h.patch
Normal file
12
src/widget/nsIWidget-h.patch
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h
|
||||||
|
index 4a5d933d865585ea772bafd233f7d4ebce3ac02b..3eae20d579330a71196c83eeb20d8fb2cd9f5fae 100644
|
||||||
|
--- a/widget/nsIWidget.h
|
||||||
|
+++ b/widget/nsIWidget.h
|
||||||
|
@@ -1118,6 +1118,7 @@ class nsIWidget : public nsSupportsWeakReference {
|
||||||
|
virtual void SetSupportsNativeFullscreen(bool aSupportsNativeFullscreen) {}
|
||||||
|
|
||||||
|
enum WindowAnimationType {
|
||||||
|
+ eZenUtilityWindowAnimation,
|
||||||
|
eGenericWindowAnimation,
|
||||||
|
eDocumentWindowAnimation
|
||||||
|
};
|
||||||
@@ -1,8 +1,17 @@
|
|||||||
diff --git a/xpfe/appshell/AppWindow.cpp b/xpfe/appshell/AppWindow.cpp
|
diff --git a/xpfe/appshell/AppWindow.cpp b/xpfe/appshell/AppWindow.cpp
|
||||||
index d980bca7b42bb8d81817756215067771d2793bfe..94992e9d96348e4c88e089e4cdc3234076efb656 100644
|
index d980bca7b42bb8d81817756215067771d2793bfe..06818048ce6752a8b55faa3144c934fadbc46a68 100644
|
||||||
--- a/xpfe/appshell/AppWindow.cpp
|
--- a/xpfe/appshell/AppWindow.cpp
|
||||||
+++ b/xpfe/appshell/AppWindow.cpp
|
+++ b/xpfe/appshell/AppWindow.cpp
|
||||||
@@ -1847,7 +1847,7 @@ nsresult AppWindow::MaybeSaveEarlyWindowPersistentValues(
|
@@ -1564,6 +1564,8 @@ void AppWindow::SyncAttributesToWidget() {
|
||||||
|
windowElement->GetAttribute(u"macanimationtype"_ns, attr);
|
||||||
|
if (attr.EqualsLiteral("document")) {
|
||||||
|
mWindow->SetWindowAnimationType(nsIWidget::eDocumentWindowAnimation);
|
||||||
|
+ } else if (attr.EqualsLiteral("zen:utilities")) {
|
||||||
|
+ mWindow->SetWindowAnimationType(nsIWidget::eZenUtilityWindowAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the client size did change and if we want to restore it.
|
||||||
|
@@ -1847,7 +1849,7 @@ nsresult AppWindow::MaybeSaveEarlyWindowPersistentValues(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,18 +59,6 @@ export class nsZenBoostEditor {
|
|||||||
this.loadBoost(domain);
|
this.loadBoost(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the ZenBoosts JSWindowActor child for the currently selected tab.
|
|
||||||
*
|
|
||||||
* @returns {ZenBoostsChild} zenBoostsChild Boost JSActor child
|
|
||||||
*/
|
|
||||||
get zenBoostsChild() {
|
|
||||||
const linkedBrowser = this.openerWindow.gBrowser.selectedTab.linkedBrowser;
|
|
||||||
const actor =
|
|
||||||
linkedBrowser.browsingContext.currentWindowGlobal.getActor("ZenBoosts");
|
|
||||||
return actor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the boost editor by setting up event listeners for all UI controls.
|
* Initializes the boost editor by setting up event listeners for all UI controls.
|
||||||
*/
|
*/
|
||||||
@@ -230,7 +218,7 @@ export class nsZenBoostEditor {
|
|||||||
const editor = new Editor({
|
const editor = new Editor({
|
||||||
mode: Editor.modes.css,
|
mode: Editor.modes.css,
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
theme: "mozilla",
|
theme: this.isDarkMode ? "mozilla" : "default",
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
gutters: ["CodeMirror-linenumbers"],
|
gutters: ["CodeMirror-linenumbers"],
|
||||||
});
|
});
|
||||||
@@ -239,9 +227,6 @@ export class nsZenBoostEditor {
|
|||||||
editor.refresh();
|
editor.refresh();
|
||||||
editor.on("change", this.onCodeEditorChange.bind(this));
|
editor.on("change", this.onCodeEditorChange.bind(this));
|
||||||
|
|
||||||
const editorEl =
|
|
||||||
container.querySelector("iframe").contentDocument.documentElement;
|
|
||||||
editorEl.className = "theme-" + (this.isDarkMode ? "dark" : "light");
|
|
||||||
this.editorWindow._editor = editor;
|
this.editorWindow._editor = editor;
|
||||||
this.codeEditorReady = true;
|
this.codeEditorReady = true;
|
||||||
}
|
}
|
||||||
@@ -444,13 +429,20 @@ export class nsZenBoostEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onZapButtonPressed() {
|
async onZapButtonPressed() {
|
||||||
this.zenBoostsChild.sendQuery("ZenBoost:ToggleZapMode");
|
const linkedBrowser = this.openerWindow.gBrowser.selectedTab.linkedBrowser;
|
||||||
|
const actor =
|
||||||
|
linkedBrowser.browsingContext.currentWindowGlobal.getActor("ZenBoosts");
|
||||||
|
actor.sendQuery("ZenBoost:ToggleZapMode");
|
||||||
|
|
||||||
// Focus the parent browser window
|
// Focus the parent browser window
|
||||||
this.openerWindow.focus();
|
this.openerWindow.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
async onPickerButtonPressed() {
|
async onPickerButtonPressed() {
|
||||||
this.zenBoostsChild.sendQuery("ZenBoost:TogglePickerMode");
|
const linkedBrowser = this.openerWindow.gBrowser.selectedTab.linkedBrowser;
|
||||||
|
const actor =
|
||||||
|
linkedBrowser.browsingContext.currentWindowGlobal.getActor("ZenBoosts");
|
||||||
|
actor.sendQuery("ZenBoost:TogglePickerMode");
|
||||||
this.openerWindow.focus();
|
this.openerWindow.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,11 +467,16 @@ ${cssSelector} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onInspectorButtonPressed() {
|
onInspectorButtonPressed() {
|
||||||
this.zenBoostsChild.sendQuery("ZenBoost:OpenInspector");
|
const linkedBrowser = this.openerWindow.gBrowser.selectedTab.linkedBrowser;
|
||||||
|
const actor =
|
||||||
|
linkedBrowser.browsingContext.currentWindowGlobal.getActor("ZenBoosts");
|
||||||
|
actor.sendQuery("ZenBoost:OpenInspector");
|
||||||
}
|
}
|
||||||
|
|
||||||
async onUpdateZapButtonVisual() {
|
async onUpdateZapButtonVisual() {
|
||||||
const actor = this.zenBoostsChild;
|
const linkedBrowser = this.openerWindow.gBrowser.selectedTab.linkedBrowser;
|
||||||
|
const actor =
|
||||||
|
linkedBrowser.browsingContext.currentWindowGlobal.getActor("ZenBoosts");
|
||||||
const zapButton = this.doc.getElementById("zen-boost-zap");
|
const zapButton = this.doc.getElementById("zen-boost-zap");
|
||||||
|
|
||||||
const zapEnabled = await actor.sendQuery("ZenBoost:ZapModeEnabled");
|
const zapEnabled = await actor.sendQuery("ZenBoost:ZapModeEnabled");
|
||||||
@@ -490,8 +487,12 @@ ${cssSelector} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onUpdatePickerButtonVisual() {
|
async onUpdatePickerButtonVisual() {
|
||||||
|
const linkedBrowser = this.openerWindow.gBrowser.selectedTab.linkedBrowser;
|
||||||
|
const actor =
|
||||||
|
linkedBrowser.browsingContext.currentWindowGlobal.getActor("ZenBoosts");
|
||||||
|
|
||||||
const pickerButton = this.doc.getElementById("zen-boost-css-picker");
|
const pickerButton = this.doc.getElementById("zen-boost-css-picker");
|
||||||
const selectEnabled = await this.zenBoostsChild.sendQuery(
|
const selectEnabled = await actor.sendQuery(
|
||||||
"ZenBoost:SelectorPickerModeEnabled"
|
"ZenBoost:SelectorPickerModeEnabled"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -630,7 +631,6 @@ ${cssSelector} {
|
|||||||
this.currentBoostData.textCaseOverride = "uppercase";
|
this.currentBoostData.textCaseOverride = "uppercase";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentBoostData.changeWasMade = true;
|
|
||||||
this.updateCaseButtonVisuals();
|
this.updateCaseButtonVisuals();
|
||||||
this.updateCurrentBoost();
|
this.updateCurrentBoost();
|
||||||
}
|
}
|
||||||
@@ -638,7 +638,7 @@ ${cssSelector} {
|
|||||||
/**
|
/**
|
||||||
* Handles the size toggle button press, cycling through size override options
|
* Handles the size toggle button press, cycling through size override options
|
||||||
*/
|
*/
|
||||||
async onBoostSizePressed() {
|
onBoostSizePressed() {
|
||||||
if (this.currentBoostData.sizeOverride == 1) {
|
if (this.currentBoostData.sizeOverride == 1) {
|
||||||
this.currentBoostData.sizeOverride = 1.1;
|
this.currentBoostData.sizeOverride = 1.1;
|
||||||
} else if (this.currentBoostData.sizeOverride == 1.1) {
|
} else if (this.currentBoostData.sizeOverride == 1.1) {
|
||||||
@@ -649,10 +649,8 @@ ${cssSelector} {
|
|||||||
this.currentBoostData.sizeOverride = 0.9;
|
this.currentBoostData.sizeOverride = 0.9;
|
||||||
} else if (this.currentBoostData.sizeOverride == 0.9) {
|
} else if (this.currentBoostData.sizeOverride == 0.9) {
|
||||||
this.currentBoostData.sizeOverride = 1;
|
this.currentBoostData.sizeOverride = 1;
|
||||||
await this.zenBoostsChild.sendQuery("ZenBoost:DisableSizeOverride");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentBoostData.changeWasMade = true;
|
|
||||||
this.updateSizeButtonVisuals();
|
this.updateSizeButtonVisuals();
|
||||||
this.updateCurrentBoost();
|
this.updateCurrentBoost();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -640,21 +640,10 @@ class nsZenBoostsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const editor = Services.ww.openWindow(
|
const editor = parentWindow.openDialog(
|
||||||
parentWindow,
|
|
||||||
"chrome://browser/content/zen-components/windows/zen-boost-editor.xhtml",
|
"chrome://browser/content/zen-components/windows/zen-boost-editor.xhtml",
|
||||||
null,
|
"",
|
||||||
`left=${left},top=${top},chrome,alwaysontop,resizable=no,minimizable=no,dependent,dialog=yes`,
|
`left=${left},top=${top},chrome,alwaysontop,resizable=no,minimizable=no,dependent,dialog=yes`
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
// Close the editor if the tab is switched
|
|
||||||
parentWindow.gBrowser.tabContainer.addEventListener(
|
|
||||||
"TabSelect",
|
|
||||||
editor.close.bind(editor),
|
|
||||||
{
|
|
||||||
once: true,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const progressListener = {
|
const progressListener = {
|
||||||
@@ -671,7 +660,6 @@ class nsZenBoostsManager {
|
|||||||
// Give the domain
|
// Give the domain
|
||||||
editor.domain = domain;
|
editor.domain = domain;
|
||||||
editor.openerWindow = parentWindow;
|
editor.openerWindow = parentWindow;
|
||||||
editor.focus();
|
|
||||||
|
|
||||||
// Make boost active
|
// Make boost active
|
||||||
this.makeBoostActiveForDomain(domain, boost.id);
|
this.makeBoostActiveForDomain(domain, boost.id);
|
||||||
|
|||||||
@@ -280,9 +280,6 @@ export class ZenBoostsChild extends JSWindowActorChild {
|
|||||||
case "ZenBoost:OpenInspector":
|
case "ZenBoost:OpenInspector":
|
||||||
this.sendAsyncMessage("ZenBoost:OpenInspector");
|
this.sendAsyncMessage("ZenBoost:OpenInspector");
|
||||||
break;
|
break;
|
||||||
case "ZenBoost:DisableSizeOverride":
|
|
||||||
this.disableSizeOverride();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -552,14 +549,6 @@ export class ZenBoostsChild extends JSWindowActorChild {
|
|||||||
this.sendNotify("selector-picker-state-update", "ondisable");
|
this.sendNotify("selector-picker-state-update", "ondisable");
|
||||||
}
|
}
|
||||||
|
|
||||||
disableSizeOverride() {
|
|
||||||
const browsingContext = this.browsingContext;
|
|
||||||
if (!browsingContext || browsingContext.parent !== null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
browsingContext.fullZoom = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendNotify(topic, msg = null) {
|
sendNotify(topic, msg = null) {
|
||||||
this.sendAsyncMessage("ZenBoost:Notify", { topic, msg });
|
this.sendAsyncMessage("ZenBoost:Notify", { topic, msg });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
customtitlebar="true"
|
customtitlebar="true"
|
||||||
sizemode="normal"
|
sizemode="normal"
|
||||||
scrolling="false"
|
scrolling="false"
|
||||||
macanimationtype="document"
|
macanimationtype="zen:utilities"
|
||||||
windowsmica="true"
|
windowsmica="true"
|
||||||
data-l10n-sync="true">
|
data-l10n-sync="true">
|
||||||
<head>
|
<head>
|
||||||
|
|||||||
@@ -100,16 +100,7 @@ class ZenStartup {
|
|||||||
delete this.promiseInitializedResolve;
|
delete this.promiseInitializedResolve;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Wait for the natural PlacesToolbar rebuild before invalidating, so
|
gZenWorkspaces._invalidateBookmarkContainers();
|
||||||
// the two async rebuilds don't interleave and duplicate bookmarks.
|
|
||||||
// promiseRebuilt() returns undefined when no rebuild is in flight.
|
|
||||||
const rebuilt =
|
|
||||||
document
|
|
||||||
.getElementById("PlacesToolbar")
|
|
||||||
?._placesView?.promiseRebuilt() ?? Promise.resolve();
|
|
||||||
rebuilt
|
|
||||||
.catch(console.error)
|
|
||||||
.then(() => gZenWorkspaces._invalidateBookmarkContainers());
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -156,7 +147,7 @@ class ZenStartup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#initUIComponents() {
|
#initUIComponents() {
|
||||||
const kUIComponents = ["ZenProgressBar", "ZenSpaceRoutingNavigation"];
|
const kUIComponents = ["ZenProgressBar"];
|
||||||
for (let component of kUIComponents) {
|
for (let component of kUIComponents) {
|
||||||
const module = ChromeUtils.importESModule(
|
const module = ChromeUtils.importESModule(
|
||||||
"resource:///modules/zen/ui/" + component + ".sys.mjs"
|
"resource:///modules/zen/ui/" + component + ".sys.mjs"
|
||||||
|
|||||||
@@ -10,6 +10,5 @@ EXTRA_JS_MODULES += [
|
|||||||
|
|
||||||
EXTRA_JS_MODULES.zen.ui += [
|
EXTRA_JS_MODULES.zen.ui += [
|
||||||
"sys/ui/ZenProgressBar.sys.mjs",
|
"sys/ui/ZenProgressBar.sys.mjs",
|
||||||
"sys/ui/ZenSpaceRoutingNavigation.sys.mjs",
|
|
||||||
"sys/ui/ZenUIComponent.sys.mjs",
|
"sys/ui/ZenUIComponent.sys.mjs",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
// 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/.
|
|
||||||
|
|
||||||
import { ZenUIComponent } from "resource:///modules/zen/ui/ZenUIComponent.sys.mjs";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Per-window listener that re-routes in-place navigations for Space Routing.
|
|
||||||
*
|
|
||||||
* When any top-level navigation (link click, address bar, JS redirect, form
|
|
||||||
* submit, ...) targets a URL whose rule points at a *different* space than the
|
|
||||||
* one the tab currently lives in, the load is cancelled and re-opened in a new
|
|
||||||
* tab. The new tab flows through tabbrowser's addTab() routing, which moves it
|
|
||||||
* into the matching space.
|
|
||||||
*/
|
|
||||||
export class ZenSpaceRoutingNavigation extends ZenUIComponent {
|
|
||||||
init() {
|
|
||||||
this.listenBrowserTabsProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {MozBrowser} aBrowser - The browser the state change happened in
|
|
||||||
* @param {nsIWebProgress} aWebProgress - The web progress
|
|
||||||
* @param {nsIRequest} aRequest - The request driving the state change
|
|
||||||
* @param {number} aStateFlags - The nsIWebProgressListener state flags
|
|
||||||
*/
|
|
||||||
onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags) {
|
|
||||||
const wpl = Ci.nsIWebProgressListener;
|
|
||||||
if (
|
|
||||||
!aWebProgress?.isTopLevel ||
|
|
||||||
!(aStateFlags & wpl.STATE_START) ||
|
|
||||||
!(aStateFlags & wpl.STATE_IS_DOCUMENT) ||
|
|
||||||
aStateFlags & wpl.STATE_RESTORING
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The tab we spawn for a route must be allowed to load once without being
|
|
||||||
// redirected again, regardless of when its workspace attribute lands.
|
|
||||||
if (aBrowser._zenSkipNavRouteOnce) {
|
|
||||||
aBrowser._zenSkipNavRouteOnce = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let uri;
|
|
||||||
try {
|
|
||||||
uri = aRequest.QueryInterface(Ci.nsIChannel).URI;
|
|
||||||
} catch (e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!uri || !(uri.schemeIs("http") || uri.schemeIs("https"))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't disturb a tab that is merely (re)loading the page it already shows:
|
|
||||||
// a reload, a session restore, or a tab that was already sitting on this URL
|
|
||||||
// before the rule was set. At STATE_START the browser's currentURI still
|
|
||||||
// points at the existing document, so an equal target means this isn't a
|
|
||||||
// new navigation worth routing.
|
|
||||||
let currentURI = null;
|
|
||||||
try {
|
|
||||||
currentURI = aBrowser.currentURI;
|
|
||||||
} catch (e) {
|
|
||||||
currentURI = null;
|
|
||||||
}
|
|
||||||
if (currentURI?.equals(uri)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const win = this.window;
|
|
||||||
const gBrowser = win.gBrowser;
|
|
||||||
const tab = gBrowser.getTabForBrowser(aBrowser);
|
|
||||||
if (
|
|
||||||
!tab ||
|
|
||||||
tab.pinned ||
|
|
||||||
tab.hasAttribute("zen-empty-tab") ||
|
|
||||||
tab.hasAttribute("zen-glance-tab")
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentWorkspaceId = tab.getAttribute("zen-workspace-id");
|
|
||||||
if (
|
|
||||||
!win.gZenSpaceRoutingManager.shouldRedirectNavigation(
|
|
||||||
uri.spec,
|
|
||||||
currentWorkspaceId,
|
|
||||||
win
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Under Fission the parent-side aRequest is a RemoteWebProgress stand-in
|
|
||||||
// whose cancel()/loadInfo throw NS_ERROR_NOT_IMPLEMENTED (the real channel
|
|
||||||
// lives in the content process). Stop the in-place load through the browser,
|
|
||||||
// which proxies the request to the content process.
|
|
||||||
try {
|
|
||||||
aBrowser.stop();
|
|
||||||
} catch (e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const urlToOpen = uri.spec;
|
|
||||||
|
|
||||||
// loadInfo isn't reachable on the remote request, so use the navigating
|
|
||||||
// page as the triggering principal (correct for link clicks), with a null
|
|
||||||
// principal as the safe last resort.
|
|
||||||
const principal =
|
|
||||||
aBrowser.contentPrincipal ||
|
|
||||||
Services.scriptSecurityManager.createNullPrincipal({});
|
|
||||||
|
|
||||||
// Defer so we don't mutate the tab strip from inside a progress notification.
|
|
||||||
win.setTimeout(() => {
|
|
||||||
const newTab = gBrowser.addTab(urlToOpen, {
|
|
||||||
triggeringPrincipal: principal,
|
|
||||||
ownerTab: tab.isConnected ? tab : null,
|
|
||||||
});
|
|
||||||
if (newTab?.linkedBrowser) {
|
|
||||||
newTab.linkedBrowser._zenSkipNavRouteOnce = true;
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -82,40 +82,6 @@ class nsZenSpaceRoutingManager {
|
|||||||
this.#routeToWorkspace(targetRoute, newTab, win);
|
this.#routeToWorkspace(targetRoute, newTab, win);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Decides whether an in-place top-level navigation should be pulled out of
|
|
||||||
* the current tab and re-opened in a new tab, so that addTab()'s routing can
|
|
||||||
* move it into the space its rule points at.
|
|
||||||
*
|
|
||||||
* Only navigations whose rule targets a *different* space than the one the
|
|
||||||
* navigating tab already lives in are redirected. Staying put when the tab is
|
|
||||||
* already in the destination space keeps normal browsing in place and also
|
|
||||||
* prevents the freshly routed tab from being redirected again (infinite loop).
|
|
||||||
*
|
|
||||||
* @param {string} uriString - The destination URI
|
|
||||||
* @param {string|null} currentWorkspaceId - The zen-workspace-id of the navigating tab
|
|
||||||
* @param {Window} win - The owning browser window
|
|
||||||
* @returns {boolean} True when the navigation should open in a new routed tab
|
|
||||||
*/
|
|
||||||
shouldRedirectNavigation(uriString, currentWorkspaceId, win) {
|
|
||||||
if (!win?.gZenWorkspaces?.workspaceEnabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetRoute = this.routeUri(uriString, { fromExternal: false });
|
|
||||||
|
|
||||||
// No specific destination, or the tab is already where the rule points.
|
|
||||||
if (
|
|
||||||
targetRoute === "most-recent-space" ||
|
|
||||||
targetRoute === currentWorkspaceId
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only redirect when the destination space actually exists.
|
|
||||||
return !!win.gZenWorkspaces.getWorkspaceFromId(targetRoute);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the tab should be processed or not
|
* Checks if the tab should be processed or not
|
||||||
*
|
*
|
||||||
@@ -389,14 +355,9 @@ class nsZenSpaceRoutingManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves all routes. The list of
|
* Saves all routes
|
||||||
* routes is stripped of empty routes
|
|
||||||
* before being saved
|
|
||||||
*/
|
*/
|
||||||
saveRoutes() {
|
saveRoutes() {
|
||||||
this.#file.data.routes = this.#file.data.routes.filter(
|
|
||||||
route => route.reference.trim() !== ""
|
|
||||||
);
|
|
||||||
this.#writeToDisk();
|
this.#writeToDisk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,11 @@
|
|||||||
--sr-border-radius: 12px;
|
--sr-border-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-button-box {
|
||||||
|
/* Remove default dialog buttons */
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
window {
|
window {
|
||||||
height: var(--sr-height);
|
height: var(--sr-height);
|
||||||
max-height: var(--sr-height);
|
max-height: var(--sr-height);
|
||||||
@@ -118,6 +123,10 @@ p {
|
|||||||
min-height: unset;
|
min-height: unset;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select,
|
||||||
|
.select * {
|
||||||
background-color: var(--select-background-color) !important;
|
background-color: var(--select-background-color) !important;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -130,6 +139,7 @@ p {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menulist[image] .menulist-icon,
|
||||||
menulist[image]::part(icon) {
|
menulist[image]::part(icon) {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
@@ -141,6 +151,10 @@ menulist[image]::part(icon) {
|
|||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menulist {
|
||||||
|
-moz-box-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.sr-rule-row {
|
.sr-rule-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -162,7 +176,8 @@ menulist[image]::part(icon) {
|
|||||||
margin-left: 87px;
|
margin-left: 87px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input,
|
||||||
|
.input * {
|
||||||
background-color: var(--input-background-color);
|
background-color: var(--input-background-color);
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -170,7 +185,8 @@ menulist[image]::part(icon) {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.invalid {
|
.invalid,
|
||||||
|
.invalid * {
|
||||||
color: var(--text-color-error) !important;
|
color: var(--text-color-error) !important;
|
||||||
|
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
|
|||||||
@@ -1494,9 +1494,12 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
|
|||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
getToolbarColor(isDarkMode = false) {
|
getToolbarColor(isDarkMode = false, accentColor = null) {
|
||||||
const opacity = 0.8;
|
const opacity = 0.8;
|
||||||
let baseColor = isDarkMode ? [255, 255, 255, opacity] : [0, 0, 0, opacity]; // Default toolbar
|
let baseColor = isDarkMode ? [255, 255, 255, opacity] : [0, 0, 0, opacity]; // Default toolbar
|
||||||
|
if (accentColor) {
|
||||||
|
return this.blendColors(baseColor.slice(0, 3), accentColor, 75).concat(1);
|
||||||
|
}
|
||||||
return baseColor;
|
return baseColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1762,7 +1765,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
|
|||||||
docElement.style.setProperty("--zen-primary-color", primaryColor);
|
docElement.style.setProperty("--zen-primary-color", primaryColor);
|
||||||
|
|
||||||
// Set `--toolbox-textcolor` to have a contrast with the primary color
|
// Set `--toolbox-textcolor` to have a contrast with the primary color
|
||||||
let textColor = this.getToolbarColor(isDarkMode);
|
let textColor = this.getToolbarColor(isDarkMode, dominantColor);
|
||||||
docElement.style.setProperty(
|
docElement.style.setProperty(
|
||||||
"--toolbox-textcolor",
|
"--toolbox-textcolor",
|
||||||
`rgba(${textColor[0]}, ${textColor[1]}, ${textColor[2]}, ${textColor[3]})`
|
`rgba(${textColor[0]}, ${textColor[1]}, ${textColor[2]}, ${textColor[3]})`
|
||||||
@@ -2008,7 +2011,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
|
|||||||
grain: theme.texture ?? 0,
|
grain: theme.texture ?? 0,
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
isExplicitMode,
|
isExplicitMode,
|
||||||
toolbarColor: this.getToolbarColor(isDarkMode),
|
toolbarColor: this.getToolbarColor(isDarkMode, dominantColor),
|
||||||
primaryColor: this.getAccentColorForUI(dominantColor, isDarkMode),
|
primaryColor: this.getAccentColorForUI(dominantColor, isDarkMode),
|
||||||
};
|
};
|
||||||
this.currentOpacity = previousOpacity;
|
this.currentOpacity = previousOpacity;
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ class nsZenWorkspaceIcons extends MozXULElement {
|
|||||||
|
|
||||||
this.initDragAndDrop();
|
this.initDragAndDrop();
|
||||||
this.addEventListener("mouseover", e => {
|
this.addEventListener("mouseover", e => {
|
||||||
if (e.shiftKey || this.isReorderMode) {
|
if (this.isReorderMode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const target = e.target.closest("toolbarbutton[zen-workspace-id]");
|
const target = e.target.closest("toolbarbutton[zen-workspace-id]");
|
||||||
if (target) {
|
if (target) {
|
||||||
target.scrollIntoView({ behavior: "smooth", inline: "nearest" });
|
this.scrollLeft = target.offsetLeft - 10;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@ class nsZenWorkspaceIcons extends MozXULElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
buttons[selected].setAttribute("active", true);
|
buttons[selected].setAttribute("active", true);
|
||||||
buttons[selected].scrollIntoView({ behavior: "smooth", inline: "nearest" });
|
this.scrollLeft = buttons[selected].offsetLeft - 10;
|
||||||
this.setAttribute("selected", selected);
|
this.setAttribute("selected", selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1010,14 +1010,6 @@ class nsZenWorkspaces {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Closing a glance tab tears down the overlay and restores selection
|
|
||||||
// to its parent tab. Don't run the last-tab handling here:
|
|
||||||
// in a pinned-only window the glance child is the only unpinned tab,
|
|
||||||
// so this would switch to an empty tab and clobber the restore-to-parent.
|
|
||||||
if (tab.hasAttribute("glance-id")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let workspaceID = tab.getAttribute("zen-workspace-id");
|
let workspaceID = tab.getAttribute("zen-workspace-id");
|
||||||
if (!workspaceID) {
|
if (!workspaceID) {
|
||||||
return null;
|
return null;
|
||||||
@@ -1506,6 +1498,7 @@ class nsZenWorkspaces {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tab.owner = null;
|
||||||
if (container) {
|
if (container) {
|
||||||
if (tab.group?.hasAttribute("split-view-group")) {
|
if (tab.group?.hasAttribute("split-view-group")) {
|
||||||
gBrowser.zenHandleTabMove(tab.group, () => {
|
gBrowser.zenHandleTabMove(tab.group, () => {
|
||||||
@@ -2289,27 +2282,6 @@ class nsZenWorkspaces {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeTabSelect(aTab) {
|
|
||||||
if (this.#inChangingWorkspace) {
|
|
||||||
// Just in case, Let's not do these checks while we are
|
|
||||||
// in the middle of changing workspace,
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const tabSpace = aTab?.getAttribute("zen-workspace-id");
|
|
||||||
if (
|
|
||||||
tabSpace &&
|
|
||||||
tabSpace !== this.activeWorkspace &&
|
|
||||||
!aTab.hasAttribute("zen-empty-tab") &&
|
|
||||||
!aTab.hasAttribute("zen-essential")
|
|
||||||
) {
|
|
||||||
this.lastSelectedWorkspaceTabs[tabSpace] =
|
|
||||||
gZenGlanceManager.getTabOrGlanceParent(aTab);
|
|
||||||
this.changeWorkspaceWithID(tabSpace);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_shouldShowTab(tab, workspaceUuid, containerId, workspaces) {
|
_shouldShowTab(tab, workspaceUuid, containerId, workspaces) {
|
||||||
const isEssential = tab.getAttribute("zen-essential") === "true";
|
const isEssential = tab.getAttribute("zen-essential") === "true";
|
||||||
const tabWorkspaceId = tab.getAttribute("zen-workspace-id");
|
const tabWorkspaceId = tab.getAttribute("zen-workspace-id");
|
||||||
@@ -3014,8 +2986,7 @@ class nsZenWorkspaces {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
triggeringPrincipal &&
|
triggeringPrincipal &&
|
||||||
triggeringPrincipal.isAddonOrExpandedAddonPrincipal &&
|
triggeringPrincipal.isAddonOrExpandedAddonPrincipal
|
||||||
typeof userContextId === "undefined"
|
|
||||||
) {
|
) {
|
||||||
return [userContextId, false, undefined];
|
return [userContextId, false, undefined];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
border-radius: var(--button-border-radius) !important;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
appearance: unset !important;
|
appearance: unset !important;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
@@ -40,7 +41,6 @@
|
|||||||
fill-opacity: 0.6;
|
fill-opacity: 0.6;
|
||||||
-moz-context-properties: fill-opacity, fill;
|
-moz-context-properties: fill-opacity, fill;
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
scroll-margin: 0 20px;
|
|
||||||
|
|
||||||
& .zen-workspace-icon {
|
& .zen-workspace-icon {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
|
|
||||||
&[icons-overflow] {
|
&[icons-overflow] {
|
||||||
gap: 0 !important;
|
gap: 0 !important;
|
||||||
justify-content: safe center;
|
justify-content: center;
|
||||||
|
|
||||||
& toolbarbutton {
|
& toolbarbutton {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -321,11 +321,7 @@ zen-workspace {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: color-mix(in srgb, var(--toolbox-textcolor) 95%, var(--zen-primary-color));
|
color: var(--toolbox-textcolor);
|
||||||
|
|
||||||
--tab-selected-bgcolor: color-mix(in srgb, light-dark(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.18)) 95%, var(--zen-primary-color)) !important;
|
|
||||||
--tab-selected-shadow: 0 0.8px 1.5px 0px light-dark(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.05)) !important;
|
|
||||||
--tab-selected-textcolor: color-mix(in srgb, var(--toolbox-textcolor) 95%, var(--zen-primary-color)) !important;
|
|
||||||
|
|
||||||
@media not (prefers-reduced-motion: reduce) {
|
@media not (prefers-reduced-motion: reduce) {
|
||||||
transition: padding-top 0.1s;
|
transition: padding-top 0.1s;
|
||||||
|
|||||||
@@ -303,6 +303,8 @@
|
|||||||
border-bottom: 0 solid transparent !important;
|
border-bottom: 0 solid transparent !important;
|
||||||
|
|
||||||
--tab-block-margin: 2px;
|
--tab-block-margin: 2px;
|
||||||
|
--tab-selected-bgcolor: light-dark(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.18));
|
||||||
|
--tab-selected-shadow: 0 0.8px 1.5px 0px light-dark(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.05)) !important;
|
||||||
grid-gap: 0 !important;
|
grid-gap: 0 !important;
|
||||||
|
|
||||||
&[overflow]::after,
|
&[overflow]::after,
|
||||||
|
|||||||
@@ -20,5 +20,3 @@ support-files = [
|
|||||||
["browser_glance_prev_tab.js"]
|
["browser_glance_prev_tab.js"]
|
||||||
|
|
||||||
["browser_glance_select_parent.js"]
|
["browser_glance_select_parent.js"]
|
||||||
|
|
||||||
["browser_issue_14049.js"]
|
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
|
||||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
// Regression test for gh-14049: closing a Glance ("preview") opened from a
|
|
||||||
// pinned tab in a pinned-only window used to drop the user on a blank new tab
|
|
||||||
// instead of restoring the pinned parent. The glance child is the last unpinned
|
|
||||||
// tab, so removing it tripped `handleTabBeforeClose`'s "last unpinned tab
|
|
||||||
// closed" handling, which switched to an empty tab and clobbered the
|
|
||||||
// restore-to-parent.
|
|
||||||
|
|
||||||
add_setup(async function () {
|
|
||||||
await SpecialPowers.pushPrefEnv({
|
|
||||||
set: [["zen.workspaces.open-new-tab-if-last-unpinned-tab-is-closed", true]],
|
|
||||||
});
|
|
||||||
registerCleanupFunction(async () => {
|
|
||||||
await SpecialPowers.popPrefEnv();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_Glance_Close_Pinned_Parent() {
|
|
||||||
if (!gZenWorkspaces.workspaceEnabled) {
|
|
||||||
ok(true, "Workspaces disabled; the regression cannot occur. Skipping.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recreate the "only pinned tabs open" state by pinning every existing tab.
|
|
||||||
// The glance child opened below is then guaranteed to be the sole unpinned
|
|
||||||
// tab, which is the precondition the regression depends on.
|
|
||||||
const pinnedByTest = gBrowser.visibleTabs.filter(t => !t.pinned);
|
|
||||||
for (const tab of pinnedByTest) {
|
|
||||||
gBrowser.pinTab(tab);
|
|
||||||
}
|
|
||||||
registerCleanupFunction(() => {
|
|
||||||
for (const tab of pinnedByTest) {
|
|
||||||
if (tab.pinned && !tab.closing) {
|
|
||||||
gBrowser.unpinTab(tab);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const parentTab = gBrowser.selectedTab;
|
|
||||||
ok(parentTab.pinned, "Parent tab should be pinned");
|
|
||||||
|
|
||||||
// selectEmptyTab() is a no-op while Zen's testing mode is enabled, so the
|
|
||||||
// regression cannot be observed through the resulting selection alone. Spy on
|
|
||||||
// it instead: the bug is "selectEmptyTab gets called when a glance is closed".
|
|
||||||
let selectEmptyTabCalled = false;
|
|
||||||
const originalSelectEmptyTab = gZenWorkspaces.selectEmptyTab;
|
|
||||||
gZenWorkspaces.selectEmptyTab = function (...args) {
|
|
||||||
selectEmptyTabCalled = true;
|
|
||||||
return originalSelectEmptyTab.apply(this, args);
|
|
||||||
};
|
|
||||||
registerCleanupFunction(() => {
|
|
||||||
gZenWorkspaces.selectEmptyTab = originalSelectEmptyTab;
|
|
||||||
});
|
|
||||||
|
|
||||||
await openGlanceOnTab(async glanceTab => {
|
|
||||||
ok(
|
|
||||||
glanceTab.hasAttribute("glance-id"),
|
|
||||||
"The glance tab should have a glance-id"
|
|
||||||
);
|
|
||||||
ok(!glanceTab.pinned, "The glance child should be unpinned");
|
|
||||||
|
|
||||||
// `handleTabBeforeClose` bails early without a workspace id, so make sure
|
|
||||||
// the glance child carries one (as it does at teardown time).
|
|
||||||
if (!glanceTab.getAttribute("zen-workspace-id")) {
|
|
||||||
glanceTab.setAttribute(
|
|
||||||
"zen-workspace-id",
|
|
||||||
gZenWorkspaces.activeWorkspace
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.deepEqual(
|
|
||||||
gBrowser.visibleTabs.filter(t => !t.pinned),
|
|
||||||
[glanceTab],
|
|
||||||
"The glance child should be the only unpinned visible tab"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Close the glance through the real tab-removal flow, which is what runs
|
|
||||||
// handleTabBeforeClose and the glance teardown.
|
|
||||||
await BrowserTestUtils.removeTab(glanceTab);
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
ok(
|
|
||||||
!selectEmptyTabCalled,
|
|
||||||
"Closing a glance tab must not switch to an empty tab"
|
|
||||||
);
|
|
||||||
|
|
||||||
await TestUtils.waitForCondition(
|
|
||||||
() => gBrowser.selectedTab === parentTab && parentTab.selected,
|
|
||||||
"The pinned parent tab should be selected after closing the glance"
|
|
||||||
);
|
|
||||||
Assert.equal(
|
|
||||||
gBrowser.selectedTab,
|
|
||||||
parentTab,
|
|
||||||
"The pinned parent tab should be selected after closing the glance"
|
|
||||||
);
|
|
||||||
ok(parentTab.selected, "The pinned parent tab should be visually selected");
|
|
||||||
});
|
|
||||||
@@ -17,6 +17,16 @@ disable = [
|
|||||||
source = "browser/components/safebrowsing/content/test"
|
source = "browser/components/safebrowsing/content/test"
|
||||||
is_direct_path = true
|
is_direct_path = true
|
||||||
|
|
||||||
|
[sandbox]
|
||||||
|
source = "security/sandbox/test"
|
||||||
|
is_direct_path = true
|
||||||
|
disable = [
|
||||||
|
"browser_bug1393259.js",
|
||||||
|
]
|
||||||
|
|
||||||
|
[sandbox.replace-manifest]
|
||||||
|
"../../../" = "../../../../"
|
||||||
|
|
||||||
[shell]
|
[shell]
|
||||||
source = "browser/components/shell/test"
|
source = "browser/components/shell/test"
|
||||||
is_direct_path = true
|
is_direct_path = true
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
BROWSER_CHROME_MANIFESTS += [
|
BROWSER_CHROME_MANIFESTS += [
|
||||||
"readermode/browser.toml",
|
"readermode/browser.toml",
|
||||||
"safebrowsing/browser.toml",
|
"safebrowsing/browser.toml",
|
||||||
|
"sandbox/browser.toml",
|
||||||
"shell/browser.toml",
|
"shell/browser.toml",
|
||||||
"tabMediaIndicator/browser.toml",
|
"tabMediaIndicator/browser.toml",
|
||||||
"tooltiptext/browser.toml",
|
"tooltiptext/browser.toml",
|
||||||
|
|||||||
44
src/zen/tests/mochitests/sandbox/browser.toml
Normal file
44
src/zen/tests/mochitests/sandbox/browser.toml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
skip-if = [
|
||||||
|
"ccov",
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
|
||||||
|
]
|
||||||
|
tags = "contentsandbox"
|
||||||
|
support-files = [
|
||||||
|
"browser_content_sandbox_utils.js",
|
||||||
|
"browser_content_sandbox_fs_tests.js",
|
||||||
|
"mac_register_font.py",
|
||||||
|
"../../../../layout/reftests/fonts/fira/FiraSans-Regular.otf"
|
||||||
|
]
|
||||||
|
|
||||||
|
["browser_bug1393259.js"]
|
||||||
|
disabled="Disabled by import_external_tests.py"
|
||||||
|
support-files = ["bug1393259.html"]
|
||||||
|
run-if = [
|
||||||
|
"os == 'mac'", # This is a Mac-specific test
|
||||||
|
]
|
||||||
|
skip-if = [
|
||||||
|
"os == 'mac' && os_version == '14.70' && arch == 'x86_64'", # Bug 1929424
|
||||||
|
]
|
||||||
|
tags = "os_integration"
|
||||||
|
|
||||||
|
["browser_content_sandbox_fs.js"]
|
||||||
|
skip-if = [
|
||||||
|
"os == 'mac' && os_version == '15.30' && arch == 'aarch64'", # Bug 2023967
|
||||||
|
"os == 'win' && os_version == '11.26100' && arch == 'x86' && debug", # bug 1379635
|
||||||
|
"os == 'win' && os_version == '11.26100' && arch == 'x86_64' && debug", # bug 1379635
|
||||||
|
"os == 'win' && os_version == '11.26200' && arch == 'x86' && debug", # bug 1379635
|
||||||
|
"os == 'win' && os_version == '11.26200' && arch == 'x86_64' && debug", # bug 1379635
|
||||||
|
]
|
||||||
|
|
||||||
|
["browser_content_sandbox_syscalls.js"]
|
||||||
|
|
||||||
|
["browser_sandbox_test.js"]
|
||||||
|
skip-if = [
|
||||||
|
"os == 'win' && os_version == '11.26200' && arch == 'x86' && debug", # bug 2028636
|
||||||
|
"os == 'win' && os_version == '11.26200' && arch == 'x86_64' && debug", # bug 2028636
|
||||||
|
]
|
||||||
|
run-if = [
|
||||||
|
"debug",
|
||||||
|
]
|
||||||
203
src/zen/tests/mochitests/sandbox/browser_bug1393259.js
Normal file
203
src/zen/tests/mochitests/sandbox/browser_bug1393259.js
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
/* 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";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This test validates that an OTF font installed in a directory not
|
||||||
|
* accessible to content processes is rendered correctly by checking that
|
||||||
|
* content displayed never uses the OS fallback font "LastResort". When
|
||||||
|
* a content process renders a page with the fallback font, that is an
|
||||||
|
* indication the content process failed to read or load the computed font.
|
||||||
|
* The test uses a version of the Fira Sans font and depends on the font
|
||||||
|
* not being already installed and enabled.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const kPageURL =
|
||||||
|
"http://example.com/browser/security/sandbox/test/bug1393259.html";
|
||||||
|
|
||||||
|
// Parameters for running the python script that registers/unregisters fonts.
|
||||||
|
let kPythonPath = "/usr/bin/python";
|
||||||
|
if (AppConstants.isPlatformAndVersionAtLeast("macosx", 23.0)) {
|
||||||
|
kPythonPath = "/usr/local/bin/python3";
|
||||||
|
}
|
||||||
|
const kFontInstallerPath = "browser/security/sandbox/test/mac_register_font.py";
|
||||||
|
const kUninstallFlag = "-u";
|
||||||
|
const kVerboseFlag = "-v";
|
||||||
|
|
||||||
|
// Where to find the font in the test environment.
|
||||||
|
const kRepoFontPath = "browser/security/sandbox/test/FiraSans-Regular.otf";
|
||||||
|
|
||||||
|
// Font name strings to check for.
|
||||||
|
const kLastResortFontName = "LastResort";
|
||||||
|
const kTestFontName = "Fira Sans";
|
||||||
|
|
||||||
|
// Home-relative path to install a private font. Where a private font is
|
||||||
|
// a font at a location not readable by content processes.
|
||||||
|
const kPrivateFontSubPath = "/FiraSans-Regular.otf";
|
||||||
|
|
||||||
|
add_task(async function () {
|
||||||
|
await new Promise(resolve => waitForFocus(resolve, window));
|
||||||
|
|
||||||
|
await BrowserTestUtils.withNewTab(
|
||||||
|
{
|
||||||
|
gBrowser,
|
||||||
|
url: kPageURL,
|
||||||
|
},
|
||||||
|
async function (aBrowser) {
|
||||||
|
function runProcess(aCmd, aArgs, blocking = true) {
|
||||||
|
let cmdFile = Cc["@mozilla.org/file/local;1"].createInstance(
|
||||||
|
Ci.nsIFile
|
||||||
|
);
|
||||||
|
cmdFile.initWithPath(aCmd);
|
||||||
|
|
||||||
|
let process = Cc["@mozilla.org/process/util;1"].createInstance(
|
||||||
|
Ci.nsIProcess
|
||||||
|
);
|
||||||
|
process.init(cmdFile);
|
||||||
|
process.run(blocking, aArgs, aArgs.length);
|
||||||
|
return process.exitValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the font at path |fontPath| and wait
|
||||||
|
// for the browser to detect the change.
|
||||||
|
async function registerFont(fontPath) {
|
||||||
|
let fontRegistered = getFontNotificationPromise();
|
||||||
|
let exitCode = runProcess(kPythonPath, [
|
||||||
|
kFontInstallerPath,
|
||||||
|
kVerboseFlag,
|
||||||
|
fontPath,
|
||||||
|
]);
|
||||||
|
Assert.equal(exitCode, 0, "registering font" + fontPath);
|
||||||
|
if (exitCode == 0) {
|
||||||
|
// Wait for the font registration to be detected by the browser.
|
||||||
|
await fontRegistered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unregister the font at path |fontPath|. If |waitForUnreg| is true,
|
||||||
|
// don't wait for the browser to detect the change and don't use
|
||||||
|
// the verbose arg for the unregister command.
|
||||||
|
async function unregisterFont(fontPath, waitForUnreg = true) {
|
||||||
|
let args = [kFontInstallerPath, kUninstallFlag];
|
||||||
|
let fontUnregistered;
|
||||||
|
|
||||||
|
if (waitForUnreg) {
|
||||||
|
args.push(kVerboseFlag);
|
||||||
|
fontUnregistered = getFontNotificationPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
let exitCode = runProcess(kPythonPath, args.concat(fontPath));
|
||||||
|
if (waitForUnreg) {
|
||||||
|
Assert.equal(exitCode, 0, "unregistering font" + fontPath);
|
||||||
|
if (exitCode == 0) {
|
||||||
|
await fontUnregistered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a promise that resolves when font info is changed.
|
||||||
|
let getFontNotificationPromise = () =>
|
||||||
|
new Promise(resolve => {
|
||||||
|
const kTopic = "font-info-updated";
|
||||||
|
function observe() {
|
||||||
|
Services.obs.removeObserver(observe, kTopic);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
Services.obs.addObserver(observe, kTopic);
|
||||||
|
});
|
||||||
|
|
||||||
|
let homeDir = Services.dirsvc.get("Home", Ci.nsIFile);
|
||||||
|
let privateFontPath = homeDir.path + kPrivateFontSubPath;
|
||||||
|
|
||||||
|
registerCleanupFunction(function () {
|
||||||
|
unregisterFont(privateFontPath, /* waitForUnreg = */ false);
|
||||||
|
runProcess("/bin/rm", [privateFontPath], /* blocking = */ false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy the font file to the private path.
|
||||||
|
runProcess("/bin/cp", [kRepoFontPath, privateFontPath]);
|
||||||
|
|
||||||
|
// Cleanup previous aborted tests.
|
||||||
|
unregisterFont(privateFontPath, /* waitForUnreg = */ false);
|
||||||
|
|
||||||
|
// Get the original width, using the fallback monospaced font
|
||||||
|
let origWidth = await SpecialPowers.spawn(
|
||||||
|
aBrowser,
|
||||||
|
[],
|
||||||
|
async function () {
|
||||||
|
let window = content.window.wrappedJSObject;
|
||||||
|
let contentDiv = window.document.getElementById("content");
|
||||||
|
return contentDiv.offsetWidth;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Activate the font we want to test at a non-standard path.
|
||||||
|
await registerFont(privateFontPath);
|
||||||
|
|
||||||
|
// Assign the new font to the content.
|
||||||
|
await SpecialPowers.spawn(aBrowser, [], async function () {
|
||||||
|
let window = content.window.wrappedJSObject;
|
||||||
|
let contentDiv = window.document.getElementById("content");
|
||||||
|
contentDiv.style.fontFamily = "'Fira Sans', monospace";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait until the width has changed, indicating the content process
|
||||||
|
// has recognized the newly-activated font.
|
||||||
|
while (true) {
|
||||||
|
let width = await SpecialPowers.spawn(aBrowser, [], async function () {
|
||||||
|
let window = content.window.wrappedJSObject;
|
||||||
|
let contentDiv = window.document.getElementById("content");
|
||||||
|
return contentDiv.offsetWidth;
|
||||||
|
});
|
||||||
|
if (width != origWidth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If the content wasn't ready yet, wait a little before re-checking.
|
||||||
|
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||||
|
await new Promise(c => setTimeout(c, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of fonts now being used to display the web content.
|
||||||
|
let fontList = await SpecialPowers.spawn(aBrowser, [], async function () {
|
||||||
|
let window = content.window.wrappedJSObject;
|
||||||
|
let range = window.document.createRange();
|
||||||
|
let contentDiv = window.document.getElementById("content");
|
||||||
|
range.selectNode(contentDiv);
|
||||||
|
let fonts = InspectorUtils.getUsedFontFaces(range);
|
||||||
|
|
||||||
|
let fontList = [];
|
||||||
|
for (let i = 0; i < fonts.length; i++) {
|
||||||
|
fontList.push({ name: fonts[i].name });
|
||||||
|
}
|
||||||
|
return fontList;
|
||||||
|
});
|
||||||
|
|
||||||
|
let lastResortFontUsed = false;
|
||||||
|
let testFontUsed = false;
|
||||||
|
|
||||||
|
for (let font of fontList) {
|
||||||
|
// Did we fall back to the "LastResort" font?
|
||||||
|
if (!lastResortFontUsed && font.name.includes(kLastResortFontName)) {
|
||||||
|
lastResortFontUsed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Did we render using our test font as expected?
|
||||||
|
if (!testFontUsed && font.name.includes(kTestFontName)) {
|
||||||
|
testFontUsed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.ok(
|
||||||
|
!lastResortFontUsed,
|
||||||
|
`The ${kLastResortFontName} fallback font was not used`
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.ok(testFontUsed, `The test font "${kTestFontName}" was used`);
|
||||||
|
|
||||||
|
await unregisterFont(privateFontPath);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Any copyright is dedicated to the Public Domain.
|
||||||
|
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
[DEFAULT]
|
||||||
|
skip-if = [
|
||||||
|
"ccov",
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
|
||||||
|
]
|
||||||
|
tags = "contentsandbox"
|
||||||
|
environment = "XDG_CONFIG_DIRS=:/opt"
|
||||||
|
|
||||||
|
["browser_content_sandbox_bug1717599_XDG-CONFIG-DIRS.js"]
|
||||||
|
run-if = [
|
||||||
|
"os == 'linux'",
|
||||||
|
]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
skip-if = [
|
||||||
|
"ccov",
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
|
||||||
|
]
|
||||||
|
tags = "contentsandbox"
|
||||||
|
environment = "XDG_CONFIG_HOME="
|
||||||
|
|
||||||
|
["browser_content_sandbox_bug1717599_XDG-CONFIG-HOME.js"]
|
||||||
|
run-if = [
|
||||||
|
"os == 'linux'",
|
||||||
|
]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
/* import-globals-from browser_content_sandbox_utils.js */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
//
|
||||||
|
// Just test that browser does not die on empty env var
|
||||||
|
//
|
||||||
|
add_task(async function () {
|
||||||
|
ok(true, "Process can run");
|
||||||
|
});
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
/* import-globals-from browser_content_sandbox_utils.js */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
//
|
||||||
|
// Just test that browser does not die on empty env var
|
||||||
|
//
|
||||||
|
add_task(async function () {
|
||||||
|
ok(true, "Process can run");
|
||||||
|
});
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
/* import-globals-from browser_content_sandbox_utils.js */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
"chrome://mochitests/content/browser/" +
|
||||||
|
"security/sandbox/test/browser_content_sandbox_utils.js",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
"chrome://mochitests/content/browser/" +
|
||||||
|
"security/sandbox/test/browser_content_sandbox_fs_tests.js",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This test exercises file I/O from web and file content processes using
|
||||||
|
* nsIFile etc. methods to validate that calls that are meant to be blocked by
|
||||||
|
* content sandboxing are blocked.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// Checks that sandboxing is enabled and at the appropriate level
|
||||||
|
// setting before triggering tests that do the file I/O.
|
||||||
|
//
|
||||||
|
// Tests attempting to write to a file in the home directory from the
|
||||||
|
// content process--expected to fail.
|
||||||
|
//
|
||||||
|
// Tests attempting to write to a file in the content temp directory
|
||||||
|
// from the content process--expected to succeed. Uses "ContentTmpD".
|
||||||
|
//
|
||||||
|
// Tests reading various files and directories from file and web
|
||||||
|
// content processes.
|
||||||
|
//
|
||||||
|
add_task(async function () {
|
||||||
|
sanityChecks();
|
||||||
|
|
||||||
|
// Test creating a file in the home directory from a web content process
|
||||||
|
add_task(createFileInHome); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
// Test creating a file content temp from a web content process
|
||||||
|
add_task(createTempFile); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
// Test reading files/dirs from web and file content processes
|
||||||
|
add_task(testFileAccessAllPlatforms); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
add_task(testFileAccessMacOnly); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
add_task(testFileAccessLinuxOnly); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
add_task(testFileAccessWindowsOnly); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
add_task(cleanupBrowserTabs); // eslint-disable-line no-undef
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
/* import-globals-from browser_content_sandbox_utils.js */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
"chrome://mochitests/content/browser/" +
|
||||||
|
"security/sandbox/test/browser_content_sandbox_utils.js",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
"chrome://mochitests/content/browser/" +
|
||||||
|
"security/sandbox/test/browser_content_sandbox_fs_tests.js",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
add_task(async function () {
|
||||||
|
// Ensure that SNAP is there
|
||||||
|
const snap = Services.env.get("SNAP");
|
||||||
|
Assert.greater(snap.length, 1, "SNAP is defined");
|
||||||
|
|
||||||
|
// If it is there, do actual testing
|
||||||
|
sanityChecks();
|
||||||
|
|
||||||
|
add_task(testFileAccessLinuxOnly); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
add_task(testFileAccessLinuxSnap); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
add_task(cleanupBrowserTabs); // eslint-disable-line no-undef
|
||||||
|
});
|
||||||
@@ -0,0 +1,719 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
/* import-globals-from browser_content_sandbox_utils.js */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const lazy = {};
|
||||||
|
|
||||||
|
/* getLibcConstants is only present on *nix */
|
||||||
|
ChromeUtils.defineLazyGetter(lazy, "LIBC", () =>
|
||||||
|
ChromeUtils.getLibcConstants()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test if the content process can create in $HOME, this should fail
|
||||||
|
async function createFileInHome() {
|
||||||
|
let browser = gBrowser.selectedBrowser;
|
||||||
|
let homeFile = fileInHomeDir();
|
||||||
|
let path = homeFile.path;
|
||||||
|
let fileCreated = await SpecialPowers.spawn(browser, [path], createFile);
|
||||||
|
ok(!fileCreated.ok, "creating a file in home dir failed");
|
||||||
|
is(
|
||||||
|
fileCreated.code,
|
||||||
|
Cr.NS_ERROR_FILE_ACCESS_DENIED,
|
||||||
|
"creating a file in home dir failed with access denied"
|
||||||
|
);
|
||||||
|
if (fileCreated.ok) {
|
||||||
|
// content process successfully created the file, now remove it
|
||||||
|
homeFile.remove(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if the content process can create a temp file, this is forbidden on all
|
||||||
|
// platforms. Also test that the content process cannot create symlinks on
|
||||||
|
// macOS/Linux or delete files.
|
||||||
|
async function createTempFile() {
|
||||||
|
// On Windows we allow access to the temp dir for DEBUG builds, because of
|
||||||
|
// logging that uses that dir.
|
||||||
|
let isDbgWin = isWin() && SpecialPowers.isDebugBuild;
|
||||||
|
|
||||||
|
let browser = gBrowser.selectedBrowser;
|
||||||
|
let path = fileInTempDir().path;
|
||||||
|
let fileCreated = await SpecialPowers.spawn(browser, [path], createFile);
|
||||||
|
if (isDbgWin) {
|
||||||
|
ok(fileCreated.ok, "creating a file in temp suceeded");
|
||||||
|
} else {
|
||||||
|
ok(!fileCreated.ok, "creating a file in temp failed");
|
||||||
|
is(
|
||||||
|
fileCreated.code,
|
||||||
|
Cr.NS_ERROR_FILE_ACCESS_DENIED,
|
||||||
|
"creating a file in temp failed with access denied"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now delete the file
|
||||||
|
let fileDeleted = await SpecialPowers.spawn(browser, [path], deleteFile);
|
||||||
|
if (isDbgWin) {
|
||||||
|
ok(fileDeleted.ok, "deleting a file in temp succeeded");
|
||||||
|
} else {
|
||||||
|
ok(!fileDeleted.ok, "deleting a file in temp failed");
|
||||||
|
const expectedError = isLinux()
|
||||||
|
? Cr.NS_ERROR_FILE_ACCESS_DENIED
|
||||||
|
: Cr.NS_ERROR_FILE_NOT_FOUND;
|
||||||
|
is(
|
||||||
|
fileDeleted.code,
|
||||||
|
expectedError,
|
||||||
|
"deleting a file in temp failed with access denied"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that symlink creation is not allowed on macOS/Linux.
|
||||||
|
if (isMac() || isLinux()) {
|
||||||
|
let path = fileInTempDir().path;
|
||||||
|
let symlinkCreated = await SpecialPowers.spawn(
|
||||||
|
browser,
|
||||||
|
[path],
|
||||||
|
createSymlink
|
||||||
|
);
|
||||||
|
ok(!symlinkCreated.ok, "created a symlink in temp failed");
|
||||||
|
const expectedError = isLinux() ? lazy.LIBC.EACCES : lazy.LIBC.EPERM;
|
||||||
|
is(
|
||||||
|
symlinkCreated.code,
|
||||||
|
expectedError,
|
||||||
|
"created a symlink in temp failed with access denied"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test reading files and dirs from web and file content processes.
|
||||||
|
async function testFileAccessAllPlatforms() {
|
||||||
|
let webBrowser = GetWebBrowser();
|
||||||
|
let fileContentProcessEnabled = isFileContentProcessEnabled();
|
||||||
|
let fileBrowser = GetFileBrowser();
|
||||||
|
|
||||||
|
// Directories/files to test accessing from content processes.
|
||||||
|
// For directories, we test whether a directory listing is allowed
|
||||||
|
// or blocked. For files, we test if we can read from the file.
|
||||||
|
// Each entry in the array represents a test file or directory
|
||||||
|
// that will be read from either a web or file process.
|
||||||
|
let tests = [];
|
||||||
|
|
||||||
|
let profileDir = GetProfileDir();
|
||||||
|
tests.push({
|
||||||
|
desc: "profile dir", // description
|
||||||
|
ok: false, // expected to succeed?
|
||||||
|
browser: webBrowser, // browser to run test in
|
||||||
|
file: profileDir, // nsIFile object
|
||||||
|
minLevel: minProfileReadSandboxLevel(), // min level to enable test
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
if (fileContentProcessEnabled) {
|
||||||
|
tests.push({
|
||||||
|
desc: "profile dir",
|
||||||
|
ok: true,
|
||||||
|
browser: fileBrowser,
|
||||||
|
file: profileDir,
|
||||||
|
minLevel: 0,
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let homeDir = GetHomeDir();
|
||||||
|
tests.push({
|
||||||
|
desc: "home dir",
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: homeDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
if (fileContentProcessEnabled) {
|
||||||
|
tests.push({
|
||||||
|
desc: "home dir",
|
||||||
|
ok: true,
|
||||||
|
browser: fileBrowser,
|
||||||
|
file: homeDir,
|
||||||
|
minLevel: 0,
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let extensionsDir = GetProfileEntry("extensions");
|
||||||
|
if (extensionsDir.exists() && extensionsDir.isDirectory()) {
|
||||||
|
tests.push({
|
||||||
|
desc: "extensions dir",
|
||||||
|
ok: true,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: extensionsDir,
|
||||||
|
minLevel: 0,
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ok(false, `${extensionsDir.path} is a valid dir`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let chromeDir = GetProfileEntry("chrome");
|
||||||
|
if (chromeDir.exists() && chromeDir.isDirectory()) {
|
||||||
|
tests.push({
|
||||||
|
desc: "chrome dir",
|
||||||
|
ok: true,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: chromeDir,
|
||||||
|
minLevel: 0,
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ok(false, `${chromeDir.path} is valid dir`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cookiesFile = GetProfileEntry("cookies.sqlite");
|
||||||
|
if (cookiesFile.exists() && !cookiesFile.isDirectory()) {
|
||||||
|
tests.push({
|
||||||
|
desc: "cookies file",
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: cookiesFile,
|
||||||
|
minLevel: minProfileReadSandboxLevel(),
|
||||||
|
func: readFile,
|
||||||
|
});
|
||||||
|
if (fileContentProcessEnabled) {
|
||||||
|
tests.push({
|
||||||
|
desc: "cookies file",
|
||||||
|
ok: true,
|
||||||
|
browser: fileBrowser,
|
||||||
|
file: cookiesFile,
|
||||||
|
minLevel: 0,
|
||||||
|
func: readFile,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok(false, `${cookiesFile.path} is a valid file`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMac() || isLinux()) {
|
||||||
|
let varDir = GetDir("/var");
|
||||||
|
|
||||||
|
if (isMac()) {
|
||||||
|
// Mac sandbox rules use /private/var because /var is a symlink
|
||||||
|
// to /private/var on OS X. Make sure that hasn't changed.
|
||||||
|
varDir.normalize();
|
||||||
|
Assert.strictEqual(
|
||||||
|
varDir.path,
|
||||||
|
"/private/var",
|
||||||
|
"/var resolves to /private/var"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
tests.push({
|
||||||
|
desc: "/var",
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: varDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
if (fileContentProcessEnabled) {
|
||||||
|
tests.push({
|
||||||
|
desc: "/var",
|
||||||
|
ok: true,
|
||||||
|
browser: fileBrowser,
|
||||||
|
file: varDir,
|
||||||
|
minLevel: 0,
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await runTestsList(tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testFileAccessMacOnly() {
|
||||||
|
if (!isMac()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let webBrowser = GetWebBrowser();
|
||||||
|
let fileContentProcessEnabled = isFileContentProcessEnabled();
|
||||||
|
let fileBrowser = GetFileBrowser();
|
||||||
|
let level = GetSandboxLevel();
|
||||||
|
|
||||||
|
let tests = [];
|
||||||
|
|
||||||
|
// If ~/Library/Caches/TemporaryItems exists, when level <= 2 we
|
||||||
|
// make sure it's readable. For level 3, we make sure it isn't.
|
||||||
|
let homeTempDir = GetHomeDir();
|
||||||
|
homeTempDir.appendRelativePath("Library/Caches/TemporaryItems");
|
||||||
|
if (homeTempDir.exists()) {
|
||||||
|
let shouldBeReadable, minLevel;
|
||||||
|
if (level >= minHomeReadSandboxLevel()) {
|
||||||
|
shouldBeReadable = false;
|
||||||
|
minLevel = minHomeReadSandboxLevel();
|
||||||
|
} else {
|
||||||
|
shouldBeReadable = true;
|
||||||
|
minLevel = 0;
|
||||||
|
}
|
||||||
|
tests.push({
|
||||||
|
desc: "home library cache temp dir",
|
||||||
|
ok: shouldBeReadable,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: homeTempDir,
|
||||||
|
minLevel,
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if we can read from $TMPDIR because we expect it
|
||||||
|
// to be within /private/var. Reading from it should be
|
||||||
|
// prevented in a 'web' process.
|
||||||
|
let macTempDir = GetDirFromEnvVariable("TMPDIR");
|
||||||
|
|
||||||
|
macTempDir.normalize();
|
||||||
|
Assert.ok(
|
||||||
|
macTempDir.path.startsWith("/private/var"),
|
||||||
|
"$TMPDIR is in /private/var"
|
||||||
|
);
|
||||||
|
|
||||||
|
tests.push({
|
||||||
|
desc: `$TMPDIR (${macTempDir.path})`,
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: macTempDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
if (fileContentProcessEnabled) {
|
||||||
|
tests.push({
|
||||||
|
desc: `$TMPDIR (${macTempDir.path})`,
|
||||||
|
ok: true,
|
||||||
|
browser: fileBrowser,
|
||||||
|
file: macTempDir,
|
||||||
|
minLevel: 0,
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The font registry directory is in the Darwin user cache dir which is
|
||||||
|
// accessible with the getconf(1) library call using DARWIN_USER_CACHE_DIR.
|
||||||
|
// For this test, assume the cache dir is located at $TMPDIR/../C and use
|
||||||
|
// the $TMPDIR to derive the path to the registry.
|
||||||
|
let fontRegistryDir = macTempDir.parent.clone();
|
||||||
|
fontRegistryDir.appendRelativePath("C/com.apple.FontRegistry");
|
||||||
|
if (fontRegistryDir.exists()) {
|
||||||
|
tests.push({
|
||||||
|
desc: `FontRegistry (${fontRegistryDir.path})`,
|
||||||
|
ok: true,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: fontRegistryDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
// Check that we can read the file named `font` which typically
|
||||||
|
// exists in the the font registry directory.
|
||||||
|
let fontFile = fontRegistryDir.clone();
|
||||||
|
fontFile.appendRelativePath("font");
|
||||||
|
if (fontFile.exists()) {
|
||||||
|
tests.push({
|
||||||
|
desc: `FontRegistry file (${fontFile.path})`,
|
||||||
|
ok: true,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: fontFile,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readFile,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that we cannot read from /Volumes at level 3
|
||||||
|
let volumes = GetDir("/Volumes");
|
||||||
|
tests.push({
|
||||||
|
desc: "/Volumes",
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: volumes,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test that we cannot read from /Users at level 3
|
||||||
|
let users = GetDir("/Users");
|
||||||
|
tests.push({
|
||||||
|
desc: "/Users",
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: users,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test that we can stat /Users at level 3
|
||||||
|
tests.push({
|
||||||
|
desc: "/Users",
|
||||||
|
ok: true,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: users,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: statPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test that we can stat /Library at level 3, but can't get a
|
||||||
|
// directory listing of /Library. This test uses "/Library"
|
||||||
|
// because it's a path that is expected to always be present.
|
||||||
|
let libraryDir = GetDir("/Library");
|
||||||
|
tests.push({
|
||||||
|
desc: "/Library",
|
||||||
|
ok: true,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: libraryDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: statPath,
|
||||||
|
});
|
||||||
|
tests.push({
|
||||||
|
desc: "/Library",
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: libraryDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Similarly, test that we can stat /private, but not /private/etc.
|
||||||
|
let privateDir = GetDir("/private");
|
||||||
|
tests.push({
|
||||||
|
desc: "/private",
|
||||||
|
ok: true,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: privateDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: statPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
await runTestsList(tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testFileAccessLinuxOnly() {
|
||||||
|
if (!isLinux()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let webBrowser = GetWebBrowser();
|
||||||
|
let fileContentProcessEnabled = isFileContentProcessEnabled();
|
||||||
|
let fileBrowser = GetFileBrowser();
|
||||||
|
|
||||||
|
let tests = [];
|
||||||
|
|
||||||
|
// Test /proc/self/fd, because that can be used to unfreeze
|
||||||
|
// frozen shared memory.
|
||||||
|
let selfFdDir = GetDir("/proc/self/fd");
|
||||||
|
tests.push({
|
||||||
|
desc: "/proc/self/fd",
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: selfFdDir,
|
||||||
|
minLevel: isContentFileIOSandboxed(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
let cacheFontConfigDir = GetHomeSubdir(".cache/fontconfig/");
|
||||||
|
tests.push({
|
||||||
|
desc: `$HOME/.cache/fontconfig/ (${cacheFontConfigDir.path})`,
|
||||||
|
ok: true,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: cacheFontConfigDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
// allows to handle both $HOME/.config/ or $XDG_CONFIG_HOME
|
||||||
|
let configDir = GetHomeSubdir(".config");
|
||||||
|
|
||||||
|
const xdgConfigHome = Services.env.get("XDG_CONFIG_HOME");
|
||||||
|
if (xdgConfigHome) {
|
||||||
|
configDir = GetDir(xdgConfigHome);
|
||||||
|
configDir.normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
tests.push({
|
||||||
|
desc: `$XDG_CONFIG_HOME (${configDir.path})`,
|
||||||
|
ok: true, // access should not be granted outside of XDG support
|
||||||
|
browser: webBrowser,
|
||||||
|
file: configDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
tests.push({
|
||||||
|
desc: `XDG_CONFIG_HOME=${configDir.path} dir should have rdonly`,
|
||||||
|
ok: true, // should be allowed only if XDG support is there
|
||||||
|
browser: webBrowser,
|
||||||
|
file: configDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fileContentProcessEnabled) {
|
||||||
|
tests.push({
|
||||||
|
desc: `${configDir.path} dir`,
|
||||||
|
ok: true, // should be allowed only if XDG support is there
|
||||||
|
browser: fileBrowser,
|
||||||
|
file: configDir,
|
||||||
|
minLevel: 0,
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isXdgEnabled() && xdgConfigHome) {
|
||||||
|
const homeConfigDir = GetHomeSubdir(".config");
|
||||||
|
tests.push({
|
||||||
|
desc: `XDG_CONFIG_HOME=${homeConfigDir.path} dir should deny $HOME/.config`,
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: homeConfigDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
if (fileContentProcessEnabled) {
|
||||||
|
tests.push({
|
||||||
|
desc: `${homeConfigDir.path} dir`,
|
||||||
|
ok: true,
|
||||||
|
browser: fileBrowser,
|
||||||
|
file: homeConfigDir,
|
||||||
|
minLevel: 0,
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// WWhen XDG_CONFIG_HOME is not set, verify we do not allow $HOME/.configlol
|
||||||
|
// (i.e., check allow the dir and not the prefix)
|
||||||
|
//
|
||||||
|
// Checking $HOME/.config is already done above.
|
||||||
|
const homeConfigPrefix = GetHomeSubdir(".configlol");
|
||||||
|
tests.push({
|
||||||
|
desc: `No XDG_CONFIG_HOME we dont allow ${homeConfigPrefix.path} access`,
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: homeConfigPrefix,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
if (fileContentProcessEnabled) {
|
||||||
|
tests.push({
|
||||||
|
desc: `No XDG_CONFIG_HOME we dont allow ${homeConfigPrefix.path} access`,
|
||||||
|
ok: false,
|
||||||
|
browser: fileBrowser,
|
||||||
|
file: homeConfigPrefix,
|
||||||
|
minLevel: 0,
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a file under $HOME/.config/ or $XDG_CONFIG_HOME and ensure we can
|
||||||
|
// read it
|
||||||
|
let fileUnderConfig = GetSubdirFile(configDir);
|
||||||
|
await IOUtils.writeUTF8(fileUnderConfig.path, "TEST FILE DUMMY DATA");
|
||||||
|
ok(
|
||||||
|
await IOUtils.exists(fileUnderConfig.path),
|
||||||
|
`File ${fileUnderConfig.path} was properly created`
|
||||||
|
);
|
||||||
|
|
||||||
|
tests.push({
|
||||||
|
desc: `${configDir.path}/xxx is readable (${fileUnderConfig.path})`,
|
||||||
|
ok: true,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: fileUnderConfig,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readFile,
|
||||||
|
cleanup: aPath => IOUtils.remove(aPath),
|
||||||
|
});
|
||||||
|
|
||||||
|
let configFile = GetSubdirFile(configDir);
|
||||||
|
tests.push({
|
||||||
|
desc: `${configDir.path} file write`,
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: configFile,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: createFile,
|
||||||
|
});
|
||||||
|
if (fileContentProcessEnabled) {
|
||||||
|
tests.push({
|
||||||
|
desc: `${configDir.path} file write`,
|
||||||
|
ok: false,
|
||||||
|
browser: fileBrowser,
|
||||||
|
file: configFile,
|
||||||
|
minLevel: 0,
|
||||||
|
func: createFile,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a $HOME/.config/mozilla/ or $XDG_CONFIG_HOME/mozilla/ if none
|
||||||
|
// exists and assert content process cannot access it
|
||||||
|
let configMozilla = GetSubdir(configDir, "mozilla");
|
||||||
|
const emptyFileName = ".test_run_browser_sandbox.tmp";
|
||||||
|
let emptyFile = configMozilla.clone();
|
||||||
|
emptyFile.appendRelativePath(emptyFileName);
|
||||||
|
|
||||||
|
let populateFakeConfigMozilla = async aPath => {
|
||||||
|
// called with configMozilla
|
||||||
|
await IOUtils.makeDirectory(aPath, { permissions: 0o700 });
|
||||||
|
await IOUtils.writeUTF8(emptyFile.path, "");
|
||||||
|
ok(
|
||||||
|
await IOUtils.exists(emptyFile.path),
|
||||||
|
`Temp file ${emptyFile.path} was created`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let unpopulateFakeConfigMozilla = async aPath => {
|
||||||
|
// called with emptyFile
|
||||||
|
await IOUtils.remove(aPath);
|
||||||
|
ok(!(await IOUtils.exists(aPath)), `Temp file ${aPath} was removed`);
|
||||||
|
const parentDir = PathUtils.parent(aPath);
|
||||||
|
try {
|
||||||
|
await IOUtils.remove(parentDir, { recursive: false });
|
||||||
|
} catch (ex) {
|
||||||
|
if (
|
||||||
|
!DOMException.isInstance(ex) ||
|
||||||
|
ex.name !== "OperationError" ||
|
||||||
|
/Could not remove the non-empty directory/.test(ex.message)
|
||||||
|
) {
|
||||||
|
// If we get here it means the directory was not empty and since we assert
|
||||||
|
// earlier we removed the temp file we created it means we should not
|
||||||
|
// worrying about removing this directory ...
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await populateFakeConfigMozilla(configMozilla.path);
|
||||||
|
|
||||||
|
tests.push({
|
||||||
|
desc: `stat ${configDir.path}/mozilla (${configMozilla.path})`,
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: configMozilla,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: statPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
tests.push({
|
||||||
|
desc: `read ${configDir.path}/mozilla (${configMozilla.path})`,
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: configMozilla,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
tests.push({
|
||||||
|
desc: `stat ${configDir.path}/mozilla/${emptyFileName} (${emptyFile.path})`,
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: emptyFile,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: statPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
tests.push({
|
||||||
|
desc: `read ${configDir.path}/mozilla/${emptyFileName} (${emptyFile.path})`,
|
||||||
|
ok: false,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: emptyFile,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readFile,
|
||||||
|
cleanup: unpopulateFakeConfigMozilla,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only needed to perform cleanup
|
||||||
|
if (isXdgEnabled()) {
|
||||||
|
tests.push({
|
||||||
|
desc: `$XDG_CONFIG_HOME (${configDir.path}) cleanup`,
|
||||||
|
ok: true,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: configDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await runTestsList(tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testFileAccessLinuxSnap() {
|
||||||
|
let webBrowser = GetWebBrowser();
|
||||||
|
|
||||||
|
let tests = [];
|
||||||
|
|
||||||
|
// Assert that if we run with SNAP= env, then we allow access to it in the
|
||||||
|
// content process
|
||||||
|
let snap = Services.env.get("SNAP");
|
||||||
|
let snapExpectedResult = false;
|
||||||
|
if (snap.length > 1) {
|
||||||
|
snapExpectedResult = true;
|
||||||
|
} else {
|
||||||
|
snap = "/tmp/.snap_firefox_current/";
|
||||||
|
}
|
||||||
|
|
||||||
|
let snapDir = GetDir(snap);
|
||||||
|
snapDir.normalize();
|
||||||
|
|
||||||
|
let snapFile = GetSubdirFile(snapDir);
|
||||||
|
await createFile(snapFile.path);
|
||||||
|
ok(await IOUtils.exists(snapFile.path), `SNAP ${snapFile.path} was created`);
|
||||||
|
info(`SNAP (file) ${snapFile.path} was created`);
|
||||||
|
|
||||||
|
tests.push({
|
||||||
|
desc: `$SNAP (${snapDir.path} => ${snapFile.path})`,
|
||||||
|
ok: snapExpectedResult,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: snapFile,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readFile,
|
||||||
|
});
|
||||||
|
|
||||||
|
await runTestsList(tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testFileAccessWindowsOnly() {
|
||||||
|
if (!isWin()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let webBrowser = GetWebBrowser();
|
||||||
|
|
||||||
|
let tests = [];
|
||||||
|
|
||||||
|
let extDir = GetPerUserExtensionDir();
|
||||||
|
// We used to unconditionally create this directory from Firefox, but that
|
||||||
|
// was dropped in bug 2001887. The value of this directory is questionable;
|
||||||
|
// the test was added in Firefox 56 (bug 1403744) to cover legacy add-ons,
|
||||||
|
// but legacy add-on support was discontinued in Firefox 57, and we stopped
|
||||||
|
// sideloading add-ons from this directory on all builds except ESR in
|
||||||
|
// Firefox 74 (bug 1602840).
|
||||||
|
await IOUtils.makeDirectory(extDir.path);
|
||||||
|
tests.push({
|
||||||
|
desc: "per-user extensions dir",
|
||||||
|
ok: true,
|
||||||
|
browser: webBrowser,
|
||||||
|
file: extDir,
|
||||||
|
minLevel: minHomeReadSandboxLevel(),
|
||||||
|
func: readDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
await runTestsList(tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupBrowserTabs() {
|
||||||
|
let fileBrowser = GetFileBrowser();
|
||||||
|
if (fileBrowser.selectedTab) {
|
||||||
|
gBrowser.removeTab(fileBrowser.selectedTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
let webBrowser = GetWebBrowser();
|
||||||
|
if (webBrowser.selectedTab) {
|
||||||
|
gBrowser.removeTab(webBrowser.selectedTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tab1 = gBrowser.tabs[1];
|
||||||
|
if (tab1) {
|
||||||
|
gBrowser.removeTab(tab1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
/* import-globals-from browser_content_sandbox_utils.js */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
"chrome://mochitests/content/browser/" +
|
||||||
|
"security/sandbox/test/browser_content_sandbox_utils.js",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
"chrome://mochitests/content/browser/" +
|
||||||
|
"security/sandbox/test/browser_content_sandbox_fs_tests.js",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
SimpleTest.requestCompleteLog();
|
||||||
|
|
||||||
|
add_setup(async function setup() {
|
||||||
|
const xdgConfigHome = Services.env.exists("XDG_CONFIG_HOME");
|
||||||
|
Assert.equal(xdgConfigHome, false, `XDG_CONFIG_HOME is not set`);
|
||||||
|
|
||||||
|
const mozLegacyHome = Services.env.exists("MOZ_LEGACY_HOME");
|
||||||
|
Assert.equal(mozLegacyHome, false, "MOZ_LEGACY_HOME is not set");
|
||||||
|
|
||||||
|
// If it is there, do actual testing
|
||||||
|
sanityChecks();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function () {
|
||||||
|
// Make sure we dont break others.
|
||||||
|
add_task(testFileAccessAllPlatforms); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
// The linux only tests are the ones that can behave differently based on
|
||||||
|
// existence of XDG_CONFIG_HOME
|
||||||
|
add_task(testFileAccessLinuxOnly); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
add_task(cleanupBrowserTabs); // eslint-disable-line no-undef
|
||||||
|
});
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
/* import-globals-from browser_content_sandbox_utils.js */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
"chrome://mochitests/content/browser/" +
|
||||||
|
"security/sandbox/test/browser_content_sandbox_utils.js",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
"chrome://mochitests/content/browser/" +
|
||||||
|
"security/sandbox/test/browser_content_sandbox_fs_tests.js",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
SimpleTest.requestCompleteLog();
|
||||||
|
|
||||||
|
add_setup(async function setup() {
|
||||||
|
const xdgConfigHome = Services.env.exists("XDG_CONFIG_HOME");
|
||||||
|
Assert.equal(xdgConfigHome, true, "XDG_CONFIG_HOME is defined");
|
||||||
|
|
||||||
|
if (isXdgEnabled()) {
|
||||||
|
const mozLegacyHome = Services.env.get("MOZ_LEGACY_HOME");
|
||||||
|
Assert.equal(mozLegacyHome, 1, "MOZ_LEGACY_HOME is set to 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it is there, do actual testing
|
||||||
|
sanityChecks();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function () {
|
||||||
|
// Make sure we dont break others.
|
||||||
|
add_task(testFileAccessAllPlatforms); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
// The linux only tests are the ones that can behave differently based on
|
||||||
|
// existence of XDG_CONFIG_HOME
|
||||||
|
add_task(testFileAccessLinuxOnly); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
add_task(cleanupBrowserTabs); // eslint-disable-line no-undef
|
||||||
|
});
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
/* import-globals-from browser_content_sandbox_utils.js */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
"chrome://mochitests/content/browser/" +
|
||||||
|
"security/sandbox/test/browser_content_sandbox_utils.js",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
"chrome://mochitests/content/browser/" +
|
||||||
|
"security/sandbox/test/browser_content_sandbox_fs_tests.js",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
SimpleTest.requestCompleteLog();
|
||||||
|
|
||||||
|
add_setup(async function setup() {
|
||||||
|
// Ensure that XDG_CONFIG_HOME is there
|
||||||
|
const xdgConfigHome = Services.env.get("XDG_CONFIG_HOME");
|
||||||
|
Assert.greater(xdgConfigHome.length, 1, "XDG_CONFIG_HOME is defined");
|
||||||
|
|
||||||
|
// Verify the profile directory is inside XDG_CONFIG_HOME
|
||||||
|
const profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||||
|
Assert.ok(
|
||||||
|
profileDir.path.startsWith(xdgConfigHome),
|
||||||
|
`Profile directory (${profileDir.path}) should be inside XDG_CONFIG_HOME (${xdgConfigHome})`
|
||||||
|
);
|
||||||
|
|
||||||
|
// If it is there, do actual testing
|
||||||
|
sanityChecks();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function () {
|
||||||
|
// Make sure we dont break others.
|
||||||
|
add_task(testFileAccessAllPlatforms); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
// The linux only tests are the ones that can behave differently based on
|
||||||
|
// existence of XDG_CONFIG_HOME
|
||||||
|
add_task(testFileAccessLinuxOnly); // eslint-disable-line no-undef
|
||||||
|
|
||||||
|
add_task(cleanupBrowserTabs); // eslint-disable-line no-undef
|
||||||
|
});
|
||||||
@@ -0,0 +1,405 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
/* import-globals-from browser_content_sandbox_utils.js */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
"chrome://mochitests/content/browser/" +
|
||||||
|
"security/sandbox/test/browser_content_sandbox_utils.js",
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
const lazy = {};
|
||||||
|
|
||||||
|
/* getLibcConstants is only present on *nix */
|
||||||
|
ChromeUtils.defineLazyGetter(lazy, "LIBC", () =>
|
||||||
|
ChromeUtils.getLibcConstants()
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This test is for executing system calls in content processes to validate
|
||||||
|
* that calls that are meant to be blocked by content sandboxing are blocked.
|
||||||
|
* We use the term system calls loosely so that any OS API call such as
|
||||||
|
* fopen could be included.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Calls the native execv library function. Include imports so this can be
|
||||||
|
// safely serialized and run remotely by SpecialPowers.spawn.
|
||||||
|
function callExec(args) {
|
||||||
|
const { ctypes } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/ctypes.sys.mjs"
|
||||||
|
);
|
||||||
|
let { lib, cmd } = args;
|
||||||
|
let libc = ctypes.open(lib);
|
||||||
|
let exec = libc.declare(
|
||||||
|
"execv",
|
||||||
|
ctypes.default_abi,
|
||||||
|
ctypes.int,
|
||||||
|
ctypes.char.ptr
|
||||||
|
);
|
||||||
|
let rv = exec(cmd);
|
||||||
|
libc.close();
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls the native fork syscall.
|
||||||
|
function callFork(args) {
|
||||||
|
const { ctypes } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/ctypes.sys.mjs"
|
||||||
|
);
|
||||||
|
let { lib } = args;
|
||||||
|
let libc = ctypes.open(lib);
|
||||||
|
let fork = libc.declare("fork", ctypes.default_abi, ctypes.int);
|
||||||
|
let rv = fork();
|
||||||
|
libc.close();
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls the native sysctl syscall.
|
||||||
|
function callSysctl(args) {
|
||||||
|
const { ctypes } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/ctypes.sys.mjs"
|
||||||
|
);
|
||||||
|
let { lib, name } = args;
|
||||||
|
let libc = ctypes.open(lib);
|
||||||
|
let sysctlbyname = libc.declare(
|
||||||
|
"sysctlbyname",
|
||||||
|
ctypes.default_abi,
|
||||||
|
ctypes.int,
|
||||||
|
ctypes.char.ptr,
|
||||||
|
ctypes.voidptr_t,
|
||||||
|
ctypes.size_t.ptr,
|
||||||
|
ctypes.voidptr_t,
|
||||||
|
ctypes.size_t.ptr
|
||||||
|
);
|
||||||
|
let rv = sysctlbyname(name, null, null, null, null);
|
||||||
|
libc.close();
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
function callPrctl(args) {
|
||||||
|
const { ctypes } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/ctypes.sys.mjs"
|
||||||
|
);
|
||||||
|
let { lib, option } = args;
|
||||||
|
let libc = ctypes.open(lib);
|
||||||
|
let prctl = libc.declare(
|
||||||
|
"prctl",
|
||||||
|
ctypes.default_abi,
|
||||||
|
ctypes.int,
|
||||||
|
ctypes.int, // option
|
||||||
|
ctypes.unsigned_long, // arg2
|
||||||
|
ctypes.unsigned_long, // arg3
|
||||||
|
ctypes.unsigned_long, // arg4
|
||||||
|
ctypes.unsigned_long // arg5
|
||||||
|
);
|
||||||
|
let rv = prctl(option, 0, 0, 0, 0);
|
||||||
|
if (rv == -1) {
|
||||||
|
rv = ctypes.errno;
|
||||||
|
}
|
||||||
|
libc.close();
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls the native open/close syscalls.
|
||||||
|
function callOpen(args) {
|
||||||
|
const { ctypes } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/ctypes.sys.mjs"
|
||||||
|
);
|
||||||
|
let { lib, path, flags } = args;
|
||||||
|
let libc = ctypes.open(lib);
|
||||||
|
let open = libc.declare(
|
||||||
|
"open",
|
||||||
|
ctypes.default_abi,
|
||||||
|
ctypes.int,
|
||||||
|
ctypes.char.ptr,
|
||||||
|
ctypes.int
|
||||||
|
);
|
||||||
|
let close = libc.declare("close", ctypes.default_abi, ctypes.int, ctypes.int);
|
||||||
|
let fd = open(path, flags);
|
||||||
|
close(fd);
|
||||||
|
libc.close();
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify faccessat2
|
||||||
|
function callFaccessat2(args) {
|
||||||
|
const { ctypes } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/ctypes.sys.mjs"
|
||||||
|
);
|
||||||
|
let { lib, dirfd, path, mode, flag } = args;
|
||||||
|
let libc = ctypes.open(lib);
|
||||||
|
let faccessat = libc.declare(
|
||||||
|
"faccessat",
|
||||||
|
ctypes.default_abi,
|
||||||
|
ctypes.int,
|
||||||
|
ctypes.int, // dirfd
|
||||||
|
ctypes.char.ptr, // path
|
||||||
|
ctypes.int, // mode
|
||||||
|
ctypes.int // flag
|
||||||
|
);
|
||||||
|
let rv = faccessat(dirfd, path, mode, flag);
|
||||||
|
if (rv == -1) {
|
||||||
|
rv = ctypes.errno;
|
||||||
|
}
|
||||||
|
libc.close();
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the name of the native library needed for native syscalls
|
||||||
|
function getOSLib() {
|
||||||
|
switch (Services.appinfo.OS) {
|
||||||
|
case "WINNT":
|
||||||
|
return "kernel32.dll";
|
||||||
|
case "Darwin":
|
||||||
|
return "libc.dylib";
|
||||||
|
case "Linux":
|
||||||
|
return "libc.so.6";
|
||||||
|
default:
|
||||||
|
Assert.ok(false, "Unknown OS");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reading a header might be weird, but the alternatives to read a stable
|
||||||
|
// version number we can easily check against are not much more fun
|
||||||
|
async function getKernelVersion() {
|
||||||
|
let header = await IOUtils.readUTF8("/usr/include/linux/version.h");
|
||||||
|
let hr = header.split("\n");
|
||||||
|
for (let line in hr) {
|
||||||
|
let hrs = hr[line].split(" ");
|
||||||
|
if (hrs[0] === "#define" && hrs[1] === "LINUX_VERSION_CODE") {
|
||||||
|
return Number(hrs[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw Error("No LINUX_VERSION_CODE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is how it is done in /usr/include/linux/version.h
|
||||||
|
function computeKernelVersion(major, minor, dot) {
|
||||||
|
return (major << 16) + (minor << 8) + dot;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGlibcVersion() {
|
||||||
|
const { ctypes } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/ctypes.sys.mjs"
|
||||||
|
);
|
||||||
|
let libc = ctypes.open(getOSLib());
|
||||||
|
let gnu_get_libc_version = libc.declare(
|
||||||
|
"gnu_get_libc_version",
|
||||||
|
ctypes.default_abi,
|
||||||
|
ctypes.char.ptr
|
||||||
|
);
|
||||||
|
let rv = gnu_get_libc_version().readString();
|
||||||
|
libc.close();
|
||||||
|
let ar = rv.split(".");
|
||||||
|
// return a number made of MAJORMINOR
|
||||||
|
return Number(ar[0] + ar[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a harmless command to execute with execv
|
||||||
|
function getOSExecCmd() {
|
||||||
|
Assert.ok(!isWin());
|
||||||
|
return "/bin/cat";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the current content sandbox level, passed in
|
||||||
|
// the |level| argument, supports syscall sandboxing.
|
||||||
|
function areContentSyscallsSandboxed(level) {
|
||||||
|
let syscallsSandboxMinLevel = 0;
|
||||||
|
|
||||||
|
// Set syscallsSandboxMinLevel to the lowest level that has
|
||||||
|
// syscall sandboxing enabled. For now, this varies across
|
||||||
|
// Windows, Mac, Linux, other.
|
||||||
|
switch (Services.appinfo.OS) {
|
||||||
|
case "WINNT":
|
||||||
|
syscallsSandboxMinLevel = 1;
|
||||||
|
break;
|
||||||
|
case "Darwin":
|
||||||
|
syscallsSandboxMinLevel = 1;
|
||||||
|
break;
|
||||||
|
case "Linux":
|
||||||
|
syscallsSandboxMinLevel = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Assert.ok(false, "Unknown OS");
|
||||||
|
}
|
||||||
|
|
||||||
|
return level >= syscallsSandboxMinLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Drive tests for a single content process.
|
||||||
|
//
|
||||||
|
// Tests executing OS API calls in the content process. Limited to Mac
|
||||||
|
// and Linux calls for now.
|
||||||
|
//
|
||||||
|
add_task(async function () {
|
||||||
|
// This test is only relevant in e10s
|
||||||
|
if (!gMultiProcessBrowser) {
|
||||||
|
ok(false, "e10s is enabled");
|
||||||
|
info("e10s is not enabled, exiting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let level = 0;
|
||||||
|
let prefExists = true;
|
||||||
|
|
||||||
|
// Read the security.sandbox.content.level pref.
|
||||||
|
// If the pref isn't set and we're running on Linux on !isNightly(),
|
||||||
|
// exit without failing. The Linux content sandbox is only enabled
|
||||||
|
// on Nightly at this time.
|
||||||
|
// eslint-disable-next-line mozilla/use-default-preference-values
|
||||||
|
try {
|
||||||
|
level = Services.prefs.getIntPref("security.sandbox.content.level");
|
||||||
|
} catch (e) {
|
||||||
|
prefExists = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok(prefExists, "pref security.sandbox.content.level exists");
|
||||||
|
if (!prefExists) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info(`security.sandbox.content.level=${level}`);
|
||||||
|
Assert.greater(level, 0, "content sandbox is enabled.");
|
||||||
|
|
||||||
|
let areSyscallsSandboxed = areContentSyscallsSandboxed(level);
|
||||||
|
|
||||||
|
// Content sandbox enabled, but level doesn't include syscall sandboxing.
|
||||||
|
ok(areSyscallsSandboxed, "content syscall sandboxing is enabled.");
|
||||||
|
if (!areSyscallsSandboxed) {
|
||||||
|
info("content sandbox level too low for syscall tests, exiting\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let browser = gBrowser.selectedBrowser;
|
||||||
|
let lib = getOSLib();
|
||||||
|
|
||||||
|
// use execv syscall
|
||||||
|
// (causes content process to be killed on Linux)
|
||||||
|
if (isMac()) {
|
||||||
|
// exec something harmless, this should fail
|
||||||
|
let cmd = getOSExecCmd();
|
||||||
|
let rv = await SpecialPowers.spawn(browser, [{ lib, cmd }], callExec);
|
||||||
|
Assert.equal(rv, -1, `exec(${cmd}) is not permitted`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// use open syscall
|
||||||
|
if (isLinux() || isMac()) {
|
||||||
|
// open a file for writing in $HOME, this should fail
|
||||||
|
let path = fileInHomeDir().path;
|
||||||
|
let flags = lazy.LIBC.O_CREAT | lazy.LIBC.O_WRONLY;
|
||||||
|
let fd = await SpecialPowers.spawn(
|
||||||
|
browser,
|
||||||
|
[{ lib, path, flags }],
|
||||||
|
callOpen
|
||||||
|
);
|
||||||
|
Assert.less(fd, 0, "opening a file for writing in home is not permitted");
|
||||||
|
}
|
||||||
|
|
||||||
|
// use open syscall
|
||||||
|
if (isLinux() || isMac()) {
|
||||||
|
// open a file for writing in the content temp dir, this should fail on
|
||||||
|
// macOS and Linux. The open handler in the content process closes the file
|
||||||
|
// for us
|
||||||
|
let path = fileInTempDir().path;
|
||||||
|
let flags = lazy.LIBC.O_CREAT | lazy.LIBC.O_WRONLY;
|
||||||
|
let fd = await SpecialPowers.spawn(
|
||||||
|
browser,
|
||||||
|
[{ lib, path, flags }],
|
||||||
|
callOpen
|
||||||
|
);
|
||||||
|
Assert.strictEqual(
|
||||||
|
fd,
|
||||||
|
-1,
|
||||||
|
"opening a file for writing in content temp is not permitted"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// use fork syscall
|
||||||
|
if (isLinux() || isMac()) {
|
||||||
|
let rv = await SpecialPowers.spawn(browser, [{ lib }], callFork);
|
||||||
|
Assert.equal(rv, -1, "calling fork is not permitted");
|
||||||
|
}
|
||||||
|
|
||||||
|
// On macOS before 10.10 the |sysctl-name| predicate didn't exist for
|
||||||
|
// filtering |sysctl| access. Check the Darwin version before running the
|
||||||
|
// tests (Darwin 14.0.0 is macOS 10.10). This branch can be removed when we
|
||||||
|
// remove support for macOS 10.9.
|
||||||
|
if (isMac() && Services.sysinfo.getProperty("version") >= "14.0.0") {
|
||||||
|
let rv = await SpecialPowers.spawn(
|
||||||
|
browser,
|
||||||
|
[{ lib, name: "kern.boottime" }],
|
||||||
|
callSysctl
|
||||||
|
);
|
||||||
|
Assert.equal(rv, -1, "calling sysctl('kern.boottime') is not permitted");
|
||||||
|
|
||||||
|
rv = await SpecialPowers.spawn(
|
||||||
|
browser,
|
||||||
|
[{ lib, name: "net.inet.ip.ttl" }],
|
||||||
|
callSysctl
|
||||||
|
);
|
||||||
|
Assert.equal(rv, -1, "calling sysctl('net.inet.ip.ttl') is not permitted");
|
||||||
|
|
||||||
|
rv = await SpecialPowers.spawn(
|
||||||
|
browser,
|
||||||
|
[{ lib, name: "hw.ncpu" }],
|
||||||
|
callSysctl
|
||||||
|
);
|
||||||
|
Assert.equal(rv, 0, "calling sysctl('hw.ncpu') is permitted");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLinux()) {
|
||||||
|
// These constants are not portable.
|
||||||
|
|
||||||
|
// verify we block PR_CAPBSET_READ with EINVAL
|
||||||
|
let option = lazy.LIBC.PR_CAPBSET_READ;
|
||||||
|
let rv = await SpecialPowers.spawn(browser, [{ lib, option }], callPrctl);
|
||||||
|
Assert.strictEqual(
|
||||||
|
rv,
|
||||||
|
lazy.LIBC.EINVAL,
|
||||||
|
"prctl(PR_CAPBSET_READ) is blocked"
|
||||||
|
);
|
||||||
|
|
||||||
|
const kernelVersion = await getKernelVersion();
|
||||||
|
const glibcVersion = getGlibcVersion();
|
||||||
|
// faccessat2 is only used with kernel 5.8+ by glibc 2.33+
|
||||||
|
if (glibcVersion >= 233 && kernelVersion >= computeKernelVersion(5, 8, 0)) {
|
||||||
|
info("Linux v5.8+, glibc 2.33+, checking faccessat2");
|
||||||
|
const dirfd = 0;
|
||||||
|
const path = "/";
|
||||||
|
const mode = 0;
|
||||||
|
// the value 0x01 is just one we know should get rejected
|
||||||
|
let rv = await SpecialPowers.spawn(
|
||||||
|
browser,
|
||||||
|
[{ lib, dirfd, path, mode, flag: 0x01 }],
|
||||||
|
callFaccessat2
|
||||||
|
);
|
||||||
|
Assert.strictEqual(
|
||||||
|
rv,
|
||||||
|
lazy.LIBC.ENOSYS,
|
||||||
|
"faccessat2 (flag=0x01) was blocked with ENOSYS"
|
||||||
|
);
|
||||||
|
|
||||||
|
rv = await SpecialPowers.spawn(
|
||||||
|
browser,
|
||||||
|
[{ lib, dirfd, path, mode, flag: lazy.LIBC.AT_EACCESS }],
|
||||||
|
callFaccessat2
|
||||||
|
);
|
||||||
|
Assert.strictEqual(
|
||||||
|
rv,
|
||||||
|
lazy.LIBC.EACCES,
|
||||||
|
"faccessat2 (flag=0x200) was allowed, errno=EACCES"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
info(
|
||||||
|
"Unsupported kernel (" +
|
||||||
|
kernelVersion +
|
||||||
|
" )/glibc (" +
|
||||||
|
glibcVersion +
|
||||||
|
"), skipping faccessat2"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,502 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const uuidGenerator = Services.uuid;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Utility functions for the browser content sandbox tests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function sanityChecks() {
|
||||||
|
// This test is only relevant in e10s
|
||||||
|
if (!gMultiProcessBrowser) {
|
||||||
|
ok(false, "e10s is enabled");
|
||||||
|
info("e10s is not enabled, exiting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let level = 0;
|
||||||
|
let prefExists = true;
|
||||||
|
|
||||||
|
// Read the security.sandbox.content.level pref.
|
||||||
|
// eslint-disable-next-line mozilla/use-default-preference-values
|
||||||
|
try {
|
||||||
|
level = Services.prefs.getIntPref("security.sandbox.content.level");
|
||||||
|
} catch (e) {
|
||||||
|
prefExists = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok(prefExists, "pref security.sandbox.content.level exists");
|
||||||
|
if (!prefExists) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info(`security.sandbox.content.level=${level}`);
|
||||||
|
Assert.greater(level, 0, "content sandbox is enabled.");
|
||||||
|
|
||||||
|
let isFileIOSandboxed = isContentFileIOSandboxed(level);
|
||||||
|
|
||||||
|
// Content sandbox enabled, but level doesn't include file I/O sandboxing.
|
||||||
|
ok(isFileIOSandboxed, "content file I/O sandboxing is enabled.");
|
||||||
|
if (!isFileIOSandboxed) {
|
||||||
|
info("content sandbox level too low for file I/O tests, exiting\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isXdgEnabled() {
|
||||||
|
try {
|
||||||
|
return Services.prefs.getBoolPref("widget.support-xdg-config");
|
||||||
|
} catch (ex) {
|
||||||
|
// if the pref is not there it means we dont have XDG support
|
||||||
|
if (ex.name === "NS_ERROR_UNEXPECTED") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates file at |path| and returns a promise that resolves with an object
|
||||||
|
// with .ok boolean to indicate true if the file was successfully created,
|
||||||
|
// otherwise false. Include imports so this can be safely serialized and run
|
||||||
|
// remotely by SpecialPowers.spawn.
|
||||||
|
//
|
||||||
|
// Report the exception's error code in .code as well.
|
||||||
|
function createFile(path) {
|
||||||
|
const { FileUtils } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/FileUtils.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fstream = Cc[
|
||||||
|
"@mozilla.org/network/file-output-stream;1"
|
||||||
|
].createInstance(Ci.nsIFileOutputStream);
|
||||||
|
|
||||||
|
fstream.init(
|
||||||
|
new FileUtils.File(path),
|
||||||
|
-1, // readonly mode
|
||||||
|
-1, // default permissions
|
||||||
|
0
|
||||||
|
); // behaviour flags
|
||||||
|
|
||||||
|
const ostream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
|
||||||
|
Ci.nsIBinaryOutputStream
|
||||||
|
);
|
||||||
|
ostream.setOutputStream(fstream);
|
||||||
|
|
||||||
|
const data = "TEST FILE DUMMY DATA";
|
||||||
|
ostream.writeBytes(data, data.length);
|
||||||
|
|
||||||
|
ostream.close();
|
||||||
|
fstream.close();
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, code: e.result };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a symlink at |path| and returns a promise that resolves with an
|
||||||
|
// object with .ok boolean to indicate true if the symlink was successfully
|
||||||
|
// created, otherwise false. Include imports so this can be safely serialized
|
||||||
|
// and run remotely by SpecialPowers.spawn.
|
||||||
|
//
|
||||||
|
// Report the exception's error code in .code as well.
|
||||||
|
// Report errno in .code if syscall returns -1.
|
||||||
|
function createSymlink(path) {
|
||||||
|
const { ctypes } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/ctypes.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Trying to open "libc.so" on linux will fail with invalid elf header error
|
||||||
|
// because it would be a linker script. Using libc.so.6 avoids that.
|
||||||
|
const libc = ctypes.open(
|
||||||
|
Services.appinfo.OS === "Darwin" ? "libSystem.B.dylib" : "libc.so.6"
|
||||||
|
);
|
||||||
|
|
||||||
|
const symlink = libc.declare(
|
||||||
|
"symlink",
|
||||||
|
ctypes.default_abi,
|
||||||
|
ctypes.int, // return value
|
||||||
|
ctypes.char.ptr, // target
|
||||||
|
ctypes.char.ptr //linkpath
|
||||||
|
);
|
||||||
|
|
||||||
|
ctypes.errno = 0;
|
||||||
|
const rv = symlink("/etc", path);
|
||||||
|
const _errno = ctypes.errno;
|
||||||
|
if (rv < 0) {
|
||||||
|
return { ok: false, code: _errno };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, code: e.result };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletes file at |path| and returns a promise that resolves with an object
|
||||||
|
// with .ok boolean to indicate true if the file was successfully deleted,
|
||||||
|
// otherwise false. Include imports so this can be safely serialized and run
|
||||||
|
// remotely by SpecialPowers.spawn.
|
||||||
|
//
|
||||||
|
// Report the exception's error code in .code as well.
|
||||||
|
function deleteFile(path) {
|
||||||
|
const { FileUtils } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/FileUtils.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file = new FileUtils.File(path);
|
||||||
|
file.remove(false);
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, code: e.result };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads the directory at |path| and returns a promise that resolves when
|
||||||
|
// iteration over the directory finishes or encounters an error. The promise
|
||||||
|
// resolves with an object where .ok indicates success or failure and
|
||||||
|
// .numEntries is the number of directory entries found.
|
||||||
|
//
|
||||||
|
// Report the exception's error code in .code as well.
|
||||||
|
function readDir(path) {
|
||||||
|
const { FileUtils } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/FileUtils.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
let numEntries = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file = new FileUtils.File(path);
|
||||||
|
const enumerator = file.directoryEntries;
|
||||||
|
|
||||||
|
while (enumerator.hasMoreElements()) {
|
||||||
|
void enumerator.nextFile;
|
||||||
|
numEntries++;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, numEntries, code: e.result };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true, numEntries };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads the file at |path| and returns a promise that resolves when
|
||||||
|
// reading is completed. Returned object has boolean .ok to indicate
|
||||||
|
// success or failure.
|
||||||
|
//
|
||||||
|
// Report the exception's error code in .code as well.
|
||||||
|
function readFile(path) {
|
||||||
|
const { FileUtils } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/FileUtils.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file = new FileUtils.File(path);
|
||||||
|
|
||||||
|
const fstream = Cc[
|
||||||
|
"@mozilla.org/network/file-input-stream;1"
|
||||||
|
].createInstance(Ci.nsIFileInputStream);
|
||||||
|
fstream.init(file, -1, -1, 0);
|
||||||
|
|
||||||
|
const istream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
|
||||||
|
Ci.nsIBinaryInputStream
|
||||||
|
);
|
||||||
|
istream.setInputStream(fstream);
|
||||||
|
|
||||||
|
const available = istream.available();
|
||||||
|
void istream.readBytes(available);
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, code: e.result };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does a stat of |path| and returns a promise that resolves if the
|
||||||
|
// stat is successful. Returned object has boolean .ok to indicate
|
||||||
|
// success or failure.
|
||||||
|
//
|
||||||
|
// Report the exception's error code in .code as well.
|
||||||
|
function statPath(path) {
|
||||||
|
const { FileUtils } = ChromeUtils.importESModule(
|
||||||
|
"resource://gre/modules/FileUtils.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file = new FileUtils.File(path);
|
||||||
|
void file.lastModifiedTime;
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, code: e.result };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the current content sandbox level, passed in
|
||||||
|
// the |level| argument, supports filesystem sandboxing.
|
||||||
|
function isContentFileIOSandboxed(level) {
|
||||||
|
let fileIOSandboxMinLevel = 0;
|
||||||
|
|
||||||
|
// Set fileIOSandboxMinLevel to the lowest level that has
|
||||||
|
// content filesystem sandboxing enabled. For now, this
|
||||||
|
// varies across Windows, Mac, Linux, other.
|
||||||
|
switch (Services.appinfo.OS) {
|
||||||
|
case "WINNT":
|
||||||
|
fileIOSandboxMinLevel = 1;
|
||||||
|
break;
|
||||||
|
case "Darwin":
|
||||||
|
fileIOSandboxMinLevel = 1;
|
||||||
|
break;
|
||||||
|
case "Linux":
|
||||||
|
fileIOSandboxMinLevel = 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Assert.ok(false, "Unknown OS");
|
||||||
|
}
|
||||||
|
|
||||||
|
return level >= fileIOSandboxMinLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the lowest sandbox level where blanket reading of the profile
|
||||||
|
// directory from the content process should be blocked by the sandbox.
|
||||||
|
function minProfileReadSandboxLevel() {
|
||||||
|
switch (Services.appinfo.OS) {
|
||||||
|
case "WINNT":
|
||||||
|
return 3;
|
||||||
|
case "Darwin":
|
||||||
|
return 2;
|
||||||
|
case "Linux":
|
||||||
|
return 3;
|
||||||
|
default:
|
||||||
|
Assert.ok(false, "Unknown OS");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the lowest sandbox level where blanket reading of the home
|
||||||
|
// directory from the content process should be blocked by the sandbox.
|
||||||
|
function minHomeReadSandboxLevel() {
|
||||||
|
switch (Services.appinfo.OS) {
|
||||||
|
case "WINNT":
|
||||||
|
return 3;
|
||||||
|
case "Darwin":
|
||||||
|
return 3;
|
||||||
|
case "Linux":
|
||||||
|
return 3;
|
||||||
|
default:
|
||||||
|
Assert.ok(false, "Unknown OS");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMac() {
|
||||||
|
return Services.appinfo.OS == "Darwin";
|
||||||
|
}
|
||||||
|
function isWin() {
|
||||||
|
return Services.appinfo.OS == "WINNT";
|
||||||
|
}
|
||||||
|
function isLinux() {
|
||||||
|
return Services.appinfo.OS == "Linux";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNightly() {
|
||||||
|
let version = SpecialPowers.Services.appinfo.version;
|
||||||
|
return version.endsWith("a1");
|
||||||
|
}
|
||||||
|
|
||||||
|
function uuid() {
|
||||||
|
return uuidGenerator.generateUUID().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a file object for a new file in the home dir ($HOME/<UUID>).
|
||||||
|
function fileInHomeDir() {
|
||||||
|
// get home directory, make sure it exists
|
||||||
|
let homeDir = Services.dirsvc.get("Home", Ci.nsIFile);
|
||||||
|
Assert.ok(homeDir.exists(), "Home dir exists");
|
||||||
|
Assert.ok(homeDir.isDirectory(), "Home dir is a directory");
|
||||||
|
|
||||||
|
// build a file object for a new file named $HOME/<UUID>
|
||||||
|
let homeFile = homeDir.clone();
|
||||||
|
homeFile.appendRelativePath(uuid());
|
||||||
|
Assert.ok(!homeFile.exists(), homeFile.path + " does not exist");
|
||||||
|
return homeFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a file object for a new file in the content temp dir (.../<UUID>).
|
||||||
|
function fileInTempDir() {
|
||||||
|
let contentTempKey = "TmpD";
|
||||||
|
|
||||||
|
// get the content temp dir, make sure it exists
|
||||||
|
let ctmp = Services.dirsvc.get(contentTempKey, Ci.nsIFile);
|
||||||
|
Assert.ok(ctmp.exists(), "Temp dir exists");
|
||||||
|
Assert.ok(ctmp.isDirectory(), "Temp dir is a directory");
|
||||||
|
|
||||||
|
// build a file object for a new file in content temp
|
||||||
|
let tempFile = ctmp.clone();
|
||||||
|
tempFile.appendRelativePath(uuid());
|
||||||
|
Assert.ok(!tempFile.exists(), tempFile.path + " does not exist");
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetProfileDir() {
|
||||||
|
// get profile directory
|
||||||
|
let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||||
|
return profileDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetHomeDir() {
|
||||||
|
// get home directory
|
||||||
|
let homeDir = Services.dirsvc.get("Home", Ci.nsIFile);
|
||||||
|
return homeDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetHomeSubdir(subdir) {
|
||||||
|
return GetSubdir(GetHomeDir(), subdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetHomeSubdirFile(subdir) {
|
||||||
|
return GetSubdirFile(GetHomeSubdir(subdir));
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetSubdir(dir, subdir) {
|
||||||
|
let newSubdir = dir.clone();
|
||||||
|
newSubdir.appendRelativePath(subdir);
|
||||||
|
return newSubdir;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetSubdirFile(dir) {
|
||||||
|
let newFile = dir.clone();
|
||||||
|
newFile.appendRelativePath(uuid());
|
||||||
|
return newFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetPerUserExtensionDir() {
|
||||||
|
return Services.dirsvc.get("XREUSysExt", Ci.nsIFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a file object for the file or directory named |name| in the
|
||||||
|
// profile directory.
|
||||||
|
function GetProfileEntry(name) {
|
||||||
|
let entry = GetProfileDir();
|
||||||
|
entry.append(name);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetDir(path) {
|
||||||
|
info(`GetDir(${path})`);
|
||||||
|
let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||||
|
dir.initWithPath(path);
|
||||||
|
Assert.ok(dir.isDirectory(), `${path} is a directory`);
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetDirFromEnvVariable(varName) {
|
||||||
|
return GetDir(Services.env.get(varName));
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetFile(path) {
|
||||||
|
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||||
|
file.initWithPath(path);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetBrowserType(type) {
|
||||||
|
let browserType = undefined;
|
||||||
|
|
||||||
|
if (!GetBrowserType[type]) {
|
||||||
|
if (type === "web") {
|
||||||
|
GetBrowserType[type] = gBrowser.selectedBrowser;
|
||||||
|
} else {
|
||||||
|
// open a tab in a `type` content process
|
||||||
|
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
|
||||||
|
preferredRemoteType: type,
|
||||||
|
allowInheritPrincipal: true,
|
||||||
|
});
|
||||||
|
// get the browser for the `type` process tab
|
||||||
|
GetBrowserType[type] = gBrowser.getBrowserForTab(gBrowser.selectedTab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
browserType = GetBrowserType[type];
|
||||||
|
Assert.strictEqual(
|
||||||
|
browserType.remoteType,
|
||||||
|
type,
|
||||||
|
`GetBrowserType(${type}) returns a ${type} process`
|
||||||
|
);
|
||||||
|
return browserType;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetWebBrowser() {
|
||||||
|
return GetBrowserType("web");
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFileContentProcessEnabled() {
|
||||||
|
// Ensure that the file content process is enabled.
|
||||||
|
let fileContentProcessEnabled = Services.prefs.getBoolPref(
|
||||||
|
"browser.tabs.remote.separateFileUriProcess"
|
||||||
|
);
|
||||||
|
ok(fileContentProcessEnabled, "separate file content process is enabled");
|
||||||
|
return fileContentProcessEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetFileBrowser() {
|
||||||
|
if (!isFileContentProcessEnabled()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return GetBrowserType("file");
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetSandboxLevel() {
|
||||||
|
// Current level
|
||||||
|
return Services.prefs.getIntPref("security.sandbox.content.level");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runTestsList(tests) {
|
||||||
|
let level = GetSandboxLevel();
|
||||||
|
|
||||||
|
// remove tests not enabled by the current sandbox level
|
||||||
|
tests = tests.filter(test => test.minLevel <= level);
|
||||||
|
|
||||||
|
for (let test of tests) {
|
||||||
|
let okString = test.ok ? "allowed" : "blocked";
|
||||||
|
let processType = test.browser.remoteType;
|
||||||
|
|
||||||
|
// ensure the file/dir exists before we ask a content process to stat
|
||||||
|
// it so we know a failure is not due to a nonexistent file/dir
|
||||||
|
if (test.func === statPath) {
|
||||||
|
ok(test.file.exists(), `${test.file.path} exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = await SpecialPowers.spawn(
|
||||||
|
test.browser,
|
||||||
|
[test.file.path],
|
||||||
|
test.func
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.equal(
|
||||||
|
result.ok,
|
||||||
|
test.ok,
|
||||||
|
`reading ${test.desc} from a ${processType} process ` +
|
||||||
|
`is ${okString} (${test.file.path})`
|
||||||
|
);
|
||||||
|
|
||||||
|
// if the directory is not expected to be readable,
|
||||||
|
// ensure the listing has zero entries
|
||||||
|
if (test.func === readDir && !test.ok) {
|
||||||
|
Assert.equal(
|
||||||
|
result.numEntries,
|
||||||
|
0,
|
||||||
|
`directory list is empty (${test.file.path})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (test.cleanup != undefined) {
|
||||||
|
await test.cleanup(test.file.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/zen/tests/mochitests/sandbox/browser_profiler.toml
Normal file
21
src/zen/tests/mochitests/sandbox/browser_profiler.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
skip-if = [
|
||||||
|
"ccov",
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517 for sandbox, bug 1885381 for profiler
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517 for sandbox, bug 1885381 for profiler
|
||||||
|
]
|
||||||
|
tags = "contentsandbox"
|
||||||
|
|
||||||
|
# This is here to make sure we will not have prelaunched processes, which will
|
||||||
|
# mess with sandbox profiling interaction: we will miss launch-related markers
|
||||||
|
# and this makes the test intermittently fail on TV jobs
|
||||||
|
prefs = [
|
||||||
|
"dom.ipc.processPrelaunch.fission.number=0"
|
||||||
|
]
|
||||||
|
|
||||||
|
environment = "MOZ_SANDBOX_LOGGING_FOR_TESTS=1"
|
||||||
|
|
||||||
|
["browser_sandbox_profiler.js"]
|
||||||
|
run-if = [
|
||||||
|
"os == 'linux'",
|
||||||
|
]
|
||||||
153
src/zen/tests/mochitests/sandbox/browser_sandbox_profiler.js
Normal file
153
src/zen/tests/mochitests/sandbox/browser_sandbox_profiler.js
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const { ProfilerTestUtils } = ChromeUtils.importESModule(
|
||||||
|
"resource://testing-common/ProfilerTestUtils.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
async function addTab() {
|
||||||
|
const tab = BrowserTestUtils.addTab(gBrowser, "https://example.com/browser", {
|
||||||
|
forceNewProcess: true,
|
||||||
|
});
|
||||||
|
const browser = gBrowser.getBrowserForTab(tab);
|
||||||
|
await BrowserTestUtils.browserLoaded(browser);
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sandboxSettingsEnabled = {
|
||||||
|
entries: 8 * 1024 * 1024, // 8M entries = 64MB
|
||||||
|
interval: 1, // ms
|
||||||
|
features: ["stackwalk", "sandbox"],
|
||||||
|
threads: ["SandboxProfilerEmitter"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const sandboxSettingsDisabled = {
|
||||||
|
entries: 8 * 1024 * 1024, // 8M entries = 64MB
|
||||||
|
interval: 1, // ms
|
||||||
|
features: ["stackwalk"],
|
||||||
|
threads: ["SandboxProfilerEmitter"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const kNewProcesses = 2;
|
||||||
|
|
||||||
|
async function waitForMaybeSandboxProfilerData(
|
||||||
|
threadName,
|
||||||
|
name1,
|
||||||
|
withStacks,
|
||||||
|
enabled
|
||||||
|
) {
|
||||||
|
let tabs = [];
|
||||||
|
for (let i = 0; i < kNewProcesses; ++i) {
|
||||||
|
tabs.push(await addTab());
|
||||||
|
}
|
||||||
|
|
||||||
|
let profile;
|
||||||
|
let intercepted = undefined;
|
||||||
|
try {
|
||||||
|
await TestUtils.waitForCondition(
|
||||||
|
async () => {
|
||||||
|
profile = await Services.profiler.getProfileDataAsync();
|
||||||
|
intercepted = profile.processes
|
||||||
|
.flatMap(ps => {
|
||||||
|
let sandboxThreads = ps.threads.filter(
|
||||||
|
th => th.name === threadName
|
||||||
|
);
|
||||||
|
return sandboxThreads.flatMap(th => {
|
||||||
|
let markersData = th.markers.data;
|
||||||
|
return markersData.flatMap(d => {
|
||||||
|
let [, , , , , o] = d;
|
||||||
|
return o;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.filter(x => "name1" in x && name1.includes(x.name1) >= 0);
|
||||||
|
return !!intercepted.length;
|
||||||
|
},
|
||||||
|
`Wait for some samples from ${threadName}`,
|
||||||
|
/* interval*/ 100,
|
||||||
|
/* maxTries */ 25
|
||||||
|
);
|
||||||
|
Assert.greater(
|
||||||
|
intercepted.length,
|
||||||
|
0,
|
||||||
|
`Should have collected some data from ${threadName}`
|
||||||
|
);
|
||||||
|
} catch (ex) {
|
||||||
|
if (!enabled && ex.includes(`Wait for some samples from ${threadName}`)) {
|
||||||
|
Assert.equal(
|
||||||
|
intercepted.length,
|
||||||
|
0,
|
||||||
|
`Should have NOT collected data from ${threadName}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (withStacks) {
|
||||||
|
let stacks = profile.processes.flatMap(ps => {
|
||||||
|
let sandboxThreads = ps.threads.filter(th => th.name === threadName);
|
||||||
|
return sandboxThreads.flatMap(th => {
|
||||||
|
let stackTableData = th.stackTable.data;
|
||||||
|
return stackTableData.flatMap(d => {
|
||||||
|
return [d];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (enabled) {
|
||||||
|
Assert.greater(stacks.length, 0, "Should have some stack as well");
|
||||||
|
} else {
|
||||||
|
Assert.equal(stacks.length, 0, "Should have NO stack as well");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let tab of tabs) {
|
||||||
|
await BrowserTestUtils.removeTab(tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async () => {
|
||||||
|
await ProfilerTestUtils.startProfiler(sandboxSettingsEnabled);
|
||||||
|
await waitForMaybeSandboxProfilerData(
|
||||||
|
"SandboxProfilerEmitterSyscalls",
|
||||||
|
["id", "init"],
|
||||||
|
/* withStacks */ true,
|
||||||
|
/* enabled */ true
|
||||||
|
);
|
||||||
|
await Services.profiler.StopProfiler();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async () => {
|
||||||
|
await ProfilerTestUtils.startProfiler(sandboxSettingsEnabled);
|
||||||
|
await waitForMaybeSandboxProfilerData(
|
||||||
|
"SandboxProfilerEmitterLogs",
|
||||||
|
["log"],
|
||||||
|
/* withStacks */ false,
|
||||||
|
/* enabled */ true
|
||||||
|
);
|
||||||
|
await Services.profiler.StopProfiler();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async () => {
|
||||||
|
await ProfilerTestUtils.startProfiler(sandboxSettingsDisabled);
|
||||||
|
await waitForMaybeSandboxProfilerData(
|
||||||
|
"SandboxProfilerEmitterSyscalls",
|
||||||
|
["id", "init"],
|
||||||
|
/* withStacks */ true,
|
||||||
|
/* enabled */ false
|
||||||
|
);
|
||||||
|
await Services.profiler.StopProfiler();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async () => {
|
||||||
|
await ProfilerTestUtils.startProfiler(sandboxSettingsEnabled);
|
||||||
|
await waitForMaybeSandboxProfilerData(
|
||||||
|
"SandboxProfilerEmitterLogs",
|
||||||
|
["log"],
|
||||||
|
/* withStacks */ false,
|
||||||
|
/* enabled */ false
|
||||||
|
);
|
||||||
|
await Services.profiler.StopProfiler();
|
||||||
|
});
|
||||||
78
src/zen/tests/mochitests/sandbox/browser_sandbox_test.js
Normal file
78
src/zen/tests/mochitests/sandbox/browser_sandbox_test.js
Normal 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 https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
waitForExplicitFinish();
|
||||||
|
|
||||||
|
// Types of processes to test, taken from GeckoProcessTypes.h
|
||||||
|
// GPU process might not run depending on the platform, so we need it to be
|
||||||
|
// the last one of the list to allow the remainingTests logic below to work
|
||||||
|
// as expected.
|
||||||
|
//
|
||||||
|
// For UtilityProcess, allow constructing a string made of the process type
|
||||||
|
// and the sandbox variant we want to test, e.g.,
|
||||||
|
// utility:0 for GENERIC_UTILITY
|
||||||
|
// utility:1 for AppleMedia/WMF on macOS/Windows
|
||||||
|
var processTypes = ["tab", "socket", "rdd", "gmplugin", "utility:0", "gpu"];
|
||||||
|
|
||||||
|
const platform = SpecialPowers.Services.appinfo.OS;
|
||||||
|
if (platform === "WINNT" || platform === "Darwin") {
|
||||||
|
processTypes.push("utility:1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// A callback called after each test-result.
|
||||||
|
let sandboxTestResult = (subject, topic, data) => {
|
||||||
|
let { testid, passed, message } = JSON.parse(data);
|
||||||
|
ok(
|
||||||
|
passed,
|
||||||
|
"Test " + testid + (passed ? " passed: " : " failed: ") + message
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Services.obs.addObserver(sandboxTestResult, "sandbox-test-result");
|
||||||
|
|
||||||
|
var remainingTests = processTypes.length;
|
||||||
|
|
||||||
|
// A callback that is notified when a child process is done running tests.
|
||||||
|
let sandboxTestDone = () => {
|
||||||
|
remainingTests = remainingTests - 1;
|
||||||
|
if (remainingTests == 0) {
|
||||||
|
// Clean up test file
|
||||||
|
if (homeTestFile.exists()) {
|
||||||
|
ok(homeTestFile.isFile(), "homeTestFile should be a file");
|
||||||
|
if (homeTestFile.isFile()) {
|
||||||
|
homeTestFile.remove(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Services.obs.removeObserver(sandboxTestResult, "sandbox-test-result");
|
||||||
|
Services.obs.removeObserver(sandboxTestDone, "sandbox-test-done");
|
||||||
|
|
||||||
|
// Notify SandboxTest component that it should terminate the connection
|
||||||
|
// with the child processes.
|
||||||
|
comp.finishTests();
|
||||||
|
// Notify mochitest that all process tests are complete.
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Services.obs.addObserver(sandboxTestDone, "sandbox-test-done");
|
||||||
|
|
||||||
|
var comp = Cc["@mozilla.org/sandbox/sandbox-test;1"].getService(
|
||||||
|
Ci.mozISandboxTest
|
||||||
|
);
|
||||||
|
|
||||||
|
let homeTestFile;
|
||||||
|
try {
|
||||||
|
homeTestFile = Services.dirsvc.get("Home", Ci.nsIFile);
|
||||||
|
homeTestFile.append(".mozilla_gpu_sandbox_read_test");
|
||||||
|
if (!homeTestFile.exists()) {
|
||||||
|
homeTestFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ok(false, "Failed to create home test file: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
comp.startTests(processTypes);
|
||||||
|
}
|
||||||
20
src/zen/tests/mochitests/sandbox/browser_snap.toml
Normal file
20
src/zen/tests/mochitests/sandbox/browser_snap.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Any copyright is dedicated to the Public Domain.
|
||||||
|
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
[DEFAULT]
|
||||||
|
skip-if = [
|
||||||
|
"ccov",
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
|
||||||
|
]
|
||||||
|
tags = "contentsandbox"
|
||||||
|
support-files = [
|
||||||
|
"browser_content_sandbox_utils.js",
|
||||||
|
"browser_content_sandbox_fs_tests.js",
|
||||||
|
]
|
||||||
|
test-directories = "/tmp/.snap_firefox_current_real/"
|
||||||
|
environment = "SNAP=/tmp/.snap_firefox_current_real/"
|
||||||
|
|
||||||
|
["browser_content_sandbox_fs_snap.js"]
|
||||||
|
run-if = [
|
||||||
|
"os == 'linux'",
|
||||||
|
]
|
||||||
26
src/zen/tests/mochitests/sandbox/browser_xdg_default.toml
Normal file
26
src/zen/tests/mochitests/sandbox/browser_xdg_default.toml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Any copyright is dedicated to the Public Domain.
|
||||||
|
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
[DEFAULT]
|
||||||
|
skip-if = [
|
||||||
|
"ccov",
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
|
||||||
|
]
|
||||||
|
tags = "contentsandbox"
|
||||||
|
support-files = [
|
||||||
|
"browser_content_sandbox_utils.js",
|
||||||
|
"browser_content_sandbox_fs_tests.js",
|
||||||
|
]
|
||||||
|
test-directories = [
|
||||||
|
"/tmp/.xdg_default_test",
|
||||||
|
"/tmp/.xdg_default_test/.config/mozilla/firefox/xdg_default_profile",
|
||||||
|
]
|
||||||
|
environment = [
|
||||||
|
"HOME=/tmp/.xdg_default_test",
|
||||||
|
]
|
||||||
|
profile-path = "/tmp/.xdg_default_test/.config/mozilla/firefox/xdg_default_profile"
|
||||||
|
|
||||||
|
["browser_content_sandbox_fs_xdg_default.js"]
|
||||||
|
run-if = [
|
||||||
|
"os == 'linux'"
|
||||||
|
]
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Any copyright is dedicated to the Public Domain.
|
||||||
|
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
[DEFAULT]
|
||||||
|
skip-if = [
|
||||||
|
"ccov",
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
|
||||||
|
]
|
||||||
|
tags = "contentsandbox"
|
||||||
|
support-files = [
|
||||||
|
"browser_content_sandbox_utils.js",
|
||||||
|
"browser_content_sandbox_fs_tests.js",
|
||||||
|
]
|
||||||
|
test-directories = [
|
||||||
|
"/tmp/.xdg_mozLegacyHome_test/.config",
|
||||||
|
"/tmp/.xdg_config_home_test",
|
||||||
|
"/tmp/.xdg_mozLegacyHome_test/.mozilla/firefox/xdg_mozLegacyHome_profile",
|
||||||
|
]
|
||||||
|
environment = [
|
||||||
|
"XDG_CONFIG_HOME=/tmp/.xdg_config_home_test",
|
||||||
|
"HOME=/tmp/.xdg_mozLegacyHome_test",
|
||||||
|
"MOZ_LEGACY_HOME=1",
|
||||||
|
]
|
||||||
|
profile-path = "/tmp/.xdg_mozLegacyHome_test/.mozilla/firefox/xdg_mozLegacyHome_profile"
|
||||||
|
|
||||||
|
["browser_content_sandbox_fs_xdg_mozLegacyHome.js"]
|
||||||
|
run-if = [
|
||||||
|
"os == 'linux'"
|
||||||
|
]
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Any copyright is dedicated to the Public Domain.
|
||||||
|
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
[DEFAULT]
|
||||||
|
skip-if = [
|
||||||
|
"ccov",
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && asan", # bug 1784517
|
||||||
|
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && tsan", # bug 1784517
|
||||||
|
]
|
||||||
|
tags = "contentsandbox"
|
||||||
|
support-files = [
|
||||||
|
"browser_content_sandbox_utils.js",
|
||||||
|
"browser_content_sandbox_fs_tests.js",
|
||||||
|
]
|
||||||
|
test-directories = [
|
||||||
|
"/tmp/.xdg_config_home_test",
|
||||||
|
"/tmp/.xdg_config_home_test/mozilla/firefox/xdg_config_home_profile",
|
||||||
|
]
|
||||||
|
environment = [
|
||||||
|
"XDG_CONFIG_HOME=/tmp/.xdg_config_home_test",
|
||||||
|
"MOZ_LEGACY_HOME=0",
|
||||||
|
]
|
||||||
|
profile-path = "/tmp/.xdg_config_home_test/mozilla/firefox/xdg_config_home_profile"
|
||||||
|
|
||||||
|
["browser_content_sandbox_fs_xdg_xdgConfigHome.js"]
|
||||||
|
run-if = [
|
||||||
|
"os == 'linux'",
|
||||||
|
]
|
||||||
19
src/zen/tests/mochitests/sandbox/bug1393259.html
Normal file
19
src/zen/tests/mochitests/sandbox/bug1393259.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
</head>
|
||||||
|
<style>
|
||||||
|
#content { display: inline-block; }
|
||||||
|
.monospace_fallback { font: 3em monospace; }
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="content" class="monospace_fallback">
|
||||||
|
abcdefghijklmnopqrstuvwxyz<br>
|
||||||
|
<b>abcdefghijklmnopqrstuvwxyz</b><br>
|
||||||
|
<i>abcdefghijklmnopqrstuvwxyz</i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
85
src/zen/tests/mochitests/sandbox/mac_register_font.py
Executable file
85
src/zen/tests/mochitests/sandbox/mac_register_font.py
Executable file
@@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# 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/. */
|
||||||
|
|
||||||
|
"""
|
||||||
|
mac_register_font.py
|
||||||
|
|
||||||
|
Mac-specific utility command to register a font file with the OS.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import CoreText
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"-v",
|
||||||
|
"--verbose",
|
||||||
|
action="store_true",
|
||||||
|
help="print verbose registration failures",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"file", nargs="*", help="font file to register or unregister", default=[]
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-u",
|
||||||
|
"--unregister",
|
||||||
|
action="store_true",
|
||||||
|
help="unregister the provided fonts",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-p",
|
||||||
|
"--persist-user",
|
||||||
|
action="store_true",
|
||||||
|
help="permanently register the font",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.persist_user:
|
||||||
|
scope = CoreText.kCTFontManagerScopeUser
|
||||||
|
scopeDesc = "user"
|
||||||
|
else:
|
||||||
|
scope = CoreText.kCTFontManagerScopeSession
|
||||||
|
scopeDesc = "session"
|
||||||
|
|
||||||
|
failureCount = 0
|
||||||
|
for fontPath in args.file:
|
||||||
|
fontURL = Cocoa.NSURL.fileURLWithPath_(fontPath)
|
||||||
|
(result, error) = register_or_unregister_font(fontURL, args.unregister, scope)
|
||||||
|
if result:
|
||||||
|
print(
|
||||||
|
"%sregistered font %s with %s scope"
|
||||||
|
% (("un" if args.unregister else ""), fontPath, scopeDesc)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"Failed to %sregister font %s with %s scope"
|
||||||
|
% (("un" if args.unregister else ""), fontPath, scopeDesc)
|
||||||
|
)
|
||||||
|
if args.verbose:
|
||||||
|
print(error)
|
||||||
|
failureCount += 1
|
||||||
|
|
||||||
|
sys.exit(failureCount)
|
||||||
|
|
||||||
|
|
||||||
|
def register_or_unregister_font(fontURL, unregister, scope):
|
||||||
|
return (
|
||||||
|
CoreText.CTFontManagerUnregisterFontsForURL(fontURL, scope, None)
|
||||||
|
if unregister
|
||||||
|
else CoreText.CTFontManagerRegisterFontsForURL(fontURL, scope, None)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -11,12 +11,8 @@ support-files = [
|
|||||||
|
|
||||||
["browser_space_routing_dialog.js"]
|
["browser_space_routing_dialog.js"]
|
||||||
|
|
||||||
["browser_space_routing_fuzz.js"]
|
|
||||||
|
|
||||||
["browser_space_routing_on_add_tab.js"]
|
["browser_space_routing_on_add_tab.js"]
|
||||||
|
|
||||||
["browser_space_routing_redirect_navigation.js"]
|
|
||||||
|
|
||||||
["browser_space_routing_route_matching.js"]
|
["browser_space_routing_route_matching.js"]
|
||||||
|
|
||||||
["browser_space_routing_route_uri.js"]
|
["browser_space_routing_route_uri.js"]
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ add_task(async function test_routes_are_saved_on_close() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const closed = promiseRoutingDialogClosed();
|
const closed = BrowserTestUtils.domWindowClosed(dlg);
|
||||||
dlg.close();
|
dlg.close();
|
||||||
await TestUtils.waitForCondition(
|
await TestUtils.waitForCondition(
|
||||||
() => saveCalls > 0,
|
() => saveCalls > 0,
|
||||||
@@ -241,15 +241,11 @@ add_task(async function test_open_broadcasts_kill_to_other_instances() {
|
|||||||
|
|
||||||
add_task(async function test_kill_notification_closes_dialog() {
|
add_task(async function test_kill_notification_closes_dialog() {
|
||||||
clearAllRoutes();
|
clearAllRoutes();
|
||||||
await openRoutingDialog();
|
const dlg = await openRoutingDialog();
|
||||||
|
|
||||||
const closed = promiseRoutingDialogClosed();
|
const closed = BrowserTestUtils.domWindowClosed(dlg);
|
||||||
Services.obs.notifyObservers(null, "zen-space-routing-kill");
|
Services.obs.notifyObservers(null, "zen-space-routing-kill");
|
||||||
await closed;
|
await closed;
|
||||||
|
|
||||||
const container = document.getElementById("window-modal-dialog");
|
ok(dlg.closed, "A 'zen-space-routing-kill' notification closes the dialog");
|
||||||
ok(
|
|
||||||
!container.open && !container.hasChildNodes(),
|
|
||||||
"A 'zen-space-routing-kill' notification closes the dialog"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,239 +0,0 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
|
||||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
// Seeded fuzzing for the pure routing decision functions. The point is not to
|
|
||||||
// assert a particular routing outcome but to prove robustness invariants under
|
|
||||||
// adversarial input: the functions must never throw, must always return the
|
|
||||||
// declared type, and routeUri must only ever return a value it is allowed to.
|
|
||||||
//
|
|
||||||
// The RNG is seeded so any failure is reproducible: re-run with the logged seed.
|
|
||||||
|
|
||||||
const FUZZ_SEED = 0x5eed1234;
|
|
||||||
|
|
||||||
// mulberry32 — small, fast, deterministic PRNG.
|
|
||||||
function makeRng(seed) {
|
|
||||||
let s = seed >>> 0;
|
|
||||||
return function rng() {
|
|
||||||
s = (s + 0x6d2b79f5) | 0;
|
|
||||||
let t = Math.imul(s ^ (s >>> 15), 1 | s);
|
|
||||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
||||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const DOMAIN_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-.";
|
|
||||||
const REGEX_CHARS = ".*+?^${}()|[]\\" + DOMAIN_CHARS;
|
|
||||||
const TRICKY_CHARS =
|
|
||||||
DOMAIN_CHARS + "%/?#:@!$&'()*+,;= []{}<>\"\\^`|~\tünïçødé日本語🚀";
|
|
||||||
const SCHEMES = [
|
|
||||||
"http://",
|
|
||||||
"https://",
|
|
||||||
"ftp://",
|
|
||||||
"file://",
|
|
||||||
"about:",
|
|
||||||
"data:text/plain,",
|
|
||||||
"javascript:",
|
|
||||||
"//",
|
|
||||||
"",
|
|
||||||
];
|
|
||||||
const MATCH_TYPES = ["contains", "equal-to", "regex", "bogus-type", ""];
|
|
||||||
|
|
||||||
function randInt(rng, n) {
|
|
||||||
return Math.floor(rng() * n);
|
|
||||||
}
|
|
||||||
function pick(rng, arr) {
|
|
||||||
return arr[randInt(rng, arr.length)];
|
|
||||||
}
|
|
||||||
function randString(rng, maxLen, charset) {
|
|
||||||
const len = randInt(rng, maxLen + 1);
|
|
||||||
let out = "";
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
out += charset[randInt(rng, charset.length)];
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function randomUrl(rng) {
|
|
||||||
const scheme = pick(rng, SCHEMES);
|
|
||||||
const host = randString(rng, 30, DOMAIN_CHARS + "ünïçødé");
|
|
||||||
const port = rng() < 0.2 ? ":" + randInt(rng, 99999) : "";
|
|
||||||
const path = rng() < 0.7 ? "/" + randString(rng, 40, TRICKY_CHARS) : "";
|
|
||||||
return scheme + host + port + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
function randomReference(rng) {
|
|
||||||
switch (randInt(rng, 5)) {
|
|
||||||
case 0:
|
|
||||||
return "";
|
|
||||||
case 1:
|
|
||||||
return " ";
|
|
||||||
case 2:
|
|
||||||
return randString(rng, 30, DOMAIN_CHARS);
|
|
||||||
case 3:
|
|
||||||
// Deliberately regex-flavoured to exercise the "regex" match path.
|
|
||||||
return randString(rng, 20, REGEX_CHARS);
|
|
||||||
default:
|
|
||||||
return randString(rng, 50, TRICKY_CHARS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function randomRoute(rng, openIn = "most-recent-space") {
|
|
||||||
return {
|
|
||||||
id: "fuzz-" + randInt(rng, 1e9),
|
|
||||||
reference: randomReference(rng),
|
|
||||||
openIn,
|
|
||||||
matchType: pick(rng, MATCH_TYPES),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
add_setup(async function () {
|
|
||||||
clearAllRoutes();
|
|
||||||
registerCleanupFunction(() => clearAllRoutes());
|
|
||||||
info(`Space Routing fuzz seed: 0x${FUZZ_SEED.toString(16)}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function fuzz_isRouteMatching_never_throws() {
|
|
||||||
const rng = makeRng(FUZZ_SEED);
|
|
||||||
const ITERATIONS = 5000;
|
|
||||||
|
|
||||||
for (let i = 0; i < ITERATIONS; i++) {
|
|
||||||
const url = randomUrl(rng);
|
|
||||||
const route = randomRoute(rng);
|
|
||||||
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = gZenSpaceRoutingManager.isRouteMatching(url, route);
|
|
||||||
} catch (e) {
|
|
||||||
ok(
|
|
||||||
false,
|
|
||||||
`isRouteMatching threw on url=${JSON.stringify(
|
|
||||||
url
|
|
||||||
)} route=${JSON.stringify(route)}: ${e}`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
is(
|
|
||||||
typeof result,
|
|
||||||
"boolean",
|
|
||||||
`isRouteMatching must return a boolean (iter ${i})`
|
|
||||||
);
|
|
||||||
|
|
||||||
// An empty / whitespace reference can never match.
|
|
||||||
if (typeof route.reference !== "string" || route.reference.trim() === "") {
|
|
||||||
ok(!result, "Empty reference never matches");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function fuzz_routeUri_returns_only_valid_destinations() {
|
|
||||||
const rng = makeRng(FUZZ_SEED ^ 0x1111);
|
|
||||||
clearAllRoutes();
|
|
||||||
|
|
||||||
// Populate the manager with a mix of routes pointing at a few destinations.
|
|
||||||
const destinations = ["most-recent-space", "ws-a", "ws-b", "ws-c"];
|
|
||||||
for (let i = 0; i < 200; i++) {
|
|
||||||
const r = randomRoute(rng, pick(rng, destinations));
|
|
||||||
addRoute({
|
|
||||||
reference: r.reference,
|
|
||||||
openIn: r.openIn,
|
|
||||||
matchType: r.matchType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const allowed = new Set(
|
|
||||||
gZenSpaceRoutingManager.getAllRoutes().map(r => r.openIn)
|
|
||||||
);
|
|
||||||
allowed.add("most-recent-space");
|
|
||||||
const defaultExternal = gZenSpaceRoutingManager.getDefaultExternalRoute();
|
|
||||||
allowed.add(defaultExternal);
|
|
||||||
|
|
||||||
const ITERATIONS = 4000;
|
|
||||||
for (let i = 0; i < ITERATIONS; i++) {
|
|
||||||
const url = randomUrl(rng);
|
|
||||||
const fromExternal = rng() < 0.5;
|
|
||||||
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = gZenSpaceRoutingManager.routeUri(url, { fromExternal });
|
|
||||||
} catch (e) {
|
|
||||||
ok(false, `routeUri threw on url=${JSON.stringify(url)}: ${e}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
is(typeof result, "string", `routeUri must return a string (iter ${i})`);
|
|
||||||
ok(
|
|
||||||
allowed.has(result),
|
|
||||||
`routeUri returned an out-of-set destination: ${JSON.stringify(result)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearAllRoutes();
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function fuzz_shouldRedirectNavigation_invariants() {
|
|
||||||
const rng = makeRng(FUZZ_SEED ^ 0x2222);
|
|
||||||
clearAllRoutes();
|
|
||||||
|
|
||||||
const workspaces = [
|
|
||||||
{ uuid: "ws-a", containerTabId: 1 },
|
|
||||||
{ uuid: "ws-b", containerTabId: 2 },
|
|
||||||
];
|
|
||||||
const win = makeFakeWindow({ workspaces });
|
|
||||||
|
|
||||||
for (let i = 0; i < 120; i++) {
|
|
||||||
const r = randomRoute(
|
|
||||||
rng,
|
|
||||||
pick(rng, ["ws-a", "ws-b", "most-recent-space"])
|
|
||||||
);
|
|
||||||
addRoute({
|
|
||||||
reference: r.reference,
|
|
||||||
openIn: r.openIn,
|
|
||||||
matchType: r.matchType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const ITERATIONS = 4000;
|
|
||||||
const currentChoices = ["ws-a", "ws-b", "ws-other", "", null];
|
|
||||||
|
|
||||||
for (let i = 0; i < ITERATIONS; i++) {
|
|
||||||
const url = randomUrl(rng);
|
|
||||||
const currentWorkspaceId = pick(rng, currentChoices);
|
|
||||||
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = gZenSpaceRoutingManager.shouldRedirectNavigation(
|
|
||||||
url,
|
|
||||||
currentWorkspaceId,
|
|
||||||
win
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
ok(
|
|
||||||
false,
|
|
||||||
`shouldRedirectNavigation threw on url=${JSON.stringify(url)}: ${e}`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
is(typeof result, "boolean", "shouldRedirectNavigation returns a boolean");
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
// If we decided to redirect, the target must be a real, *different* space.
|
|
||||||
const target = gZenSpaceRoutingManager.routeUri(url, {
|
|
||||||
fromExternal: false,
|
|
||||||
});
|
|
||||||
ok(
|
|
||||||
target !== "most-recent-space" && target !== currentWorkspaceId,
|
|
||||||
`Redirect target must differ from current space (url=${url})`
|
|
||||||
);
|
|
||||||
ok(
|
|
||||||
!!win.gZenWorkspaces.getWorkspaceFromId(target),
|
|
||||||
"Redirect target must be an existing workspace"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clearAllRoutes();
|
|
||||||
});
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
|
||||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
// Exercises nsZenSpaceRoutingManager.shouldRedirectNavigation: an in-place
|
|
||||||
// navigation is only redirected into a new tab when its rule points at a space
|
|
||||||
// that differs from the one the navigating tab already lives in.
|
|
||||||
|
|
||||||
const TARGET_WS = { uuid: "ws-target", containerTabId: 7 };
|
|
||||||
|
|
||||||
add_setup(async function () {
|
|
||||||
clearAllRoutes();
|
|
||||||
registerCleanupFunction(() => clearAllRoutes());
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_redirect_when_route_targets_other_space() {
|
|
||||||
clearAllRoutes();
|
|
||||||
addRoute({
|
|
||||||
reference: "github.com",
|
|
||||||
matchType: "contains",
|
|
||||||
openIn: TARGET_WS.uuid,
|
|
||||||
});
|
|
||||||
const win = makeFakeWindow({ workspaces: [TARGET_WS] });
|
|
||||||
|
|
||||||
ok(
|
|
||||||
gZenSpaceRoutingManager.shouldRedirectNavigation(
|
|
||||||
"https://github.com/zen",
|
|
||||||
"ws-current",
|
|
||||||
win
|
|
||||||
),
|
|
||||||
"Navigating to a routed site from a different space redirects"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_no_redirect_when_already_in_target_space() {
|
|
||||||
clearAllRoutes();
|
|
||||||
addRoute({
|
|
||||||
reference: "github.com",
|
|
||||||
matchType: "contains",
|
|
||||||
openIn: TARGET_WS.uuid,
|
|
||||||
});
|
|
||||||
const win = makeFakeWindow({ workspaces: [TARGET_WS] });
|
|
||||||
|
|
||||||
ok(
|
|
||||||
!gZenSpaceRoutingManager.shouldRedirectNavigation(
|
|
||||||
"https://github.com/zen",
|
|
||||||
TARGET_WS.uuid,
|
|
||||||
win
|
|
||||||
),
|
|
||||||
"Already in the destination space navigates in place (and avoids a loop)"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_no_redirect_when_no_rule_matches() {
|
|
||||||
clearAllRoutes();
|
|
||||||
const win = makeFakeWindow({ workspaces: [TARGET_WS] });
|
|
||||||
|
|
||||||
ok(
|
|
||||||
!gZenSpaceRoutingManager.shouldRedirectNavigation(
|
|
||||||
"https://example.com",
|
|
||||||
"ws-current",
|
|
||||||
win
|
|
||||||
),
|
|
||||||
"An unmatched URL is never redirected"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_no_redirect_when_rule_targets_most_recent() {
|
|
||||||
clearAllRoutes();
|
|
||||||
addRoute({
|
|
||||||
reference: "github.com",
|
|
||||||
matchType: "contains",
|
|
||||||
openIn: "most-recent-space",
|
|
||||||
});
|
|
||||||
const win = makeFakeWindow({ workspaces: [TARGET_WS] });
|
|
||||||
|
|
||||||
ok(
|
|
||||||
!gZenSpaceRoutingManager.shouldRedirectNavigation(
|
|
||||||
"https://github.com",
|
|
||||||
"ws-current",
|
|
||||||
win
|
|
||||||
),
|
|
||||||
"A rule that opens in the most recent space is not redirected"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_no_redirect_when_target_workspace_missing() {
|
|
||||||
clearAllRoutes();
|
|
||||||
addRoute({
|
|
||||||
reference: "github.com",
|
|
||||||
matchType: "contains",
|
|
||||||
openIn: "ws-does-not-exist",
|
|
||||||
});
|
|
||||||
const win = makeFakeWindow({ workspaces: [TARGET_WS] });
|
|
||||||
|
|
||||||
ok(
|
|
||||||
!gZenSpaceRoutingManager.shouldRedirectNavigation(
|
|
||||||
"https://github.com",
|
|
||||||
"ws-current",
|
|
||||||
win
|
|
||||||
),
|
|
||||||
"A rule pointing at a missing workspace is not redirected"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_no_redirect_when_workspaces_disabled() {
|
|
||||||
clearAllRoutes();
|
|
||||||
addRoute({
|
|
||||||
reference: "github.com",
|
|
||||||
matchType: "contains",
|
|
||||||
openIn: TARGET_WS.uuid,
|
|
||||||
});
|
|
||||||
const win = makeFakeWindow({
|
|
||||||
workspaces: [TARGET_WS],
|
|
||||||
workspaceEnabled: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
ok(
|
|
||||||
!gZenSpaceRoutingManager.shouldRedirectNavigation(
|
|
||||||
"https://github.com",
|
|
||||||
"ws-current",
|
|
||||||
win
|
|
||||||
),
|
|
||||||
"Nothing is redirected when workspaces are disabled"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -29,15 +29,10 @@ function addRoute({
|
|||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeFakeWindow({
|
function makeFakeWindow({ ready = true, workspaces = [] } = {}) {
|
||||||
ready = true,
|
|
||||||
workspaces = [],
|
|
||||||
workspaceEnabled = true,
|
|
||||||
} = {}) {
|
|
||||||
return {
|
return {
|
||||||
gZenStartup: { isReady: ready },
|
gZenStartup: { isReady: ready },
|
||||||
gZenWorkspaces: {
|
gZenWorkspaces: {
|
||||||
workspaceEnabled,
|
|
||||||
moveCalls: [],
|
moveCalls: [],
|
||||||
changeCalls: [],
|
changeCalls: [],
|
||||||
lastSelectedWorkspaceTabs: {},
|
lastSelectedWorkspaceTabs: {},
|
||||||
@@ -62,17 +57,12 @@ async function flushEventLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openRoutingDialog() {
|
async function openRoutingDialog() {
|
||||||
// openSpaceRoutingDialog() presents an in-window modal through gDialogBox, so
|
const dialogPromise = BrowserTestUtils.domWindowOpenedAndLoaded(null, win =>
|
||||||
// the dialog is a subdialog rather than a separate top-level window.
|
win.document?.documentURI?.includes("zen-space-routing.xhtml")
|
||||||
const dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
|
|
||||||
null,
|
|
||||||
SR_DIALOG_URI,
|
|
||||||
{ isSubDialog: true }
|
|
||||||
);
|
);
|
||||||
// gDialogBox.open() only resolves once the dialog is dismissed, so kick it off
|
|
||||||
// without awaiting and wait on the open notification instead.
|
|
||||||
executeSoon(() => gZenSpaceRoutingManager.openSpaceRoutingDialog(window));
|
executeSoon(() => gZenSpaceRoutingManager.openSpaceRoutingDialog(window));
|
||||||
const dialogWin = await dialogPromise;
|
const dialogWin = await dialogPromise;
|
||||||
|
await SimpleTest.promiseFocus(dialogWin);
|
||||||
await TestUtils.waitForCondition(
|
await TestUtils.waitForCondition(
|
||||||
() => dialogWin.spaceroutingDialog?.initialized,
|
() => dialogWin.spaceroutingDialog?.initialized,
|
||||||
"Space Routing dialog finished initializing"
|
"Space Routing dialog finished initializing"
|
||||||
@@ -80,23 +70,11 @@ async function openRoutingDialog() {
|
|||||||
return dialogWin;
|
return dialogWin;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolves once the gDialogBox subdialog has fully torn down. Use this instead
|
|
||||||
// of BrowserTestUtils.domWindowClosed(), which only fires for separate
|
|
||||||
// top-level windows and so never resolves for an in-window subdialog.
|
|
||||||
function promiseRoutingDialogClosed() {
|
|
||||||
const container = document.getElementById("window-modal-dialog");
|
|
||||||
if (!container?.open) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
return BrowserTestUtils.waitForMutationCondition(
|
|
||||||
container,
|
|
||||||
{ childList: true, attributes: true },
|
|
||||||
() => !container.hasChildNodes() && !container.open
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function closeRoutingDialog(dialogWin) {
|
async function closeRoutingDialog(dialogWin) {
|
||||||
const closed = promiseRoutingDialogClosed();
|
if (dialogWin.closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const closed = BrowserTestUtils.domWindowClosed(dialogWin);
|
||||||
dialogWin.close();
|
dialogWin.close();
|
||||||
await closed;
|
await closed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ support-files = [
|
|||||||
|
|
||||||
["browser_private_mode_startup.js"]
|
["browser_private_mode_startup.js"]
|
||||||
|
|
||||||
["browser_select_tab_switches_space.js"]
|
|
||||||
|
|
||||||
["browser_unload_all_other_spaces.js"]
|
["browser_unload_all_other_spaces.js"]
|
||||||
|
|
||||||
["browser_workspace_bookmarks.js"]
|
["browser_workspace_bookmarks.js"]
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
|
||||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
function fakeTab(workspaceId) {
|
|
||||||
return {
|
|
||||||
getAttribute(name) {
|
|
||||||
return name === "zen-workspace-id" ? workspaceId : null;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function withRecordedSwitch(fn) {
|
|
||||||
const calls = [];
|
|
||||||
gZenWorkspaces.changeWorkspaceWithID = id => {
|
|
||||||
calls.push(id);
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
fn(calls);
|
|
||||||
} finally {
|
|
||||||
// Remove the own property so the prototype method shows through again.
|
|
||||||
delete gZenWorkspaces.changeWorkspaceWithID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
add_task(function test_switches_when_tab_in_other_space() {
|
|
||||||
withRecordedSwitch(calls => {
|
|
||||||
const otherSpace = gZenWorkspaces.activeWorkspace + "-different";
|
|
||||||
gZenWorkspaces.onBeforeTabSelect(fakeTab(otherSpace));
|
|
||||||
Assert.deepEqual(
|
|
||||||
calls,
|
|
||||||
[otherSpace],
|
|
||||||
"Selecting a tab from another space switches to that space"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(function test_no_switch_when_tab_in_active_space() {
|
|
||||||
withRecordedSwitch(calls => {
|
|
||||||
const active = gZenWorkspaces.activeWorkspace;
|
|
||||||
Assert.ok(active, "Test relies on a non-empty active workspace");
|
|
||||||
gZenWorkspaces.onBeforeTabSelect(fakeTab(active));
|
|
||||||
Assert.deepEqual(
|
|
||||||
calls,
|
|
||||||
[],
|
|
||||||
"Selecting a tab already in the active space does not switch"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(function test_no_switch_when_tab_has_no_space() {
|
|
||||||
withRecordedSwitch(calls => {
|
|
||||||
gZenWorkspaces.onBeforeTabSelect(fakeTab(null));
|
|
||||||
Assert.deepEqual(
|
|
||||||
calls,
|
|
||||||
[],
|
|
||||||
"A tab with no zen-workspace-id does not switch spaces"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(function test_handles_missing_tab() {
|
|
||||||
withRecordedSwitch(calls => {
|
|
||||||
gZenWorkspaces.onBeforeTabSelect(null);
|
|
||||||
gZenWorkspaces.onBeforeTabSelect(undefined);
|
|
||||||
Assert.deepEqual(calls, [], "A missing tab is ignored without throwing");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -142,9 +142,7 @@ export class nsZenSiteDataPanel {
|
|||||||
this.anchor.removeAttribute("boosting");
|
this.anchor.removeAttribute("boosting");
|
||||||
}
|
}
|
||||||
// Force a reflow to ensure the attribute change is applied before any potential animation.
|
// Force a reflow to ensure the attribute change is applied before any potential animation.
|
||||||
if (this.unifiedPanel.state === "open") {
|
this.anchor.getBoundingClientRect();
|
||||||
this.anchor.getBoundingClientRect();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#initCopyUrlButton() {
|
#initCopyUrlButton() {
|
||||||
@@ -889,8 +887,8 @@ export class nsZenSiteDataPanel {
|
|||||||
domain,
|
domain,
|
||||||
boostId
|
boostId
|
||||||
);
|
);
|
||||||
lazy.gZenBoostsManager.openBoostWindow(this.window, boost, uri);
|
|
||||||
this.unifiedPanel.hidePopup();
|
this.unifiedPanel.hidePopup();
|
||||||
|
lazy.gZenBoostsManager.openBoostWindow(this.window, boost, uri);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
surfer.json
10
surfer.json
@@ -5,8 +5,8 @@
|
|||||||
"binaryName": "zen",
|
"binaryName": "zen",
|
||||||
"version": {
|
"version": {
|
||||||
"product": "firefox",
|
"product": "firefox",
|
||||||
"version": "151.0.4",
|
"version": "151.0.3",
|
||||||
"candidate": "151.0.4",
|
"candidate": "151.0.3",
|
||||||
"candidateBuild": 1
|
"candidateBuild": 1
|
||||||
},
|
},
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"brandShortName": "Zen",
|
"brandShortName": "Zen",
|
||||||
"brandFullName": "Zen Browser",
|
"brandFullName": "Zen Browser",
|
||||||
"release": {
|
"release": {
|
||||||
"displayVersion": "1.21.1b",
|
"displayVersion": "1.20.2b",
|
||||||
"github": {
|
"github": {
|
||||||
"repo": "zen-browser/desktop"
|
"repo": "zen-browser/desktop"
|
||||||
},
|
},
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
"brandShortName": "Twilight",
|
"brandShortName": "Twilight",
|
||||||
"brandFullName": "Zen Twilight",
|
"brandFullName": "Zen Twilight",
|
||||||
"release": {
|
"release": {
|
||||||
"displayVersion": "1.22t",
|
"displayVersion": "1.21t",
|
||||||
"github": {
|
"github": {
|
||||||
"repo": "zen-browser/desktop"
|
"repo": "zen-browser/desktop"
|
||||||
}
|
}
|
||||||
@@ -54,4 +54,4 @@
|
|||||||
"licenseType": "MPL-2.0"
|
"licenseType": "MPL-2.0"
|
||||||
},
|
},
|
||||||
"updateHostname": "updates.zen-browser.app"
|
"updateHostname": "updates.zen-browser.app"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user