| ... | ... | @@ -159,12 +159,17 @@ const ClipboardPrivacy = { | 
| 159 | 159 |    _globalActivation: false,
 | 
| 160 | 160 |    _isPrivateClipboard: false,
 | 
| 161 | 161 |    _hasher: null,
 | 
|  | 162 | +  _shuttingDown: false,
 | 
| 162 | 163 |  
 | 
| 163 |  | -  _computeClipboardHash(win = Services.ww.activeWindow) {
 | 
|  | 164 | +  _createTransferable() {
 | 
| 164 | 165 |      const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
 | 
| 165 | 166 |        Ci.nsITransferable
 | 
| 166 | 167 |      );
 | 
| 167 |  | -    trans.init(win?.docShell?.QueryInterface(Ci.nsILoadContext) || null);
 | 
|  | 168 | +    trans.init(null);
 | 
|  | 169 | +    return trans;
 | 
|  | 170 | +  },
 | 
|  | 171 | +  _computeClipboardHash() {
 | 
|  | 172 | +    const trans = this._createTransferable();
 | 
| 168 | 173 |      ["text/x-moz-url", "text/plain"].forEach(trans.addDataFlavor);
 | 
| 169 | 174 |      try {
 | 
| 170 | 175 |        Services.clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard);
 | 
| ... | ... | @@ -203,16 +208,26 @@ const ClipboardPrivacy = { | 
| 203 | 208 |            this._globalActivation = !Services.focus.activeWindow;
 | 
| 204 | 209 |          }, 100);
 | 
| 205 | 210 |        }
 | 
| 206 |  | -      const clipboardHash = this._computeClipboardHash(win);
 | 
| 207 |  | -      if (clipboardHash !== this._lastClipboardHash) {
 | 
| 208 |  | -        this._isPrivateClipboard =
 | 
| 209 |  | -          !activation &&
 | 
| 210 |  | -          (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
 | 
| 211 |  | -            lazy.PrivateBrowsingUtils.isWindowPrivate(win));
 | 
| 212 |  | -        this._lastClipboardHash = clipboardHash;
 | 
| 213 |  | -        console.log(
 | 
| 214 |  | -          `Clipboard changed: private ${this._isPrivateClipboard}, hash ${clipboardHash}.`
 | 
| 215 |  | -        );
 | 
|  | 211 | +
 | 
|  | 212 | +      const checkClipboardContent = () => {
 | 
|  | 213 | +        const clipboardHash = this._computeClipboardHash();
 | 
|  | 214 | +        if (clipboardHash !== this._lastClipboardHash) {
 | 
|  | 215 | +          this._isPrivateClipboard =
 | 
|  | 216 | +            !activation &&
 | 
|  | 217 | +            (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
 | 
|  | 218 | +              lazy.PrivateBrowsingUtils.isWindowPrivate(win));
 | 
|  | 219 | +          this._lastClipboardHash = clipboardHash;
 | 
|  | 220 | +          console.log(
 | 
|  | 221 | +            `Clipboard changed: private ${this._isPrivateClipboard}, hash ${clipboardHash}.`
 | 
|  | 222 | +          );
 | 
|  | 223 | +        }
 | 
|  | 224 | +      };
 | 
|  | 225 | +
 | 
|  | 226 | +      if (win.closed) {
 | 
|  | 227 | +        checkClipboardContent();
 | 
|  | 228 | +      } else {
 | 
|  | 229 | +        // defer clipboard access on DOM events to work-around tor-browser#42306
 | 
|  | 230 | +        lazy.setTimeout(checkClipboardContent, 0);
 | 
| 216 | 231 |        }
 | 
| 217 | 232 |      };
 | 
| 218 | 233 |      const focusListener = e =>
 | 
