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/. */
6 const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties";
8 export function WebProtocolHandlerRegistrar() {}
12 ChromeUtils.defineESModuleGetters(lazy, {
13 NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
16 ChromeUtils.defineLazyGetter(lazy, "log", () => {
17 let { ConsoleAPI } = ChromeUtils.importESModule(
18 "resource://gre/modules/Console.sys.mjs"
20 let consoleOptions = {
21 // tip: set maxLogLevel to "debug" and use lazy.log.debug() to create
22 // detailed messages during development. See LOG_LEVELS in Console.sys.mjs
24 maxLogLevel: "warning",
25 maxLogLevelPref: "browser.protocolhandler.loglevel",
26 prefix: "WebProtocolHandlerRegistrar.sys.mjs",
28 return new ConsoleAPI(consoleOptions);
31 WebProtocolHandlerRegistrar.prototype = {
33 let sb = Services.strings.createBundle(STRING_BUNDLE_URI);
34 delete WebProtocolHandlerRegistrar.prototype.stringBundle;
35 return (WebProtocolHandlerRegistrar.prototype.stringBundle = sb);
38 _getFormattedString(key, params) {
39 return this.stringBundle.formatStringFromName(key, params);
43 return this.stringBundle.GetStringFromName(key);
47 * See nsIWebProtocolHandlerRegistrar
49 removeProtocolHandler(aProtocol, aURITemplate) {
51 "@mozilla.org/uriloader/external-protocol-service;1"
52 ].getService(Ci.nsIExternalProtocolService);
53 let handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
54 let handlers = handlerInfo.possibleApplicationHandlers;
55 for (let i = 0; i < handlers.length; i++) {
57 // We only want to test web handlers
58 let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
59 if (handler.uriTemplate == aURITemplate) {
60 handlers.removeElementAt(i);
61 let hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
64 hs.store(handlerInfo);
68 /* it wasn't a web handler */
74 * Determines if a web handler is already registered.
76 * @param {string} aProtocol
77 * The scheme of the web handler we are checking for.
78 * @param {string} aURITemplate
79 * The URI template that the handler uses to handle the protocol.
80 * @returns {boolean} true if it is already registered, false otherwise.
82 _protocolHandlerRegistered(aProtocol, aURITemplate) {
84 "@mozilla.org/uriloader/external-protocol-service;1"
85 ].getService(Ci.nsIExternalProtocolService);
86 let handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
87 let handlers = handlerInfo.possibleApplicationHandlers;
88 for (let i = 0; i < handlers.length; i++) {
90 // We only want to test web handlers
91 let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
92 if (handler.uriTemplate == aURITemplate) {
96 /* it wasn't a web handler */
97 lazy.log.debug("No protocolHandler registered, because: " + e.message);
104 * Private method to return the installHash, which is important for app
105 * registration on OS level. Without it apps cannot be default/handler apps
106 * under Windows. Because this value is used to check if its possible to reset
107 * the default and to actually set it as well, this function puts the
108 * acquisition of the installHash in one place in the hopes that check and set
109 * conditions will never deviate.
111 * @returns {string} installHash
114 const xreDirProvider = Cc[
115 "@mozilla.org/xre/directory-provider;1"
116 ].getService(Ci.nsIXREDirProvider);
117 return xreDirProvider.getInstallHash();
121 * Private method to determine if we can set a new OS default for a certain
124 * @param {string} protocol name, e.g. mailto (without ://)
127 _canSetOSDefault(protocol) {
128 // can be toggled off individually if necessary...
129 if (!lazy.NimbusFeatures.mailto.getVariable("dualPrompt.os")) {
130 lazy.log.debug("_canSetOSDefault: false: mailto rollout deactivated.");
134 // this preferences saves that the user has dismissed the bar before...
135 if (!Services.prefs.getBoolPref("browser.mailto.prompt.os", true)) {
136 lazy.log.debug("_canSetOSDefault: false: prompt dismissed before.");
140 // an installHash is required for the association with a scheme handler
141 if ("" == this._getInstallHash()) {
142 lazy.log.debug("_canSetOSDefault: false: no installation hash.");
146 // check if we are already the protocolhandler...
147 let shellService = Cc[
148 "@mozilla.org/browser/shell-service;1"
149 ].createInstance(Ci.nsIWindowsShellService);
151 if (shellService.isDefaultHandlerFor(protocol)) {
152 lazy.log.debug("_canSetOSDefault: false: is already default handler.");
160 * Private method to reset the OS default for a certain protocol/uri scheme.
161 * We basically ignore, that setDefaultExtensionHandlersUserChoice can fail
162 * when the installHash is wrong or cannot be determined.
164 * @param {string} protocol name, e.g. mailto (without ://)
167 _setOSDefault(protocol) {
169 let defaultAgent = Cc["@mozilla.org/default-agent;1"].createInstance(
172 defaultAgent.setDefaultExtensionHandlersUserChoice(
173 this._getInstallHash(),
174 [protocol, "FirefoxURL"]
178 // TODO: why could not we just add the installHash and promote the running
179 // install to be a properly installed one?
181 "Could not set Firefox as default application for " +
191 * Private method to set the default uri to handle a certain protocol. This
192 * automates in a way what a user can do in settings under applications,
193 * where different 'actions' can be chosen for different 'content types'.
195 * @param {string} protocol
196 * @param {handler} handler
198 _setLocalDefault(protocol, handler) {
200 "@mozilla.org/uriloader/external-protocol-service;1"
201 ].getService(Ci.nsIExternalProtocolService);
203 let handlerInfo = eps.getProtocolHandlerInfo(protocol);
204 handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp; // this is IMPORTANT!
205 handlerInfo.preferredApplicationHandler = handler;
206 handlerInfo.alwaysAskBeforeHandling = false;
207 let hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
210 hs.store(handlerInfo);
214 * Private method to set the default uri to handle a certain protocol. This
215 * automates in a way what a user can do in settings under applications,
216 * where different 'actions' can be chosen for different 'content types'.
218 * @param {string} protocol - e.g. 'mailto', so again without ://
219 * @param {string} name - the protocol associated 'Action'
220 * @param {string} uri - the uri (compare 'use other...' in the preferences)
221 * @returns {handler} handler - either the existing one or a newly created
223 _addLocal(protocol, name, uri) {
225 "@mozilla.org/uriloader/external-protocol-service;1"
226 ].getService(Ci.nsIExternalProtocolService);
228 let phi = eps.getProtocolHandlerInfo(protocol);
229 // not adding duplicates and bail out with the existing entry
230 for (let h of phi.possibleApplicationHandlers.enumerate()) {
231 if (h.uriTemplate == uri) {
236 let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(
240 handler.uriTemplate = uri;
242 let handlerInfo = eps.getProtocolHandlerInfo(protocol);
243 handlerInfo.possibleApplicationHandlers.appendElement(handler);
245 // Since the user has agreed to add a new handler, chances are good
246 // that the next time they see a handler of this type, they're going
247 // to want to use it. Reset the handlerInfo to ask before the next
249 handlerInfo.alwaysAskBeforeHandling = true;
251 let hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
254 hs.store(handlerInfo);
260 * See nsIWebProtocolHandlerRegistrar
262 registerProtocolHandler(
269 // first mitigation: check if the API call comes from another domain
270 aProtocol = (aProtocol || "").toLowerCase();
271 if (!aURI || !aDocumentURI) {
275 let browser = aBrowserOrWindow; // This is the e10s case.
276 if (aBrowserOrWindow instanceof Ci.nsIDOMWindow) {
277 // In the non-e10s case, grab the browser off the same-process window.
278 let rootDocShell = aBrowserOrWindow.docShell.sameTypeRootTreeItem;
279 browser = rootDocShell.QueryInterface(Ci.nsIDocShell).chromeEventHandler;
282 let browserWindow = browser.ownerGlobal;
284 browserWindow.navigator.checkProtocolHandlerAllowed(
290 // We should have already shown the user an error.
293 if (lazy.NimbusFeatures.mailto.getVariable("dualPrompt")) {
294 if ("mailto" === aProtocol) {
295 this._askUserToSetMailtoHandler(browser, aProtocol, aURI, aTitle);
300 // If the protocol handler is already registered, just return early.
301 if (this._protocolHandlerRegistered(aProtocol, aURI.spec)) {
305 // Now Ask the user and provide the proper callback
306 let message = this._getFormattedString("addProtocolHandlerMessage", [
311 let notificationIcon = aURI.prePath + "/favicon.ico";
312 let notificationValue = "Protocol Registration: " + aProtocol;
314 label: this._getString("addProtocolHandlerAddButton"),
315 accessKey: this._getString("addProtocolHandlerAddButtonAccesskey"),
316 protocolInfo: { protocol: aProtocol, uri: aURI.spec, name: aTitle },
318 callback(aNotification, aButtonInfo) {
319 let protocol = aButtonInfo.protocolInfo.protocol;
320 let name = aButtonInfo.protocolInfo.name;
323 "@mozilla.org/uriloader/web-handler-app;1"
324 ].createInstance(Ci.nsIWebHandlerApp);
326 handler.uriTemplate = aButtonInfo.protocolInfo.uri;
329 "@mozilla.org/uriloader/external-protocol-service;1"
330 ].getService(Ci.nsIExternalProtocolService);
331 let handlerInfo = eps.getProtocolHandlerInfo(protocol);
332 handlerInfo.possibleApplicationHandlers.appendElement(handler);
334 // Since the user has agreed to add a new handler, chances are good
335 // that the next time they see a handler of this type, they're going
336 // to want to use it. Reset the handlerInfo to ask before the next
338 handlerInfo.alwaysAskBeforeHandling = true;
340 let hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
343 hs.store(handlerInfo);
347 let notificationBox = browser.getTabBrowser().getNotificationBox(browser);
349 // check if the notification box is already shown
350 if (notificationBox.getNotificationWithValue(notificationValue)) {
354 notificationBox.appendNotification(
358 image: notificationIcon,
359 priority: notificationBox.PRIORITY_INFO_LOW,
366 * Special implementation for mailto
368 * @param {string} browser
369 * @param {string} aProtocol
370 * @param {string} aURI
371 * @param {string} aTitle
373 async _askUserToSetMailtoHandler(browser, aProtocol, aURI, aTitle) {
374 // shortcut for Localization
375 let l10n = new Localization([
376 "branding/brand.ftl",
377 "browser/webProtocolHandler.ftl",
388 ] = await l10n.formatValues([
389 { id: "protocolhandler-mailto-os-handler-notificationbox" },
390 { id: "protocolhandler-mailto-os-handler-yes-confirm" },
391 { id: "protocolhandler-mailto-os-handler-yes-button" },
392 { id: "protocolhandler-mailto-os-handler-no-button" },
394 id: "protocolhandler-mailto-handler-notificationbox-always",
395 args: { url: aURI.prePath },
398 id: "protocolhandler-mailto-handler-yes-confirm",
399 args: { url: aURI.prePath },
401 { id: "protocolhandler-mailto-handler-yes-button" },
402 { id: "protocolhandler-mailto-handler-no-button" },
406 // Only shown if there is a realistic chance that we can really set the OS
407 // default and can also be disabled with a preference or experiement
408 if (this._canSetOSDefault(aProtocol)) {
409 // Only show if not already set and if we have been properly installed
410 let notificationId = "OS Protocol Registration: " + aProtocol;
411 let osDefaultNotificationBox = browser
413 .getNotificationBox(browser);
414 if (!osDefaultNotificationBox.getNotificationWithValue(notificationId)) {
415 osDefaultNotificationBox.appendNotification(
419 priority: osDefaultNotificationBox.PRIORITY_INFO_LOW,
425 this._setOSDefault(aProtocol);
426 Glean.protocolhandlerMailto.promptClicked.set_os_default.add();
427 osDefaultNotificationBox.appendNotification(
430 label: msg_os_yes_confirm,
431 priority: osDefaultNotificationBox.PRIORITY_INFO_LOW,
441 Services.prefs.setBoolPref("browser.mailto.prompt.os", false);
442 Glean.protocolhandlerMailto.promptClicked.dismiss_os_default.add();
449 Glean.protocolhandlerMailto.handlerPromptShown.os_default.add();
454 // Only shown if the protocol handler is not already registered
455 if (!this._protocolHandlerRegistered(aProtocol, aURI.spec)) {
456 let notificationId = "Protocol Registration: " + aProtocol;
457 let FxDefaultNotificationBox = browser
459 .getNotificationBox(browser);
460 if (!FxDefaultNotificationBox.getNotificationWithValue(notificationId)) {
461 FxDefaultNotificationBox.appendNotification(
465 priority: FxDefaultNotificationBox.PRIORITY_INFO_LOW,
471 this._setLocalDefault(
473 this._addLocal(aProtocol, aTitle, aURI.spec)
475 Glean.protocolhandlerMailto.promptClicked.set_local_default.add();
476 FxDefaultNotificationBox.appendNotification(
479 label: msg_yes_confirm,
480 priority: FxDefaultNotificationBox.PRIORITY_INFO_LOW,
490 Glean.protocolhandlerMailto.promptClicked.dismiss_local_default.add();
497 Glean.protocolhandlerMailto.handlerPromptShown.fx_default.add();
505 QueryInterface: ChromeUtils.generateQI(["nsIWebProtocolHandlerRegistrar"]),