Loading browser/app/profile/001-base-profile.js +3 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,9 @@ pref("browser.pagethumbnails.capturing_disabled", true); pref("privacy.exposeContentTitleInWindow", false); pref("privacy.exposeContentTitleInWindow.pbm", false); // 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 +116 −1 Original line number Diff line number Diff line Loading @@ -154,6 +154,119 @@ 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, _computeClipboardHash(win = Services.ww.activeWindow) { const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( Ci.nsITransferable ); trans.init(win?.docShell?.QueryInterface(Ci.nsILoadContext) || null); ["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 clipboardHash = this._computeClipboardHash(win); 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}.` ); } }; 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) && !( lazy.PrivateBrowsingUtils.permanentPrivateBrowsing || Array.from(Services.ww.getWindowEnumerator()).find(w => lazy.PrivateBrowsingUtils.isWindowPrivate(w) ) ) ) { // no more private windows, empty private content if needed this.emptyPrivate(); } } }); }, emptyPrivate() { if ( this._isPrivateClipboard && !Services.prefs.getBoolPref( "browser.privatebrowsing.preserveClipboard", false ) && this._lastClipboardHash === this._computeClipboardHash() ) { Services.clipboard.emptyClipboard(Ci.nsIClipboard.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 +1838,8 @@ BrowserGlue.prototype = { lazy.DoHController.init(); ClipboardPrivacy.startup(); this._firstWindowTelemetry(aWindow); this._firstWindowLoaded(); Loading Loading @@ -1986,7 +2101,7 @@ BrowserGlue.prototype = { lazy.UpdateListener.reset(); } }, () => Services.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard), // tor-browser#42019 () => 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 @@ -93,6 +93,9 @@ pref("browser.pagethumbnails.capturing_disabled", true); pref("privacy.exposeContentTitleInWindow", false); pref("privacy.exposeContentTitleInWindow.pbm", false); // 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 +116 −1 Original line number Diff line number Diff line Loading @@ -154,6 +154,119 @@ 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, _computeClipboardHash(win = Services.ww.activeWindow) { const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( Ci.nsITransferable ); trans.init(win?.docShell?.QueryInterface(Ci.nsILoadContext) || null); ["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 clipboardHash = this._computeClipboardHash(win); 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}.` ); } }; 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) && !( lazy.PrivateBrowsingUtils.permanentPrivateBrowsing || Array.from(Services.ww.getWindowEnumerator()).find(w => lazy.PrivateBrowsingUtils.isWindowPrivate(w) ) ) ) { // no more private windows, empty private content if needed this.emptyPrivate(); } } }); }, emptyPrivate() { if ( this._isPrivateClipboard && !Services.prefs.getBoolPref( "browser.privatebrowsing.preserveClipboard", false ) && this._lastClipboardHash === this._computeClipboardHash() ) { Services.clipboard.emptyClipboard(Ci.nsIClipboard.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 +1838,8 @@ BrowserGlue.prototype = { lazy.DoHController.init(); ClipboardPrivacy.startup(); this._firstWindowTelemetry(aWindow); this._firstWindowLoaded(); Loading Loading @@ -1986,7 +2101,7 @@ BrowserGlue.prototype = { lazy.UpdateListener.reset(); } }, () => Services.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard), // tor-browser#42019 () => ClipboardPrivacy.emptyPrivate(), // tor-browser#42019 ]; for (let task of tasks) { Loading