forked from mirrors/gecko-dev
DocShells are associated with outer DOM Windows, rather than Documents, so having the getter on the document is a bit odd to begin with. But it's also considerably less convenient, since most of the times when we want a docShell from JS, we're dealing most directly with a window, and have to detour through the document to get it. MozReview-Commit-ID: LUj1H9nG3QL --HG-- extra : source : fcfb99baa0f0fb60a7c420a712c6ae7c72576871 extra : histedit_source : 5be9b7b29a52a4b8376ee0bdfc5c08b12e3c775a
380 lines
13 KiB
JavaScript
380 lines
13 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/. */
|
|
|
|
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
|
|
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
|
|
|
function LOG(str) {
|
|
// dump("*** " + str + "\n");
|
|
}
|
|
|
|
const WCCR_CLASSID = Components.ID("{792a7e82-06a0-437c-af63-b2d12e808acc}");
|
|
|
|
const WCC_CLASSID = Components.ID("{db7ebf28-cc40-415f-8a51-1b111851df1e}");
|
|
const WCC_CLASSNAME = "Web Service Handler";
|
|
|
|
const PREF_HANDLER_EXTERNAL_PREFIX = "network.protocol-handler.external";
|
|
|
|
const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties";
|
|
|
|
const NS_ERROR_MODULE_DOM = 2152923136;
|
|
const NS_ERROR_DOM_SYNTAX_ERR = NS_ERROR_MODULE_DOM + 12;
|
|
|
|
const Utils = {
|
|
makeURI(aURL, aOriginCharset, aBaseURI) {
|
|
return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
|
|
},
|
|
|
|
checkAndGetURI(aURIString, aContentWindow) {
|
|
let uri;
|
|
try {
|
|
let baseURI = aContentWindow.document.baseURIObject;
|
|
uri = this.makeURI(aURIString, null, baseURI);
|
|
} catch (ex) {
|
|
throw NS_ERROR_DOM_SYNTAX_ERR;
|
|
}
|
|
|
|
// For security reasons we reject non-http(s) urls (see bug 354316),
|
|
// we may need to revise this once we support more content types
|
|
if (uri.scheme != "http" && uri.scheme != "https") {
|
|
throw this.getSecurityError(
|
|
"Permission denied to add " + uri.spec + " as a content or protocol handler",
|
|
aContentWindow);
|
|
}
|
|
|
|
// We also reject handlers registered from a different host (see bug 402287)
|
|
if (!["http:", "https:"].includes(aContentWindow.location.protocol) ||
|
|
aContentWindow.location.hostname != uri.host) {
|
|
throw this.getSecurityError(
|
|
"Permission denied to add " + uri.spec + " as a content or protocol handler",
|
|
aContentWindow);
|
|
}
|
|
|
|
// If the uri doesn't contain '%s', it won't be a good handler
|
|
if (!uri.spec.includes("%s"))
|
|
throw NS_ERROR_DOM_SYNTAX_ERR;
|
|
|
|
return uri;
|
|
},
|
|
|
|
// This list should be kept up-to-date with the spec:
|
|
// https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers
|
|
_safeProtocols: new Set([
|
|
"bitcoin",
|
|
"geo",
|
|
"im",
|
|
"irc",
|
|
"ircs",
|
|
"magnet",
|
|
"mailto",
|
|
"mms",
|
|
"news",
|
|
"nntp",
|
|
"openpgp4fpr",
|
|
"sip",
|
|
"sms",
|
|
"smsto",
|
|
"ssh",
|
|
"tel",
|
|
"urn",
|
|
"webcal",
|
|
"wtai",
|
|
"xmpp",
|
|
]),
|
|
|
|
// NB: Throws if aProtocol is not allowed.
|
|
checkProtocolHandlerAllowed(aProtocol, aURIString, aWindowOrNull) {
|
|
if (aProtocol.startsWith("web+")) {
|
|
if (!/[a-z]+/.test(aProtocol.substring(4 /* web+ */))) {
|
|
throw this.getSecurityError(
|
|
`Permission denied to add a protocol handler for ${aProtocol}`,
|
|
aWindowOrNull);
|
|
}
|
|
} else if (!this._safeProtocols.has(aProtocol)) {
|
|
throw this.getSecurityError(
|
|
`Permission denied to add a protocol handler for ${aProtocol}`,
|
|
aWindowOrNull);
|
|
}
|
|
|
|
// First, check to make sure this isn't already handled internally (we don't
|
|
// want to let them take over, say "chrome").
|
|
let handler = Services.io.getProtocolHandler(aProtocol);
|
|
if (!(handler instanceof Ci.nsIExternalProtocolHandler)) {
|
|
// This is handled internally, so we don't want them to register
|
|
throw this.getSecurityError(
|
|
`Permission denied to add ${aURIString} as a protocol handler`,
|
|
aWindowOrNull);
|
|
}
|
|
|
|
// check if it is in the black list
|
|
let pb = Services.prefs;
|
|
let allowed =
|
|
pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol,
|
|
pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default"));
|
|
if (!allowed) {
|
|
throw this.getSecurityError(
|
|
`Not allowed to register a protocol handler for ${aProtocol}`,
|
|
aWindowOrNull);
|
|
}
|
|
},
|
|
|
|
// Return a SecurityError exception from the given Window if one is given. If
|
|
// none is given, just return the given error string, for lack of anything
|
|
// better.
|
|
getSecurityError(errorString, aWindowOrNull) {
|
|
if (!aWindowOrNull) {
|
|
return errorString;
|
|
}
|
|
|
|
return new aWindowOrNull.DOMException(errorString, "SecurityError");
|
|
},
|
|
};
|
|
|
|
function WebContentConverterRegistrar() {
|
|
}
|
|
|
|
WebContentConverterRegistrar.prototype = {
|
|
get stringBundle() {
|
|
let sb = Services.strings.createBundle(STRING_BUNDLE_URI);
|
|
delete WebContentConverterRegistrar.prototype.stringBundle;
|
|
return WebContentConverterRegistrar.prototype.stringBundle = sb;
|
|
},
|
|
|
|
_getFormattedString(key, params) {
|
|
return this.stringBundle.formatStringFromName(key, params, params.length);
|
|
},
|
|
|
|
_getString(key) {
|
|
return this.stringBundle.GetStringFromName(key);
|
|
},
|
|
|
|
/**
|
|
* See nsIWebContentHandlerRegistrar
|
|
*/
|
|
removeProtocolHandler(aProtocol, aURITemplate) {
|
|
let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
|
|
getService(Ci.nsIExternalProtocolService);
|
|
let handlerInfo = eps.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 aProtocol
|
|
* The scheme of the web handler we are checking for.
|
|
* @param aURITemplate
|
|
* The URI template that the handler uses to handle the protocol.
|
|
* @return true if it is already registered, false otherwise.
|
|
*/
|
|
_protocolHandlerRegistered(aProtocol, aURITemplate) {
|
|
let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
|
|
getService(Ci.nsIExternalProtocolService);
|
|
let handlerInfo = eps.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)
|
|
return true;
|
|
} catch (e) { /* it wasn't a web handler */ }
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* See nsIWebContentHandlerRegistrar
|
|
*/
|
|
registerProtocolHandler(aProtocol, aURIString, aTitle, aBrowserOrWindow) {
|
|
aProtocol = (aProtocol || "").toLowerCase();
|
|
LOG("registerProtocolHandler(" + aProtocol + "," + aURIString + "," + aTitle + ")");
|
|
let haveWindow = (aBrowserOrWindow instanceof Ci.nsIDOMWindow);
|
|
let uri;
|
|
if (haveWindow) {
|
|
uri = Utils.checkAndGetURI(aURIString, aBrowserOrWindow);
|
|
} else {
|
|
// aURIString must not be a relative URI.
|
|
uri = Utils.makeURI(aURIString, null);
|
|
}
|
|
|
|
// If the protocol handler is already registered, just return early.
|
|
if (this._protocolHandlerRegistered(aProtocol, uri.spec)) {
|
|
return;
|
|
}
|
|
|
|
let browser;
|
|
if (haveWindow) {
|
|
let browserWindow =
|
|
this._getBrowserWindowForContentWindow(aBrowserOrWindow);
|
|
browser = this._getBrowserForContentWindow(browserWindow,
|
|
aBrowserOrWindow);
|
|
} else {
|
|
browser = aBrowserOrWindow;
|
|
}
|
|
if (PrivateBrowsingUtils.isBrowserPrivate(browser)) {
|
|
// Inside the private browsing mode, we don't want to alert the user to save
|
|
// a protocol handler. We log it to the error console so that web developers
|
|
// would have some way to tell what's going wrong.
|
|
Services.console.
|
|
logStringMessage("Web page denied access to register a protocol handler inside private browsing mode");
|
|
return;
|
|
}
|
|
|
|
Utils.checkProtocolHandlerAllowed(aProtocol, aURIString,
|
|
haveWindow ? aBrowserOrWindow : null);
|
|
|
|
// Now Ask the user and provide the proper callback
|
|
let message = this._getFormattedString("addProtocolHandler",
|
|
[aTitle, uri.host, aProtocol]);
|
|
|
|
let notificationIcon = uri.prePath + "/favicon.ico";
|
|
let notificationValue = "Protocol Registration: " + aProtocol;
|
|
let addButton = {
|
|
label: this._getString("addProtocolHandlerAddButton"),
|
|
accessKey: this._getString("addProtocolHandlerAddButtonAccesskey"),
|
|
protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle },
|
|
|
|
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 eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
|
|
getService(Ci.nsIExternalProtocolService);
|
|
let handlerInfo = eps.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);
|
|
notificationBox.appendNotification(message,
|
|
notificationValue,
|
|
notificationIcon,
|
|
notificationBox.PRIORITY_INFO_LOW,
|
|
[addButton]);
|
|
},
|
|
|
|
/**
|
|
* Returns the browser chrome window in which the content window is in
|
|
*/
|
|
_getBrowserWindowForContentWindow(aContentWindow) {
|
|
return aContentWindow.docShell.rootTreeItem.domWindow
|
|
.wrappedJSObject;
|
|
},
|
|
|
|
/**
|
|
* Returns the <xul:browser> element associated with the given content
|
|
* window.
|
|
*
|
|
* @param aBrowserWindow
|
|
* The browser window in which the content window is in.
|
|
* @param aContentWindow
|
|
* The content window. It's possible to pass a child content window
|
|
* (i.e. the content window of a frame/iframe).
|
|
*/
|
|
_getBrowserForContentWindow(aBrowserWindow, aContentWindow) {
|
|
// This depends on pseudo APIs of browser.js and tabbrowser.xml
|
|
aContentWindow = aContentWindow.top;
|
|
return aBrowserWindow.gBrowser.browsers.find((browser) =>
|
|
browser.contentWindow == aContentWindow);
|
|
},
|
|
|
|
/**
|
|
* See nsIFactory
|
|
*/
|
|
createInstance(outer, iid) {
|
|
if (outer != null)
|
|
throw Cr.NS_ERROR_NO_AGGREGATION;
|
|
return this.QueryInterface(iid);
|
|
},
|
|
|
|
classID: WCCR_CLASSID,
|
|
|
|
/**
|
|
* See nsISupports
|
|
*/
|
|
QueryInterface: ChromeUtils.generateQI(
|
|
[Ci.nsIWebContentHandlerRegistrar,
|
|
Ci.nsIFactory]),
|
|
|
|
_xpcom_categories: [{
|
|
category: "app-startup",
|
|
service: true
|
|
}]
|
|
};
|
|
|
|
function WebContentConverterRegistrarContent() {
|
|
}
|
|
|
|
WebContentConverterRegistrarContent.prototype = {
|
|
registerProtocolHandler(aProtocol, aURIString, aTitle, aBrowserOrWindow) {
|
|
aProtocol = (aProtocol || "").toLowerCase();
|
|
// aBrowserOrWindow must be a window.
|
|
let messageManager = aBrowserOrWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIWebNavigation)
|
|
.QueryInterface(Ci.nsIDocShell)
|
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsITabChild)
|
|
.messageManager;
|
|
|
|
let uri = Utils.checkAndGetURI(aURIString, aBrowserOrWindow);
|
|
Utils.checkProtocolHandlerAllowed(aProtocol, aURIString, aBrowserOrWindow);
|
|
|
|
messageManager.sendAsyncMessage("WCCR:registerProtocolHandler",
|
|
{ protocol: aProtocol,
|
|
uri: uri.spec,
|
|
title: aTitle });
|
|
},
|
|
|
|
/**
|
|
* See nsIFactory
|
|
*/
|
|
createInstance(outer, iid) {
|
|
if (outer != null)
|
|
throw Cr.NS_ERROR_NO_AGGREGATION;
|
|
return this.QueryInterface(iid);
|
|
},
|
|
|
|
classID: WCCR_CLASSID,
|
|
|
|
/**
|
|
* See nsISupports
|
|
*/
|
|
QueryInterface: ChromeUtils.generateQI(
|
|
[Ci.nsIWebContentHandlerRegistrar,
|
|
Ci.nsIFactory])
|
|
};
|
|
|
|
this.NSGetFactory =
|
|
(Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) ?
|
|
XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrarContent]) :
|
|
XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrar]);
|