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