michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: michael@0: function LOG(str) { michael@0: dump("*** " + str + "\n"); michael@0: } michael@0: michael@0: const WCCR_CONTRACTID = "@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"; michael@0: const WCCR_CLASSID = Components.ID("{792a7e82-06a0-437c-af63-b2d12e808acc}"); michael@0: michael@0: const WCC_CLASSID = Components.ID("{db7ebf28-cc40-415f-8a51-1b111851df1e}"); michael@0: const WCC_CLASSNAME = "Web Service Handler"; michael@0: michael@0: const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed"; michael@0: const TYPE_ANY = "*/*"; michael@0: michael@0: const PREF_CONTENTHANDLERS_AUTO = "browser.contentHandlers.auto."; michael@0: const PREF_CONTENTHANDLERS_BRANCH = "browser.contentHandlers.types."; michael@0: const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice"; michael@0: const PREF_SELECTED_ACTION = "browser.feeds.handler"; michael@0: const PREF_SELECTED_READER = "browser.feeds.handler.default"; michael@0: const PREF_HANDLER_EXTERNAL_PREFIX = "network.protocol-handler.external"; michael@0: const PREF_ALLOW_DIFFERENT_HOST = "gecko.handlerService.allowRegisterFromDifferentHost"; michael@0: michael@0: const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties"; michael@0: michael@0: const NS_ERROR_MODULE_DOM = 2152923136; michael@0: const NS_ERROR_DOM_SYNTAX_ERR = NS_ERROR_MODULE_DOM + 12; michael@0: michael@0: function WebContentConverter() { michael@0: } michael@0: WebContentConverter.prototype = { michael@0: convert: function WCC_convert() { }, michael@0: asyncConvertData: function WCC_asyncConvertData() { }, michael@0: onDataAvailable: function WCC_onDataAvailable() { }, michael@0: onStopRequest: function WCC_onStopRequest() { }, michael@0: michael@0: onStartRequest: function WCC_onStartRequest(request, context) { michael@0: var wccr = michael@0: Cc[WCCR_CONTRACTID]. michael@0: getService(Ci.nsIWebContentConverterService); michael@0: wccr.loadPreferredHandler(request); michael@0: }, michael@0: michael@0: QueryInterface: function WCC_QueryInterface(iid) { michael@0: if (iid.equals(Ci.nsIStreamConverter) || michael@0: iid.equals(Ci.nsIStreamListener) || michael@0: iid.equals(Ci.nsISupports)) michael@0: return this; michael@0: throw Cr.NS_ERROR_NO_INTERFACE; michael@0: } michael@0: }; michael@0: michael@0: var WebContentConverterFactory = { michael@0: createInstance: function WCCF_createInstance(outer, iid) { michael@0: if (outer != null) michael@0: throw Cr.NS_ERROR_NO_AGGREGATION; michael@0: return new WebContentConverter().QueryInterface(iid); michael@0: }, michael@0: michael@0: QueryInterface: function WCC_QueryInterface(iid) { michael@0: if (iid.equals(Ci.nsIFactory) || michael@0: iid.equals(Ci.nsISupports)) michael@0: return this; michael@0: throw Cr.NS_ERROR_NO_INTERFACE; michael@0: } michael@0: }; michael@0: michael@0: function ServiceInfo(contentType, uri, name) { michael@0: this._contentType = contentType; michael@0: this._uri = uri; michael@0: this._name = name; michael@0: } michael@0: ServiceInfo.prototype = { michael@0: /** michael@0: * See nsIHandlerApp michael@0: */ michael@0: get name() { michael@0: return this._name; michael@0: }, michael@0: michael@0: /** michael@0: * See nsIHandlerApp michael@0: */ michael@0: equals: function SI_equals(aHandlerApp) { michael@0: if (!aHandlerApp) michael@0: throw Cr.NS_ERROR_NULL_POINTER; michael@0: michael@0: if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo && michael@0: aHandlerApp.contentType == this.contentType && michael@0: aHandlerApp.uri == this.uri) michael@0: return true; michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentHandlerInfo michael@0: */ michael@0: get contentType() { michael@0: return this._contentType; michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentHandlerInfo michael@0: */ michael@0: get uri() { michael@0: return this._uri; michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentHandlerInfo michael@0: */ michael@0: getHandlerURI: function SI_getHandlerURI(uri) { michael@0: return this._uri.replace(/%s/gi, encodeURIComponent(uri)); michael@0: }, michael@0: michael@0: QueryInterface: function SI_QueryInterface(iid) { michael@0: if (iid.equals(Ci.nsIWebContentHandlerInfo) || michael@0: iid.equals(Ci.nsISupports)) michael@0: return this; michael@0: throw Cr.NS_ERROR_NO_INTERFACE; michael@0: } michael@0: }; michael@0: michael@0: function WebContentConverterRegistrar() { michael@0: this._contentTypes = { }; michael@0: this._autoHandleContentTypes = { }; michael@0: } michael@0: michael@0: WebContentConverterRegistrar.prototype = { michael@0: get stringBundle() { michael@0: var sb = Cc["@mozilla.org/intl/stringbundle;1"]. michael@0: getService(Ci.nsIStringBundleService). michael@0: createBundle(STRING_BUNDLE_URI); michael@0: delete WebContentConverterRegistrar.prototype.stringBundle; michael@0: return WebContentConverterRegistrar.prototype.stringBundle = sb; michael@0: }, michael@0: michael@0: _getFormattedString: function WCCR__getFormattedString(key, params) { michael@0: return this.stringBundle.formatStringFromName(key, params, params.length); michael@0: }, michael@0: michael@0: _getString: function WCCR_getString(key) { michael@0: return this.stringBundle.GetStringFromName(key); michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentConverterService michael@0: */ michael@0: getAutoHandler: michael@0: function WCCR_getAutoHandler(contentType) { michael@0: contentType = this._resolveContentType(contentType); michael@0: if (contentType in this._autoHandleContentTypes) michael@0: return this._autoHandleContentTypes[contentType]; michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentConverterService michael@0: */ michael@0: setAutoHandler: michael@0: function WCCR_setAutoHandler(contentType, handler) { michael@0: if (handler && !this._typeIsRegistered(contentType, handler.uri)) michael@0: throw Cr.NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: contentType = this._resolveContentType(contentType); michael@0: this._setAutoHandler(contentType, handler); michael@0: michael@0: var ps = michael@0: Cc["@mozilla.org/preferences-service;1"]. michael@0: getService(Ci.nsIPrefService); michael@0: var autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO); michael@0: if (handler) michael@0: autoBranch.setCharPref(contentType, handler.uri); michael@0: else if (autoBranch.prefHasUserValue(contentType)) michael@0: autoBranch.clearUserPref(contentType); michael@0: michael@0: ps.savePrefFile(null); michael@0: }, michael@0: michael@0: /** michael@0: * Update the internal data structure (not persistent) michael@0: */ michael@0: _setAutoHandler: michael@0: function WCCR__setAutoHandler(contentType, handler) { michael@0: if (handler) michael@0: this._autoHandleContentTypes[contentType] = handler; michael@0: else if (contentType in this._autoHandleContentTypes) michael@0: delete this._autoHandleContentTypes[contentType]; michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentConverterService michael@0: */ michael@0: getWebContentHandlerByURI: michael@0: function WCCR_getWebContentHandlerByURI(contentType, uri) { michael@0: var handlers = this.getContentHandlers(contentType, { }); michael@0: for (var i = 0; i < handlers.length; ++i) { michael@0: if (handlers[i].uri == uri) michael@0: return handlers[i]; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentConverterService michael@0: */ michael@0: loadPreferredHandler: michael@0: function WCCR_loadPreferredHandler(request) { michael@0: var channel = request.QueryInterface(Ci.nsIChannel); michael@0: var contentType = this._resolveContentType(channel.contentType); michael@0: var handler = this.getAutoHandler(contentType); michael@0: if (handler) { michael@0: request.cancel(Cr.NS_ERROR_FAILURE); michael@0: michael@0: var webNavigation = michael@0: channel.notificationCallbacks.getInterface(Ci.nsIWebNavigation); michael@0: webNavigation.loadURI(handler.getHandlerURI(channel.URI.spec), michael@0: Ci.nsIWebNavigation.LOAD_FLAGS_NONE, michael@0: null, null, null); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentConverterService michael@0: */ michael@0: removeProtocolHandler: michael@0: function WCCR_removeProtocolHandler(aProtocol, aURITemplate) { michael@0: var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. michael@0: getService(Ci.nsIExternalProtocolService); michael@0: var handlerInfo = eps.getProtocolHandlerInfo(aProtocol); michael@0: var handlers = handlerInfo.possibleApplicationHandlers; michael@0: for (let i = 0; i < handlers.length; i++) { michael@0: try { // We only want to test web handlers michael@0: let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp); michael@0: if (handler.uriTemplate == aURITemplate) { michael@0: handlers.removeElementAt(i); michael@0: var hs = Cc["@mozilla.org/uriloader/handler-service;1"]. michael@0: getService(Ci.nsIHandlerService); michael@0: hs.store(handlerInfo); michael@0: return; michael@0: } michael@0: } catch (e) { /* it wasn't a web handler */ } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentConverterService michael@0: */ michael@0: removeContentHandler: michael@0: function WCCR_removeContentHandler(contentType, uri) { michael@0: function notURI(serviceInfo) { michael@0: return serviceInfo.uri != uri; michael@0: } michael@0: michael@0: if (contentType in this._contentTypes) { michael@0: this._contentTypes[contentType] = michael@0: this._contentTypes[contentType].filter(notURI); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * michael@0: */ michael@0: _mappings: { michael@0: "application/rss+xml": TYPE_MAYBE_FEED, michael@0: "application/atom+xml": TYPE_MAYBE_FEED, michael@0: }, michael@0: michael@0: /** michael@0: * These are types for which there is a separate content converter aside michael@0: * from our built in generic one. We should not automatically register michael@0: * a factory for creating a converter for these types. michael@0: */ michael@0: _blockedTypes: { michael@0: "application/vnd.mozilla.maybe.feed": true, michael@0: }, michael@0: michael@0: /** michael@0: * Determines the "internal" content type based on the _mappings. michael@0: * @param contentType michael@0: * @returns The resolved contentType value. michael@0: */ michael@0: _resolveContentType: michael@0: function WCCR__resolveContentType(contentType) { michael@0: if (contentType in this._mappings) michael@0: return this._mappings[contentType]; michael@0: return contentType; michael@0: }, michael@0: michael@0: _makeURI: function(aURL, aOriginCharset, aBaseURI) { michael@0: var ioService = Components.classes["@mozilla.org/network/io-service;1"] michael@0: .getService(Components.interfaces.nsIIOService); michael@0: return ioService.newURI(aURL, aOriginCharset, aBaseURI); michael@0: }, michael@0: michael@0: _checkAndGetURI: michael@0: function WCCR_checkAndGetURI(aURIString, aContentWindow) michael@0: { michael@0: try { michael@0: let baseURI = aContentWindow.document.baseURIObject; michael@0: var uri = this._makeURI(aURIString, null, baseURI); michael@0: } catch (ex) { michael@0: // not supposed to throw according to spec michael@0: return; michael@0: } michael@0: michael@0: // For security reasons we reject non-http(s) urls (see bug 354316), michael@0: // we may need to revise this once we support more content types michael@0: // XXX this should be a "security exception" according to spec, but that michael@0: // isn't defined yet. michael@0: if (uri.scheme != "http" && uri.scheme != "https") michael@0: throw("Permission denied to add " + uri.spec + " as a content or protocol handler"); michael@0: michael@0: // We also reject handlers registered from a different host (see bug 402287) michael@0: // The pref allows us to test the feature michael@0: var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); michael@0: if ((!pb.prefHasUserValue(PREF_ALLOW_DIFFERENT_HOST) || michael@0: !pb.getBoolPref(PREF_ALLOW_DIFFERENT_HOST)) && michael@0: aContentWindow.location.hostname != uri.host) michael@0: throw("Permission denied to add " + uri.spec + " as a content or protocol handler"); michael@0: michael@0: // If the uri doesn't contain '%s', it won't be a good handler michael@0: if (uri.spec.indexOf("%s") < 0) michael@0: throw NS_ERROR_DOM_SYNTAX_ERR; michael@0: michael@0: return uri; michael@0: }, michael@0: michael@0: /** michael@0: * Determines if a web handler is already registered. michael@0: * michael@0: * @param aProtocol michael@0: * The scheme of the web handler we are checking for. michael@0: * @param aURITemplate michael@0: * The URI template that the handler uses to handle the protocol. michael@0: * @return true if it is already registered, false otherwise. michael@0: */ michael@0: _protocolHandlerRegistered: michael@0: function WCCR_protocolHandlerRegistered(aProtocol, aURITemplate) { michael@0: var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. michael@0: getService(Ci.nsIExternalProtocolService); michael@0: var handlerInfo = eps.getProtocolHandlerInfo(aProtocol); michael@0: var handlers = handlerInfo.possibleApplicationHandlers; michael@0: for (let i = 0; i < handlers.length; i++) { michael@0: try { // We only want to test web handlers michael@0: let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp); michael@0: if (handler.uriTemplate == aURITemplate) michael@0: return true; michael@0: } catch (e) { /* it wasn't a web handler */ } michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentHandlerRegistrar michael@0: */ michael@0: registerProtocolHandler: michael@0: function WCCR_registerProtocolHandler(aProtocol, aURIString, aTitle, aContentWindow) { michael@0: LOG("registerProtocolHandler(" + aProtocol + "," + aURIString + "," + aTitle + ")"); michael@0: michael@0: var uri = this._checkAndGetURI(aURIString, aContentWindow); michael@0: michael@0: // If the protocol handler is already registered, just return early. michael@0: if (this._protocolHandlerRegistered(aProtocol, uri.spec)) { michael@0: return; michael@0: } michael@0: michael@0: var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow); michael@0: if (PrivateBrowsingUtils.isWindowPrivate(browserWindow)) { michael@0: // Inside the private browsing mode, we don't want to alert the user to save michael@0: // a protocol handler. We log it to the error console so that web developers michael@0: // would have some way to tell what's going wrong. michael@0: Cc["@mozilla.org/consoleservice;1"]. michael@0: getService(Ci.nsIConsoleService). michael@0: logStringMessage("Web page denied access to register a protocol handler inside private browsing mode"); michael@0: return; michael@0: } michael@0: michael@0: // First, check to make sure this isn't already handled internally (we don't michael@0: // want to let them take over, say "chrome"). michael@0: var ios = Cc["@mozilla.org/network/io-service;1"]. michael@0: getService(Ci.nsIIOService); michael@0: var handler = ios.getProtocolHandler(aProtocol); michael@0: if (!(handler instanceof Ci.nsIExternalProtocolHandler)) { michael@0: // This is handled internally, so we don't want them to register michael@0: // XXX this should be a "security exception" according to spec, but that michael@0: // isn't defined yet. michael@0: throw("Permission denied to add " + aURIString + "as a protocol handler"); michael@0: } michael@0: michael@0: // check if it is in the black list michael@0: var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); michael@0: var allowed; michael@0: try { michael@0: allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol); michael@0: } michael@0: catch (e) { michael@0: allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default"); michael@0: } michael@0: if (!allowed) { michael@0: // XXX this should be a "security exception" according to spec michael@0: throw("Not allowed to register a protocol handler for " + aProtocol); michael@0: } michael@0: michael@0: // Now Ask the user and provide the proper callback michael@0: var message = this._getFormattedString("addProtocolHandler", michael@0: [aTitle, uri.host, aProtocol]); michael@0: michael@0: var notificationIcon = uri.prePath + "/favicon.ico"; michael@0: var notificationValue = "Protocol Registration: " + aProtocol; michael@0: var addButton = { michael@0: label: this._getString("addProtocolHandlerAddButton"), michael@0: accessKey: this._getString("addHandlerAddButtonAccesskey"), michael@0: protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle }, michael@0: michael@0: callback: michael@0: function WCCR_addProtocolHandlerButtonCallback(aNotification, aButtonInfo) { michael@0: var protocol = aButtonInfo.protocolInfo.protocol; michael@0: var uri = aButtonInfo.protocolInfo.uri; michael@0: var name = aButtonInfo.protocolInfo.name; michael@0: michael@0: var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"]. michael@0: createInstance(Ci.nsIWebHandlerApp); michael@0: handler.name = name; michael@0: handler.uriTemplate = uri; michael@0: michael@0: var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. michael@0: getService(Ci.nsIExternalProtocolService); michael@0: var handlerInfo = eps.getProtocolHandlerInfo(protocol); michael@0: handlerInfo.possibleApplicationHandlers.appendElement(handler, false); michael@0: michael@0: // Since the user has agreed to add a new handler, chances are good michael@0: // that the next time they see a handler of this type, they're going michael@0: // to want to use it. Reset the handlerInfo to ask before the next michael@0: // use. michael@0: handlerInfo.alwaysAskBeforeHandling = true; michael@0: michael@0: var hs = Cc["@mozilla.org/uriloader/handler-service;1"]. michael@0: getService(Ci.nsIHandlerService); michael@0: hs.store(handlerInfo); michael@0: } michael@0: }; michael@0: var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow); michael@0: var notificationBox = browserWindow.getBrowser().getNotificationBox(browserElement); michael@0: notificationBox.appendNotification(message, michael@0: notificationValue, michael@0: notificationIcon, michael@0: notificationBox.PRIORITY_INFO_LOW, michael@0: [addButton]); michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentHandlerRegistrar michael@0: * If a DOM window is provided, then the request came from content, so we michael@0: * prompt the user to confirm the registration. michael@0: */ michael@0: registerContentHandler: michael@0: function WCCR_registerContentHandler(aContentType, aURIString, aTitle, aContentWindow) { michael@0: LOG("registerContentHandler(" + aContentType + "," + aURIString + "," + aTitle + ")"); michael@0: michael@0: // We only support feed types at present. michael@0: // XXX this should be a "security exception" according to spec, but that michael@0: // isn't defined yet. michael@0: var contentType = this._resolveContentType(aContentType); michael@0: if (contentType != TYPE_MAYBE_FEED) michael@0: return; michael@0: michael@0: if (aContentWindow) { michael@0: var uri = this._checkAndGetURI(aURIString, aContentWindow); michael@0: michael@0: var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow); michael@0: var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow); michael@0: var notificationBox = browserWindow.getBrowser().getNotificationBox(browserElement); michael@0: this._appendFeedReaderNotification(uri, aTitle, notificationBox); michael@0: } michael@0: else michael@0: this._registerContentHandler(contentType, aURIString, aTitle); michael@0: }, michael@0: michael@0: /** michael@0: * Returns the browser chrome window in which the content window is in michael@0: */ michael@0: _getBrowserWindowForContentWindow: michael@0: function WCCR__getBrowserWindowForContentWindow(aContentWindow) { michael@0: return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem) michael@0: .rootTreeItem michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow) michael@0: .wrappedJSObject; michael@0: }, michael@0: michael@0: /** michael@0: * Returns the element associated with the given content michael@0: * window. michael@0: * michael@0: * @param aBrowserWindow michael@0: * The browser window in which the content window is in. michael@0: * @param aContentWindow michael@0: * The content window. It's possible to pass a child content window michael@0: * (i.e. the content window of a frame/iframe). michael@0: */ michael@0: _getBrowserForContentWindow: michael@0: function WCCR__getBrowserForContentWindow(aBrowserWindow, aContentWindow) { michael@0: // This depends on pseudo APIs of browser.js and tabbrowser.xml michael@0: aContentWindow = aContentWindow.top; michael@0: var browsers = aBrowserWindow.getBrowser().browsers; michael@0: for (var i = 0; i < browsers.length; ++i) { michael@0: if (browsers[i].contentWindow == aContentWindow) michael@0: return browsers[i]; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Appends a notifcation for the given feed reader details. michael@0: * michael@0: * The notification could be either a pseudo-dialog which lets michael@0: * the user to add the feed reader: michael@0: * [ [icon] Add %feed-reader-name% (%feed-reader-host%) as a Feed Reader? (Add) [x] ] michael@0: * michael@0: * or a simple message for the case where the feed reader is already registered: michael@0: * [ [icon] %feed-reader-name% is already registered as a Feed Reader [x] ] michael@0: * michael@0: * A new notification isn't appended if the given notificationbox has a michael@0: * notification for the same feed reader. michael@0: * michael@0: * @param aURI michael@0: * The url of the feed reader as a nsIURI object michael@0: * @param aName michael@0: * The feed reader name as it was passed to registerContentHandler michael@0: * @param aNotificationBox michael@0: * The notification box to which a notification might be appended michael@0: * @return true if a notification has been appended, false otherwise. michael@0: */ michael@0: _appendFeedReaderNotification: michael@0: function WCCR__appendFeedReaderNotification(aURI, aName, aNotificationBox) { michael@0: var uriSpec = aURI.spec; michael@0: var notificationValue = "feed reader notification: " + uriSpec; michael@0: var notificationIcon = aURI.prePath + "/favicon.ico"; michael@0: michael@0: // Don't append a new notification if the notificationbox michael@0: // has a notification for the given feed reader already michael@0: if (aNotificationBox.getNotificationWithValue(notificationValue)) michael@0: return false; michael@0: michael@0: var buttons, message; michael@0: if (this.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uriSpec)) michael@0: message = this._getFormattedString("handlerRegistered", [aName]); michael@0: else { michael@0: message = this._getFormattedString("addHandler", [aName, aURI.host]); michael@0: var self = this; michael@0: var addButton = { michael@0: _outer: self, michael@0: label: self._getString("addHandlerAddButton"), michael@0: accessKey: self._getString("addHandlerAddButtonAccesskey"), michael@0: feedReaderInfo: { uri: uriSpec, name: aName }, michael@0: michael@0: /* static */ michael@0: callback: michael@0: function WCCR__addFeedReaderButtonCallback(aNotification, aButtonInfo) { michael@0: var uri = aButtonInfo.feedReaderInfo.uri; michael@0: var name = aButtonInfo.feedReaderInfo.name; michael@0: var outer = aButtonInfo._outer; michael@0: michael@0: // The reader could have been added from another window mean while michael@0: if (!outer.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uri)) michael@0: outer._registerContentHandler(TYPE_MAYBE_FEED, uri, name); michael@0: michael@0: // avoid reference cycles michael@0: aButtonInfo._outer = null; michael@0: michael@0: return false; michael@0: } michael@0: }; michael@0: buttons = [addButton]; michael@0: } michael@0: michael@0: aNotificationBox.appendNotification(message, michael@0: notificationValue, michael@0: notificationIcon, michael@0: aNotificationBox.PRIORITY_INFO_LOW, michael@0: buttons); michael@0: return true; michael@0: }, michael@0: michael@0: /** michael@0: * Save Web Content Handler metadata to persistent preferences. michael@0: * @param contentType michael@0: * The content Type being handled michael@0: * @param uri michael@0: * The uri of the web service michael@0: * @param title michael@0: * The human readable name of the web service michael@0: * michael@0: * This data is stored under: michael@0: * michael@0: * browser.contentHandlers.type0 = content/type michael@0: * browser.contentHandlers.uri0 = http://www.foo.com/q=%s michael@0: * browser.contentHandlers.title0 = Foo 2.0alphr michael@0: */ michael@0: _saveContentHandlerToPrefs: michael@0: function WCCR__saveContentHandlerToPrefs(contentType, uri, title) { michael@0: var ps = michael@0: Cc["@mozilla.org/preferences-service;1"]. michael@0: getService(Ci.nsIPrefService); michael@0: var i = 0; michael@0: var typeBranch = null; michael@0: while (true) { michael@0: typeBranch = michael@0: ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + i + "."); michael@0: try { michael@0: typeBranch.getCharPref("type"); michael@0: ++i; michael@0: } michael@0: catch (e) { michael@0: // No more handlers michael@0: break; michael@0: } michael@0: } michael@0: if (typeBranch) { michael@0: typeBranch.setCharPref("type", contentType); michael@0: var pls = michael@0: Cc["@mozilla.org/pref-localizedstring;1"]. michael@0: createInstance(Ci.nsIPrefLocalizedString); michael@0: pls.data = uri; michael@0: typeBranch.setComplexValue("uri", Ci.nsIPrefLocalizedString, pls); michael@0: pls.data = title; michael@0: typeBranch.setComplexValue("title", Ci.nsIPrefLocalizedString, pls); michael@0: michael@0: ps.savePrefFile(null); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Determines if there is a type with a particular uri registered for the michael@0: * specified content type already. michael@0: * @param contentType michael@0: * The content type that the uri handles michael@0: * @param uri michael@0: * The uri of the michael@0: */ michael@0: _typeIsRegistered: function WCCR__typeIsRegistered(contentType, uri) { michael@0: if (!(contentType in this._contentTypes)) michael@0: return false; michael@0: michael@0: var services = this._contentTypes[contentType]; michael@0: for (var i = 0; i < services.length; ++i) { michael@0: // This uri has already been registered michael@0: if (services[i].uri == uri) michael@0: return true; michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * Gets a stream converter contract id for the specified content type. michael@0: * @param contentType michael@0: * The source content type for the conversion. michael@0: * @returns A contract id to construct a converter to convert between the michael@0: * contentType and *\/*. michael@0: */ michael@0: _getConverterContractID: function WCCR__getConverterContractID(contentType) { michael@0: const template = "@mozilla.org/streamconv;1?from=%s&to=*/*"; michael@0: return template.replace(/%s/, contentType); michael@0: }, michael@0: michael@0: /** michael@0: * Register a web service handler for a content type. michael@0: * michael@0: * @param contentType michael@0: * the content type being handled michael@0: * @param uri michael@0: * the URI of the web service michael@0: * @param title michael@0: * the human readable name of the web service michael@0: */ michael@0: _registerContentHandler: michael@0: function WCCR__registerContentHandler(contentType, uri, title) { michael@0: this._updateContentTypeHandlerMap(contentType, uri, title); michael@0: this._saveContentHandlerToPrefs(contentType, uri, title); michael@0: michael@0: if (contentType == TYPE_MAYBE_FEED) { michael@0: // Make the new handler the last-selected reader in the preview page michael@0: // and make sure the preview page is shown the next time a feed is visited michael@0: var pb = Cc["@mozilla.org/preferences-service;1"]. michael@0: getService(Ci.nsIPrefService).getBranch(null); michael@0: pb.setCharPref(PREF_SELECTED_READER, "web"); michael@0: michael@0: var supportsString = michael@0: Cc["@mozilla.org/supports-string;1"]. michael@0: createInstance(Ci.nsISupportsString); michael@0: supportsString.data = uri; michael@0: pb.setComplexValue(PREF_SELECTED_WEB, Ci.nsISupportsString, michael@0: supportsString); michael@0: pb.setCharPref(PREF_SELECTED_ACTION, "ask"); michael@0: this._setAutoHandler(TYPE_MAYBE_FEED, null); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Update the content type -> handler map. This mapping is not persisted, use michael@0: * registerContentHandler or _saveContentHandlerToPrefs for that purpose. michael@0: * @param contentType michael@0: * The content Type being handled michael@0: * @param uri michael@0: * The uri of the web service michael@0: * @param title michael@0: * The human readable name of the web service michael@0: */ michael@0: _updateContentTypeHandlerMap: michael@0: function WCCR__updateContentTypeHandlerMap(contentType, uri, title) { michael@0: if (!(contentType in this._contentTypes)) michael@0: this._contentTypes[contentType] = []; michael@0: michael@0: // Avoid adding duplicates michael@0: if (this._typeIsRegistered(contentType, uri)) michael@0: return; michael@0: michael@0: this._contentTypes[contentType].push(new ServiceInfo(contentType, uri, title)); michael@0: michael@0: if (!(contentType in this._blockedTypes)) { michael@0: var converterContractID = this._getConverterContractID(contentType); michael@0: var cr = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); michael@0: cr.registerFactory(WCC_CLASSID, WCC_CLASSNAME, converterContractID, michael@0: WebContentConverterFactory); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentConverterService michael@0: */ michael@0: getContentHandlers: michael@0: function WCCR_getContentHandlers(contentType, countRef) { michael@0: countRef.value = 0; michael@0: if (!(contentType in this._contentTypes)) michael@0: return []; michael@0: michael@0: var handlers = this._contentTypes[contentType]; michael@0: countRef.value = handlers.length; michael@0: return handlers; michael@0: }, michael@0: michael@0: /** michael@0: * See nsIWebContentConverterService michael@0: */ michael@0: resetHandlersForType: michael@0: function WCCR_resetHandlersForType(contentType) { michael@0: // currently unused within the tree, so only useful for extensions; previous michael@0: // impl. was buggy (and even infinite-looped!), so I argue that this is a michael@0: // definite improvement michael@0: throw Cr.NS_ERROR_NOT_IMPLEMENTED; michael@0: }, michael@0: michael@0: /** michael@0: * Registers a handler from the settings on a preferences branch. michael@0: * michael@0: * @param branch michael@0: * an nsIPrefBranch containing "type", "uri", and "title" preferences michael@0: * corresponding to the content handler to be registered michael@0: */ michael@0: _registerContentHandlerWithBranch: function(branch) { michael@0: /** michael@0: * Since we support up to six predefined readers, we need to handle gaps michael@0: * better, since the first branch with user-added values will be .6 michael@0: * michael@0: * How we deal with that is to check to see if there's no prefs in the michael@0: * branch and stop cycling once that's true. This doesn't fix the case michael@0: * where a user manually removes a reader, but that's not supported yet! michael@0: */ michael@0: var vals = branch.getChildList(""); michael@0: if (vals.length == 0) michael@0: return; michael@0: michael@0: try { michael@0: var type = branch.getCharPref("type"); michael@0: var uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data; michael@0: var title = branch.getComplexValue("title", michael@0: Ci.nsIPrefLocalizedString).data; michael@0: this._updateContentTypeHandlerMap(type, uri, title); michael@0: } michael@0: catch(ex) { michael@0: // do nothing, the next branch might have values michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Load the auto handler, content handler and protocol tables from michael@0: * preferences. michael@0: */ michael@0: _init: function WCCR__init() { michael@0: var ps = michael@0: Cc["@mozilla.org/preferences-service;1"]. michael@0: getService(Ci.nsIPrefService); michael@0: michael@0: var kids = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH) michael@0: .getChildList(""); michael@0: michael@0: // first get the numbers of the providers by getting all ###.uri prefs michael@0: var nums = []; michael@0: for (var i = 0; i < kids.length; i++) { michael@0: var match = /^(\d+)\.uri$/.exec(kids[i]); michael@0: if (!match) michael@0: continue; michael@0: else michael@0: nums.push(match[1]); michael@0: } michael@0: michael@0: // sort them, to get them back in order michael@0: nums.sort(function(a, b) {return a - b;}); michael@0: michael@0: // now register them michael@0: for (var i = 0; i < nums.length; i++) { michael@0: var branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + nums[i] + "."); michael@0: this._registerContentHandlerWithBranch(branch); michael@0: } michael@0: michael@0: // We need to do this _after_ registering all of the available handlers, michael@0: // so that getWebContentHandlerByURI can return successfully. michael@0: try { michael@0: var autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO); michael@0: var childPrefs = autoBranch.getChildList(""); michael@0: for (var i = 0; i < childPrefs.length; ++i) { michael@0: var type = childPrefs[i]; michael@0: var uri = autoBranch.getCharPref(type); michael@0: if (uri) { michael@0: var handler = this.getWebContentHandlerByURI(type, uri); michael@0: this._setAutoHandler(type, handler); michael@0: } michael@0: } michael@0: } michael@0: catch (e) { michael@0: // No auto branch yet, that's fine michael@0: //LOG("WCCR.init: There is no auto branch, benign"); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * See nsIObserver michael@0: */ michael@0: observe: function WCCR_observe(subject, topic, data) { michael@0: var os = michael@0: Cc["@mozilla.org/observer-service;1"]. michael@0: getService(Ci.nsIObserverService); michael@0: switch (topic) { michael@0: case "app-startup": michael@0: os.addObserver(this, "browser-ui-startup-complete", false); michael@0: break; michael@0: case "browser-ui-startup-complete": michael@0: os.removeObserver(this, "browser-ui-startup-complete"); michael@0: this._init(); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * See nsIFactory michael@0: */ michael@0: createInstance: function WCCR_createInstance(outer, iid) { michael@0: if (outer != null) michael@0: throw Cr.NS_ERROR_NO_AGGREGATION; michael@0: return this.QueryInterface(iid); michael@0: }, michael@0: michael@0: classID: WCCR_CLASSID, michael@0: michael@0: /** michael@0: * See nsISupports michael@0: */ michael@0: QueryInterface: XPCOMUtils.generateQI( michael@0: [Ci.nsIWebContentConverterService, michael@0: Ci.nsIWebContentHandlerRegistrar, michael@0: Ci.nsIObserver, michael@0: Ci.nsIFactory]), michael@0: michael@0: _xpcom_categories: [{ michael@0: category: "app-startup", michael@0: service: true michael@0: }] michael@0: }; michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrar]);