| ... | ... | @@ -235,18 +250,28 @@ const ClipboardPrivacy = { | 
| 235 | 250 |            if (
 | 
| 236 | 251 |              this._isPrivateClipboard &&
 | 
| 237 | 252 |              lazy.PrivateBrowsingUtils.isWindowPrivate(win) &&
 | 
| 238 |  | -            !(
 | 
| 239 |  | -              lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
 | 
| 240 |  | -              Array.from(Services.ww.getWindowEnumerator()).find(w =>
 | 
| 241 |  | -                lazy.PrivateBrowsingUtils.isWindowPrivate(w)
 | 
| 242 |  | -              )
 | 
| 243 |  | -            )
 | 
|  | 253 | +            (this._shuttingDown ||
 | 
|  | 254 | +              !Array.from(Services.ww.getWindowEnumerator()).find(
 | 
|  | 255 | +                w =>
 | 
|  | 256 | +                  lazy.PrivateBrowsingUtils.isWindowPrivate(w) &&
 | 
|  | 257 | +                  // We need to filter out the HIDDEN WebExtensions window,
 | 
|  | 258 | +                  // which might be private as well but is not UI-relevant.
 | 
|  | 259 | +                  !w.location.href.startsWith("chrome://extensions/")
 | 
|  | 260 | +              ))
 | 
| 244 | 261 |            ) {
 | 
| 245 | 262 |              // no more private windows, empty private content if needed
 | 
| 246 | 263 |              this.emptyPrivate();
 | 
| 247 | 264 |            }
 | 
| 248 | 265 |        }
 | 
| 249 | 266 |      });
 | 
|  | 267 | +
 | 
|  | 268 | +    lazy.AsyncShutdown.quitApplicationGranted.addBlocker(
 | 
|  | 269 | +      "ClipboardPrivacy: removing private data",
 | 
|  | 270 | +      () => {
 | 
|  | 271 | +        this._shuttingDown = true;
 | 
|  | 272 | +        this.emptyPrivate();
 | 
|  | 273 | +      }
 | 
|  | 274 | +    );
 | 
| 250 | 275 |    },
 | 
| 251 | 276 |    emptyPrivate() {
 | 
| 252 | 277 |      if (
 | 
| ... | ... | @@ -257,7 +282,20 @@ const ClipboardPrivacy = { | 
| 257 | 282 |        ) &&
 | 
| 258 | 283 |        this._lastClipboardHash === this._computeClipboardHash()
 | 
| 259 | 284 |      ) {
 | 
| 260 |  | -      Services.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
 | 
|  | 285 | +      // nsIClipboard.emptyClipboard() does nothing in Wayland:
 | 
|  | 286 | +      // we'll set an empty string as a work-around.
 | 
|  | 287 | +      const trans = this._createTransferable();
 | 
|  | 288 | +      const flavor = "text/plain";
 | 
|  | 289 | +      trans.addDataFlavor(flavor);
 | 
|  | 290 | +      const emptyString = Cc["@mozilla.org/supports-string;1"].createInstance(
 | 
|  | 291 | +        Ci.nsISupportsString
 | 
|  | 292 | +      );
 | 
|  | 293 | +      emptyString.data = "";
 | 
|  | 294 | +      trans.setTransferData(flavor, emptyString);
 | 
|  | 295 | +      const { clipboard } = Services,
 | 
|  | 296 | +        { kGlobalClipboard } = clipboard;
 | 
|  | 297 | +      clipboard.setData(trans, null, kGlobalClipboard);
 | 
|  | 298 | +      clipboard.emptyClipboard(kGlobalClipboard);
 | 
| 261 | 299 |        this._lastClipboardHash = null;
 | 
| 262 | 300 |        this._isPrivateClipboard = false;
 | 
| 263 | 301 |        console.log("Private clipboard emptied.");
 | 
| ... | ... | @@ -2118,7 +2156,6 @@ BrowserGlue.prototype = { | 
| 2118 | 2156 |          }
 | 
| 2119 | 2157 |        },
 | 
| 2120 | 2158 |        () => lazy.OnionAliasStore.uninit(),
 | 
| 2121 |  | -      () => ClipboardPrivacy.emptyPrivate(), // tor-browser#42019
 | 
| 2122 | 2159 |      ];
 | 
| 2123 | 2160 |  
 | 
| 2124 | 2161 |      for (let task of tasks) {
 |