browser/components/feeds/src/WebContentConverter.js

changeset 0
6474c204b198
     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]);

mercurial