refactor: private tab
This commit is contained in:
@@ -29,7 +29,10 @@ export var PrivateBrowsingUtils = {
|
|||||||
|
|
||||||
// This should be used only in frame scripts.
|
// This should be used only in frame scripts.
|
||||||
isContentWindowPrivate: function pbu_isWindowPrivate(aWindow) {
|
isContentWindowPrivate: function pbu_isWindowPrivate(aWindow) {
|
||||||
return this.privacyContextFromWindow(aWindow).usePrivateBrowsing;
|
// Waterfox: Essential to prevent form data from being saved.
|
||||||
|
return this.privacyContextFromWindow(aWindow).usePrivateBrowsing
|
||||||
|
? true
|
||||||
|
: Services.prefs.getBoolPref("browser.tabs.selectedTabPrivate", false);
|
||||||
},
|
},
|
||||||
|
|
||||||
isBrowserPrivate(aBrowser) {
|
isBrowserPrivate(aBrowser) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ XPCOMUtils.defineLazyModuleGetters(lazy, {
|
|||||||
ChromeManifest: "resource:///modules/ChromeManifest.sys.mjs",
|
ChromeManifest: "resource:///modules/ChromeManifest.sys.mjs",
|
||||||
Overlays: "resource:///modules/Overlays.sys.mjs",
|
Overlays: "resource:///modules/Overlays.sys.mjs",
|
||||||
PrefUtils: "resource:///modules/PrefUtils.jsm",
|
PrefUtils: "resource:///modules/PrefUtils.jsm",
|
||||||
PrivateTab: "resource:///modules/PrivateTab.jsm",
|
PrivateTab: "resource:///modules/PrivateTab.sys.mjs",
|
||||||
StatusBar: "resource:///modules/StatusBar.jsm",
|
StatusBar: "resource:///modules/StatusBar.jsm",
|
||||||
TabFeatures: "resource:///modules/TabFeatures.jsm",
|
TabFeatures: "resource:///modules/TabFeatures.jsm",
|
||||||
UICustomizations: "resource:///modules/UICustomizations.jsm",
|
UICustomizations: "resource:///modules/UICustomizations.jsm",
|
||||||
|
|||||||
@@ -1,503 +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/. */
|
|
||||||
|
|
||||||
const EXPORTED_SYMBOLS = ["PrivateTab"];
|
|
||||||
|
|
||||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
|
|
||||||
const { ContextualIdentityService } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/ContextualIdentityService.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
const { PlacesUIUtils } = ChromeUtils.import(
|
|
||||||
"resource:///modules/PlacesUIUtils.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
const { TabStateCache } = ChromeUtils.import(
|
|
||||||
"resource:///modules/sessionstore/TabStateCache.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
const { TabStateFlusher } = ChromeUtils.import(
|
|
||||||
"resource:///modules/sessionstore/TabStateFlusher.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
const { BrowserUtils } = ChromeUtils.import(
|
|
||||||
"resource:///modules/BrowserUtils.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
const { PrefUtils } = ChromeUtils.import("resource:///modules/PrefUtils.jsm");
|
|
||||||
|
|
||||||
const PrivateTab = {
|
|
||||||
config: {
|
|
||||||
neverClearData: false, // TODO: change to pref controlled value; if you want to not record history but don"t care about other data, maybe even want to keep private logins
|
|
||||||
restoreTabsOnRestart: true,
|
|
||||||
doNotClearDataUntilFxIsClosed: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
openTabs: new Set(),
|
|
||||||
|
|
||||||
BTN_ID: "privateTab-button",
|
|
||||||
BTN2_ID: "newPrivateTab-button",
|
|
||||||
|
|
||||||
get style() {
|
|
||||||
return `
|
|
||||||
@-moz-document url('chrome://browser/content/browser.xhtml') {
|
|
||||||
#private-mask[enabled="true"] {
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
#${this.BTN_ID}, #${this.BTN2_ID} {
|
|
||||||
list-style-image: url(chrome://browser/skin/privateBrowsing.svg);
|
|
||||||
}
|
|
||||||
#tabbrowser-tabs[hasadjacentnewprivatetabbutton]:not([overflow="true"]) ~ #${this.BTN_ID},
|
|
||||||
#tabbrowser-tabs[overflow="true"] > #tabbrowser-arrowscrollbox > #${this.BTN2_ID},
|
|
||||||
#tabbrowser-tabs:not([hasadjacentnewprivatetabbutton]) > #tabbrowser-arrowscrollbox > #${this.BTN2_ID},
|
|
||||||
#TabsToolbar[customizing="true"] #${this.BTN2_ID} {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.tabbrowser-tab[usercontextid="${this.container.userContextId}"] .tab-label {
|
|
||||||
text-decoration: underline !important;
|
|
||||||
text-decoration-color: -moz-nativehyperlinktext !important;
|
|
||||||
text-decoration-style: dashed !important;
|
|
||||||
}
|
|
||||||
.tabbrowser-tab[usercontextid="${this.container.userContextId}"][pinned] .tab-icon-image,
|
|
||||||
.tabbrowser-tab[usercontextid="${this.container.userContextId}"][pinned] .tab-throbber {
|
|
||||||
border-bottom: 1px dashed -moz-nativehyperlinktext !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
},
|
|
||||||
|
|
||||||
init(window) {
|
|
||||||
// Only init in a non-private window
|
|
||||||
if (!window.PrivateBrowsingUtils.isWindowPrivate(window)) {
|
|
||||||
window.PrivateTab = this;
|
|
||||||
this.initContainer("Private");
|
|
||||||
this.initObservers(window);
|
|
||||||
this.initListeners(window);
|
|
||||||
this.initCustomFunctions(window);
|
|
||||||
this.overridePlacesUIUtils();
|
|
||||||
this.updatePrivateMaskId(window);
|
|
||||||
BrowserUtils.setStyle(this.style);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
initContainer(aName) {
|
|
||||||
ContextualIdentityService.ensureDataReady();
|
|
||||||
this.container = ContextualIdentityService._identities.find(
|
|
||||||
container => container.name == aName
|
|
||||||
);
|
|
||||||
if (this.container && this.container.icon == "private") {
|
|
||||||
ContextualIdentityService.remove(this.container.userContextId);
|
|
||||||
this.container = undefined;
|
|
||||||
}
|
|
||||||
if (!this.container) {
|
|
||||||
ContextualIdentityService.create(aName, "fingerprint", "purple");
|
|
||||||
this.container = ContextualIdentityService._identities.find(
|
|
||||||
container => container.name == aName
|
|
||||||
);
|
|
||||||
} else if (!this.config.neverClearData) {
|
|
||||||
this.clearData();
|
|
||||||
}
|
|
||||||
return this.container;
|
|
||||||
},
|
|
||||||
|
|
||||||
clearData() {
|
|
||||||
Services.clearData.deleteDataFromOriginAttributesPattern({
|
|
||||||
userContextId: this.container.userContextId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
initObservers(aWindow) {
|
|
||||||
this.setPrivateObserver();
|
|
||||||
},
|
|
||||||
|
|
||||||
initListeners(aWindow) {
|
|
||||||
this.initPrivateTabListeners(aWindow);
|
|
||||||
aWindow.document
|
|
||||||
.getElementById("placesContext")
|
|
||||||
?.addEventListener("popupshowing", this.placesContext);
|
|
||||||
aWindow.document
|
|
||||||
.getElementById("contentAreaContextMenu")
|
|
||||||
?.addEventListener("popupshowing", this.contentContext);
|
|
||||||
aWindow.document
|
|
||||||
.getElementById("contentAreaContextMenu")
|
|
||||||
?.addEventListener("popuphidden", this.hideContext);
|
|
||||||
aWindow.document
|
|
||||||
.getElementById("tabContextMenu")
|
|
||||||
?.addEventListener("popupshowing", this.tabContext);
|
|
||||||
aWindow.document
|
|
||||||
.getElementById("newPrivateTab-button")
|
|
||||||
?.addEventListener("click", this.toolbarClick);
|
|
||||||
},
|
|
||||||
|
|
||||||
async updatePrivateMaskId(aWindow) {
|
|
||||||
let privateMask = aWindow.document.getElementsByClassName(
|
|
||||||
"private-browsing-indicator"
|
|
||||||
)[0];
|
|
||||||
privateMask.id = "private-mask";
|
|
||||||
},
|
|
||||||
|
|
||||||
setPrivateObserver() {
|
|
||||||
if (!this.config.neverClearData) {
|
|
||||||
let observe = () => {
|
|
||||||
this.clearData();
|
|
||||||
if (!this.config.restoreTabsOnRestart) {
|
|
||||||
this.closeTabs();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Services.obs.addObserver(observe, "quit-application-granted");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
closeTabs() {
|
|
||||||
ContextualIdentityService._forEachContainerTab((tab, tabbrowser) => {
|
|
||||||
if (tab.userContextId == this.container.userContextId) {
|
|
||||||
tabbrowser.removeTab(tab);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
placesContext(aEvent) {
|
|
||||||
let win = aEvent.view;
|
|
||||||
if (!win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let { document } = win;
|
|
||||||
let openAll = "placesContext_openBookmarkContainer:tabs";
|
|
||||||
let openAllLinks = "placesContext_openLinks:tabs";
|
|
||||||
let openTab = "placesContext_open:newtab";
|
|
||||||
// let document = event.target.ownerDocument;
|
|
||||||
document.getElementById("openPrivate").disabled = document.getElementById(
|
|
||||||
openTab
|
|
||||||
).disabled;
|
|
||||||
document.getElementById("openPrivate").hidden = document.getElementById(
|
|
||||||
openTab
|
|
||||||
).hidden;
|
|
||||||
document.getElementById(
|
|
||||||
"openAllPrivate"
|
|
||||||
).disabled = document.getElementById(openAll).disabled;
|
|
||||||
document.getElementById("openAllPrivate").hidden = document.getElementById(
|
|
||||||
openAll
|
|
||||||
).hidden;
|
|
||||||
document.getElementById(
|
|
||||||
"openAllLinksPrivate"
|
|
||||||
).disabled = document.getElementById(openAllLinks).disabled;
|
|
||||||
document.getElementById(
|
|
||||||
"openAllLinksPrivate"
|
|
||||||
).hidden = document.getElementById(openAllLinks).hidden;
|
|
||||||
},
|
|
||||||
|
|
||||||
isPrivate(aTab) {
|
|
||||||
return aTab.getAttribute("usercontextid") == this.container.userContextId;
|
|
||||||
},
|
|
||||||
|
|
||||||
contentContext(aEvent) {
|
|
||||||
let win = aEvent.view;
|
|
||||||
if (!win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let { gContextMenu, gBrowser, PrivateTab } = win;
|
|
||||||
let tab = gBrowser.getTabForBrowser(gContextMenu.browser);
|
|
||||||
gContextMenu.showItem(
|
|
||||||
"openLinkInPrivateTab",
|
|
||||||
gContextMenu.onSaveableLink || gContextMenu.onPlainTextLink
|
|
||||||
);
|
|
||||||
let isPrivate = PrivateTab.isPrivate(tab);
|
|
||||||
if (isPrivate) {
|
|
||||||
gContextMenu.showItem("context-openlinkincontainertab", false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hideContext(aEvent) {
|
|
||||||
if (!aEvent.view) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (aEvent.target == this) {
|
|
||||||
aEvent.view.document.getElementById("openLinkInPrivateTab").hidden = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
tabContext(aEvent) {
|
|
||||||
let win = aEvent.view;
|
|
||||||
if (!win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let { document, PrivateTab } = win;
|
|
||||||
const isPrivate =
|
|
||||||
win.TabContextMenu.contextTab.userContextId ===
|
|
||||||
PrivateTab.container.userContextId;
|
|
||||||
document
|
|
||||||
.getElementById("toggleTabPrivateState")
|
|
||||||
.setAttribute("data-l10n-args", JSON.stringify({ isPrivate }));
|
|
||||||
},
|
|
||||||
|
|
||||||
openLink(aEvent) {
|
|
||||||
let win = aEvent.view;
|
|
||||||
if (!win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let { gContextMenu, PrivateTab, document } = win;
|
|
||||||
win.openLinkIn(
|
|
||||||
gContextMenu.linkURL,
|
|
||||||
"tab",
|
|
||||||
gContextMenu._openLinkInParameters({
|
|
||||||
userContextId: PrivateTab.container.userContextId,
|
|
||||||
triggeringPrincipal: document.nodePrincipal,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
toolbarClick(aEvent) {
|
|
||||||
let win = aEvent.view;
|
|
||||||
if (!win) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let { PrivateTab, document } = win;
|
|
||||||
if (aEvent.button == 0) {
|
|
||||||
PrivateTab.browserOpenTabPrivate(win);
|
|
||||||
} else if (aEvent.button == 2) {
|
|
||||||
document.popupNode = document.getElementById(PrivateTab.BTN_ID);
|
|
||||||
document
|
|
||||||
.getElementById("toolbar-context-menu")
|
|
||||||
.openPopup(this, "after_start", 14, -10, false, false);
|
|
||||||
document.getElementsByClassName(
|
|
||||||
"customize-context-removeFromToolbar"
|
|
||||||
)[0].disabled = false;
|
|
||||||
document.getElementsByClassName(
|
|
||||||
"customize-context-moveToPanel"
|
|
||||||
)[0].disabled = false;
|
|
||||||
aEvent.preventDefault();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
overridePlacesUIUtils() {
|
|
||||||
/* globals BrowserWindowTracker */
|
|
||||||
// Unused vars required for eval to execute
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const { PlacesUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/PlacesUtils.jsm"
|
|
||||||
);
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const { PrivateBrowsingUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/PrivateBrowsingUtils.jsm"
|
|
||||||
);
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
function getBrowserWindow(aWindow) {
|
|
||||||
// Prefer the caller window if it's a browser window, otherwise use
|
|
||||||
// the top browser window.
|
|
||||||
return aWindow &&
|
|
||||||
aWindow.document.documentElement.getAttribute("windowtype") ==
|
|
||||||
"navigator:browser"
|
|
||||||
? aWindow
|
|
||||||
: BrowserWindowTracker.getTopWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: replace eval with new Function()();
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line no-eval
|
|
||||||
eval(
|
|
||||||
"PlacesUIUtils.openTabset = function " +
|
|
||||||
PlacesUIUtils.openTabset
|
|
||||||
.toString()
|
|
||||||
.replace(
|
|
||||||
/(\s+)(inBackground: loadInBackground,)/,
|
|
||||||
"$1$2$1userContextId: aEvent.userContextId || 0,"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (ex) {}
|
|
||||||
},
|
|
||||||
|
|
||||||
openAllPrivate(event) {
|
|
||||||
event.userContextId = this.container.userContextId;
|
|
||||||
PlacesUIUtils.openSelectionInTabs(event);
|
|
||||||
},
|
|
||||||
|
|
||||||
openPrivateTab(event) {
|
|
||||||
let view = event.target.parentElement._view;
|
|
||||||
PlacesUIUtils._openNodeIn(view.selectedNode, "tab", view.ownerWindow, {
|
|
||||||
aPrivate: false,
|
|
||||||
userContextId: this.container.userContextId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
togglePrivate(aWindow, aTab = aWindow.gBrowser.selectedTab) {
|
|
||||||
let newTab;
|
|
||||||
const { gBrowser, gURLBar } = aWindow;
|
|
||||||
aTab.setAttribute("isToggling", true);
|
|
||||||
const shouldSelect = aTab == aWindow.gBrowser.selectedTab;
|
|
||||||
try {
|
|
||||||
newTab = gBrowser.duplicateTab(aTab);
|
|
||||||
if (shouldSelect) {
|
|
||||||
gBrowser.selectedTab = newTab;
|
|
||||||
const focusUrlbar = gURLBar.focused;
|
|
||||||
if (focusUrlbar) {
|
|
||||||
gURLBar.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gBrowser.removeTab(aTab);
|
|
||||||
} catch (ex) {
|
|
||||||
// Can use this to pop up failure message
|
|
||||||
}
|
|
||||||
return newTab;
|
|
||||||
},
|
|
||||||
|
|
||||||
browserOpenTabPrivate(aWindow) {
|
|
||||||
aWindow.openTrustedLinkIn(aWindow.BROWSER_NEW_TAB_URL, "tab", {
|
|
||||||
userContextId: this.container.userContextId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
initPrivateTabListeners(aWindow) {
|
|
||||||
let { gBrowser } = aWindow;
|
|
||||||
gBrowser.tabContainer.addEventListener("TabSelect", this.onTabSelect);
|
|
||||||
gBrowser.tabContainer.addEventListener("TabOpen", this.onTabOpen);
|
|
||||||
|
|
||||||
gBrowser.privateListener = e => {
|
|
||||||
let browser = e.target;
|
|
||||||
let tab = gBrowser.getTabForBrowser(browser);
|
|
||||||
if (!tab) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let isPrivate = this.isPrivate(tab);
|
|
||||||
|
|
||||||
if (!isPrivate) {
|
|
||||||
if (this.observePrivateTabs) {
|
|
||||||
this.openTabs.delete(tab);
|
|
||||||
if (!this.openTabs.size) {
|
|
||||||
this.clearData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.observePrivateTabs) {
|
|
||||||
this.openTabs.add(tab);
|
|
||||||
}
|
|
||||||
|
|
||||||
browser.browsingContext.useGlobalHistory = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
aWindow.addEventListener("XULFrameLoaderCreated", gBrowser.privateListener);
|
|
||||||
|
|
||||||
if (this.observePrivateTabs) {
|
|
||||||
gBrowser.tabContainer.addEventListener("TabClose", this.onTabClose);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onTabSelect(aEvent) {
|
|
||||||
let tab = aEvent.target;
|
|
||||||
if (!tab) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let win = tab.ownerGlobal;
|
|
||||||
let { PrivateTab } = win;
|
|
||||||
let prevTab = aEvent.detail.previousTab;
|
|
||||||
let isPrivate = PrivateTab.isPrivate(tab);
|
|
||||||
|
|
||||||
if (tab.userContextId != prevTab.userContextId) {
|
|
||||||
// Show/hide private mask on browser window
|
|
||||||
PrivateTab.toggleMask(win);
|
|
||||||
// Ensure we don't save search suggestions for PrivateTab
|
|
||||||
win.gURLBar.isPrivate = isPrivate;
|
|
||||||
// Update selected tab private status for autofill
|
|
||||||
PrefUtils.set("browser.tabs.selectedTabPrivate", isPrivate);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async onTabOpen(aEvent) {
|
|
||||||
// Update tab state cache
|
|
||||||
let tab = aEvent.target;
|
|
||||||
if (!tab) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let { PrivateTab } = tab.ownerGlobal;
|
|
||||||
let isPrivate = PrivateTab.isPrivate(tab);
|
|
||||||
// if statement is temp solution to prevent containers being dropped on restart.
|
|
||||||
// The flushing and cache updating should only occur if the parent tab was
|
|
||||||
// private and the new tab is non-private OR the parent tab was non-private
|
|
||||||
// and the new tab is private. Otherwise we should rely on default behaviour.
|
|
||||||
// We also need to be wary of pinned state of tabs, as that may also have been
|
|
||||||
// affected in the same case.
|
|
||||||
if (isPrivate) {
|
|
||||||
let userContextId = isPrivate ? PrivateTab.container.userContextId : 0;
|
|
||||||
// Duplicating a tab copies the tab state cache from the parent tab.
|
|
||||||
// Therefore we need to flush the tab state to ensure it's updated,
|
|
||||||
// then overwrite the tab usercontextid so that any restored tabs
|
|
||||||
// are opened in the correct container, rather than that of their
|
|
||||||
// parent tab.
|
|
||||||
let browser = tab.linkedBrowser;
|
|
||||||
// Can't update tab state if we can't get the browser
|
|
||||||
if (browser) {
|
|
||||||
TabStateFlusher.flush(browser)
|
|
||||||
.then(() => {
|
|
||||||
TabStateCache.update(tab.linkedBrowser.permanentKey, {
|
|
||||||
isPrivate,
|
|
||||||
userContextId,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(ex => {
|
|
||||||
// Sometimes tests fail here
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onTabClose(aEvent) {
|
|
||||||
let tab = aEvent.target;
|
|
||||||
if (!tab) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let { PrivateTab } = tab.ownerGlobal;
|
|
||||||
if (PrivateTab.isPrivate(tab)) {
|
|
||||||
PrivateTab.openTabs.delete(tab);
|
|
||||||
if (!PrivateTab.openTabs.size) {
|
|
||||||
PrivateTab.clearData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleMask(aWindow) {
|
|
||||||
let { gBrowser } = aWindow;
|
|
||||||
let privateMask = aWindow.document.getElementById("private-mask");
|
|
||||||
if (gBrowser.selectedTab.isToggling) {
|
|
||||||
privateMask.setAttribute(
|
|
||||||
"enabled",
|
|
||||||
gBrowser.selectedTab.userContextId == this.container.userContextId
|
|
||||||
? "false"
|
|
||||||
: "true"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
privateMask.setAttribute(
|
|
||||||
"enabled",
|
|
||||||
gBrowser.selectedTab.userContextId == this.container.userContextId
|
|
||||||
? "true"
|
|
||||||
: "false"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get observePrivateTabs() {
|
|
||||||
return (
|
|
||||||
!this.config.neverClearData && !this.config.doNotClearDataUntilFxIsClosed
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
initCustomFunctions(aWindow) {
|
|
||||||
let { MozElements, PrivateTab } = aWindow;
|
|
||||||
MozElements.MozTab.prototype.getAttribute = function(att) {
|
|
||||||
if (att == "usercontextid" && this.getAttribute("isToggling", false)) {
|
|
||||||
this.removeAttribute("isToggling");
|
|
||||||
// If in private tab and we attempt to toggle, remove container, else convert to private tab
|
|
||||||
return PrivateTab.orig_getAttribute.call(this, att) ==
|
|
||||||
PrivateTab.container.userContextId
|
|
||||||
? 0
|
|
||||||
: PrivateTab.container.userContextId;
|
|
||||||
}
|
|
||||||
return PrivateTab.orig_getAttribute.call(this, att);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
orig_getAttribute: Services.wm.getMostRecentBrowserWindow("navigator:browser")
|
|
||||||
.MozElements.MozTab.prototype.getAttribute,
|
|
||||||
};
|
|
||||||
1250
waterfox/browser/components/privatetab/PrivateTab.sys.mjs
Normal file
1250
waterfox/browser/components/privatetab/PrivateTab.sys.mjs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,35 +8,34 @@
|
|||||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
|
|
||||||
<keyset id="privateTab-keyset" insertbefore="mainPopupSet">
|
<keyset id="privateTab-keyset" insertbefore="mainPopupSet">
|
||||||
<key id="togglePrivateTab-key" modifiers="alt control" key="T" oncommand="PrivateTab.togglePrivate(window)" />
|
<key id="togglePrivateTab-key" modifiers="alt control" key="T" />
|
||||||
<key id="newPrivateTab-key" modifiers="alt control" key="P"
|
<key id="newPrivateTab-key" modifiers="alt control" key="P" />
|
||||||
oncommand="PrivateTab.browserOpenTabPrivate(window)" />
|
|
||||||
</keyset>
|
</keyset>
|
||||||
|
|
||||||
<popupset id="mainPopupSet">
|
<popupset id="mainPopupSet">
|
||||||
<menupopup id="tabContextMenu">
|
<menupopup id="tabContextMenu">
|
||||||
<menuitem id="toggleTabPrivateState" data-l10n-id="private-tab"
|
<menuitem id="toggleTabPrivateState" data-l10n-id="private-tab"
|
||||||
data-l10n-args="{"isPrivate":false}" class="privatetab-icon" acceltext="Ctrl+Alt+T"
|
data-l10n-args="{"isPrivate":false}" class="privatetab-icon" acceltext="Ctrl+Alt+T"
|
||||||
oncommand="PrivateTab.togglePrivate(window, TabContextMenu.contextTab)" insertafter="context_pinTab" />
|
insertafter="context_pinTab" />
|
||||||
</menupopup>
|
</menupopup>
|
||||||
<menupopup id="placesContext">
|
<menupopup id="placesContext">
|
||||||
<menuitem id="openAllPrivate" data-l10n-id="open-all-private" class="privatetab-icon"
|
<menuitem id="openAllPrivate" data-l10n-id="open-all-private" class="privatetab-icon"
|
||||||
oncommand="PrivateTab.openAllPrivate(event);" insertafter="placesContext_openBookmarkLinks:tabs" />
|
insertafter="placesContext_openBookmarkLinks:tabs" />
|
||||||
<menuitem id="openPrivate" data-l10n-id="open-private-tab" class="privatetab-icon"
|
<menuitem id="openPrivate" data-l10n-id="open-private-tab" class="privatetab-icon"
|
||||||
oncommand="PrivateTab.openPrivateTab(event);" insertafter="placesContext_open:newtab" />
|
insertafter="placesContext_open:newtab" />
|
||||||
<menuitem id="openAllLinksPrivate" data-l10n-id="open-all-links-private" class="privatetab-icon"
|
<menuitem id="openAllLinksPrivate" data-l10n-id="open-all-links-private" class="privatetab-icon"
|
||||||
oncommand="PrivateTab.openAllPrivate(event);" insertafter="placesContext_openLinks:tabs" />
|
insertafter="placesContext_openLinks:tabs" />
|
||||||
</menupopup>
|
</menupopup>
|
||||||
</popupset>
|
</popupset>
|
||||||
|
|
||||||
<menupopup id="menu_FilePopup">
|
<menupopup id="menu_FilePopup">
|
||||||
<menuitem id="menu_newPrivateTab" data-l10n-id="new-private-tab" acceltext="Ctrl+Alt+P"
|
<menuitem id="menu_newPrivateTab" data-l10n-id="new-private-tab" acceltext="Ctrl+Alt+P"
|
||||||
oncommand="PrivateTab.browserOpenTabPrivate(window)" insertafter="menu_newNavigatorTab" />
|
insertafter="menu_newNavigatorTab" />
|
||||||
</menupopup>
|
</menupopup>
|
||||||
|
|
||||||
<menupopup id="contentAreaContextMenu">
|
<menupopup id="contentAreaContextMenu">
|
||||||
<menuitem id="openLinkInPrivateTab" data-l10n-id="open-link-private" class="privatetab-icon"
|
<menuitem id="openLinkInPrivateTab" data-l10n-id="open-link-private" class="privatetab-icon"
|
||||||
oncommand="PrivateTab.openLink(event);" insertafter="context-openlinkintab" />
|
insertafter="context-openlinkintab" />
|
||||||
</menupopup>
|
</menupopup>
|
||||||
|
|
||||||
</overlay>
|
</overlay>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
EXTRA_JS_MODULES += [
|
EXTRA_JS_MODULES += [
|
||||||
"PrivateTab.jsm",
|
"PrivateTab.sys.mjs",
|
||||||
]
|
]
|
||||||
|
|
||||||
BROWSER_CHROME_MANIFESTS += ["test/browser/browser.ini"]
|
BROWSER_CHROME_MANIFESTS += ["test/browser/browser.ini"]
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
requestLongerTimeout(2);
|
requestLongerTimeout(2);
|
||||||
|
|
||||||
async function togglePrivate(tab, skip = false) {
|
async function togglePrivate(tab, skip = false) {
|
||||||
@@ -7,32 +5,32 @@ async function togglePrivate(tab, skip = false) {
|
|||||||
return PrivateTab.togglePrivate(window, tab);
|
return PrivateTab.togglePrivate(window, tab);
|
||||||
}
|
}
|
||||||
await openTabContextMenu(tab);
|
await openTabContextMenu(tab);
|
||||||
let openPrivate = document.getElementById("toggleTabPrivateState");
|
const openPrivate = document.getElementById("toggleTabPrivateState");
|
||||||
openPrivate.click();
|
openPrivate.click();
|
||||||
return gBrowser.selectedTab;
|
return gBrowser.selectedTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test elements exist in correct locations
|
// Test elements exist in correct locations
|
||||||
add_task(async function testButtonsExist() {
|
add_task(async function testButtonsExist() {
|
||||||
let b1 = document.getElementById("openAllPrivate");
|
const b1 = document.getElementById("openAllPrivate");
|
||||||
ok(b1, "Multiple bookmark context menu item added.");
|
ok(b1, "Multiple bookmark context menu item added.");
|
||||||
let b2 = document.getElementById("openAllLinksPrivate");
|
const b2 = document.getElementById("openAllLinksPrivate");
|
||||||
ok(b2, "Multiple link context menu item added.");
|
ok(b2, "Multiple link context menu item added.");
|
||||||
let b3 = document.getElementById("openPrivate");
|
const b3 = document.getElementById("openPrivate");
|
||||||
ok(b3, "New private tab item added.");
|
ok(b3, "New private tab item added.");
|
||||||
let b4 = document.getElementById("menu_newPrivateTab");
|
const b4 = document.getElementById("menu_newPrivateTab");
|
||||||
ok(b4, "Menu item added.");
|
ok(b4, "Menu item added.");
|
||||||
let b5 = document.getElementById("openLinkInPrivateTab");
|
const b5 = document.getElementById("openLinkInPrivateTab");
|
||||||
ok(b5, "Link context menu item added.");
|
ok(b5, "Link context menu item added.");
|
||||||
let b6 = document.getElementById("toggleTabPrivateState");
|
const b6 = document.getElementById("toggleTabPrivateState");
|
||||||
ok(b6, "Tab context menu item added.");
|
ok(b6, "Tab context menu item added.");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test container exists
|
// Test container exists
|
||||||
add_task(async function testContainer() {
|
add_task(async function testContainer() {
|
||||||
ContextualIdentityService.ensureDataReady();
|
ContextualIdentityService.ensureDataReady();
|
||||||
let container = ContextualIdentityService._identities.find(
|
const container = ContextualIdentityService._identities.find(
|
||||||
c => c.name == "Private"
|
(c) => c.name === "Private"
|
||||||
);
|
);
|
||||||
ok(container, "Found Private container.");
|
ok(container, "Found Private container.");
|
||||||
});
|
});
|
||||||
@@ -58,8 +56,8 @@ add_task(async function testAutofillNotStored() {
|
|||||||
"http://mochi.test:8888/browser/browser/components/" +
|
"http://mochi.test:8888/browser/browser/components/" +
|
||||||
"sessionstore/test/browser_formdata_sample.html";
|
"sessionstore/test/browser_formdata_sample.html";
|
||||||
|
|
||||||
const OUTER_VALUE = "browser_formdata_" + Math.random();
|
const OUTER_VALUE = `browser_formdata_${Math.random()}`;
|
||||||
const INNER_VALUE = "browser_formdata_" + Math.random();
|
const INNER_VALUE = `browser_formdata_${Math.random()}`;
|
||||||
|
|
||||||
// Creates a tab, loads a page with some form fields,
|
// Creates a tab, loads a page with some form fields,
|
||||||
// modifies their values and closes the tab.
|
// modifies their values and closes the tab.
|
||||||
@@ -68,7 +66,7 @@ add_task(async function testAutofillNotStored() {
|
|||||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
|
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
|
||||||
// Toggle to a private tab
|
// Toggle to a private tab
|
||||||
tab = await togglePrivate(tab, true);
|
tab = await togglePrivate(tab, true);
|
||||||
let browser = tab.linkedBrowser;
|
const browser = tab.linkedBrowser;
|
||||||
await promiseBrowserLoaded(browser);
|
await promiseBrowserLoaded(browser);
|
||||||
|
|
||||||
// Modify form data.
|
// Modify form data.
|
||||||
@@ -85,7 +83,7 @@ add_task(async function testAutofillNotStored() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await createAndRemoveTab();
|
await createAndRemoveTab();
|
||||||
let [
|
const [
|
||||||
{
|
{
|
||||||
state: { formdata },
|
state: { formdata },
|
||||||
},
|
},
|
||||||
@@ -99,7 +97,7 @@ add_task(async function testTabHistoryNotStored() {
|
|||||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URI1);
|
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URI1);
|
||||||
// Make it private
|
// Make it private
|
||||||
tab = await togglePrivate(tab, true);
|
tab = await togglePrivate(tab, true);
|
||||||
let browser = tab.linkedBrowser;
|
const browser = tab.linkedBrowser;
|
||||||
await promiseBrowserLoaded(browser);
|
await promiseBrowserLoaded(browser);
|
||||||
// Open a new URL
|
// Open a new URL
|
||||||
BrowserTestUtils.loadURI(browser, URI2);
|
BrowserTestUtils.loadURI(browser, URI2);
|
||||||
@@ -107,14 +105,14 @@ add_task(async function testTabHistoryNotStored() {
|
|||||||
// Remove tab to save state
|
// Remove tab to save state
|
||||||
BrowserTestUtils.removeTab(tab);
|
BrowserTestUtils.removeTab(tab);
|
||||||
// Verify only non-private data stored
|
// Verify only non-private data stored
|
||||||
let closedTabData = JSON.parse(SessionStore.getClosedTabData(window)).filter(
|
const closedTabData = JSON.parse(SessionStore.getClosedTabData(window)).filter(
|
||||||
data => {
|
(data) => {
|
||||||
return (
|
return (
|
||||||
data.state.entries[0].url === URI1 || data.state.entries[0].url === URI2
|
data.state.entries[0].url === URI1 || data.state.entries[0].url === URI2
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
let privateData = closedTabData.filter(data => {
|
const privateData = closedTabData.filter((data) => {
|
||||||
return data.state.isPrivate === true;
|
return data.state.isPrivate === true;
|
||||||
});
|
});
|
||||||
const oneClosedTabWithNoPrivateData =
|
const oneClosedTabWithNoPrivateData =
|
||||||
@@ -140,12 +138,12 @@ const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
|
|||||||
async function getSuggestionResults() {
|
async function getSuggestionResults() {
|
||||||
await UrlbarTestUtils.promiseSearchComplete(window);
|
await UrlbarTestUtils.promiseSearchComplete(window);
|
||||||
|
|
||||||
let results = [];
|
const results = [];
|
||||||
let matchCount = UrlbarTestUtils.getResultCount(window);
|
const matchCount = UrlbarTestUtils.getResultCount(window);
|
||||||
for (let i = 0; i < matchCount; i++) {
|
for (let i = 0; i < matchCount; i++) {
|
||||||
let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
|
const result = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
|
||||||
if (
|
if (
|
||||||
result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
|
result.type === UrlbarUtils.RESULT_TYPE.SEARCH &&
|
||||||
result.searchParams.suggestion
|
result.searchParams.suggestion
|
||||||
) {
|
) {
|
||||||
result.index = i;
|
result.index = i;
|
||||||
@@ -157,15 +155,15 @@ async function getSuggestionResults() {
|
|||||||
|
|
||||||
// Must run first.
|
// Must run first.
|
||||||
add_task(async function prepare() {
|
add_task(async function prepare() {
|
||||||
let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
|
const suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
|
||||||
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
|
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
|
||||||
let engine = await SearchTestUtils.promiseNewSearchEngine(
|
const engine = await SearchTestUtils.promiseNewSearchEngine(
|
||||||
getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME
|
getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME
|
||||||
);
|
);
|
||||||
let oldDefaultEngine = await Services.search.getDefault();
|
const oldDefaultEngine = await Services.search.getDefault();
|
||||||
await Services.search.setDefault(engine);
|
await Services.search.setDefault(engine);
|
||||||
await UrlbarTestUtils.formHistory.clear();
|
await UrlbarTestUtils.formHistory.clear();
|
||||||
registerCleanupFunction(async function() {
|
registerCleanupFunction(async () => {
|
||||||
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
|
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
|
||||||
await Services.search.setDefault(oldDefaultEngine);
|
await Services.search.setDefault(oldDefaultEngine);
|
||||||
|
|
||||||
@@ -184,7 +182,7 @@ add_task(async function testSearchSuggestionsNotStored() {
|
|||||||
window,
|
window,
|
||||||
value: "foo",
|
value: "foo",
|
||||||
});
|
});
|
||||||
let results = await getSuggestionResults();
|
const results = await getSuggestionResults();
|
||||||
ok(!results.length, "Suggestion not be stored in private tab");
|
ok(!results.length, "Suggestion not be stored in private tab");
|
||||||
// Cleanup
|
// Cleanup
|
||||||
BrowserTestUtils.removeTab(tab);
|
BrowserTestUtils.removeTab(tab);
|
||||||
|
|||||||
@@ -1,37 +1,33 @@
|
|||||||
"use strict";
|
const { TabStateFlusher } = ChromeUtils.importESModule(
|
||||||
|
"resource:///modules/sessionstore/TabStateFlusher.sys.mjs"
|
||||||
const { AppConstants } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/AppConstants.jsm"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { TabStateFlusher } = ChromeUtils.import(
|
const { TabStateCache } = ChromeUtils.importESModule(
|
||||||
"resource:///modules/sessionstore/TabStateFlusher.jsm"
|
"resource:///modules/sessionstore/TabStateCache.sys.mjs"
|
||||||
);
|
);
|
||||||
|
|
||||||
const { TabStateCache } = ChromeUtils.import(
|
const { SearchTestUtils } = ChromeUtils.importESModule(
|
||||||
"resource:///modules/sessionstore/TabStateCache.jsm"
|
"resource://testing-common/SearchTestUtils.sys.mjs"
|
||||||
);
|
|
||||||
|
|
||||||
const { SearchTestUtils } = ChromeUtils.import(
|
|
||||||
"resource://testing-common/SearchTestUtils.jsm"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
SearchTestUtils.init(this);
|
SearchTestUtils.init(this);
|
||||||
|
|
||||||
const { UrlbarTestUtils } = ChromeUtils.import(
|
const { UrlbarTestUtils } = ChromeUtils.importESModule(
|
||||||
"resource://testing-common/UrlbarTestUtils.jsm"
|
"resource://testing-common/UrlbarTestUtils.sys.mjs"
|
||||||
);
|
);
|
||||||
|
|
||||||
UrlbarTestUtils.init(this);
|
UrlbarTestUtils.init(this);
|
||||||
|
|
||||||
const { PrivateTab } = ChromeUtils.import("resource:///modules/PrivateTab.jsm");
|
const { PrivateTab } = ChromeUtils.importESModule(
|
||||||
|
"resource:///modules/PrivateTab.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
const URI1 = "https://test1.example.com/";
|
const _URI1 = "https://test1.example.com/";
|
||||||
const URI2 = "https://example.com/";
|
const _URI2 = "https://example.com/";
|
||||||
|
|
||||||
let OS = AppConstants.platform;
|
const _OS = AppConstants.platform;
|
||||||
|
|
||||||
function promiseBrowserLoaded(
|
function _promiseBrowserLoaded(
|
||||||
aBrowser,
|
aBrowser,
|
||||||
ignoreSubFrames = true,
|
ignoreSubFrames = true,
|
||||||
wantLoad = null
|
wantLoad = null
|
||||||
@@ -41,21 +37,21 @@ function promiseBrowserLoaded(
|
|||||||
|
|
||||||
// Removes the given tab immediately and returns a promise that resolves when
|
// Removes the given tab immediately and returns a promise that resolves when
|
||||||
// all pending status updates (messages) of the closing tab have been received.
|
// all pending status updates (messages) of the closing tab have been received.
|
||||||
function promiseRemoveTabAndSessionState(tab) {
|
function _promiseRemoveTabAndSessionState(tab) {
|
||||||
let sessionUpdatePromise = BrowserTestUtils.waitForSessionStoreUpdate(tab);
|
const sessionUpdatePromise = BrowserTestUtils.waitForSessionStoreUpdate(tab);
|
||||||
BrowserTestUtils.removeTab(tab);
|
BrowserTestUtils.removeTab(tab);
|
||||||
return sessionUpdatePromise;
|
return sessionUpdatePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPropertyOfFormField(browserContext, selector, propName, newValue) {
|
function _setPropertyOfFormField(browserContext, selector, propName, newValue) {
|
||||||
return SpecialPowers.spawn(
|
return SpecialPowers.spawn(
|
||||||
browserContext,
|
browserContext,
|
||||||
[selector, propName, newValue],
|
[selector, propName, newValue],
|
||||||
(selectorChild, propNameChild, newValueChild) => {
|
(selectorChild, propNameChild, newValueChild) => {
|
||||||
let node = content.document.querySelector(selectorChild);
|
const node = content.document.querySelector(selectorChild);
|
||||||
node[propNameChild] = newValueChild;
|
node[propNameChild] = newValueChild;
|
||||||
|
|
||||||
let event = node.ownerDocument.createEvent("UIEvents");
|
const event = node.ownerDocument.createEvent("UIEvents");
|
||||||
event.initUIEvent("input", true, true, node.ownerGlobal, 0);
|
event.initUIEvent("input", true, true, node.ownerGlobal, 0);
|
||||||
node.dispatchEvent(event);
|
node.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
@@ -65,10 +61,10 @@ function setPropertyOfFormField(browserContext, selector, propName, newValue) {
|
|||||||
/**
|
/**
|
||||||
* Helper for opening the toolbar context menu.
|
* Helper for opening the toolbar context menu.
|
||||||
*/
|
*/
|
||||||
async function openTabContextMenu(tab) {
|
async function _openTabContextMenu(tab) {
|
||||||
info("Opening tab context menu");
|
info("Opening tab context menu");
|
||||||
let contextMenu = document.getElementById("tabContextMenu");
|
const contextMenu = document.getElementById("tabContextMenu");
|
||||||
let openTabContextMenuPromise = BrowserTestUtils.waitForPopupEvent(
|
const openTabContextMenuPromise = BrowserTestUtils.waitForPopupEvent(
|
||||||
contextMenu,
|
contextMenu,
|
||||||
"shown"
|
"shown"
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user