| ... | ... | @@ -148,6 +148,119 @@ const PRIVATE_BROWSING_EXE_ICON_INDEX = 1; | 
| 148 | 148 |  const PREF_PRIVATE_BROWSING_SHORTCUT_CREATED =
 | 
| 149 | 149 |    "browser.privacySegmentation.createdShortcut";
 | 
| 150 | 150 |  
 | 
|  | 151 | +// Empty clipboard content from private windows on exit
 | 
|  | 152 | +// (tor-browser#42154)
 | 
|  | 153 | +const ClipboardPrivacy = {
 | 
|  | 154 | +  _lastClipboardHash: null,
 | 
|  | 155 | +  _globalActivation: false,
 | 
|  | 156 | +  _isPrivateClipboard: false,
 | 
|  | 157 | +  _hasher: null,
 | 
|  | 158 | +
 | 
|  | 159 | +  _computeClipboardHash(win = Services.ww.activeWindow) {
 | 
|  | 160 | +    const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
 | 
|  | 161 | +      Ci.nsITransferable
 | 
|  | 162 | +    );
 | 
|  | 163 | +    trans.init(win?.docShell?.QueryInterface(Ci.nsILoadContext) || null);
 | 
|  | 164 | +    ["text/x-moz-url", "text/plain"].forEach(trans.addDataFlavor);
 | 
|  | 165 | +    try {
 | 
|  | 166 | +      Services.clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard);
 | 
|  | 167 | +      const clipboardContent = {};
 | 
|  | 168 | +      trans.getAnyTransferData({}, clipboardContent);
 | 
|  | 169 | +      const { data } = clipboardContent.value.QueryInterface(
 | 
|  | 170 | +        Ci.nsISupportsString
 | 
|  | 171 | +      );
 | 
|  | 172 | +      const bytes = new TextEncoder().encode(data);
 | 
|  | 173 | +      const hasher = (this._hasher ||= Cc[
 | 
|  | 174 | +        "@mozilla.org/security/hash;1"
 | 
|  | 175 | +      ].createInstance(Ci.nsICryptoHash));
 | 
|  | 176 | +      hasher.init(hasher.SHA256);
 | 
|  | 177 | +      hasher.update(bytes, bytes.length);
 | 
|  | 178 | +      return hasher.finish(true);
 | 
|  | 179 | +    } catch (e) {}
 | 
|  | 180 | +    return null;
 | 
|  | 181 | +  },
 | 
|  | 182 | +
 | 
