1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/components/feeds/src/WebContentConverter.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,893 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); 1.10 +Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); 1.11 + 1.12 +const Cc = Components.classes; 1.13 +const Ci = Components.interfaces; 1.14 +const Cr = Components.results; 1.15 + 1.16 +function LOG(str) { 1.17 + dump("*** " + str + "\n"); 1.18 +} 1.19 + 1.20 +const WCCR_CONTRACTID = "@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"; 1.21 +const WCCR_CLASSID = Components.ID("{792a7e82-06a0-437c-af63-b2d12e808acc}"); 1.22 + 1.23 +const WCC_CLASSID = Components.ID("{db7ebf28-cc40-415f-8a51-1b111851df1e}"); 1.24 +const WCC_CLASSNAME = "Web Service Handler"; 1.25 + 1.26 +const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed"; 1.27 +const TYPE_ANY = "*/*"; 1.28 + 1.29 +const PREF_CONTENTHANDLERS_AUTO = "browser.contentHandlers.auto."; 1.30 +const PREF_CONTENTHANDLERS_BRANCH = "browser.contentHandlers.types."; 1.31 +const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice"; 1.32 +const PREF_SELECTED_ACTION = "browser.feeds.handler"; 1.33 +const PREF_SELECTED_READER = "browser.feeds.handler.default"; 1.34 +const PREF_HANDLER_EXTERNAL_PREFIX = "network.protocol-handler.external"; 1.35 +const PREF_ALLOW_DIFFERENT_HOST = "gecko.handlerService.allowRegisterFromDifferentHost"; 1.36 + 1.37 +const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties"; 1.38 + 1.39 +const NS_ERROR_MODULE_DOM = 2152923136; 1.40 +const NS_ERROR_DOM_SYNTAX_ERR = NS_ERROR_MODULE_DOM + 12; 1.41 + 1.42 +function WebContentConverter() { 1.43 +} 1.44 +WebContentConverter.prototype = { 1.45 + convert: function WCC_convert() { }, 1.46 + asyncConvertData: function WCC_asyncConvertData() { }, 1.47 + onDataAvailable: function WCC_onDataAvailable() { }, 1.48 + onStopRequest: function WCC_onStopRequest() { }, 1.49 + 1.50 + onStartRequest: function WCC_onStartRequest(request, context) { 1.51 + var wccr = 1.52 + Cc[WCCR_CONTRACTID]. 1.53 + getService(Ci.nsIWebContentConverterService); 1.54 + wccr.loadPreferredHandler(request); 1.55 + }, 1.56 + 1.57 + QueryInterface: function WCC_QueryInterface(iid) { 1.58 + if (iid.equals(Ci.nsIStreamConverter) || 1.59 + iid.equals(Ci.nsIStreamListener) || 1.60 + iid.equals(Ci.nsISupports)) 1.61 + return this; 1.62 + throw Cr.NS_ERROR_NO_INTERFACE; 1.63 + } 1.64 +}; 1.65 + 1.66 +var WebContentConverterFactory = { 1.67 + createInstance: function WCCF_createInstance(outer, iid) { 1.68 + if (outer != null) 1.69 + throw Cr.NS_ERROR_NO_AGGREGATION; 1.70 + return new WebContentConverter().QueryInterface(iid); 1.71 + }, 1.72 + 1.73 + QueryInterface: function WCC_QueryInterface(iid) { 1.74 + if (iid.equals(Ci.nsIFactory) || 1.75 + iid.equals(Ci.nsISupports)) 1.76 + return this; 1.77 + throw Cr.NS_ERROR_NO_INTERFACE; 1.78 + } 1.79 +}; 1.80 + 1.81 +function ServiceInfo(contentType, uri, name) { 1.82 + this._contentType = contentType; 1.83 + this._uri = uri; 1.84 + this._name = name; 1.85 +} 1.86 +ServiceInfo.prototype = { 1.87 + /** 1.88 + * See nsIHandlerApp 1.89 + */ 1.90 + get name() { 1.91 + return this._name; 1.92 + }, 1.93 + 1.94 + /** 1.95 + * See nsIHandlerApp 1.96 + */ 1.97 + equals: function SI_equals(aHandlerApp) { 1.98 + if (!aHandlerApp) 1.99 + throw Cr.NS_ERROR_NULL_POINTER; 1.100 + 1.101 + if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo && 1.102 + aHandlerApp.contentType == this.contentType && 1.103 + aHandlerApp.uri == this.uri) 1.104 + return true; 1.105 + 1.106 + return false; 1.107 + }, 1.108 + 1.109 + /** 1.110 + * See nsIWebContentHandlerInfo 1.111 + */ 1.112 + get contentType() { 1.113 + return this._contentType; 1.114 + }, 1.115 + 1.116 + /** 1.117 + * See nsIWebContentHandlerInfo 1.118 + */ 1.119 + get uri() { 1.120 + return this._uri; 1.121 + }, 1.122 + 1.123 + /** 1.124 + * See nsIWebContentHandlerInfo 1.125 + */ 1.126 + getHandlerURI: function SI_getHandlerURI(uri) { 1.127 + return this._uri.replace(/%s/gi, encodeURIComponent(uri)); 1.128 + }, 1.129 + 1.130 + QueryInterface: function SI_QueryInterface(iid) { 1.131 + if (iid.equals(Ci.nsIWebContentHandlerInfo) || 1.132 + iid.equals(Ci.nsISupports)) 1.133 + return this; 1.134 + throw Cr.NS_ERROR_NO_INTERFACE; 1.135 + } 1.136 +}; 1.137 + 1.138 +function WebContentConverterRegistrar() { 1.139 + this._contentTypes = { }; 1.140 + this._autoHandleContentTypes = { }; 1.141 +} 1.142 + 1.143 +WebContentConverterRegistrar.prototype = { 1.144 + get stringBundle() { 1.145 + var sb = Cc["@mozilla.org/intl/stringbundle;1"]. 1.146 + getService(Ci.nsIStringBundleService). 1.147 + createBundle(STRING_BUNDLE_URI); 1.148 + delete WebContentConverterRegistrar.prototype.stringBundle; 1.149 + return WebContentConverterRegistrar.prototype.stringBundle = sb; 1.150 + }, 1.151 + 1.152 + _getFormattedString: function WCCR__getFormattedString(key, params) { 1.153 + return this.stringBundle.formatStringFromName(key, params, params.length); 1.154 + }, 1.155 + 1.156 + _getString: function WCCR_getString(key) { 1.157 + return this.stringBundle.GetStringFromName(key); 1.158 + }, 1.159 + 1.160 + /** 1.161 + * See nsIWebContentConverterService 1.162 + */ 1.163 + getAutoHandler: 1.164 + function WCCR_getAutoHandler(contentType) { 1.165 + contentType = this._resolveContentType(contentType); 1.166 + if (contentType in this._autoHandleContentTypes) 1.167 + return this._autoHandleContentTypes[contentType]; 1.168 + return null; 1.169 + }, 1.170 + 1.171 + /** 1.172 + * See nsIWebContentConverterService 1.173 + */ 1.174 + setAutoHandler: 1.175 + function WCCR_setAutoHandler(contentType, handler) { 1.176 + if (handler && !this._typeIsRegistered(contentType, handler.uri)) 1.177 + throw Cr.NS_ERROR_NOT_AVAILABLE; 1.178 + 1.179 + contentType = this._resolveContentType(contentType); 1.180 + this._setAutoHandler(contentType, handler); 1.181 + 1.182 + var ps = 1.183 + Cc["@mozilla.org/preferences-service;1"]. 1.184 + getService(Ci.nsIPrefService); 1.185 + var autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO); 1.186 + if (handler) 1.187 + autoBranch.setCharPref(contentType, handler.uri); 1.188 + else if (autoBranch.prefHasUserValue(contentType)) 1.189 + autoBranch.clearUserPref(contentType); 1.190 + 1.191 + ps.savePrefFile(null); 1.192 + }, 1.193 + 1.194 + /** 1.195 + * Update the internal data structure (not persistent) 1.196 + */ 1.197 + _setAutoHandler: 1.198 + function WCCR__setAutoHandler(contentType, handler) { 1.199 + if (handler) 1.200 + this._autoHandleContentTypes[contentType] = handler; 1.201 + else if (contentType in this._autoHandleContentTypes) 1.202 + delete this._autoHandleContentTypes[contentType]; 1.203 + }, 1.204 + 1.205 + /** 1.206 + * See nsIWebContentConverterService 1.207 + */ 1.208 + getWebContentHandlerByURI: 1.209 + function WCCR_getWebContentHandlerByURI(contentType, uri) { 1.210 + var handlers = this.getContentHandlers(contentType, { }); 1.211 + for (var i = 0; i < handlers.length; ++i) { 1.212 + if (handlers[i].uri == uri) 1.213 + return handlers[i]; 1.214 + } 1.215 + return null; 1.216 + }, 1.217 + 1.218 + /** 1.219 + * See nsIWebContentConverterService 1.220 + */ 1.221 + loadPreferredHandler: 1.222 + function WCCR_loadPreferredHandler(request) { 1.223 + var channel = request.QueryInterface(Ci.nsIChannel); 1.224 + var contentType = this._resolveContentType(channel.contentType); 1.225 + var handler = this.getAutoHandler(contentType); 1.226 + if (handler) { 1.227 + request.cancel(Cr.NS_ERROR_FAILURE); 1.228 + 1.229 + var webNavigation = 1.230 + channel.notificationCallbacks.getInterface(Ci.nsIWebNavigation); 1.231 + webNavigation.loadURI(handler.getHandlerURI(channel.URI.spec), 1.232 + Ci.nsIWebNavigation.LOAD_FLAGS_NONE, 1.233 + null, null, null); 1.234 + } 1.235 + }, 1.236 + 1.237 + /** 1.238 + * See nsIWebContentConverterService 1.239 + */ 1.240 + removeProtocolHandler: 1.241 + function WCCR_removeProtocolHandler(aProtocol, aURITemplate) { 1.242 + var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. 1.243 + getService(Ci.nsIExternalProtocolService); 1.244 + var handlerInfo = eps.getProtocolHandlerInfo(aProtocol); 1.245 + var handlers = handlerInfo.possibleApplicationHandlers; 1.246 + for (let i = 0; i < handlers.length; i++) { 1.247 + try { // We only want to test web handlers 1.248 + let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp); 1.249 + if (handler.uriTemplate == aURITemplate) { 1.250 + handlers.removeElementAt(i); 1.251 + var hs = Cc["@mozilla.org/uriloader/handler-service;1"]. 1.252 + getService(Ci.nsIHandlerService); 1.253 + hs.store(handlerInfo); 1.254 + return; 1.255 + } 1.256 + } catch (e) { /* it wasn't a web handler */ } 1.257 + } 1.258 + }, 1.259 + 1.260 + /** 1.261 + * See nsIWebContentConverterService 1.262 + */ 1.263 + removeContentHandler: 1.264 + function WCCR_removeContentHandler(contentType, uri) { 1.265 + function notURI(serviceInfo) { 1.266 + return serviceInfo.uri != uri; 1.267 + } 1.268 + 1.269 + if (contentType in this._contentTypes) { 1.270 + this._contentTypes[contentType] = 1.271 + this._contentTypes[contentType].filter(notURI); 1.272 + } 1.273 + }, 1.274 + 1.275 + /** 1.276 + * 1.277 + */ 1.278 + _mappings: { 1.279 + "application/rss+xml": TYPE_MAYBE_FEED, 1.280 + "application/atom+xml": TYPE_MAYBE_FEED, 1.281 + }, 1.282 + 1.283 + /** 1.284 + * These are types for which there is a separate content converter aside 1.285 + * from our built in generic one. We should not automatically register 1.286 + * a factory for creating a converter for these types. 1.287 + */ 1.288 + _blockedTypes: { 1.289 + "application/vnd.mozilla.maybe.feed": true, 1.290 + }, 1.291 + 1.292 + /** 1.293 + * Determines the "internal" content type based on the _mappings. 1.294 + * @param contentType 1.295 + * @returns The resolved contentType value. 1.296 + */ 1.297 + _resolveContentType: 1.298 + function WCCR__resolveContentType(contentType) { 1.299 + if (contentType in this._mappings) 1.300 + return this._mappings[contentType]; 1.301 + return contentType; 1.302 + }, 1.303 + 1.304 + _makeURI: function(aURL, aOriginCharset, aBaseURI) { 1.305 + var ioService = Components.classes["@mozilla.org/network/io-service;1"] 1.306 + .getService(Components.interfaces.nsIIOService); 1.307 + return ioService.newURI(aURL, aOriginCharset, aBaseURI); 1.308 + }, 1.309 + 1.310 + _checkAndGetURI: 1.311 + function WCCR_checkAndGetURI(aURIString, aContentWindow) 1.312 + { 1.313 + try { 1.314 + let baseURI = aContentWindow.document.baseURIObject; 1.315 + var uri = this._makeURI(aURIString, null, baseURI); 1.316 + } catch (ex) { 1.317 + // not supposed to throw according to spec 1.318 + return; 1.319 + } 1.320 + 1.321 + // For security reasons we reject non-http(s) urls (see bug 354316), 1.322 + // we may need to revise this once we support more content types 1.323 + // XXX this should be a "security exception" according to spec, but that 1.324 + // isn't defined yet. 1.325 + if (uri.scheme != "http" && uri.scheme != "https") 1.326 + throw("Permission denied to add " + uri.spec + " as a content or protocol handler"); 1.327 + 1.328 + // We also reject handlers registered from a different host (see bug 402287) 1.329 + // The pref allows us to test the feature 1.330 + var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); 1.331 + if ((!pb.prefHasUserValue(PREF_ALLOW_DIFFERENT_HOST) || 1.332 + !pb.getBoolPref(PREF_ALLOW_DIFFERENT_HOST)) && 1.333 + aContentWindow.location.hostname != uri.host) 1.334 + throw("Permission denied to add " + uri.spec + " as a content or protocol handler"); 1.335 + 1.336 + // If the uri doesn't contain '%s', it won't be a good handler 1.337 + if (uri.spec.indexOf("%s") < 0) 1.338 + throw NS_ERROR_DOM_SYNTAX_ERR; 1.339 + 1.340 + return uri; 1.341 + }, 1.342 + 1.343 + /** 1.344 + * Determines if a web handler is already registered. 1.345 + * 1.346 + * @param aProtocol 1.347 + * The scheme of the web handler we are checking for. 1.348 + * @param aURITemplate 1.349 + * The URI template that the handler uses to handle the protocol. 1.350 + * @return true if it is already registered, false otherwise. 1.351 + */ 1.352 + _protocolHandlerRegistered: 1.353 + function WCCR_protocolHandlerRegistered(aProtocol, aURITemplate) { 1.354 + var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. 1.355 + getService(Ci.nsIExternalProtocolService); 1.356 + var handlerInfo = eps.getProtocolHandlerInfo(aProtocol); 1.357 + var handlers = handlerInfo.possibleApplicationHandlers; 1.358 + for (let i = 0; i < handlers.length; i++) { 1.359 + try { // We only want to test web handlers 1.360 + let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp); 1.361 + if (handler.uriTemplate == aURITemplate) 1.362 + return true; 1.363 + } catch (e) { /* it wasn't a web handler */ } 1.364 + } 1.365 + return false; 1.366 + }, 1.367 + 1.368 + /** 1.369 + * See nsIWebContentHandlerRegistrar 1.370 + */ 1.371 + registerProtocolHandler: 1.372 + function WCCR_registerProtocolHandler(aProtocol, aURIString, aTitle, aContentWindow) { 1.373 + LOG("registerProtocolHandler(" + aProtocol + "," + aURIString + "," + aTitle + ")"); 1.374 + 1.375 + var uri = this._checkAndGetURI(aURIString, aContentWindow); 1.376 + 1.377 + // If the protocol handler is already registered, just return early. 1.378 + if (this._protocolHandlerRegistered(aProtocol, uri.spec)) { 1.379 + return; 1.380 + } 1.381 + 1.382 + var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow); 1.383 + if (PrivateBrowsingUtils.isWindowPrivate(browserWindow)) { 1.384 + // Inside the private browsing mode, we don't want to alert the user to save 1.385 + // a protocol handler. We log it to the error console so that web developers 1.386 + // would have some way to tell what's going wrong. 1.387 + Cc["@mozilla.org/consoleservice;1"]. 1.388 + getService(Ci.nsIConsoleService). 1.389 + logStringMessage("Web page denied access to register a protocol handler inside private browsing mode"); 1.390 + return; 1.391 + } 1.392 + 1.393 + // First, check to make sure this isn't already handled internally (we don't 1.394 + // want to let them take over, say "chrome"). 1.395 + var ios = Cc["@mozilla.org/network/io-service;1"]. 1.396 + getService(Ci.nsIIOService); 1.397 + var handler = ios.getProtocolHandler(aProtocol); 1.398 + if (!(handler instanceof Ci.nsIExternalProtocolHandler)) { 1.399 + // This is handled internally, so we don't want them to register 1.400 + // XXX this should be a "security exception" according to spec, but that 1.401 + // isn't defined yet. 1.402 + throw("Permission denied to add " + aURIString + "as a protocol handler"); 1.403 + } 1.404 + 1.405 + // check if it is in the black list 1.406 + var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); 1.407 + var allowed; 1.408 + try { 1.409 + allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol); 1.410 + } 1.411 + catch (e) { 1.412 + allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default"); 1.413 + } 1.414 + if (!allowed) { 1.415 + // XXX this should be a "security exception" according to spec 1.416 + throw("Not allowed to register a protocol handler for " + aProtocol); 1.417 + } 1.418 + 1.419 + // Now Ask the user and provide the proper callback 1.420 + var message = this._getFormattedString("addProtocolHandler", 1.421 + [aTitle, uri.host, aProtocol]); 1.422 + 1.423 + var notificationIcon = uri.prePath + "/favicon.ico"; 1.424 + var notificationValue = "Protocol Registration: " + aProtocol; 1.425 + var addButton = { 1.426 + label: this._getString("addProtocolHandlerAddButton"), 1.427 + accessKey: this._getString("addHandlerAddButtonAccesskey"), 1.428 + protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle }, 1.429 + 1.430 + callback: 1.431 + function WCCR_addProtocolHandlerButtonCallback(aNotification, aButtonInfo) { 1.432 + var protocol = aButtonInfo.protocolInfo.protocol; 1.433 + var uri = aButtonInfo.protocolInfo.uri; 1.434 + var name = aButtonInfo.protocolInfo.name; 1.435 + 1.436 + var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"]. 1.437 + createInstance(Ci.nsIWebHandlerApp); 1.438 + handler.name = name; 1.439 + handler.uriTemplate = uri; 1.440 + 1.441 + var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. 1.442 + getService(Ci.nsIExternalProtocolService); 1.443 + var handlerInfo = eps.getProtocolHandlerInfo(protocol); 1.444 + handlerInfo.possibleApplicationHandlers.appendElement(handler, false); 1.445 + 1.446 + // Since the user has agreed to add a new handler, chances are good 1.447 + // that the next time they see a handler of this type, they're going 1.448 + // to want to use it. Reset the handlerInfo to ask before the next 1.449 + // use. 1.450 + handlerInfo.alwaysAskBeforeHandling = true; 1.451 + 1.452 + var hs = Cc["@mozilla.org/uriloader/handler-service;1"]. 1.453 + getService(Ci.nsIHandlerService); 1.454 + hs.store(handlerInfo); 1.455 + } 1.456 + }; 1.457 + var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow); 1.458 + var notificationBox = browserWindow.getBrowser().getNotificationBox(browserElement); 1.459 + notificationBox.appendNotification(message, 1.460 + notificationValue, 1.461 + notificationIcon, 1.462 + notificationBox.PRIORITY_INFO_LOW, 1.463 + [addButton]); 1.464 + }, 1.465 + 1.466 + /** 1.467 + * See nsIWebContentHandlerRegistrar 1.468 + * If a DOM window is provided, then the request came from content, so we 1.469 + * prompt the user to confirm the registration. 1.470 + */ 1.471 + registerContentHandler: 1.472 + function WCCR_registerContentHandler(aContentType, aURIString, aTitle, aContentWindow) { 1.473 + LOG("registerContentHandler(" + aContentType + "," + aURIString + "," + aTitle + ")"); 1.474 + 1.475 + // We only support feed types at present. 1.476 + // XXX this should be a "security exception" according to spec, but that 1.477 + // isn't defined yet. 1.478 + var contentType = this._resolveContentType(aContentType); 1.479 + if (contentType != TYPE_MAYBE_FEED) 1.480 + return; 1.481 + 1.482 + if (aContentWindow) { 1.483 + var uri = this._checkAndGetURI(aURIString, aContentWindow); 1.484 + 1.485 + var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow); 1.486 + var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow); 1.487 + var notificationBox = browserWindow.getBrowser().getNotificationBox(browserElement); 1.488 + this._appendFeedReaderNotification(uri, aTitle, notificationBox); 1.489 + } 1.490 + else 1.491 + this._registerContentHandler(contentType, aURIString, aTitle); 1.492 + }, 1.493 + 1.494 + /** 1.495 + * Returns the browser chrome window in which the content window is in 1.496 + */ 1.497 + _getBrowserWindowForContentWindow: 1.498 + function WCCR__getBrowserWindowForContentWindow(aContentWindow) { 1.499 + return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.500 + .getInterface(Ci.nsIWebNavigation) 1.501 + .QueryInterface(Ci.nsIDocShellTreeItem) 1.502 + .rootTreeItem 1.503 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.504 + .getInterface(Ci.nsIDOMWindow) 1.505 + .wrappedJSObject; 1.506 + }, 1.507 + 1.508 + /** 1.509 + * Returns the <xul:browser> element associated with the given content 1.510 + * window. 1.511 + * 1.512 + * @param aBrowserWindow 1.513 + * The browser window in which the content window is in. 1.514 + * @param aContentWindow 1.515 + * The content window. It's possible to pass a child content window 1.516 + * (i.e. the content window of a frame/iframe). 1.517 + */ 1.518 + _getBrowserForContentWindow: 1.519 + function WCCR__getBrowserForContentWindow(aBrowserWindow, aContentWindow) { 1.520 + // This depends on pseudo APIs of browser.js and tabbrowser.xml 1.521 + aContentWindow = aContentWindow.top; 1.522 + var browsers = aBrowserWindow.getBrowser().browsers; 1.523 + for (var i = 0; i < browsers.length; ++i) { 1.524 + if (browsers[i].contentWindow == aContentWindow) 1.525 + return browsers[i]; 1.526 + } 1.527 + }, 1.528 + 1.529 + /** 1.530 + * Appends a notifcation for the given feed reader details. 1.531 + * 1.532 + * The notification could be either a pseudo-dialog which lets 1.533 + * the user to add the feed reader: 1.534 + * [ [icon] Add %feed-reader-name% (%feed-reader-host%) as a Feed Reader? (Add) [x] ] 1.535 + * 1.536 + * or a simple message for the case where the feed reader is already registered: 1.537 + * [ [icon] %feed-reader-name% is already registered as a Feed Reader [x] ] 1.538 + * 1.539 + * A new notification isn't appended if the given notificationbox has a 1.540 + * notification for the same feed reader. 1.541 + * 1.542 + * @param aURI 1.543 + * The url of the feed reader as a nsIURI object 1.544 + * @param aName 1.545 + * The feed reader name as it was passed to registerContentHandler 1.546 + * @param aNotificationBox 1.547 + * The notification box to which a notification might be appended 1.548 + * @return true if a notification has been appended, false otherwise. 1.549 + */ 1.550 + _appendFeedReaderNotification: 1.551 + function WCCR__appendFeedReaderNotification(aURI, aName, aNotificationBox) { 1.552 + var uriSpec = aURI.spec; 1.553 + var notificationValue = "feed reader notification: " + uriSpec; 1.554 + var notificationIcon = aURI.prePath + "/favicon.ico"; 1.555 + 1.556 + // Don't append a new notification if the notificationbox 1.557 + // has a notification for the given feed reader already 1.558 + if (aNotificationBox.getNotificationWithValue(notificationValue)) 1.559 + return false; 1.560 + 1.561 + var buttons, message; 1.562 + if (this.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uriSpec)) 1.563 + message = this._getFormattedString("handlerRegistered", [aName]); 1.564 + else { 1.565 + message = this._getFormattedString("addHandler", [aName, aURI.host]); 1.566 + var self = this; 1.567 + var addButton = { 1.568 + _outer: self, 1.569 + label: self._getString("addHandlerAddButton"), 1.570 + accessKey: self._getString("addHandlerAddButtonAccesskey"), 1.571 + feedReaderInfo: { uri: uriSpec, name: aName }, 1.572 + 1.573 + /* static */ 1.574 + callback: 1.575 + function WCCR__addFeedReaderButtonCallback(aNotification, aButtonInfo) { 1.576 + var uri = aButtonInfo.feedReaderInfo.uri; 1.577 + var name = aButtonInfo.feedReaderInfo.name; 1.578 + var outer = aButtonInfo._outer; 1.579 + 1.580 + // The reader could have been added from another window mean while 1.581 + if (!outer.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uri)) 1.582 + outer._registerContentHandler(TYPE_MAYBE_FEED, uri, name); 1.583 + 1.584 + // avoid reference cycles 1.585 + aButtonInfo._outer = null; 1.586 + 1.587 + return false; 1.588 + } 1.589 + }; 1.590 + buttons = [addButton]; 1.591 + } 1.592 + 1.593 + aNotificationBox.appendNotification(message, 1.594 + notificationValue, 1.595 + notificationIcon, 1.596 + aNotificationBox.PRIORITY_INFO_LOW, 1.597 + buttons); 1.598 + return true; 1.599 + }, 1.600 + 1.601 + /** 1.602 + * Save Web Content Handler metadata to persistent preferences. 1.603 + * @param contentType 1.604 + * The content Type being handled 1.605 + * @param uri 1.606 + * The uri of the web service 1.607 + * @param title 1.608 + * The human readable name of the web service 1.609 + * 1.610 + * This data is stored under: 1.611 + * 1.612 + * browser.contentHandlers.type0 = content/type 1.613 + * browser.contentHandlers.uri0 = http://www.foo.com/q=%s 1.614 + * browser.contentHandlers.title0 = Foo 2.0alphr 1.615 + */ 1.616 + _saveContentHandlerToPrefs: 1.617 + function WCCR__saveContentHandlerToPrefs(contentType, uri, title) { 1.618 + var ps = 1.619 + Cc["@mozilla.org/preferences-service;1"]. 1.620 + getService(Ci.nsIPrefService); 1.621 + var i = 0; 1.622 + var typeBranch = null; 1.623 + while (true) { 1.624 + typeBranch = 1.625 + ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + i + "."); 1.626 + try { 1.627 + typeBranch.getCharPref("type"); 1.628 + ++i; 1.629 + } 1.630 + catch (e) { 1.631 + // No more handlers 1.632 + break; 1.633 + } 1.634 + } 1.635 + if (typeBranch) { 1.636 + typeBranch.setCharPref("type", contentType); 1.637 + var pls = 1.638 + Cc["@mozilla.org/pref-localizedstring;1"]. 1.639 + createInstance(Ci.nsIPrefLocalizedString); 1.640 + pls.data = uri; 1.641 + typeBranch.setComplexValue("uri", Ci.nsIPrefLocalizedString, pls); 1.642 + pls.data = title; 1.643 + typeBranch.setComplexValue("title", Ci.nsIPrefLocalizedString, pls); 1.644 + 1.645 + ps.savePrefFile(null); 1.646 + } 1.647 + }, 1.648 + 1.649 + /** 1.650 + * Determines if there is a type with a particular uri registered for the 1.651 + * specified content type already. 1.652 + * @param contentType 1.653 + * The content type that the uri handles 1.654 + * @param uri 1.655 + * The uri of the 1.656 + */ 1.657 + _typeIsRegistered: function WCCR__typeIsRegistered(contentType, uri) { 1.658 + if (!(contentType in this._contentTypes)) 1.659 + return false; 1.660 + 1.661 + var services = this._contentTypes[contentType]; 1.662 + for (var i = 0; i < services.length; ++i) { 1.663 + // This uri has already been registered 1.664 + if (services[i].uri == uri) 1.665 + return true; 1.666 + } 1.667 + return false; 1.668 + }, 1.669 + 1.670 + /** 1.671 + * Gets a stream converter contract id for the specified content type. 1.672 + * @param contentType 1.673 + * The source content type for the conversion. 1.674 + * @returns A contract id to construct a converter to convert between the 1.675 + * contentType and *\/*. 1.676 + */ 1.677 + _getConverterContractID: function WCCR__getConverterContractID(contentType) { 1.678 + const template = "@mozilla.org/streamconv;1?from=%s&to=*/*"; 1.679 + return template.replace(/%s/, contentType); 1.680 + }, 1.681 + 1.682 + /** 1.683 + * Register a web service handler for a content type. 1.684 + * 1.685 + * @param contentType 1.686 + * the content type being handled 1.687 + * @param uri 1.688 + * the URI of the web service 1.689 + * @param title 1.690 + * the human readable name of the web service 1.691 + */ 1.692 + _registerContentHandler: 1.693 + function WCCR__registerContentHandler(contentType, uri, title) { 1.694 + this._updateContentTypeHandlerMap(contentType, uri, title); 1.695 + this._saveContentHandlerToPrefs(contentType, uri, title); 1.696 + 1.697 + if (contentType == TYPE_MAYBE_FEED) { 1.698 + // Make the new handler the last-selected reader in the preview page 1.699 + // and make sure the preview page is shown the next time a feed is visited 1.700 + var pb = Cc["@mozilla.org/preferences-service;1"]. 1.701 + getService(Ci.nsIPrefService).getBranch(null); 1.702 + pb.setCharPref(PREF_SELECTED_READER, "web"); 1.703 + 1.704 + var supportsString = 1.705 + Cc["@mozilla.org/supports-string;1"]. 1.706 + createInstance(Ci.nsISupportsString); 1.707 + supportsString.data = uri; 1.708 + pb.setComplexValue(PREF_SELECTED_WEB, Ci.nsISupportsString, 1.709 + supportsString); 1.710 + pb.setCharPref(PREF_SELECTED_ACTION, "ask"); 1.711 + this._setAutoHandler(TYPE_MAYBE_FEED, null); 1.712 + } 1.713 + }, 1.714 + 1.715 + /** 1.716 + * Update the content type -> handler map. This mapping is not persisted, use 1.717 + * registerContentHandler or _saveContentHandlerToPrefs for that purpose. 1.718 + * @param contentType 1.719 + * The content Type being handled 1.720 + * @param uri 1.721 + * The uri of the web service 1.722 + * @param title 1.723 + * The human readable name of the web service 1.724 + */ 1.725 + _updateContentTypeHandlerMap: 1.726 + function WCCR__updateContentTypeHandlerMap(contentType, uri, title) { 1.727 + if (!(contentType in this._contentTypes)) 1.728 + this._contentTypes[contentType] = []; 1.729 + 1.730 + // Avoid adding duplicates 1.731 + if (this._typeIsRegistered(contentType, uri)) 1.732 + return; 1.733 + 1.734 + this._contentTypes[contentType].push(new ServiceInfo(contentType, uri, title)); 1.735 + 1.736 + if (!(contentType in this._blockedTypes)) { 1.737 + var converterContractID = this._getConverterContractID(contentType); 1.738 + var cr = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); 1.739 + cr.registerFactory(WCC_CLASSID, WCC_CLASSNAME, converterContractID, 1.740 + WebContentConverterFactory); 1.741 + } 1.742 + }, 1.743 + 1.744 + /** 1.745 + * See nsIWebContentConverterService 1.746 + */ 1.747 + getContentHandlers: 1.748 + function WCCR_getContentHandlers(contentType, countRef) { 1.749 + countRef.value = 0; 1.750 + if (!(contentType in this._contentTypes)) 1.751 + return []; 1.752 + 1.753 + var handlers = this._contentTypes[contentType]; 1.754 + countRef.value = handlers.length; 1.755 + return handlers; 1.756 + }, 1.757 + 1.758 + /** 1.759 + * See nsIWebContentConverterService 1.760 + */ 1.761 + resetHandlersForType: 1.762 + function WCCR_resetHandlersForType(contentType) { 1.763 + // currently unused within the tree, so only useful for extensions; previous 1.764 + // impl. was buggy (and even infinite-looped!), so I argue that this is a 1.765 + // definite improvement 1.766 + throw Cr.NS_ERROR_NOT_IMPLEMENTED; 1.767 + }, 1.768 + 1.769 + /** 1.770 + * Registers a handler from the settings on a preferences branch. 1.771 + * 1.772 + * @param branch 1.773 + * an nsIPrefBranch containing "type", "uri", and "title" preferences 1.774 + * corresponding to the content handler to be registered 1.775 + */ 1.776 + _registerContentHandlerWithBranch: function(branch) { 1.777 + /** 1.778 + * Since we support up to six predefined readers, we need to handle gaps 1.779 + * better, since the first branch with user-added values will be .6 1.780 + * 1.781 + * How we deal with that is to check to see if there's no prefs in the 1.782 + * branch and stop cycling once that's true. This doesn't fix the case 1.783 + * where a user manually removes a reader, but that's not supported yet! 1.784 + */ 1.785 + var vals = branch.getChildList(""); 1.786 + if (vals.length == 0) 1.787 + return; 1.788 + 1.789 + try { 1.790 + var type = branch.getCharPref("type"); 1.791 + var uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data; 1.792 + var title = branch.getComplexValue("title", 1.793 + Ci.nsIPrefLocalizedString).data; 1.794 + this._updateContentTypeHandlerMap(type, uri, title); 1.795 + } 1.796 + catch(ex) { 1.797 + // do nothing, the next branch might have values 1.798 + } 1.799 + }, 1.800 + 1.801 + /** 1.802 + * Load the auto handler, content handler and protocol tables from 1.803 + * preferences. 1.804 + */ 1.805 + _init: function WCCR__init() { 1.806 + var ps = 1.807 + Cc["@mozilla.org/preferences-service;1"]. 1.808 + getService(Ci.nsIPrefService); 1.809 + 1.810 + var kids = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH) 1.811 + .getChildList(""); 1.812 + 1.813 + // first get the numbers of the providers by getting all ###.uri prefs 1.814 + var nums = []; 1.815 + for (var i = 0; i < kids.length; i++) { 1.816 + var match = /^(\d+)\.uri$/.exec(kids[i]); 1.817 + if (!match) 1.818 + continue; 1.819 + else 1.820 + nums.push(match[1]); 1.821 + } 1.822 + 1.823 + // sort them, to get them back in order 1.824 + nums.sort(function(a, b) {return a - b;}); 1.825 + 1.826 + // now register them 1.827 + for (var i = 0; i < nums.length; i++) { 1.828 + var branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + nums[i] + "."); 1.829 + this._registerContentHandlerWithBranch(branch); 1.830 + } 1.831 + 1.832 + // We need to do this _after_ registering all of the available handlers, 1.833 + // so that getWebContentHandlerByURI can return successfully. 1.834 + try { 1.835 + var autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO); 1.836 + var childPrefs = autoBranch.getChildList(""); 1.837 + for (var i = 0; i < childPrefs.length; ++i) { 1.838 + var type = childPrefs[i]; 1.839 + var uri = autoBranch.getCharPref(type); 1.840 + if (uri) { 1.841 + var handler = this.getWebContentHandlerByURI(type, uri); 1.842 + this._setAutoHandler(type, handler); 1.843 + } 1.844 + } 1.845 + } 1.846 + catch (e) { 1.847 + // No auto branch yet, that's fine 1.848 + //LOG("WCCR.init: There is no auto branch, benign"); 1.849 + } 1.850 + }, 1.851 + 1.852 + /** 1.853 + * See nsIObserver 1.854 + */ 1.855 + observe: function WCCR_observe(subject, topic, data) { 1.856 + var os = 1.857 + Cc["@mozilla.org/observer-service;1"]. 1.858 + getService(Ci.nsIObserverService); 1.859 + switch (topic) { 1.860 + case "app-startup": 1.861 + os.addObserver(this, "browser-ui-startup-complete", false); 1.862 + break; 1.863 + case "browser-ui-startup-complete": 1.864 + os.removeObserver(this, "browser-ui-startup-complete"); 1.865 + this._init(); 1.866 + break; 1.867 + } 1.868 + }, 1.869 + 1.870 + /** 1.871 + * See nsIFactory 1.872 + */ 1.873 + createInstance: function WCCR_createInstance(outer, iid) { 1.874 + if (outer != null) 1.875 + throw Cr.NS_ERROR_NO_AGGREGATION; 1.876 + return this.QueryInterface(iid); 1.877 + }, 1.878 + 1.879 + classID: WCCR_CLASSID, 1.880 + 1.881 + /** 1.882 + * See nsISupports 1.883 + */ 1.884 + QueryInterface: XPCOMUtils.generateQI( 1.885 + [Ci.nsIWebContentConverterService, 1.886 + Ci.nsIWebContentHandlerRegistrar, 1.887 + Ci.nsIObserver, 1.888 + Ci.nsIFactory]), 1.889 + 1.890 + _xpcom_categories: [{ 1.891 + category: "app-startup", 1.892 + service: true 1.893 + }] 1.894 +}; 1.895 + 1.896 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrar]);