mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			748 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			748 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
 | 
						|
 | 
						|
const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties";
 | 
						|
 | 
						|
export function WebProtocolHandlerRegistrar() {}
 | 
						|
 | 
						|
const lazy = {};
 | 
						|
 | 
						|
XPCOMUtils.defineLazyServiceGetters(lazy, {
 | 
						|
  ExternalProtocolService: [
 | 
						|
    "@mozilla.org/uriloader/external-protocol-service;1",
 | 
						|
    "nsIExternalProtocolService",
 | 
						|
  ],
 | 
						|
});
 | 
						|
 | 
						|
ChromeUtils.defineESModuleGetters(lazy, {
 | 
						|
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
 | 
						|
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
 | 
						|
});
 | 
						|
 | 
						|
ChromeUtils.defineLazyGetter(lazy, "log", () => {
 | 
						|
  let { ConsoleAPI } = ChromeUtils.importESModule(
 | 
						|
    "resource://gre/modules/Console.sys.mjs"
 | 
						|
  );
 | 
						|
  let consoleOptions = {
 | 
						|
    // tip: set maxLogLevel to "debug" and use lazy.log.debug() to create
 | 
						|
    // detailed messages during development. See LOG_LEVELS in Console.sys.mjs
 | 
						|
    // for details.
 | 
						|
    maxLogLevel: "warning",
 | 
						|
    maxLogLevelPref: "browser.protocolhandler.loglevel",
 | 
						|
    prefix: "WebProtocolHandlerRegistrar.sys.mjs",
 | 
						|
  };
 | 
						|
  return new ConsoleAPI(consoleOptions);
 | 
						|
});
 | 
						|
 | 
						|
