tor-browser

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

utilityOverlay.js (17200B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
      2 * This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 // Services = object with smart getters for common XPCOM services
      7 var { AppConstants } = ChromeUtils.importESModule(
      8  "resource://gre/modules/AppConstants.sys.mjs"
      9 );
     10 var { XPCOMUtils } = ChromeUtils.importESModule(
     11  "resource://gre/modules/XPCOMUtils.sys.mjs"
     12 );
     13 
     14 ChromeUtils.defineESModuleGetters(this, {
     15  AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
     16  AIWindow:
     17    "moz-src:///browser/components/aiwindow/ui/modules/AIWindow.sys.mjs",
     18  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
     19  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
     20  ContextualIdentityService:
     21    "resource://gre/modules/ContextualIdentityService.sys.mjs",
     22  ExtensionSettingsStore:
     23    "resource://gre/modules/ExtensionSettingsStore.sys.mjs",
     24  ExtensionUtils: "resource://gre/modules/ExtensionUtils.sys.mjs",
     25  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
     26  ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs",
     27  URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs",
     28 });
     29 
     30 ChromeUtils.defineLazyGetter(this, "ReferrerInfo", () =>
     31  Components.Constructor(
     32    "@mozilla.org/referrer-info;1",
     33    "nsIReferrerInfo",
     34    "init"
     35  )
     36 );
     37 
     38 Object.defineProperty(this, "BROWSER_NEW_TAB_URL", {
     39  enumerable: true,
     40  get() {
     41    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
     42      if (
     43        !PrivateBrowsingUtils.permanentPrivateBrowsing &&
     44        !AboutNewTab.newTabURLOverridden
     45      ) {
     46        return "about:privatebrowsing";
     47      }
     48      // If an extension controls the setting and does not have private
     49      // browsing permission, use the default setting.
     50      let extensionControlled = Services.prefs.getBoolPref(
     51        "browser.newtab.extensionControlled",
     52        false
     53      );
     54      let privateAllowed = Services.prefs.getBoolPref(
     55        "browser.newtab.privateAllowed",
     56        false
     57      );
     58      // There is a potential on upgrade that the prefs are not set yet, so we double check
     59      // for moz-extension.
     60      if (
     61        !privateAllowed &&
     62        (extensionControlled ||
     63          ExtensionUtils.isExtensionUrl(AboutNewTab.newTabURL))
     64      ) {
     65        return "about:privatebrowsing";
     66      }
     67    }
     68    if (AIWindow.isAIWindowActive(window)) {
     69      return AIWindow.newTabURL;
     70    }
     71    return AboutNewTab.newTabURL;
     72  },
     73 });
     74 
     75 var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
     76 
     77 var gBidiUI = false;
     78 
     79 /**
     80 * Determines whether the given url is considered a special URL for new tabs.
     81 */
     82 function isBlankPageURL(aURL) {
     83  return (
     84    aURL == "about:blank" ||
     85    aURL == "about:home" ||
     86    aURL == "about:tor" ||
     87    aURL == BROWSER_NEW_TAB_URL ||
     88    aURL == "chrome://browser/content/blanktab.html"
     89  );
     90 }
     91 
     92 function doGetProtocolFlags(aURI) {
     93  return Services.io.getDynamicProtocolFlags(aURI);
     94 }
     95 
     96 function openUILink(
     97  url,
     98  event,
     99  aIgnoreButton,
    100  aIgnoreAlt,
    101  aAllowThirdPartyFixup,
    102  aPostData,
    103  aReferrerInfo
    104 ) {
    105  return URILoadingHelper.openUILink(
    106    window,
    107    url,
    108    event,
    109    aIgnoreButton,
    110    aIgnoreAlt,
    111    aAllowThirdPartyFixup,
    112    aPostData,
    113    aReferrerInfo
    114  );
    115 }
    116 
    117 function openTrustedLinkIn(url, where, params) {
    118  URILoadingHelper.openTrustedLinkIn(window, url, where, params);
    119 }
    120 
    121 function openWebLinkIn(url, where, params) {
    122  URILoadingHelper.openWebLinkIn(window, url, where, params);
    123 }
    124 
    125 function openLinkIn(url, where, params) {
    126  return URILoadingHelper.openLinkIn(window, url, where, params);
    127 }
    128 
    129 // Used as an onclick handler for UI elements with link-like behavior.
    130 // e.g. onclick="checkForMiddleClick(this, event);"
    131 // Not needed for menuitems because those fire command events even on middle clicks.
    132 function checkForMiddleClick(node, event) {
    133  // We should be using the disabled property here instead of the attribute,
    134  // but some elements that this function is used with don't support it (e.g.
    135  // menuitem).
    136  if (node.getAttribute("disabled") == "true") {
    137    return;
    138  } // Do nothing
    139 
    140  if (event.target.tagName == "menuitem") {
    141    // Menu items fire command on middle-click by themselves.
    142    return;
    143  }
    144 
    145  if (event.button == 1) {
    146    /* Execute the node's oncommand or command.
    147     */
    148 
    149    let cmdEvent = document.createEvent("xulcommandevent");
    150    cmdEvent.initCommandEvent(
    151      "command",
    152      true,
    153      true,
    154      window,
    155      0,
    156      event.ctrlKey,
    157      event.altKey,
    158      event.shiftKey,
    159      event.metaKey,
    160      0,
    161      event,
    162      event.inputSource
    163    );
    164    node.dispatchEvent(cmdEvent);
    165 
    166    // Stop the propagation of the click event, to prevent the event from being
    167    // handled more than once.
    168    // E.g. see https://bugzilla.mozilla.org/show_bug.cgi?id=1657992#c4
    169    event.stopPropagation();
    170    event.preventDefault();
    171 
    172    // If the middle-click was on part of a menu, close the menu.
    173    // (Menus close automatically with left-click but not with middle-click.)
    174    closeMenus(event.target);
    175  }
    176 }
    177 
    178 // Populate a menu with user-context menu items. This method should be called
    179 // by onpopupshowing passing the event as first argument.
    180 function createUserContextMenu(
    181  event,
    182  {
    183    isContextMenu = false,
    184    excludeUserContextId = 0,
    185    showDefaultTab = false,
    186    useAccessKeys = true,
    187  } = {}
    188 ) {
    189  while (event.target.hasChildNodes()) {
    190    event.target.firstChild.remove();
    191  }
    192 
    193  MozXULElement.insertFTLIfNeeded("toolkit/global/contextual-identity.ftl");
    194  let docfrag = document.createDocumentFragment();
    195 
    196  // If we are excluding a userContextId, we want to add a 'no-container' item.
    197  if (excludeUserContextId || showDefaultTab) {
    198    let menuitem = document.createXULElement("menuitem");
    199    if (useAccessKeys) {
    200      document.l10n.setAttributes(menuitem, "user-context-none");
    201    } else {
    202      const label =
    203        ContextualIdentityService.formatContextLabel("user-context-none");
    204      menuitem.setAttribute("label", label);
    205    }
    206    menuitem.setAttribute("data-usercontextid", "0");
    207    if (!isContextMenu) {
    208      menuitem.setAttribute("command", "Browser:NewUserContextTab");
    209    }
    210 
    211    docfrag.appendChild(menuitem);
    212 
    213    let menuseparator = document.createXULElement("menuseparator");
    214    docfrag.appendChild(menuseparator);
    215  }
    216 
    217  ContextualIdentityService.getPublicIdentities().forEach(identity => {
    218    if (identity.userContextId == excludeUserContextId) {
    219      return;
    220    }
    221 
    222    let menuitem = document.createXULElement("menuitem");
    223    menuitem.setAttribute("data-usercontextid", identity.userContextId);
    224    if (identity.name) {
    225      menuitem.setAttribute("label", identity.name);
    226    } else if (useAccessKeys) {
    227      document.l10n.setAttributes(menuitem, identity.l10nId);
    228    } else {
    229      const label = ContextualIdentityService.formatContextLabel(
    230        identity.l10nId
    231      );
    232      menuitem.setAttribute("label", label);
    233    }
    234 
    235    menuitem.classList.add("menuitem-iconic");
    236    menuitem.classList.add("identity-color-" + identity.color);
    237 
    238    if (!isContextMenu) {
    239      menuitem.setAttribute("command", "Browser:NewUserContextTab");
    240    }
    241 
    242    menuitem.classList.add("identity-icon-" + identity.icon);
    243 
    244    docfrag.appendChild(menuitem);
    245  });
    246 
    247  if (!isContextMenu) {
    248    docfrag.appendChild(document.createXULElement("menuseparator"));
    249 
    250    let menuitem = document.createXULElement("menuitem");
    251    if (useAccessKeys) {
    252      document.l10n.setAttributes(menuitem, "user-context-manage-containers");
    253    } else {
    254      const label = ContextualIdentityService.formatContextLabel(
    255        "user-context-manage-containers"
    256      );
    257      menuitem.setAttribute("label", label);
    258    }
    259    menuitem.setAttribute("command", "Browser:OpenAboutContainers");
    260    docfrag.appendChild(menuitem);
    261  }
    262 
    263  event.target.appendChild(docfrag);
    264  return true;
    265 }
    266 
    267 // Closes all popups that are ancestors of the node.
    268 function closeMenus(node) {
    269  if ("tagName" in node) {
    270    if (
    271      node.namespaceURI ==
    272        "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" &&
    273      (node.tagName == "menupopup" || node.tagName == "popup")
    274    ) {
    275      node.hidePopup();
    276    }
    277 
    278    closeMenus(node.parentNode);
    279  }
    280 }
    281 
    282 /**
    283 * This function takes in a key element and compares it to the keys pressed during an event.
    284 *
    285 * @param aEvent
    286 *        The KeyboardEvent event you want to compare against your key.
    287 *
    288 * @param aKey
    289 *        The <key> element checked to see if it was called in aEvent.
    290 *        For example, aKey can be a variable set to document.getElementById("key_close")
    291 *        to check if the close command key was pressed in aEvent.
    292 */
    293 function eventMatchesKey(aEvent, aKey) {
    294  let keyPressed = (aKey.getAttribute("key") || "").toLowerCase();
    295  let keyModifiers = aKey.getAttribute("modifiers");
    296  let modifiers = ["Alt", "Control", "Meta", "Shift"];
    297 
    298  if (aEvent.key != keyPressed) {
    299    return false;
    300  }
    301  let eventModifiers = modifiers.filter(modifier =>
    302    aEvent.getModifierState(modifier)
    303  );
    304  // Check if aEvent has a modifier and aKey doesn't
    305  if (eventModifiers.length && !keyModifiers.length) {
    306    return false;
    307  }
    308  // Check whether aKey's modifiers match aEvent's modifiers
    309  if (keyModifiers) {
    310    keyModifiers = keyModifiers.split(/[\s,]+/);
    311    // Capitalize first letter of aKey's modifers to compare to aEvent's modifier
    312    keyModifiers.forEach(function (modifier, index) {
    313      if (modifier == "accel") {
    314        keyModifiers[index] =
    315          AppConstants.platform == "macosx" ? "Meta" : "Control";
    316      } else {
    317        keyModifiers[index] = modifier[0].toUpperCase() + modifier.slice(1);
    318      }
    319    });
    320    return modifiers.every(
    321      modifier =>
    322        keyModifiers.includes(modifier) == aEvent.getModifierState(modifier)
    323    );
    324  }
    325  return true;
    326 }
    327 
    328 // Gather all descendent text under given document node.
    329 function gatherTextUnder(root) {
    330  var text = "";
    331  var node = root.firstChild;
    332  var depth = 1;
    333  while (node && depth > 0) {
    334    // See if this node is text.
    335    if (node.nodeType == Node.TEXT_NODE) {
    336      // Add this text to our collection.
    337      text += " " + node.data;
    338    } else if (HTMLImageElement.isInstance(node)) {
    339      // If it has an "alt" attribute, add that.
    340      var altText = node.getAttribute("alt");
    341      if (altText) {
    342        text += " " + altText;
    343      }
    344    }
    345    // Find next node to test.
    346    // First, see if this node has children.
    347    if (node.hasChildNodes()) {
    348      // Go to first child.
    349      node = node.firstChild;
    350      depth++;
    351    } else {
    352      // No children, try next sibling (or parent next sibling).
    353      while (depth > 0 && !node.nextSibling) {
    354        node = node.parentNode;
    355        depth--;
    356      }
    357      if (node.nextSibling) {
    358        node = node.nextSibling;
    359      }
    360    }
    361  }
    362  // Strip leading and tailing whitespace.
    363  text = text.trim();
    364  // Compress remaining whitespace.
    365  text = text.replace(/\s+/g, " ");
    366  return text;
    367 }
    368 
    369 // This function exists for legacy reasons.
    370 function getShellService() {
    371  return ShellService;
    372 }
    373 
    374 function isBidiEnabled() {
    375  // first check the pref.
    376  if (Services.prefs.getBoolPref("bidi.browser.ui", false)) {
    377    return true;
    378  }
    379 
    380  // now see if the app locale is an RTL one.
    381  const isRTL = Services.locale.isAppLocaleRTL;
    382 
    383  if (isRTL) {
    384    Services.prefs.setBoolPref("bidi.browser.ui", true);
    385  }
    386  return isRTL;
    387 }
    388 
    389 function openAboutDialog() {
    390  for (let win of Services.wm.getEnumerator("Browser:About")) {
    391    // Only open one about window (Bug 599573)
    392    if (win.closed) {
    393      continue;
    394    }
    395    win.focus();
    396    return;
    397  }
    398 
    399  var features = "chrome,";
    400  if (AppConstants.platform == "win") {
    401    features += "centerscreen,dependent";
    402  } else if (AppConstants.platform == "macosx") {
    403    features += "centerscreen,resizable=no,minimizable=no";
    404  } else {
    405    features += "centerscreen,dependent,dialog=no";
    406  }
    407 
    408  window.openDialog("chrome://browser/content/aboutDialog.xhtml", "", features);
    409 }
    410 
    411 async function openPreferences(paneID, extraArgs) {
    412  // This function is duplicated from preferences.js.
    413  function internalPrefCategoryNameToFriendlyName(aName) {
    414    return (aName || "").replace(/^pane./, function (toReplace) {
    415      return toReplace[4].toLowerCase();
    416    });
    417  }
    418 
    419  let win = Services.wm.getMostRecentWindow("navigator:browser");
    420  let friendlyCategoryName = internalPrefCategoryNameToFriendlyName(paneID);
    421  let params;
    422  if (extraArgs && extraArgs.urlParams) {
    423    params = new URLSearchParams();
    424    let urlParams = extraArgs.urlParams;
    425    for (let name in urlParams) {
    426      if (urlParams[name] !== undefined) {
    427        params.set(name, urlParams[name]);
    428      }
    429    }
    430  }
    431  let preferencesURLSuffix =
    432    (params ? "?" + params : "") +
    433    (friendlyCategoryName ? "#" + friendlyCategoryName : "");
    434  let newLoad = true;
    435  let browser = null;
    436  if (!win) {
    437    let windowArguments = Cc["@mozilla.org/array;1"].createInstance(
    438      Ci.nsIMutableArray
    439    );
    440    let supportsStringPrefURL = Cc[
    441      "@mozilla.org/supports-string;1"
    442    ].createInstance(Ci.nsISupportsString);
    443    supportsStringPrefURL.data = "about:preferences" + preferencesURLSuffix;
    444    windowArguments.appendElement(supportsStringPrefURL);
    445 
    446    win = Services.ww.openWindow(
    447      null,
    448      AppConstants.BROWSER_CHROME_URL,
    449      "_blank",
    450      "chrome,dialog=no,all",
    451      windowArguments
    452    );
    453  } else {
    454    let shouldReplaceFragment = friendlyCategoryName
    455      ? "whenComparingAndReplace"
    456      : "whenComparing";
    457    newLoad = !win.switchToTabHavingURI(
    458      "about:settings" + preferencesURLSuffix,
    459      false,
    460      {
    461        ignoreFragment: shouldReplaceFragment,
    462        replaceQueryString: true,
    463        triggeringPrincipal:
    464          Services.scriptSecurityManager.getSystemPrincipal(),
    465      }
    466    );
    467    if (newLoad) {
    468      newLoad = !win.switchToTabHavingURI(
    469        "about:preferences" + preferencesURLSuffix,
    470        true,
    471        {
    472          ignoreFragment: shouldReplaceFragment,
    473          replaceQueryString: true,
    474          triggeringPrincipal:
    475            Services.scriptSecurityManager.getSystemPrincipal(),
    476        }
    477      );
    478    }
    479    browser = win.gBrowser.selectedBrowser;
    480  }
    481 
    482  if (!newLoad && paneID) {
    483    if (browser.contentDocument?.readyState != "complete") {
    484      await new Promise(resolve => {
    485        browser.addEventListener("load", resolve, {
    486          capture: true,
    487          once: true,
    488        });
    489      });
    490    }
    491    browser.contentWindow.gotoPref(paneID);
    492  }
    493 }
    494 
    495 /**
    496 * Opens the troubleshooting information (about:support) page for this version
    497 * of the application.
    498 */
    499 function openTroubleshootingPage() {
    500  openTrustedLinkIn("about:support", "tab");
    501 }
    502 
    503 /**
    504 * Opens the feedback page for this version of the application.
    505 */
    506 function openFeedbackPage() {
    507  var url = Services.urlFormatter.formatURLPref("app.feedback.baseURL");
    508  openTrustedLinkIn(url, "tab");
    509 }
    510 
    511 /**
    512 * Appends UTM parameters to then opens the SUMO URL for device migration.
    513 */
    514 function openSwitchingDevicesPage() {
    515  let url = getHelpLinkURL("switching-devices");
    516  let parsedUrl = new URL(url);
    517  parsedUrl.searchParams.set("utm_content", "switch-to-new-device");
    518  parsedUrl.searchParams.set("utm_source", "help-menu");
    519  parsedUrl.searchParams.set("utm_medium", "firefox-desktop");
    520  parsedUrl.searchParams.set("utm_campaign", "migration");
    521  openTrustedLinkIn(parsedUrl.href, "tab");
    522 }
    523 
    524 function buildHelpMenu() {
    525  document.getElementById("feedbackPage").disabled =
    526    !Services.policies.isAllowed("feedbackCommands");
    527 
    528  document.getElementById("helpSafeMode").disabled =
    529    !Services.policies.isAllowed("safeMode");
    530 
    531  document.getElementById("troubleShooting").disabled =
    532    !Services.policies.isAllowed("aboutSupport");
    533 
    534  let supportMenu = Services.policies.getSupportMenu();
    535  if (supportMenu) {
    536    let menuitem = document.getElementById("helpPolicySupport");
    537    menuitem.hidden = false;
    538    menuitem.setAttribute("label", supportMenu.Title);
    539    if ("AccessKey" in supportMenu) {
    540      menuitem.setAttribute("accesskey", supportMenu.AccessKey);
    541    }
    542    document.getElementById("helpPolicySeparator").hidden = false;
    543  }
    544 
    545  // Enable/disable the "Report Web Forgery" menu item.
    546  if (typeof gSafeBrowsing != "undefined") {
    547    gSafeBrowsing.setReportPhishingMenu();
    548  }
    549 }
    550 
    551 function isElementVisible(aElement) {
    552  if (!aElement) {
    553    return false;
    554  }
    555 
    556  // If aElement or a direct or indirect parent is hidden or collapsed,
    557  // height, width or both will be 0.
    558  var rect = aElement.getBoundingClientRect();
    559  return rect.height > 0 && rect.width > 0;
    560 }
    561 
    562 function makeURLAbsolute(aBase, aUrl) {
    563  // Note:  makeURI() will throw if aUri is not a valid URI
    564  return makeURI(aUrl, null, makeURI(aBase)).spec;
    565 }
    566 
    567 function getHelpLinkURL(aHelpTopic) {
    568  if (aHelpTopic === "firefox-help" || aHelpTopic === "firefox-osxkey") {
    569    return "about:manual";
    570  }
    571  var url = Services.urlFormatter.formatURLPref("app.support.baseURL");
    572  return url + aHelpTopic;
    573 }
    574 
    575 function openHelpLink(aHelpTopic) {
    576  openTrustedLinkIn(getHelpLinkURL(aHelpTopic), "tab");
    577 }