tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

UnexpectedScriptObserver.sys.mjs (5499B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      4 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  EveryWindow: "resource:///modules/EveryWindow.sys.mjs",
      9 });
     10 
     11 const NOTIFICATION_VALUE = "unexpected-script-notification";
     12 
     13 export let UnexpectedScriptObserver = {
     14  // EveryWindow has a race condition in early startup where a callback may be called twice
     15  // for the same window. This WeakSet is used to track which windows have already been
     16  // handled to avoid showing the notification bar multiple times. See Bug 1982859
     17  _windowsHandled: new WeakSet(),
     18 
     19  _notificationHasBeenShown: false,
     20 
     21  async observe(aSubject, aTopic, aScriptName) {
     22    if (
     23      aTopic != "UnexpectedJavaScriptLoad-Live" &&
     24      aTopic != "UnexpectedJavaScriptLoad-ResetNotification" &&
     25      aTopic != "UnexpectedJavaScriptLoad-UserTookAction"
     26    ) {
     27      return;
     28    }
     29 
     30    if (aTopic == "UnexpectedJavaScriptLoad-ResetNotification") {
     31      this._notificationHasBeenShown = false;
     32      this._windowsHandled = new WeakSet();
     33      return;
     34    }
     35 
     36    if (aTopic == "UnexpectedJavaScriptLoad-UserTookAction") {
     37      lazy.EveryWindow.unregisterCallback("UnexpectedScriptLoadPanel");
     38      return;
     39    }
     40 
     41    if (
     42      Services.prefs.getBoolPref(
     43        "security.hide_parent_unrestricted_js_loads_warning.temporary",
     44        false
     45      )
     46    ) {
     47      return;
     48    }
     49 
     50    if (this._notificationHasBeenShown) {
     51      // There is already a notification bar, or we showed one in the past and we
     52      // won't show it again until browser restart
     53      return;
     54    }
     55 
     56    GleanPings.unexpectedScriptLoad.setEnabled(true);
     57 
     58    let scriptOrigin;
     59    try {
     60      let scriptUrl = new URL(aScriptName);
     61      scriptOrigin = scriptUrl.hostname;
     62 
     63      if (scriptUrl.protocol != "http:" && scriptUrl.protocol != "https:") {
     64        // For this dialog, we only care about loading scripts from web origins
     65        return;
     66      }
     67    } catch (e) {
     68      console.error("Invalid scriptName URL:", aScriptName);
     69      // For this dialog, we only care about loading scripts from web origins,
     70      // so if we couldn't parse it, just exit.
     71      return;
     72    }
     73 
     74    lazy.EveryWindow.registerCallback(
     75      "UnexpectedScriptLoadPanel",
     76      async window => {
     77        if (this._windowsHandled.has(window)) {
     78          return;
     79        }
     80        this._windowsHandled.add(window);
     81 
     82        let MozXULElement = window.MozXULElement;
     83        let document = window.document;
     84 
     85        MozXULElement.insertFTLIfNeeded("browser/unexpectedScript.ftl");
     86        let messageFragment = document.createDocumentFragment();
     87        let message = document.createElement("span");
     88        document.l10n.setAttributes(message, "unexpected-script-load-message", {
     89          origin: scriptOrigin,
     90        });
     91        messageFragment.appendChild(message);
     92 
     93        // ----------------------------------------------------------------
     94        let openWindow = action => {
     95          let args = {
     96            action,
     97            scriptName: aScriptName,
     98          };
     99          window.gDialogBox.open(
    100            "chrome://browser/content/security/unexpectedScriptLoad.xhtml",
    101            args
    102          );
    103        };
    104 
    105        // ----------------------------------------------------------------
    106        let buttons = [
    107          {
    108            supportPage: "unexpected-script-load",
    109            callback: () => {
    110              Glean.unexpectedScriptLoad.moreInfoOpened.record();
    111            },
    112          },
    113        ];
    114        buttons.push({
    115          "l10n-id": "unexpected-script-load-message-button-allow",
    116          callback: () => {
    117            openWindow("allow");
    118            return true;
    119          },
    120        });
    121        buttons.push({
    122          "l10n-id": "unexpected-script-load-message-button-block",
    123          callback: () => {
    124            openWindow("block");
    125            return true; // Do not close the dialog bar until the user has done so explcitly or taken an action
    126          },
    127        });
    128 
    129        let notificationBox = window.gNotificationBox;
    130 
    131        if (!notificationBox.getNotificationWithValue(NOTIFICATION_VALUE)) {
    132          await notificationBox.appendNotification(
    133            NOTIFICATION_VALUE,
    134            {
    135              label: messageFragment,
    136              priority: notificationBox.PRIORITY_WARNING_HIGH,
    137              eventCallback: event => {
    138                if (event == "dismissed") {
    139                  Glean.unexpectedScriptLoad.infobarDismissed.record();
    140                  GleanPings.unexpectedScriptLoad.submit();
    141                }
    142              },
    143            },
    144            buttons
    145          );
    146        }
    147      },
    148      // Unregister Callback:
    149      // This is needed to remove the notification bar from all the windows
    150      // once the user has taken action on one of them, and ensure any open
    151      // dialog box is also closed.
    152      async window => {
    153        window.gNotificationBox
    154          .getNotificationWithValue(NOTIFICATION_VALUE)
    155          ?.close();
    156        if (
    157          window.gDialogBox?.dialog?._openedURL ==
    158          "chrome://browser/content/security/unexpectedScriptLoad.xhtml"
    159        ) {
    160          window.gDialogBox.dialog.close();
    161        }
    162        GleanPings.unexpectedScriptLoad.submit();
    163      }
    164    );
    165 
    166    Glean.unexpectedScriptLoad.infobarShown.record();
    167 
    168    this._notificationHasBeenShown = true;
    169  },
    170 };