Verified Commit 36476c38 authored by ma1's avatar ma1 Committed by Pier Angelo Vendrame
Browse files

fixup! Bug 42019: Empty browser's clipboard on browser shutdown

Bug 42154: empty clipboard content from private windows on exit.
parent 173e8520
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -93,6 +93,9 @@ pref("browser.pagethumbnails.capturing_disabled", true);
pref("privacy.exposeContentTitleInWindow", false);
pref("privacy.exposeContentTitleInWindow.pbm", false);

// Empty clipboard content from private windows on exit (tor-browser#42154)
pref("browser.privatebrowsing.preserveClipboard", false);

// Enable HTTPS-Only mode (tor-browser#19850)
pref("dom.security.https_only_mode", true);
// The previous pref automatically sets this to true (see StaticPrefList.yaml),
+116 −1
Original line number Diff line number Diff line
@@ -154,6 +154,119 @@ const PRIVATE_BROWSING_EXE_ICON_INDEX = 1;
const PREF_PRIVATE_BROWSING_SHORTCUT_CREATED =
  "browser.privacySegmentation.createdShortcut";

// Empty clipboard content from private windows on exit
// (tor-browser#42154)
const ClipboardPrivacy = {
  _lastClipboardHash: null,
  _globalActivation: false,
  _isPrivateClipboard: false,
  _hasher: null,

  _computeClipboardHash(win = Services.ww.activeWindow) {
    const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
      Ci.nsITransferable
    );
    trans.init(win?.docShell?.QueryInterface(Ci.nsILoadContext) || null);
    ["text/x-moz-url", "text/plain"].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;
  },

  startup() {
    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 clipboardHash = this._computeClipboardHash(win);
      if (clipboardHash !== this._lastClipboardHash) {
        this._isPrivateClipboard =
          !activation &&
          (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
            lazy.PrivateBrowsingUtils.isWindowPrivate(win));
        this._lastClipboardHash = clipboardHash;
        console.log(
          `Clipboard changed: private ${this._isPrivateClipboard}, hash ${clipboardHash}.`
        );
      }
    };
    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) &&
            !(
              lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
              Array.from(Services.ww.getWindowEnumerator()).find(w =>
                lazy.PrivateBrowsingUtils.isWindowPrivate(w)
              )
            )
          ) {
            // no more private windows, empty private content if needed
            this.emptyPrivate();
          }
      }
    });
  },
  emptyPrivate() {
    if (
      this._isPrivateClipboard &&
      !Services.prefs.getBoolPref(
        "browser.privatebrowsing.preserveClipboard",
        false
      ) &&
      this._lastClipboardHash === this._computeClipboardHash()
    ) {
      Services.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
      this._lastClipboardHash = null;
      this._isPrivateClipboard = false;
      console.log("Private clipboard emptied.");
    }
  },
};

/**
 * Fission-compatible JSProcess implementations.
 * Each actor options object takes the form of a ProcessActorOptions dictionary.
@@ -1725,6 +1838,8 @@ BrowserGlue.prototype = {

    lazy.DoHController.init();

    ClipboardPrivacy.startup();

    this._firstWindowTelemetry(aWindow);
    this._firstWindowLoaded();

@@ -1986,7 +2101,7 @@ BrowserGlue.prototype = {
          lazy.UpdateListener.reset();
        }
      },
      () => Services.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard), // tor-browser#42019
      () => ClipboardPrivacy.emptyPrivate(), // tor-browser#42019
    ];

    for (let task of tasks) {