Loading browser/app/profile/001-base-profile.js +3 −0 Original line number Diff line number Diff line Loading @@ -131,6 +131,9 @@ pref("browser.pagethumbnails.capturing_disabled", true); pref("browser.backup.enabled", false); pref("browser.backup.scheduled.enabled", false); // Empty clipboard content from private windows on exit (tor-browser#42154) pref("browser.privatebrowsing.preserveClipboard", false); // tor-browser#42611: Do not include the URL of the image, when copying it. // Also, do not save clipboard in history/cloud. pref("clipboard.imageAsFile.enabled", false); Loading browser/components/BrowserComponents.manifest +1 −0 Original line number Diff line number Diff line Loading @@ -85,6 +85,7 @@ category browser-first-window-ready resource://gre/modules/SandboxUtils.sys.mjs category browser-window-delayed-startup resource://gre/modules/SandboxUtils.sys.mjs SandboxUtils.maybeWarnAboutDisabledContentSandbox category browser-first-window-ready resource://gre/modules/SandboxUtils.sys.mjs SandboxUtils.observeContentSandboxPref #endif category browser-first-window-ready moz-src:///browser/modules/ClipboardPrivacy.sys.mjs ClipboardPrivacy.init category browser-idle-startup moz-src:///browser/components/places/PlacesUIUtils.sys.mjs PlacesUIUtils.unblockToolbars category browser-idle-startup resource:///modules/BuiltInThemes.sys.mjs BuiltInThemes.ensureBuiltInThemes Loading browser/modules/ClipboardPrivacy.sys.mjs 0 → 100644 +178 −0 Original line number Diff line number Diff line /* 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 lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", setTimeout: "resource://gre/modules/Timer.sys.mjs", }); /** * Empty clipboard content from private windows on exit. * * See tor-browser#42154. */ export const ClipboardPrivacy = { _lastClipboardHash: null, _globalActivation: false, _isPrivateClipboard: false, _hasher: null, _shuttingDown: false, _log: null, _createTransferable() { const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( Ci.nsITransferable ); trans.init(null); return trans; }, _computeClipboardHash() { const flavors = ["text/x-moz-url", "text/plain"]; if ( !Services.clipboard.hasDataMatchingFlavors( flavors, Ci.nsIClipboard.kGlobalClipboard ) ) { return null; } const trans = this._createTransferable(); flavors.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; }, init() { this._log = console.createInstance({ prefix: "ClipboardPrivacy", }); 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; this._log.debug( `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.appShutdownConfirmed.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; this._log.info("Private clipboard emptied."); } }, }; browser/modules/moz.build +1 −0 Original line number Diff line number Diff line Loading @@ -148,6 +148,7 @@ EXTRA_JS_MODULES += [ MOZ_SRC_FILES += [ "CanvasPermissionPromptHelper.sys.mjs", "ClipboardPrivacy.sys.mjs", "ContextId.sys.mjs", "ObserverForwarder.sys.mjs", "PrivateBrowsingUI.sys.mjs", Loading Loading
browser/app/profile/001-base-profile.js +3 −0 Original line number Diff line number Diff line Loading @@ -131,6 +131,9 @@ pref("browser.pagethumbnails.capturing_disabled", true); pref("browser.backup.enabled", false); pref("browser.backup.scheduled.enabled", false); // Empty clipboard content from private windows on exit (tor-browser#42154) pref("browser.privatebrowsing.preserveClipboard", false); // tor-browser#42611: Do not include the URL of the image, when copying it. // Also, do not save clipboard in history/cloud. pref("clipboard.imageAsFile.enabled", false); Loading
browser/components/BrowserComponents.manifest +1 −0 Original line number Diff line number Diff line Loading @@ -85,6 +85,7 @@ category browser-first-window-ready resource://gre/modules/SandboxUtils.sys.mjs category browser-window-delayed-startup resource://gre/modules/SandboxUtils.sys.mjs SandboxUtils.maybeWarnAboutDisabledContentSandbox category browser-first-window-ready resource://gre/modules/SandboxUtils.sys.mjs SandboxUtils.observeContentSandboxPref #endif category browser-first-window-ready moz-src:///browser/modules/ClipboardPrivacy.sys.mjs ClipboardPrivacy.init category browser-idle-startup moz-src:///browser/components/places/PlacesUIUtils.sys.mjs PlacesUIUtils.unblockToolbars category browser-idle-startup resource:///modules/BuiltInThemes.sys.mjs BuiltInThemes.ensureBuiltInThemes Loading
browser/modules/ClipboardPrivacy.sys.mjs 0 → 100644 +178 −0 Original line number Diff line number Diff line /* 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 lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", setTimeout: "resource://gre/modules/Timer.sys.mjs", }); /** * Empty clipboard content from private windows on exit. * * See tor-browser#42154. */ export const ClipboardPrivacy = { _lastClipboardHash: null, _globalActivation: false, _isPrivateClipboard: false, _hasher: null, _shuttingDown: false, _log: null, _createTransferable() { const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( Ci.nsITransferable ); trans.init(null); return trans; }, _computeClipboardHash() { const flavors = ["text/x-moz-url", "text/plain"]; if ( !Services.clipboard.hasDataMatchingFlavors( flavors, Ci.nsIClipboard.kGlobalClipboard ) ) { return null; } const trans = this._createTransferable(); flavors.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; }, init() { this._log = console.createInstance({ prefix: "ClipboardPrivacy", }); 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; this._log.debug( `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.appShutdownConfirmed.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; this._log.info("Private clipboard emptied."); } }, };
browser/modules/moz.build +1 −0 Original line number Diff line number Diff line Loading @@ -148,6 +148,7 @@ EXTRA_JS_MODULES += [ MOZ_SRC_FILES += [ "CanvasPermissionPromptHelper.sys.mjs", "ClipboardPrivacy.sys.mjs", "ContextId.sys.mjs", "ObserverForwarder.sys.mjs", "PrivateBrowsingUI.sys.mjs", Loading