Loading browser/app/profile/001-base-profile.js +3 −0 Original line number Diff line number Diff line Loading @@ -87,6 +87,9 @@ pref("browser.sessionstore.resume_from_crash", false); // Also not needed in PBM at the moment. pref("browser.pagethumbnails.capturing_disabled", true); // Empty clipboard content from private windows on exit (tor-browser#42154) pref("browser.privatebrowsing.preserveClipboard", false); // Enable HTTPS-Only mode (tor-browser#19850) pref("dom.security.https_only_mode", true); // The previous pref automatically sets this to true (see StaticPrefList.yaml), Loading browser/components/BrowserGlue.sys.mjs +154 −0 Original line number Diff line number Diff line Loading @@ -154,6 +154,157 @@ const PRIVATE_BROWSING_EXE_ICON_INDEX = 1; const PREF_PRIVATE_BROWSING_SHORTCUT_CREATED = "browser.privacySegmentation.createdShortcut"; // Empty clipboard content from private windows on exit // (tor-browser#42154) const ClipboardPrivacy = { _lastClipboardHash: null, _globalActivation: false, _isPrivateClipboard: false, _hasher: null, _shuttingDown: false, _createTransferable() { const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( Ci.nsITransferable ); trans.init(null); return trans; }, _computeClipboardHash() { const trans = this._createTransferable(); ["text/x-moz-url", "text/plain"].forEach(trans.addDataFlavor); try { Services.clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard); const clipboardContent = {}; trans.getAnyTransferData({}, clipboardContent); const { data } = clipboardContent.value.QueryInterface( Ci.nsISupportsString ); const bytes = new TextEncoder().encode(data); const hasher = (this._hasher ||= Cc[ "@mozilla.org/security/hash;1" ].createInstance(Ci.nsICryptoHash)); hasher.init(hasher.SHA256); hasher.update(bytes, bytes.length); return hasher.finish(true); } catch (e) {} return null; }, startup() { this._lastClipboardHash = this._computeClipboardHash(); // Here we track changes in active window / application, // by filtering focus events and window closures. const handleActivation = (win, activation) => { if (activation) { if (!this._globalActivation) { // focus changed within this window, bail out. return; } this._globalActivation = false; } else if (!Services.focus.activeWindow) { // focus is leaving this window: // let's track whether it remains within the browser. lazy.setTimeout(() => { this._globalActivation = !Services.focus.activeWindow; }, 100); } const checkClipboardContent = () => { const clipboardHash = this._computeClipboardHash(); if (clipboardHash !== this._lastClipboardHash) { this._isPrivateClipboard = !activation && (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing || lazy.PrivateBrowsingUtils.isWindowPrivate(win)); this._lastClipboardHash = clipboardHash; console.log( `Clipboard changed: private ${this._isPrivateClipboard}, hash ${clipboardHash}.` ); } }; if (win.closed) { checkClipboardContent(); } else { // defer clipboard access on DOM events to work-around tor-browser#42306 lazy.setTimeout(checkClipboardContent, 0); } }; const focusListener = e => e.isTrusted && handleActivation(e.currentTarget, e.type === "focusin"); const initWindow = win => { for (const e of ["focusin", "focusout"]) { win.addEventListener(e, focusListener); } }; for (const w of Services.ww.getWindowEnumerator()) { initWindow(w); } Services.ww.registerNotification((win, event) => { switch (event) { case "domwindowopened": initWindow(win); break; case "domwindowclosed": handleActivation(win, false); if ( this._isPrivateClipboard && lazy.PrivateBrowsingUtils.isWindowPrivate(win) && (this._shuttingDown || !Array.from(Services.ww.getWindowEnumerator()).find( w => lazy.PrivateBrowsingUtils.isWindowPrivate(w) && // We need to filter out the HIDDEN WebExtensions window, // which might be private as well but is not UI-relevant. !w.location.href.startsWith("chrome://extensions/") )) ) { // no more private windows, empty private content if needed this.emptyPrivate(); } } }); lazy.AsyncShutdown.quitApplicationGranted.addBlocker( "ClipboardPrivacy: removing private data", () => { this._shuttingDown = true; this.emptyPrivate(); } ); }, emptyPrivate() { if ( this._isPrivateClipboard && !Services.prefs.getBoolPref( "browser.privatebrowsing.preserveClipboard", false ) && this._lastClipboardHash === this._computeClipboardHash() ) { // nsIClipboard.emptyClipboard() does nothing in Wayland: // we'll set an empty string as a work-around. const trans = this._createTransferable(); const flavor = "text/plain"; trans.addDataFlavor(flavor); const emptyString = Cc["@mozilla.org/supports-string;1"].createInstance( Ci.nsISupportsString ); emptyString.data = ""; trans.setTransferData(flavor, emptyString); const { clipboard } = Services, { kGlobalClipboard } = clipboard; clipboard.setData(trans, null, kGlobalClipboard); clipboard.emptyClipboard(kGlobalClipboard); this._lastClipboardHash = null; this._isPrivateClipboard = false; console.log("Private clipboard emptied."); } }, }; /** * Fission-compatible JSProcess implementations. * Each actor options object takes the form of a ProcessActorOptions dictionary. Loading Loading @@ -1725,6 +1876,8 @@ BrowserGlue.prototype = { lazy.DoHController.init(); ClipboardPrivacy.startup(); this._firstWindowTelemetry(aWindow); this._firstWindowLoaded(); Loading Loading @@ -1986,6 +2139,7 @@ BrowserGlue.prototype = { lazy.UpdateListener.reset(); } }, () => ClipboardPrivacy.emptyPrivate(), // tor-browser#42019 ]; for (let task of tasks) { Loading Loading
browser/app/profile/001-base-profile.js +3 −0 Original line number Diff line number Diff line Loading @@ -87,6 +87,9 @@ pref("browser.sessionstore.resume_from_crash", false); // Also not needed in PBM at the moment. pref("browser.pagethumbnails.capturing_disabled", true); // Empty clipboard content from private windows on exit (tor-browser#42154) pref("browser.privatebrowsing.preserveClipboard", false); // Enable HTTPS-Only mode (tor-browser#19850) pref("dom.security.https_only_mode", true); // The previous pref automatically sets this to true (see StaticPrefList.yaml), Loading
browser/components/BrowserGlue.sys.mjs +154 −0 Original line number Diff line number Diff line Loading @@ -154,6 +154,157 @@ const PRIVATE_BROWSING_EXE_ICON_INDEX = 1; const PREF_PRIVATE_BROWSING_SHORTCUT_CREATED = "browser.privacySegmentation.createdShortcut"; // Empty clipboard content from private windows on exit // (tor-browser#42154) const ClipboardPrivacy = { _lastClipboardHash: null, _globalActivation: false, _isPrivateClipboard: false, _hasher: null, _shuttingDown: false, _createTransferable() { const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( Ci.nsITransferable ); trans.init(null); return trans; }, _computeClipboardHash() { const trans = this._createTransferable(); ["text/x-moz-url", "text/plain"].forEach(trans.addDataFlavor); try { Services.clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard); const clipboardContent = {}; trans.getAnyTransferData({}, clipboardContent); const { data } = clipboardContent.value.QueryInterface( Ci.nsISupportsString ); const bytes = new TextEncoder().encode(data); const hasher = (this._hasher ||= Cc[ "@mozilla.org/security/hash;1" ].createInstance(Ci.nsICryptoHash)); hasher.init(hasher.SHA256); hasher.update(bytes, bytes.length); return hasher.finish(true); } catch (e) {} return null; }, startup() { this._lastClipboardHash = this._computeClipboardHash(); // Here we track changes in active window / application, // by filtering focus events and window closures. const handleActivation = (win, activation) => { if (activation) { if (!this._globalActivation) { // focus changed within this window, bail out. return; } this._globalActivation = false; } else if (!Services.focus.activeWindow) { // focus is leaving this window: // let's track whether it remains within the browser. lazy.setTimeout(() => { this._globalActivation = !Services.focus.activeWindow; }, 100); } const checkClipboardContent = () => { const clipboardHash = this._computeClipboardHash(); if (clipboardHash !== this._lastClipboardHash) { this._isPrivateClipboard = !activation && (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing || lazy.PrivateBrowsingUtils.isWindowPrivate(win)); this._lastClipboardHash = clipboardHash; console.log( `Clipboard changed: private ${this._isPrivateClipboard}, hash ${clipboardHash}.` ); } }; if (win.closed) { checkClipboardContent(); } else { // defer clipboard access on DOM events to work-around tor-browser#42306 lazy.setTimeout(checkClipboardContent, 0); } }; const focusListener = e => e.isTrusted && handleActivation(e.currentTarget, e.type === "focusin"); const initWindow = win => { for (const e of ["focusin", "focusout"]) { win.addEventListener(e, focusListener); } }; for (const w of Services.ww.getWindowEnumerator()) { initWindow(w); } Services.ww.registerNotification((win, event) => { switch (event) { case "domwindowopened": initWindow(win); break; case "domwindowclosed": handleActivation(win, false); if ( this._isPrivateClipboard && lazy.PrivateBrowsingUtils.isWindowPrivate(win) && (this._shuttingDown || !Array.from(Services.ww.getWindowEnumerator()).find( w => lazy.PrivateBrowsingUtils.isWindowPrivate(w) && // We need to filter out the HIDDEN WebExtensions window, // which might be private as well but is not UI-relevant. !w.location.href.startsWith("chrome://extensions/") )) ) { // no more private windows, empty private content if needed this.emptyPrivate(); } } }); lazy.AsyncShutdown.quitApplicationGranted.addBlocker( "ClipboardPrivacy: removing private data", () => { this._shuttingDown = true; this.emptyPrivate(); } ); }, emptyPrivate() { if ( this._isPrivateClipboard && !Services.prefs.getBoolPref( "browser.privatebrowsing.preserveClipboard", false ) && this._lastClipboardHash === this._computeClipboardHash() ) { // nsIClipboard.emptyClipboard() does nothing in Wayland: // we'll set an empty string as a work-around. const trans = this._createTransferable(); const flavor = "text/plain"; trans.addDataFlavor(flavor); const emptyString = Cc["@mozilla.org/supports-string;1"].createInstance( Ci.nsISupportsString ); emptyString.data = ""; trans.setTransferData(flavor, emptyString); const { clipboard } = Services, { kGlobalClipboard } = clipboard; clipboard.setData(trans, null, kGlobalClipboard); clipboard.emptyClipboard(kGlobalClipboard); this._lastClipboardHash = null; this._isPrivateClipboard = false; console.log("Private clipboard emptied."); } }, }; /** * Fission-compatible JSProcess implementations. * Each actor options object takes the form of a ProcessActorOptions dictionary. Loading Loading @@ -1725,6 +1876,8 @@ BrowserGlue.prototype = { lazy.DoHController.init(); ClipboardPrivacy.startup(); this._firstWindowTelemetry(aWindow); this._firstWindowLoaded(); Loading Loading @@ -1986,6 +2139,7 @@ BrowserGlue.prototype = { lazy.UpdateListener.reset(); } }, () => ClipboardPrivacy.emptyPrivate(), // tor-browser#42019 ]; for (let task of tasks) { Loading