WebProtocolHandlerRegistrar.prototype = {
 | 
						|
  get stringBundle() {
 | 
						|
    let sb = Services.strings.createBundle(STRING_BUNDLE_URI);
 | 
						|
    delete WebProtocolHandlerRegistrar.prototype.stringBundle;
 | 
						|
    return (WebProtocolHandlerRegistrar.prototype.stringBundle = sb);
 | 
						|
  },
 | 
						|
 | 
						|
  _getFormattedString(key, params) {
 | 
						|
    return this.stringBundle.formatStringFromName(key, params);
 | 
						|
  },
 | 
						|
 | 
						|
  _getString(key) {
 | 
						|
    return this.stringBundle.GetStringFromName(key);
 | 
						|
  },
 | 
						|
 | 
						|
  /* Because we want to iterate over the known webmailers in the observe method
 | 
						|
   * and with each site visited, we want to check as fast as possible if the
 | 
						|
   * current site is already registered as a mailto handler. Using the sites
 | 
						|
   * domain name as a key ensures that we can use Map.has(...) later to find
 | 
						|
   * it.
 | 
						|
   */
 | 
						|
  _addedObservers: 0,
 | 
						|
  _knownWebmailerCache: new Map(),
 | 
						|
  _ensureWebmailerCache() {
 | 
						|
    this._knownWebmailerCache = new Map();
 | 
						|
 | 
						|
    const handler =
 | 
						|
      lazy.ExternalProtocolService.getProtocolHandlerInfo("mailto");
 | 
						|
 | 
						|
    for (const h of handler.possibleApplicationHandlers.enumerate()) {
 | 
						|
      // Services.io.newURI could fail for broken handlers in which case we
 | 
						|
      // simply leave them out, but write a debug message (just in case)
 | 
						|
      try {
 | 
						|
        if (h instanceof Ci.nsIWebHandlerApp && h.uriTemplate) {
 | 
						|
          const mailerUri = Services.io.newURI(h.uriTemplate);
 | 
						|
          if (mailerUri.scheme == "https") {
 | 
						|
            this._knownWebmailerCache.set(
 | 
						|
              Services.io.newURI(h.uriTemplate).host,
 | 
						|
              {
 | 
						|
                uriPath: Services.io.newURI(h.uriTemplate).resolve("."),
 | 
						|
                uriTemplate: Services.io.newURI(h.uriTemplate),
 | 
						|
                name: h.name,
 | 
						|
              }
 | 
						|
            );
 | 
						|
          }
 | 
						|
        }
 | 
						|
      } catch (e) {
 | 
						|
        lazy.log.debug(`Could not add ${h.uriTemplate} to cache: ${e.message}`);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /* This function can be called multiple times and (re-)initializes the cache
 | 
						|
   * if the feature is toggled on. If called with the feature off it will also
 | 
						|
   * unregister the observers.
 | 
						|
   *
 | 
						|
   * @param {boolean} firstInit
 | 
						|
   *
 | 
						|
   */
 | 
						|
  init(firstInit = false) {
 | 
						|
    if (firstInit) {
 | 
						|
      lazy.NimbusFeatures.mailto.onUpdate(() =>
 | 
						|
        // make firstInit explicitly false to avoid multiple registrations.
 | 
						|
        this.init(false)
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    const observers = ["mailto::onLocationChange", "mailto::onClearCache"];
 | 
						|
    if (
 | 
						|
      lazy.NimbusFeatures.mailto.getVariable("dualPrompt") &&
 | 
						|
      lazy.NimbusFeatures.mailto.getVariable("dualPrompt.onLocationChange")
 | 
						|
    ) {
 | 
						|
      this._ensureWebmailerCache();
 | 
						|
      // Make sure, that our local observers are never registered twice:
 | 
						|
      if (0 == this._addedObservers) {
 | 
						|
        observers.forEach(o => {
 | 
						|
          this._addedObservers++;
 | 
						|
          Services.obs.addObserver(this, o);
 | 
						|
        });
 | 
						|
        lazy.log.debug(`mailto observers activated: [${observers}]`);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      // With `dualPrompt` and `dualPrompt.onLocationChange` toggled on we get
 | 
						|
      // up to two notifications when we turn the feature off again, but we
 | 
						|
      // only want to unregister the observers once.
 | 
						|
      //
 | 
						|
      // Using `hasObservers` would allow us to loop over all observers as long
 | 
						|
      // as there are more, but hasObservers is not implemented hence why we
 | 
						|
      // use `enumerateObservers` here to create the loop and `hasMoreElements`
 | 
						|
      // to return true or false as `hasObservers` would if it existed.
 | 
						|
      observers.forEach(o => {
 | 
						|
        if (
 | 
						|
          0 < this._addedObservers &&
 | 
						|
          Services.obs.enumerateObservers(o).hasMoreElements()
 | 
						|
        ) {
 | 
						|
          Services.obs.removeObserver(this, o);
 | 
						|
          this._addedObservers--;
 | 
						|
          lazy.log.debug(`mailto observer "${o}" deactivated.`);
 | 
						|
        }
 | 
						|
      });
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  async observe(aBrowser, aTopic) {
 | 
						|
    try {
 | 
						|
      switch (aTopic) {
 | 
						|
        case "mailto::onLocationChange": {
 | 
						|
          // registerProtocolHandler only works for https
 | 
						|
          const uri = aBrowser.currentURI;
 | 
						|
          if (!uri?.schemeIs("https")) {
 | 
						|
            return;
 | 
						|
          }
 | 
						|
 | 
						|
          const host = uri.host;
 | 
						|
          if (this._knownWebmailerCache.has(host)) {
 | 
						|
            // second: search the cache for an entry which starts with the path
 | 
						|
            // of the current uri. If it exists we identified the current page as
 | 
						|
            // webmailer (again).
 | 
						|
            const value = this._knownWebmailerCache.get(host);
 | 
						|
            this._askUserToSetMailtoHandler(
 | 
						|
              aBrowser,
 | 
						|
              "mailto",
 | 
						|
              value.uriTemplate,
 | 
						|
              value.name
 | 
						|
            );
 | 
						|
          }
 | 
						|
          break; // the switch(topic) statement
 | 
						|
        }
 | 
						|
        case "mailto::onClearCache":
 | 
						|
          // clear the cache for now. We could try to dynamically update the
 | 
						|
          // cache, which is easy if a webmailer is added to the settings, but
 | 
						|
          // becomes more complicated when webmailers are removed, because then
 | 
						|
          // the store gets rewritten and we would require an event to deal with
 | 
						|
          // that as well. So instead we recreate it entirely.
 | 
						|
          this._ensureWebmailerCache();
 | 
						|
          break;
 | 
						|
        default:
 | 
						|
          lazy.log.debug(`observe reached with unknown topic: ${aTopic}`);
 | 
						|
      }
 | 
						|
    } catch (e) {
 | 
						|
      lazy.log.debug(`Problem in observer: ${e}`);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * See nsIWebProtocolHandlerRegistrar
 | 
						|
   */
 | 
						|
  removeProtocolHandler(aProtocol, aURITemplate) {
 | 
						|
    let handlerInfo =
 | 
						|
      lazy.ExternalProtocolService.getProtocolHandlerInfo(aProtocol);
 | 
						|
    let handlers = handlerInfo.possibleApplicationHandlers;
 | 
						|
    for (let i = 0; i < handlers.length; i++) {
 | 
						|
      try {
 | 
						|
        // We only want to test web handlers
 | 
						|
        let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
 | 
						|
        if (handler.uriTemplate == aURITemplate) {
 | 
						|
          handlers.removeElementAt(i);
 | 
						|
          let hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
 | 
						|
            Ci.nsIHandlerService
 | 
						|
          );
 | 
						|
          hs.store(handlerInfo);
 | 
						|
          return;
 | 
						|
        }
 | 
						|
      } catch (e) {
 | 
						|
        /* it wasn't a web handler */
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Determines if a web handler is already registered.
 | 
						|
   *
 | 
						|
   * @param {string} aProtocol
 | 
						|
   *        The scheme of the web handler we are checking for.
 | 
						|
   * @param {string} aURITemplate
 | 
						|
   *        The URI template that the handler uses to handle the protocol.
 | 
						|
   * @returns {boolean} true if it is already registered, false otherwise.
 | 
						|
   */
 | 
						|
  _protocolHandlerRegistered(aProtocol, aURITemplate) {
 | 
						|
    let handlerInfo =
 | 
						|
      lazy.ExternalProtocolService.getProtocolHandlerInfo(aProtocol);
 | 
						|
    let handlers = handlerInfo.possibleApplicationHandlers;
 | 
						|
    for (let handler of handlers.enumerate()) {
 | 
						|
      // We only want to test web handlers
 | 
						|
      if (
 | 
						|
        handler instanceof Ci.nsIWebHandlerApp &&
 | 
						|
        handler.uriTemplate == aURITemplate
 | 
						|
      ) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns true if aURITemplate.spec points to the currently configured
 | 
						|
   * handler for aProtocol links and the OS default is also readily configured.
 | 
						|
   * Returns false if some of it can be made default.
 | 
						|
   *
 | 
						|
   * @param {string} aProtocol
 | 
						|
   *        The scheme of the web handler we are checking for.
 | 
						|
   * @param {string} aURITemplate
 | 
						|
   *        The URI template that the handler uses to handle the protocol.
 | 
						|
   */
 | 
						|
  _isProtocolHandlerDefault(aProtocol, aURITemplate) {
 | 
						|
    const handlerInfo =
 | 
						|
      lazy.ExternalProtocolService.getProtocolHandlerInfo(aProtocol);
 | 
						|
 | 
						|
    if (
 | 
						|
      handlerInfo.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
 | 
						|
      handlerInfo.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp
 | 
						|
    ) {
 | 
						|
      let webHandlerApp =
 | 
						|
        handlerInfo.preferredApplicationHandler.QueryInterface(
 | 
						|
          Ci.nsIWebHandlerApp
 | 
						|
        );
 | 
						|
 | 
						|
      // If we are already configured as default, we cannot set a new default
 | 
						|
      // and if the current site is already registered as default webmailer we
 | 
						|
      // are fully set up as the default app for webmail.
 | 
						|
      if (
 | 
						|
        !this._canSetOSDefault(aProtocol) &&
 | 
						|
        webHandlerApp.uriTemplate == aURITemplate.spec
 | 
						|
      ) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Private method to return the installHash, which is important for app
 | 
						|
   * registration on OS level. Without it apps cannot be default/handler apps
 | 
						|
   * under Windows. Because this value is used to check if its possible to reset
 | 
						|
   * the default and to actually set it as well, this function puts the
 | 
						|
   * acquisition of the installHash in one place in the hopes that check and set
 | 
						|
   * conditions will never deviate.
 | 
						|
   *
 | 
						|
   * @returns {string} installHash
 | 
						|
   */
 | 
						|
  _getInstallHash() {
 | 
						|
    const xreDirProvider = Cc[
 | 
						|
      "@mozilla.org/xre/directory-provider;1"
 | 
						|
    ].getService(Ci.nsIXREDirProvider);
 | 
						|
    return xreDirProvider.getInstallHash();
 | 
						|
  },
 | 
						|
 | 
						|
  /* Private method to check if we are already the default protocolhandler
 | 
						|
   * for `protocol`.
 | 
						|
   *
 | 
						|
   * @param {string} protocol name, e.g. mailto (without ://)
 | 
						|
   * @returns {boolean}
 | 
						|
   */
 | 
						|
  _isOsDefault(protocol) {
 | 
						|
    let shellService = Cc[
 | 
						|
      "@mozilla.org/browser/shell-service;1"
 | 
						|
    ].createInstance(Ci.nsIWindowsShellService);
 | 
						|
 | 
						|
    if (shellService.isDefaultHandlerFor(protocol)) {
 | 
						|
      lazy.log.debug("_isOsDefault returns true.");
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
 | 
						|
    lazy.log.debug("_isOsDefault returns false.");
 | 
						|
    return false;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Private method to determine if we can set a new OS default for a certain
 | 
						|
   * protocol.
 | 
						|
   *
 | 
						|
   * @param {string} protocol name, e.g. mailto (without ://)
 | 
						|
   * @returns {boolean}
 | 
						|
   */
 | 
						|
  _canSetOSDefault(protocol) {
 | 
						|
    // an installHash is required for the association with a scheme handler,
 | 
						|
    // also see _setOSDefault()
 | 
						|
    if ("" == this._getInstallHash()) {
 | 
						|
      lazy.log.debug("_canSetOSDefault returns false.");
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (this._isOsDefault(protocol)) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    return true;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Private method to reset the OS default for a certain protocol/uri scheme.
 | 
						|
   * We basically ignore, that setDefaultExtensionHandlersUserChoice can fail
 | 
						|
   * when the installHash is wrong or cannot be determined.
 | 
						|
   *
 | 
						|
   * @param {string} protocol name, e.g. mailto (without ://)
 | 
						|
   * @returns {boolean}
 | 
						|
   */
 | 
						|
  _setOSDefault(protocol) {
 | 
						|
    try {
 | 
						|
      let defaultAgent = Cc["@mozilla.org/default-agent;1"].createInstance(
 | 
						|
        Ci.nsIDefaultAgent
 | 
						|
      );
 | 
						|
      defaultAgent.setDefaultExtensionHandlersUserChoice(
 | 
						|
        this._getInstallHash(),
 | 
						|
        [protocol, "FirefoxURL"]
 | 
						|
      );
 | 
						|
      return true;
 | 
						|
    } catch (e) {
 | 
						|
      // TODO: why could not we just add the installHash and promote the running
 | 
						|
      // install to be a properly installed one?
 | 
						|
      lazy.log.debug(
 | 
						|
        "Could not set Firefox as default application for " +
 | 
						|
          protocol +
 | 
						|
          ", because: " +
 | 
						|
          e.message
 | 
						|
      );
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Private method to set the default uri to handle a certain protocol. This
 | 
						|
   * automates in a way what a user can do in settings under applications,
 | 
						|
   * where different 'actions' can be chosen for different 'content types'.
 | 
						|
   *
 | 
						|
   * @param {string} protocol
 | 
						|
   * @param {handler} handler
 | 
						|
   */
 | 
						|
  _setProtocolHandlerDefault(protocol, handler) {
 | 
						|
    let handlerInfo =
 | 
						|
      lazy.ExternalProtocolService.getProtocolHandlerInfo(protocol);
 | 
						|
    handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
 | 
						|
    handlerInfo.preferredApplicationHandler = handler;
 | 
						|
    handlerInfo.alwaysAskBeforeHandling = false;
 | 
						|
    let hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
 | 
						|
      Ci.nsIHandlerService
 | 
						|
    );
 | 
						|
    hs.store(handlerInfo);
 | 
						|
    return handlerInfo;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Private method to add a ProtocolHandler of type nsIWebHandlerApp to the
 | 
						|
   * list of possible handlers for a protocol.
 | 
						|
   *
 | 
						|
   * @param {string} protocol - e.g. 'mailto', so again without ://
 | 
						|
   * @param {string} name - the protocol associated 'Action'
 | 
						|
   * @param {string} uri - the uri (compare 'use other...' in the preferences)
 | 
						|
   * @returns {handler} handler - either the existing one or a newly created
 | 
						|
   */
 | 
						|
  _addWebProtocolHandler(protocol, name, uri) {
 | 
						|
    let phi = lazy.ExternalProtocolService.getProtocolHandlerInfo(protocol);
 | 
						|
    // not adding duplicates and bail out with the existing entry
 | 
						|
    for (let h of phi.possibleApplicationHandlers.enumerate()) {
 | 
						|
      if (h instanceof Ci.nsIWebHandlerApp && h.uriTemplate === uri) {
 | 
						|
        return h;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(
 | 
						|
      Ci.nsIWebHandlerApp
 | 
						|
    );
 | 
						|
    handler.name = name;
 | 
						|
    handler.uriTemplate = uri;
 | 
						|
 | 
						|
    let handlerInfo =
 | 
						|
      lazy.ExternalProtocolService.getProtocolHandlerInfo(protocol);
 | 
						|
    handlerInfo.possibleApplicationHandlers.appendElement(handler);
 | 
						|
 | 
						|
    let hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
 | 
						|
      Ci.nsIHandlerService
 | 
						|
    );
 | 
						|
    hs.store(handlerInfo);
 | 
						|
 | 
						|
    return handler;
 | 
						|
  },
 | 
						|
 | 
						|
  /*
 | 
						|
   * Function to store a value associated to a domain using the content pref
 | 
						|
   * service.
 | 
						|
   *
 | 
						|
   * @param {string} domain: the domain for this setting
 | 
						|
   * @param {string} setting: the name of the setting
 | 
						|
   * @param {string} value: the actual setting to be stored
 | 
						|
   * @param {string} context (optional): private window or not
 | 
						|
   * @returns {string} the stored preference (see: nsIContentPrefService2.idl)
 | 
						|
   */
 | 
						|
  async _saveSiteSpecificSetting(domain, setting, value, context = null) {
 | 
						|
    const gContentPrefs = Cc["@mozilla.org/content-pref/service;1"].getService(
 | 
						|
      Ci.nsIContentPrefService2
 | 
						|
    );
 | 
						|
 | 
						|
    return new Promise((resolve, reject) => {
 | 
						|
      gContentPrefs.set(domain, setting, value, context, {
 | 
						|
        handleResult(pref) {
 | 
						|
          resolve(pref);
 | 
						|
        },
 | 
						|
        handleCompletion() {},
 | 
						|
        handleError(err) {
 | 
						|
          reject(err);
 | 
						|
        },
 | 
						|
      });
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  /*
 | 
						|
   * Function to return a stored value from the content pref service. Returns
 | 
						|
   * a promise, so await can be used to synchonize the retrieval.
 | 
						|
   *
 | 
						|
   * @param {string} domain: the domain for this setting
 | 
						|
   * @param {string} setting: the name of the setting
 | 
						|
   * @param {string} context (optional): private window or not
 | 
						|
   * @param {string} def (optional): the default value to return
 | 
						|
   * @returns {string} either stored value or ""
 | 
						|
   */
 | 
						|
  async _getSiteSpecificSetting(domain, setting, context = null, def = null) {
 | 
						|
    const gContentPrefs = Cc["@mozilla.org/content-pref/service;1"].getService(
 | 
						|
      Ci.nsIContentPrefService2
 | 
						|
    );
 | 
						|
 | 
						|
    return await new Promise((resolve, reject) => {
 | 
						|
      gContentPrefs.getByDomainAndName(domain, setting, context, {
 | 
						|
        _result: def,
 | 
						|
        handleResult(pref) {
 | 
						|
          this._result = pref.value;
 | 
						|
        },
 | 
						|
        handleCompletion(_) {
 | 
						|
          resolve(this._result);
 | 
						|
        },
 | 
						|
        handleError(err) {
 | 
						|
          reject(err);
 | 
						|
        },
 | 
						|
      });
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * See nsIWebProtocolHandlerRegistrar
 | 
						|
   */
 | 
						|
  registerProtocolHandler(
 | 
						|
    aProtocol,
 | 
						|
    aURI,
 | 
						|
    aTitle,
 | 
						|
    aDocumentURI,
 | 
						|
    aBrowserOrWindow
 | 
						|
  ) {
 | 
						|
    // first mitigation: check if the API call comes from another domain
 | 
						|
    aProtocol = (aProtocol || "").toLowerCase();
 | 
						|
    if (!aURI || !aDocumentURI) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let browser = aBrowserOrWindow; // This is the e10s case.
 | 
						|
    if (aBrowserOrWindow instanceof Ci.nsIDOMWindow) {
 | 
						|
      // In the non-e10s case, grab the browser off the same-process window.
 | 
						|
      let rootDocShell = aBrowserOrWindow.docShell.sameTypeRootTreeItem;
 | 
						|
      browser = rootDocShell.QueryInterface(Ci.nsIDocShell).chromeEventHandler;
 | 
						|
    }
 | 
						|
 | 
						|
    let browserWindow = browser.ownerGlobal;
 | 
						|
    try {
 | 
						|
      browserWindow.navigator.checkProtocolHandlerAllowed(
 | 
						|
        aProtocol,
 | 
						|
        aURI,
 | 
						|
        aDocumentURI
 | 
						|
      );
 | 
						|
    } catch (ex) {
 | 
						|
      // We should have already shown the user an error.
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    if (lazy.NimbusFeatures.mailto.getVariable("dualPrompt")) {
 | 
						|
      if ("mailto" === aProtocol) {
 | 
						|
        lazy.NimbusFeatures.mailto.recordExposureEvent();
 | 
						|
        this._askUserToSetMailtoHandler(browser, aProtocol, aURI, aTitle);
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // If the protocol handler is already registered, just return early.
 | 
						|
    if (this._protocolHandlerRegistered(aProtocol, aURI.spec)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Now Ask the user and provide the proper callback
 | 
						|
    let message = this._getFormattedString("addProtocolHandlerMessage", [
 | 
						|
      aURI.host,
 | 
						|
      aProtocol,
 | 
						|
    ]);
 | 
						|
 | 
						|
    let notificationIcon = aURI.prePath + "/favicon.ico";
 | 
						|
    let notificationValue = "Protocol Registration: " + aProtocol;
 | 
						|
    let addButton = {
 | 
						|
      label: this._getString("addProtocolHandlerAddButton"),
 | 
						|
      accessKey: this._getString("addProtocolHandlerAddButtonAccesskey"),
 | 
						|
      protocolInfo: { protocol: aProtocol, uri: aURI.spec, name: aTitle },
 | 
						|
      primary: true,
 | 
						|
 | 
						|
      callback(aNotification, aButtonInfo) {
 | 
						|
        let protocol = aButtonInfo.protocolInfo.protocol;
 | 
						|
        let name = aButtonInfo.protocolInfo.name;
 | 
						|
 | 
						|
        let handler = Cc[
 | 
						|
          "@mozilla.org/uriloader/web-handler-app;1"
 | 
						|
        ].createInstance(Ci.nsIWebHandlerApp);
 | 
						|
        handler.name = name;
 | 
						|
        handler.uriTemplate = aButtonInfo.protocolInfo.uri;
 | 
						|
 | 
						|
        let handlerInfo =
 | 
						|
          lazy.ExternalProtocolService.getProtocolHandlerInfo(protocol);
 | 
						|
        handlerInfo.possibleApplicationHandlers.appendElement(handler);
 | 
						|
 | 
						|
        // Since the user has agreed to add a new handler, chances are good
 | 
						|
        // that the next time they see a handler of this type, they're going
 | 
						|
        // to want to use it.  Reset the handlerInfo to ask before the next
 | 
						|
        // use.
 | 
						|
        handlerInfo.alwaysAskBeforeHandling = true;
 | 
						|
 | 
						|
        let hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
 | 
						|
          Ci.nsIHandlerService
 | 
						|
        );
 | 
						|
        hs.store(handlerInfo);
 | 
						|
      },
 | 
						|
    };
 | 
						|
 | 
						|
    let notificationBox = browser.getTabBrowser().getNotificationBox(browser);
 | 
						|
 | 
						|
    // check if the notification box is already shown
 | 
						|
    if (notificationBox.getNotificationWithValue(notificationValue)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    notificationBox.appendNotification(
 | 
						|
      notificationValue,
 | 
						|
      {
 | 
						|
        label: message,
 | 
						|
        image: notificationIcon,
 | 
						|
        priority: notificationBox.PRIORITY_INFO_LOW,
 | 
						|
      },
 | 
						|
      [addButton]
 | 
						|
    );
 | 
						|
  },
 | 
						|
 | 
						|
  /*
 | 
						|
   * Special implementation for mailto: A prompt (notificationbox.js) is only
 | 
						|
   * shown if there is a realistic chance that we can really set the OS default,
 | 
						|
   * e.g. if we have been properly installed and the current page is not already
 | 
						|
   * the default and we have not asked users too often the same question.
 | 
						|
   *
 | 
						|
   * @param {string} browser
 | 
						|
   * @param {string} aProtocol
 | 
						|
   * @param {nsIURI} aURI
 | 
						|
   * @param {string} aTitle
 | 
						|
   */
 | 
						|
  async _askUserToSetMailtoHandler(browser, aProtocol, aURI, aTitle) {
 | 
						|
    let notificationId = "OS Protocol Registration: " + aProtocol;
 | 
						|
 | 
						|
    // guard: we do not want to reconfigure settings in private browsing mode
 | 
						|
    if (lazy.PrivateBrowsingUtils.isWindowPrivate(browser.ownerGlobal)) {
 | 
						|
      lazy.log.debug("prompt not shown, because this is a private window.");
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // guard: check if everything has been configured to use the current site
 | 
						|
    // as default webmailer and bail out if so.
 | 
						|
    if (this._isProtocolHandlerDefault(aProtocol, aURI)) {
 | 
						|
      lazy.log.debug(
 | 
						|
        `prompt not shown, because ${aTitle} is already configured to` +
 | 
						|
          ` handle ${aProtocol}-links under ${aURI.spec}.`
 | 
						|
      );
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // guard: bail out if this site has been dismissed before (either by
 | 
						|
    // clicking the 'x' button or the 'not now' button.
 | 
						|
    let principal = browser.getTabBrowser().contentPrincipal;
 | 
						|
    if (
 | 
						|
      Ci.nsIPermissionManager.DENY_ACTION ==
 | 
						|
      Services.perms.testExactPermissionFromPrincipal(
 | 
						|
        principal,
 | 
						|
        "mailto-infobar-dismissed"
 | 
						|
      )
 | 
						|
    ) {
 | 
						|
      let expiry =
 | 
						|
        Services.perms.getPermissionObject(
 | 
						|
          principal,
 | 
						|
          "mailto-infobar-dismissed",
 | 
						|
          true
 | 
						|
        ).expireTime - Date.now();
 | 
						|
 | 
						|
      lazy.log.debug(
 | 
						|
        `prompt not shown, because it is still dismissed for` +
 | 
						|
          ` ${principal.host} and will be shown in` +
 | 
						|
          ` ${(expiry / 1000).toFixed()} seconds again.`
 | 
						|
      );
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Now show the prompt if there is not already one...
 | 
						|
    let osDefaultNotificationBox = browser
 | 
						|
      .getTabBrowser()
 | 
						|
      .getNotificationBox(browser);
 | 
						|
 | 
						|
    if (!osDefaultNotificationBox.getNotificationWithValue(notificationId)) {
 | 
						|
      let win = browser.ownerGlobal;
 | 
						|
      win.MozXULElement.insertFTLIfNeeded("browser/webProtocolHandler.ftl");
 | 
						|
 | 
						|
      let notification = await osDefaultNotificationBox.appendNotification(
 | 
						|
        notificationId,
 | 
						|
        {
 | 
						|
          label: {
 | 
						|
            "l10n-id": "protocolhandler-mailto-handler-set",
 | 
						|
            "l10n-args": { url: aURI.host },
 | 
						|
          },
 | 
						|
          priority: osDefaultNotificationBox.PRIORITY_INFO_LOW,
 | 
						|
          eventCallback: eventType => {
 | 
						|
            if (eventType === "dismissed") {
 | 
						|
              // after a click on 'X' save a timestamp after which we can show
 | 
						|
              // the prompt again
 | 
						|
              Services.perms.addFromPrincipal(
 | 
						|
                principal,
 | 
						|
                "mailto-infobar-dismissed",
 | 
						|
                Ci.nsIPermissionManager.DENY_ACTION,
 | 
						|
                Ci.nsIPermissionManager.EXPIRE_TIME,
 | 
						|
                lazy.NimbusFeatures.mailto.getVariable(
 | 
						|
                  "dualPrompt.dismissXClickMinutes"
 | 
						|
                ) *
 | 
						|
                  60 *
 | 
						|
                  1000 +
 | 
						|
                  Date.now()
 | 
						|
              );
 | 
						|
              Glean.protocolhandlerMailto.promptClicked.dismiss_os_default.add();
 | 
						|
            }
 | 
						|
          },
 | 
						|
        },
 | 
						|
        [
 | 
						|
          {
 | 
						|
            "l10n-id": "protocolhandler-mailto-os-handler-yes-button",
 | 
						|
            primary: true,
 | 
						|
            callback: newitem => {
 | 
						|
              let currentHandler = this._addWebProtocolHandler(
 | 
						|
                aProtocol,
 | 
						|
                aTitle,
 | 
						|
                aURI.spec
 | 
						|
              );
 | 
						|
              this._setProtocolHandlerDefault(aProtocol, currentHandler);
 | 
						|
              Glean.protocolhandlerMailto.promptClicked.set_local_default.add();
 | 
						|
 | 
						|
              if (this._canSetOSDefault(aProtocol)) {
 | 
						|
                if (this._setOSDefault(aProtocol)) {
 | 
						|
                  Glean.protocolhandlerMailto.promptClicked.set_os_default.add();
 | 
						|
                  newitem.messageL10nId =
 | 
						|
                    "protocolhandler-mailto-handler-confirm";
 | 
						|
                  newitem.removeChild(newitem.buttonContainer);
 | 
						|
                  newitem.setAttribute("type", "success"); // from moz-message-bar.css
 | 
						|
                  newitem.eventCallback = null; // disable show only once per day for success
 | 
						|
                  return true; // `true` does not hide the bar
 | 
						|
                }
 | 
						|
 | 
						|
                // if anything goes wrong with setting the OS default, we want
 | 
						|
                // to be informed so that we can fix it.
 | 
						|
                Glean.protocolhandlerMailto.promptClicked.set_os_default_error.add();
 | 
						|
                return false;
 | 
						|
              }
 | 
						|
 | 
						|
              // if the installation does not have an install hash, we cannot
 | 
						|
              // set the OS default, but mailto links from within the browser
 | 
						|
              // should still work.
 | 
						|
              Glean.protocolhandlerMailto.promptClicked.set_os_default_impossible.add();
 | 
						|
              return false;
 | 
						|
            },
 | 
						|
          },
 | 
						|
          {
 | 
						|
            "l10n-id": "protocolhandler-mailto-os-handler-no-button",
 | 
						|
            callback: () => {
 | 
						|
              // after a click on 'Not Now' save a timestamp after which we can
 | 
						|
              // show the prompt again
 | 
						|
              Services.perms.addFromPrincipal(
 | 
						|
                principal,
 | 
						|
                "mailto-infobar-dismissed",
 | 
						|
                Ci.nsIPermissionManager.DENY_ACTION,
 | 
						|
                Ci.nsIPermissionManager.EXPIRE_TIME,
 | 
						|
                lazy.NimbusFeatures.mailto.getVariable(
 | 
						|
                  "dualPrompt.dismissNotNowMinutes"
 | 
						|
                ) *
 | 
						|
                  60 *
 | 
						|
                  1000 +
 | 
						|
                  Date.now()
 | 
						|
              );
 | 
						|
              Glean.protocolhandlerMailto.promptClicked.dismiss_os_default.add();
 | 
						|
              return false;
 | 
						|
            },
 | 
						|
          },
 | 
						|
        ]
 | 
						|
      );
 | 
						|
 | 
						|
      Glean.protocolhandlerMailto.handlerPromptShown.os_default.add();
 | 
						|
      // remove the icon from the infobar, which is automatically assigned
 | 
						|
      // after its priority, because the priority is also an indicator which
 | 
						|
      // type of bar it is, e.g. a warning or error:
 | 
						|
      notification.setAttribute("type", "system");
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * See nsISupports
 | 
						|
   */
 | 
						|
  QueryInterface: ChromeUtils.generateQI(["nsIWebProtocolHandlerRegistrar"]),
 | 
						|
};
 |