|  | 183 | +  startup() {
 | 
|  | 184 | +    this._lastClipboardHash = this._computeClipboardHash();
 | 
|  | 185 | +
 | 
|  | 186 | +    // Here we track changes in active window / application,
 | 
|  | 187 | +    // by filtering focus events and window closures.
 | 
|  | 188 | +    const handleActivation = (win, activation) => {
 | 
|  | 189 | +      if (activation) {
 | 
|  | 190 | +        if (!this._globalActivation) {
 | 
|  | 191 | +          // focus changed within this window, bail out.
 | 
|  | 192 | +          return;
 | 
|  | 193 | +        }
 | 
|  | 194 | +        this._globalActivation = false;
 | 
|  | 195 | +      } else if (!Services.focus.activeWindow) {
 | 
|  | 196 | +        // focus is leaving this window:
 | 
|  | 197 | +        // let's track whether it remains within the browser.
 | 
|  | 198 | +        lazy.setTimeout(() => {
 | 
|  | 199 | +          this._globalActivation = !Services.focus.activeWindow;
 | 
|  | 200 | +        }, 100);
 | 
|  | 201 | +      }
 | 
|  | 202 | +      const clipboardHash = this._computeClipboardHash(win);
 | 
|  | 203 | +      if (clipboardHash !== this._lastClipboardHash) {
 | 
|  | 204 | +        this._isPrivateClipboard =
 | 
|  | 205 | +          !activation &&
 | 
|  | 206 | +          (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
 | 
|  | 207 | +            lazy.PrivateBrowsingUtils.isWindowPrivate(win));
 | 
|  | 208 | +        this._lastClipboardHash = clipboardHash;
 | 
|  | 209 | +        console.log(
 | 
|  | 210 | +          `Clipboard changed: private ${this._isPrivateClipboard}, hash ${clipboardHash}.`
 | 
|  | 211 | +        );
 | 
|  | 212 | +      }
 | 
|  | 213 | +    };
 | 
|  | 214 | +    const focusListener = e =>
 | 
|  | 215 | +      e.isTrusted && handleActivation(e.currentTarget, e.type === "focusin");
 | 
|  | 216 | +    const initWindow = win => {
 | 
|  | 217 | +      for (const e of ["focusin", "focusout"]) {
 | 
|  | 218 | +        win.addEventListener(e, focusListener);
 | 
|  | 219 | +      }
 | 
|  | 220 | +    };
 | 
|  | 221 | +    for (const w of Services.ww.getWindowEnumerator()) {
 | 
|  | 222 | +      initWindow(w);
 | 
|  | 223 | +    }
 | 
|  | 224 | +    Services.ww.registerNotification((win, event) => {
 | 
|  | 225 | +      switch (event) {
 | 
|  | 226 | +        case "domwindowopened":
 | 
|  | 227 | +          initWindow(win);
 | 
|  | 228 | +          break;
 | 
|  | 229 | +        case "domwindowclosed":
 | 
|  | 230 | +          handleActivation(win, false);
 | 
|  | 231 | +          if (
 | 
|  | 232 | +            this._isPrivateClipboard &&
 | 
|  | 233 | +            lazy.PrivateBrowsingUtils.isWindowPrivate(win) &&
 | 
|  | 234 | +            !(
 | 
|  | 235 | +              lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
 | 
|  | 236 | +              Array.from(Services.ww.getWindowEnumerator()).find(w =>
 | 
|  | 237 | +                lazy.PrivateBrowsingUtils.isWindowPrivate(w)
 | 
|  | 238 | +              )
 | 
|  | 239 | +            )
 | 
|  | 240 | +          ) {
 | 
|  | 241 | +            // no more private windows, empty private content if needed
 | 
|  | 242 | +            this.emptyPrivate();
 | 
|  | 243 | +          }
 | 
|  | 244 | +      }
 | 
|  | 245 | +    });
 | 
|  | 246 | +  },
 | 
|  | 247 | +  emptyPrivate() {
 | 
|  | 248 | +    if (
 | 
|  | 249 | +      this._isPrivateClipboard &&
 | 
|  | 250 | +      !Services.prefs.getBoolPref(
 | 
|  | 251 | +        "browser.privatebrowsing.preserveClipboard",
 | 
|  | 252 | +        false
 | 
|  | 253 | +      ) &&
 | 
|  | 254 | +      this._lastClipboardHash === this._computeClipboardHash()
 | 
|  | 255 | +    ) {
 | 
|  | 256 | +      Services.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
 | 
|  | 257 | +      this._lastClipboardHash = null;
 | 
|  | 258 | +      this._isPrivateClipboard = false;
 | 
|  | 259 | +      console.log("Private clipboard emptied.");
 | 
|  | 260 | +    }
 | 
|  | 261 | +  },
 | 
|  | 262 | +};
 | 
|  | 263 | +
 | 
| 151 | 264 |  /**
 | 
| 152 | 265 |   * Fission-compatible JSProcess implementations.
 | 
| 153 | 266 |   * Each actor options object takes the form of a ProcessActorOptions dictionary.
 | 
| ... | ... | @@ -1619,6 +1732,8 @@ BrowserGlue.prototype = { | 
| 1619 | 1732 |  
 | 
| 1620 | 1733 |      lazy.DoHController.init();
 | 
| 1621 | 1734 |  
 | 
|  | 1735 | +    ClipboardPrivacy.startup();
 | 
|  | 1736 | +
 | 
| 1622 | 1737 |      this._firstWindowTelemetry(aWindow);
 | 
| 1623 | 1738 |      this._firstWindowLoaded();
 | 
| 1624 | 1739 |  
 | 
| ... | ... | @@ -1879,7 +1994,7 @@ BrowserGlue.prototype = { | 
| 1879 | 1994 |            lazy.UpdateListener.reset();
 | 
| 1880 | 1995 |          }
 | 
| 1881 | 1996 |        },
 | 
| 1882 |  | -      () => Services.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard), // tor-browser#42019
 | 
|  | 1997 | +      () => ClipboardPrivacy.emptyPrivate(), // tor-browser#42019
 | 
| 1883 | 1998 |      ];
 | 
| 1884 | 1999 |  
 | 
| 1885 | 2000 |      for (let task of tasks) {
 |