Mypal/application/basilisk/components/feeds/WebContentConverter.js

1067 lines
35 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/. */
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
function LOG(str) {
dump("*** " + str + "\n");
}
const WCCR_CONTRACTID = "@mozilla.org/embeddor.implemented/web-content-handler-registrar;1";
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 TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
const TYPE_ANY = "*/*";
const PREF_CONTENTHANDLERS_AUTO = "browser.contentHandlers.auto.";
const PREF_CONTENTHANDLERS_BRANCH = "browser.contentHandlers.types.";
const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
const PREF_SELECTED_ACTION = "browser.feeds.handler";
const PREF_SELECTED_READER = "browser.feeds.handler.default";
const PREF_HANDLER_EXTERNAL_PREFIX = "network.protocol-handler.external";
const PREF_ALLOW_DIFFERENT_HOST = "gecko.handlerService.allowRegisterFromDifferentHost";
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;
function WebContentConverter() {
}
WebContentConverter.prototype = {
convert() { },
asyncConvertData() { },
onDataAvailable() { },
onStopRequest() { },
onStartRequest(request, context) {
let wccr =
Cc[WCCR_CONTRACTID].
getService(Ci.nsIWebContentConverterService);
wccr.loadPreferredHandler(request);
},
QueryInterface(iid) {
if (iid.equals(Ci.nsIStreamConverter) ||
iid.equals(Ci.nsIStreamListener) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
let WebContentConverterFactory = {
createInstance(outer, iid) {
if (outer != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return new WebContentConverter().QueryInterface(iid);
},
QueryInterface(iid) {
if (iid.equals(Ci.nsIFactory) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
function ServiceInfo(contentType, uri, name) {
this._contentType = contentType;
this._uri = uri;
this._name = name;
}
ServiceInfo.prototype = {
/**
* See nsIHandlerApp
*/
get name() {
return this._name;
},
/**
* See nsIHandlerApp
*/
equals(aHandlerApp) {
if (!aHandlerApp)
throw Cr.NS_ERROR_NULL_POINTER;
if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo &&
aHandlerApp.contentType == this.contentType &&
aHandlerApp.uri == this.uri)
return true;
return false;
},
/**
* See nsIWebContentHandlerInfo
*/
get contentType() {
return this._contentType;
},
/**
* See nsIWebContentHandlerInfo
*/
get uri() {
return this._uri;
},
/**
* See nsIWebContentHandlerInfo
*/
getHandlerURI(uri) {
return this._uri.replace(/%s/gi, encodeURIComponent(uri));
},
QueryInterface(iid) {
if (iid.equals(Ci.nsIWebContentHandlerInfo) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
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)
// The pref allows us to test the feature
let pb = Services.prefs;
if (!pb.getBoolPref(PREF_ALLOW_DIFFERENT_HOST) &&
(!["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.indexOf("%s") < 0)
throw NS_ERROR_DOM_SYNTAX_ERR;
return uri;
},
// NB: Throws if aProtocol is not allowed.
checkProtocolHandlerAllowed(aProtocol, aURIString, 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");
},
/**
* Mappings from known feed types to our internal content type.
*/
_mappings: {
"application/rss+xml": TYPE_MAYBE_FEED,
"application/atom+xml": TYPE_MAYBE_FEED,
},
resolveContentType(aContentType) {
if (aContentType in this._mappings)
return this._mappings[aContentType];
return aContentType;
}
};
function WebContentConverterRegistrar() {
this._contentTypes = {};
this._autoHandleContentTypes = {};
}
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 nsIWebContentConverterService
*/
getAutoHandler(contentType) {
contentType = Utils.resolveContentType(contentType);
if (contentType in this._autoHandleContentTypes)
return this._autoHandleContentTypes[contentType];
return null;
},
/**
* See nsIWebContentConverterService
*/
setAutoHandler(contentType, handler) {
if (handler && !this._typeIsRegistered(contentType, handler.uri))
throw Cr.NS_ERROR_NOT_AVAILABLE;
contentType = Utils.resolveContentType(contentType);
this._setAutoHandler(contentType, handler);
let ps = Services.prefs;
let autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
if (handler)
autoBranch.setCharPref(contentType, handler.uri);
else if (autoBranch.prefHasUserValue(contentType))
autoBranch.clearUserPref(contentType);
ps.savePrefFile(null);
},
/**
* Update the internal data structure (not persistent)
*/
_setAutoHandler(contentType, handler) {
if (handler)
this._autoHandleContentTypes[contentType] = handler;
else if (contentType in this._autoHandleContentTypes)
delete this._autoHandleContentTypes[contentType];
},
/**
* See nsIWebContentConverterService
*/
getWebContentHandlerByURI(contentType, uri) {
return this.getContentHandlers(contentType)
.find(e => e.uri == uri) || null;
},
/**
* See nsIWebContentConverterService
*/
loadPreferredHandler(request) {
let channel = request.QueryInterface(Ci.nsIChannel);
let contentType = Utils.resolveContentType(channel.contentType);
let handler = this.getAutoHandler(contentType);
if (handler) {
request.cancel(Cr.NS_ERROR_FAILURE);
let webNavigation =
channel.notificationCallbacks.getInterface(Ci.nsIWebNavigation);
webNavigation.loadURI(handler.getHandlerURI(channel.URI.spec),
Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
null, null, null);
}
},
/**
* See nsIWebContentConverterService
*/
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 */ }
}
},
/**
* See nsIWebContentConverterService
*/
removeContentHandler(contentType, uri) {
function notURI(serviceInfo) {
return serviceInfo.uri != uri;
}
if (contentType in this._contentTypes) {
this._contentTypes[contentType] =
this._contentTypes[contentType].filter(notURI);
}
},
/**
* These are types for which there is a separate content converter aside
* from our built in generic one. We should not automatically register
* a factory for creating a converter for these types.
*/
_blockedTypes: {
"application/vnd.mozilla.maybe.feed": true,
},
/**
* 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) {
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 uri = aButtonInfo.protocolInfo.uri;
let name = aButtonInfo.protocolInfo.name;
let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
createInstance(Ci.nsIWebHandlerApp);
handler.name = name;
handler.uriTemplate = uri;
let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
getService(Ci.nsIExternalProtocolService);
let handlerInfo = eps.getProtocolHandlerInfo(protocol);
handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
// 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]);
},
/**
* See nsIWebContentHandlerRegistrar
* If a DOM window is provided, then the request came from content, so we
* prompt the user to confirm the registration.
*/
registerContentHandler(aContentType, aURIString, aTitle, aWindowOrBrowser) {
LOG("registerContentHandler(" + aContentType + "," + aURIString + "," + aTitle + ")");
// Make sure to do our URL checks up front, before our content type check,
// just like the WebContentConverterRegistrarContent does.
let haveWindow = aWindowOrBrowser &&
(aWindowOrBrowser instanceof Ci.nsIDOMWindow);
let uri;
if (haveWindow) {
uri = Utils.checkAndGetURI(aURIString, aWindowOrBrowser);
} else if (aWindowOrBrowser) {
// uri was vetted in the content process.
uri = Utils.makeURI(aURIString, null);
}
// We only support feed types at present.
let contentType = Utils.resolveContentType(aContentType);
// XXX We should be throwing a Utils.getSecurityError() here in at least
// some cases. See bug 1266492.
if (contentType != TYPE_MAYBE_FEED) {
return;
}
if (aWindowOrBrowser) {
let notificationBox;
if (haveWindow) {
let browserWindow = this._getBrowserWindowForContentWindow(aWindowOrBrowser);
let browserElement = this._getBrowserForContentWindow(browserWindow, aWindowOrBrowser);
notificationBox = browserElement.getTabBrowser().getNotificationBox(browserElement);
} else {
notificationBox = aWindowOrBrowser.getTabBrowser()
.getNotificationBox(aWindowOrBrowser);
}
this._appendFeedReaderNotification(uri, aTitle, notificationBox);
}
else {
this._registerContentHandler(contentType, aURIString, aTitle);
}
},
/**
* Returns the browser chrome window in which the content window is in
*/
_getBrowserWindowForContentWindow(aContentWindow) {
return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.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);
},
/**
* Appends a notifcation for the given feed reader details.
*
* The notification could be either a pseudo-dialog which lets
* the user to add the feed reader:
* [ [icon] Add %feed-reader-name% (%feed-reader-host%) as a Feed Reader? (Add) [x] ]
*
* or a simple message for the case where the feed reader is already registered:
* [ [icon] %feed-reader-name% is already registered as a Feed Reader [x] ]
*
* A new notification isn't appended if the given notificationbox has a
* notification for the same feed reader.
*
* @param aURI
* The url of the feed reader as a nsIURI object
* @param aName
* The feed reader name as it was passed to registerContentHandler
* @param aNotificationBox
* The notification box to which a notification might be appended
* @return true if a notification has been appended, false otherwise.
*/
_appendFeedReaderNotification(aURI, aName, aNotificationBox) {
let uriSpec = aURI.spec;
let notificationValue = "feed reader notification: " + uriSpec;
let notificationIcon = aURI.prePath + "/favicon.ico";
// Don't append a new notification if the notificationbox
// has a notification for the given feed reader already
if (aNotificationBox.getNotificationWithValue(notificationValue))
return false;
let buttons;
let message;
if (this.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uriSpec))
message = this._getFormattedString("handlerRegistered", [aName]);
else {
message = this._getFormattedString("addHandler", [aName, aURI.host]);
let self = this;
let addButton = {
_outer: self,
label: self._getString("addHandlerAddButton"),
accessKey: self._getString("addHandlerAddButtonAccesskey"),
feedReaderInfo: { uri: uriSpec, name: aName },
/* static */
callback(aNotification, aButtonInfo) {
let uri = aButtonInfo.feedReaderInfo.uri;
let name = aButtonInfo.feedReaderInfo.name;
let outer = aButtonInfo._outer;
// The reader could have been added from another window mean while
if (!outer.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uri))
outer._registerContentHandler(TYPE_MAYBE_FEED, uri, name);
// avoid reference cycles
aButtonInfo._outer = null;
return false;
}
};
buttons = [addButton];
}
aNotificationBox.appendNotification(message,
notificationValue,
notificationIcon,
aNotificationBox.PRIORITY_INFO_LOW,
buttons);
return true;
},
/**
* Save Web Content Handler metadata to persistent preferences.
* @param contentType
* The content Type being handled
* @param uri
* The uri of the web service
* @param title
* The human readable name of the web service
*
* This data is stored under:
*
* browser.contentHandlers.type0 = content/type
* browser.contentHandlers.uri0 = http://www.foo.com/q=%s
* browser.contentHandlers.title0 = Foo 2.0alphr
*/
_saveContentHandlerToPrefs(contentType, uri, title) {
let ps = Services.prefs;
let i = 0;
let typeBranch = null;
while (true) {
typeBranch =
ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + i + ".");
try {
typeBranch.getCharPref("type");
++i;
}
catch (e) {
// No more handlers
break;
}
}
if (typeBranch) {
typeBranch.setCharPref("type", contentType);
let pls =
Cc["@mozilla.org/pref-localizedstring;1"].
createInstance(Ci.nsIPrefLocalizedString);
pls.data = uri;
typeBranch.setComplexValue("uri", Ci.nsIPrefLocalizedString, pls);
pls.data = title;
typeBranch.setComplexValue("title", Ci.nsIPrefLocalizedString, pls);
ps.savePrefFile(null);
}
},
/**
* Determines if there is a type with a particular uri registered for the
* specified content type already.
* @param contentType
* The content type that the uri handles
* @param uri
* The uri of the content type
*/
_typeIsRegistered(contentType, uri) {
if (!(contentType in this._contentTypes))
return false;
return this._contentTypes[contentType]
.some(t => t.uri == uri);
},
/**
* Gets a stream converter contract id for the specified content type.
* @param contentType
* The source content type for the conversion.
* @returns A contract id to construct a converter to convert between the
* contentType and *\/*.
*/
_getConverterContractID(contentType) {
const template = "@mozilla.org/streamconv;1?from=%s&to=*/*";
return template.replace(/%s/, contentType);
},
/**
* Register a web service handler for a content type.
*
* @param contentType
* the content type being handled
* @param uri
* the URI of the web service
* @param title
* the human readable name of the web service
*/
_registerContentHandler(contentType, uri, title) {
this._updateContentTypeHandlerMap(contentType, uri, title);
this._saveContentHandlerToPrefs(contentType, uri, title);
if (contentType == TYPE_MAYBE_FEED) {
// Make the new handler the last-selected reader in the preview page
// and make sure the preview page is shown the next time a feed is visited
let pb = Services.prefs.getBranch(null);
pb.setCharPref(PREF_SELECTED_READER, "web");
let supportsString =
Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
supportsString.data = uri;
pb.setComplexValue(PREF_SELECTED_WEB, Ci.nsISupportsString,
supportsString);
pb.setCharPref(PREF_SELECTED_ACTION, "ask");
this._setAutoHandler(TYPE_MAYBE_FEED, null);
}
},
/**
* Update the content type -> handler map. This mapping is not persisted, use
* registerContentHandler or _saveContentHandlerToPrefs for that purpose.
* @param contentType
* The content Type being handled
* @param uri
* The uri of the web service
* @param title
* The human readable name of the web service
*/
_updateContentTypeHandlerMap(contentType, uri, title) {
if (!(contentType in this._contentTypes))
this._contentTypes[contentType] = [];
// Avoid adding duplicates
if (this._typeIsRegistered(contentType, uri))
return;
this._contentTypes[contentType].push(new ServiceInfo(contentType, uri, title));
if (!(contentType in this._blockedTypes)) {
let converterContractID = this._getConverterContractID(contentType);
let cr = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
cr.registerFactory(WCC_CLASSID, WCC_CLASSNAME, converterContractID,
WebContentConverterFactory);
}
},
/**
* See nsIWebContentConverterService
*/
getContentHandlers(contentType, countRef) {
if (countRef) {
countRef.value = 0;
}
if (!(contentType in this._contentTypes))
return [];
let handlers = this._contentTypes[contentType];
if (countRef) {
countRef.value = handlers.length;
}
return handlers;
},
/**
* See nsIWebContentConverterService
*/
resetHandlersForType(contentType) {
// currently unused within the tree, so only useful for extensions; previous
// impl. was buggy (and even infinite-looped!), so I argue that this is a
// definite improvement
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
/**
* Registers a handler from the settings on a preferences branch.
*
* Since we support up to six predefined readers, we need to handle gaps
* better, since the first branch with user-added values will be .6
*
* How we deal with that is to check to see if there's no prefs in the
* branch and stop cycling once that's true. This doesn't fix the case
* where a user manually removes a reader, but that's not supported yet!
*
* @param branch
* an nsIPrefBranch containing "type", "uri", and "title" preferences
* corresponding to the content handler to be registered
*/
_registerContentHandlerHavingBranch(branch) {
let vals = branch.getChildList("");
if (vals.length == 0)
return;
let type = branch.getCharPref("type");
let uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data;
let title = branch.getComplexValue("title",
Ci.nsIPrefLocalizedString).data;
this._updateContentTypeHandlerMap(type, uri, title);
},
/**
* Load the auto handler, content handler and protocol tables from
* preferences.
*/
_init() {
let ps = Services.prefs;
let children = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH)
.getChildList("");
// first get the numbers of the providers by getting all ###.uri prefs
let nums = children.map((child) => {
let match = /^(\d+)\.uri$/.exec(child);
return match ? match[1] : "";
}).filter(child => !!child)
.sort();
// now register them
for (let num of nums) {
let branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + num + ".");
try {
this._registerContentHandlerHavingBranch(branch);
} catch (ex) {
// do nothing, the next branch might have values
}
}
// We need to do this _after_ registering all of the available handlers,
// so that getWebContentHandlerByURI can return successfully.
let autoBranch;
try {
autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
} catch (e) {
// No auto branch yet, that's fine
// LOG("WCCR.init: There is no auto branch, benign");
}
if (autoBranch) {
for (let type of autoBranch.getChildList("")) {
let uri = autoBranch.getCharPref(type);
if (uri) {
let handler = this.getWebContentHandlerByURI(type, uri);
if (handler) {
this._setAutoHandler(type, handler);
}
}
}
}
},
/**
* See nsIObserver
*/
observe(subject, topic, data) {
let os = Services.obs;
switch (topic) {
case "app-startup":
os.addObserver(this, "browser-ui-startup-complete", false);
break;
case "browser-ui-startup-complete":
os.removeObserver(this, "browser-ui-startup-complete");
this._init();
break;
}
},
/**
* See nsIFactory
*/
createInstance(outer, iid) {
if (outer != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return this.QueryInterface(iid);
},
classID: WCCR_CLASSID,
/**
* See nsISupports
*/
QueryInterface: XPCOMUtils.generateQI(
[Ci.nsIWebContentConverterService,
Ci.nsIWebContentHandlerRegistrar,
Ci.nsIObserver,
Ci.nsIFactory]),
_xpcom_categories: [{
category: "app-startup",
service: true
}]
};
function WebContentConverterRegistrarContent() {
this._contentTypes = {};
}
WebContentConverterRegistrarContent.prototype = {
/**
* Load the auto handler, content handler and protocol tables from
* preferences.
*/
_init() {
let ps = Services.prefs;
let children = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH)
.getChildList("");
// first get the numbers of the providers by getting all ###.uri prefs
let nums = children.map((child) => {
let match = /^(\d+)\.uri$/.exec(child);
return match ? match[1] : "";
}).filter(child => !!child)
.sort();
// now register them
for (num of nums) {
let branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + num + ".");
try {
this._registerContentHandlerHavingBranch(branch);
} catch (ex) {
// do nothing, the next branch might have values
}
}
},
_typeIsRegistered(contentType, uri) {
return this._contentTypes[contentType]
.some(e => e.uri == uri);
},
/**
* Since we support up to six predefined readers, we need to handle gaps
* better, since the first branch with user-added values will be .6
*
* How we deal with that is to check to see if there's no prefs in the
* branch and stop cycling once that's true. This doesn't fix the case
* where a user manually removes a reader, but that's not supported yet!
*
* @param branch
* The pref branch to register the content handler under
*
*/
_registerContentHandlerHavingBranch(branch) {
let vals = branch.getChildList("");
if (vals.length == 0)
return;
let type = branch.getCharPref("type");
let uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data;
let title = branch.getComplexValue("title",
Ci.nsIPrefLocalizedString).data;
this._updateContentTypeHandlerMap(type, uri, title);
},
_updateContentTypeHandlerMap(contentType, uri, title) {
if (!(contentType in this._contentTypes))
this._contentTypes[contentType] = [];
// Avoid adding duplicates
if (this._typeIsRegistered(contentType, uri))
return;
this._contentTypes[contentType].push(new ServiceInfo(contentType, uri, title));
if (!(contentType in this._blockedTypes)) {
let converterContractID = this._getConverterContractID(contentType);
let cr = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
cr.registerFactory(WCC_CLASSID, WCC_CLASSNAME, converterContractID,
WebContentConverterFactory);
}
},
/**
* See nsIWebContentConverterService
*/
getContentHandlers(contentType, countRef) {
this._init();
if (countRef) {
countRef.value = 0;
}
if (!(contentType in this._contentTypes))
return [];
let handlers = this._contentTypes[contentType];
if (countRef) {
countRef.value = handlers.length;
}
return handlers;
},
setAutoHandler(contentType, handler) {
Services.cpmm.sendAsyncMessage("WCCR:setAutoHandler",
{ contentType, handler });
},
getWebContentHandlerByURI(contentType, uri) {
return this.getContentHandlers(contentType)
.find(e => e.uri == uri) || null;
},
/**
* See nsIWebContentHandlerRegistrar
*/
registerContentHandler(aContentType, aURIString, aTitle, aBrowserOrWindow) {
// 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);
// XXX We should be throwing a Utils.getSecurityError() here in at least
// some cases. See bug 1266492.
if (Utils.resolveContentType(aContentType) != TYPE_MAYBE_FEED) {
return;
}
messageManager.sendAsyncMessage("WCCR:registerContentHandler",
{ contentType: aContentType,
uri: uri.spec,
title: aTitle });
},
registerProtocolHandler(aProtocol, aURIString, aTitle, aBrowserOrWindow) {
// 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: XPCOMUtils.generateQI(
[Ci.nsIWebContentHandlerRegistrar,
Ci.nsIWebContentConverterService,
Ci.nsIFactory])
};
this.NSGetFactory =
(Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) ?
XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrarContent]) :
XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrar]);