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