browser/components/feeds/src/WebContentConverter.js

Wed, 31 Dec 2014 07:53:36 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:53:36 +0100
branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
permissions
-rw-r--r--

Correct small whitespace inconsistency, lost while renaming variables.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
     7 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
     9 const Cc = Components.classes;
    10 const Ci = Components.interfaces;
    11 const Cr = Components.results;
    13 function LOG(str) {
    14   dump("*** " + str + "\n");
    15 }
    17 const WCCR_CONTRACTID = "@mozilla.org/embeddor.implemented/web-content-handler-registrar;1";
    18 const WCCR_CLASSID = Components.ID("{792a7e82-06a0-437c-af63-b2d12e808acc}");
    20 const WCC_CLASSID = Components.ID("{db7ebf28-cc40-415f-8a51-1b111851df1e}");
    21 const WCC_CLASSNAME = "Web Service Handler";
    23 const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
    24 const TYPE_ANY = "*/*";
    26 const PREF_CONTENTHANDLERS_AUTO = "browser.contentHandlers.auto.";
    27 const PREF_CONTENTHANDLERS_BRANCH = "browser.contentHandlers.types.";
    28 const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
    29 const PREF_SELECTED_ACTION = "browser.feeds.handler";
    30 const PREF_SELECTED_READER = "browser.feeds.handler.default";
    31 const PREF_HANDLER_EXTERNAL_PREFIX = "network.protocol-handler.external";
    32 const PREF_ALLOW_DIFFERENT_HOST = "gecko.handlerService.allowRegisterFromDifferentHost";
    34 const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties";
    36 const NS_ERROR_MODULE_DOM = 2152923136;
    37 const NS_ERROR_DOM_SYNTAX_ERR = NS_ERROR_MODULE_DOM + 12;
    39 function WebContentConverter() {
    40 }
    41 WebContentConverter.prototype = {
    42   convert: function WCC_convert() { },
    43   asyncConvertData: function WCC_asyncConvertData() { },
    44   onDataAvailable: function WCC_onDataAvailable() { },
    45   onStopRequest: function WCC_onStopRequest() { },
    47   onStartRequest: function WCC_onStartRequest(request, context) {
    48     var wccr = 
    49         Cc[WCCR_CONTRACTID].
    50         getService(Ci.nsIWebContentConverterService);
    51     wccr.loadPreferredHandler(request);
    52   },
    54   QueryInterface: function WCC_QueryInterface(iid) {
    55     if (iid.equals(Ci.nsIStreamConverter) ||
    56         iid.equals(Ci.nsIStreamListener) ||
    57         iid.equals(Ci.nsISupports))
    58       return this;
    59     throw Cr.NS_ERROR_NO_INTERFACE;
    60   }
    61 };
    63 var WebContentConverterFactory = {
    64   createInstance: function WCCF_createInstance(outer, iid) {
    65     if (outer != null)
    66       throw Cr.NS_ERROR_NO_AGGREGATION;
    67     return new WebContentConverter().QueryInterface(iid);
    68   },
    70   QueryInterface: function WCC_QueryInterface(iid) {
    71     if (iid.equals(Ci.nsIFactory) ||
    72         iid.equals(Ci.nsISupports))
    73       return this;
    74     throw Cr.NS_ERROR_NO_INTERFACE;
    75   }
    76 };
    78 function ServiceInfo(contentType, uri, name) {
    79   this._contentType = contentType;
    80   this._uri = uri;
    81   this._name = name;
    82 }
    83 ServiceInfo.prototype = {
    84   /**
    85    * See nsIHandlerApp
    86    */
    87   get name() {
    88     return this._name;
    89   },
    91   /**
    92    * See nsIHandlerApp
    93    */
    94   equals: function SI_equals(aHandlerApp) {
    95     if (!aHandlerApp)
    96       throw Cr.NS_ERROR_NULL_POINTER;
    98     if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo &&
    99         aHandlerApp.contentType == this.contentType &&
   100         aHandlerApp.uri == this.uri)
   101       return true;
   103     return false;
   104   },
   106   /**
   107    * See nsIWebContentHandlerInfo
   108    */
   109   get contentType() {
   110     return this._contentType;
   111   },
   113   /**
   114    * See nsIWebContentHandlerInfo
   115    */
   116   get uri() {
   117     return this._uri;
   118   },
   120   /**
   121    * See nsIWebContentHandlerInfo
   122    */
   123   getHandlerURI: function SI_getHandlerURI(uri) {
   124     return this._uri.replace(/%s/gi, encodeURIComponent(uri));
   125   },
   127   QueryInterface: function SI_QueryInterface(iid) {
   128     if (iid.equals(Ci.nsIWebContentHandlerInfo) ||
   129         iid.equals(Ci.nsISupports))
   130       return this;
   131     throw Cr.NS_ERROR_NO_INTERFACE;
   132   }
   133 };
   135 function WebContentConverterRegistrar() {
   136   this._contentTypes = { };
   137   this._autoHandleContentTypes = { };
   138 }
   140 WebContentConverterRegistrar.prototype = {
   141   get stringBundle() {
   142     var sb = Cc["@mozilla.org/intl/stringbundle;1"].
   143               getService(Ci.nsIStringBundleService).
   144               createBundle(STRING_BUNDLE_URI);
   145     delete WebContentConverterRegistrar.prototype.stringBundle;
   146     return WebContentConverterRegistrar.prototype.stringBundle = sb;
   147   },
   149   _getFormattedString: function WCCR__getFormattedString(key, params) {
   150     return this.stringBundle.formatStringFromName(key, params, params.length);
   151   },
   153   _getString: function WCCR_getString(key) {
   154     return this.stringBundle.GetStringFromName(key);
   155   },
   157   /**
   158    * See nsIWebContentConverterService
   159    */
   160   getAutoHandler: 
   161   function WCCR_getAutoHandler(contentType) {
   162     contentType = this._resolveContentType(contentType);
   163     if (contentType in this._autoHandleContentTypes)
   164       return this._autoHandleContentTypes[contentType];
   165     return null;
   166   },
   168   /**
   169    * See nsIWebContentConverterService
   170    */
   171   setAutoHandler:
   172   function WCCR_setAutoHandler(contentType, handler) {
   173     if (handler && !this._typeIsRegistered(contentType, handler.uri))
   174       throw Cr.NS_ERROR_NOT_AVAILABLE;
   176     contentType = this._resolveContentType(contentType);
   177     this._setAutoHandler(contentType, handler);
   179     var ps = 
   180         Cc["@mozilla.org/preferences-service;1"].
   181         getService(Ci.nsIPrefService);
   182     var autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
   183     if (handler)
   184       autoBranch.setCharPref(contentType, handler.uri);
   185     else if (autoBranch.prefHasUserValue(contentType))
   186       autoBranch.clearUserPref(contentType);
   188     ps.savePrefFile(null);
   189   },
   191   /**
   192    * Update the internal data structure (not persistent)
   193    */
   194   _setAutoHandler:
   195   function WCCR__setAutoHandler(contentType, handler) {
   196     if (handler) 
   197       this._autoHandleContentTypes[contentType] = handler;
   198     else if (contentType in this._autoHandleContentTypes)
   199       delete this._autoHandleContentTypes[contentType];
   200   },
   202   /**
   203    * See nsIWebContentConverterService
   204    */
   205   getWebContentHandlerByURI:
   206   function WCCR_getWebContentHandlerByURI(contentType, uri) {
   207     var handlers = this.getContentHandlers(contentType, { });
   208     for (var i = 0; i < handlers.length; ++i) {
   209       if (handlers[i].uri == uri) 
   210         return handlers[i];
   211     }
   212     return null;
   213   },
   215   /**
   216    * See nsIWebContentConverterService
   217    */
   218   loadPreferredHandler: 
   219   function WCCR_loadPreferredHandler(request) {
   220     var channel = request.QueryInterface(Ci.nsIChannel);
   221     var contentType = this._resolveContentType(channel.contentType);
   222     var handler = this.getAutoHandler(contentType);
   223     if (handler) {
   224       request.cancel(Cr.NS_ERROR_FAILURE);
   226       var webNavigation = 
   227           channel.notificationCallbacks.getInterface(Ci.nsIWebNavigation);
   228       webNavigation.loadURI(handler.getHandlerURI(channel.URI.spec), 
   229                             Ci.nsIWebNavigation.LOAD_FLAGS_NONE, 
   230                             null, null, null);
   231     }      
   232   },
   234   /**
   235    * See nsIWebContentConverterService
   236    */
   237   removeProtocolHandler: 
   238   function WCCR_removeProtocolHandler(aProtocol, aURITemplate) {
   239     var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
   240               getService(Ci.nsIExternalProtocolService);
   241     var handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
   242     var handlers =  handlerInfo.possibleApplicationHandlers;
   243     for (let i = 0; i < handlers.length; i++) {
   244       try { // We only want to test web handlers
   245         let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
   246         if (handler.uriTemplate == aURITemplate) {
   247           handlers.removeElementAt(i);
   248           var hs = Cc["@mozilla.org/uriloader/handler-service;1"].
   249                    getService(Ci.nsIHandlerService);
   250           hs.store(handlerInfo);
   251           return;
   252         }
   253       } catch (e) { /* it wasn't a web handler */ }
   254     }
   255   },
   257   /**
   258    * See nsIWebContentConverterService
   259    */
   260   removeContentHandler: 
   261   function WCCR_removeContentHandler(contentType, uri) {
   262     function notURI(serviceInfo) {
   263       return serviceInfo.uri != uri;
   264     }
   266     if (contentType in this._contentTypes) {
   267       this._contentTypes[contentType] = 
   268         this._contentTypes[contentType].filter(notURI);
   269     }
   270   },
   272   /**
   273    *
   274    */
   275   _mappings: { 
   276     "application/rss+xml": TYPE_MAYBE_FEED,
   277     "application/atom+xml": TYPE_MAYBE_FEED,
   278   },
   280   /**
   281    * These are types for which there is a separate content converter aside 
   282    * from our built in generic one. We should not automatically register
   283    * a factory for creating a converter for these types.
   284    */
   285   _blockedTypes: {
   286     "application/vnd.mozilla.maybe.feed": true,
   287   },
   289   /**
   290    * Determines the "internal" content type based on the _mappings.
   291    * @param   contentType
   292    * @returns The resolved contentType value. 
   293    */
   294   _resolveContentType: 
   295   function WCCR__resolveContentType(contentType) {
   296     if (contentType in this._mappings)
   297       return this._mappings[contentType];
   298     return contentType;
   299   },
   301   _makeURI: function(aURL, aOriginCharset, aBaseURI) {
   302     var ioService = Components.classes["@mozilla.org/network/io-service;1"]
   303                               .getService(Components.interfaces.nsIIOService);
   304     return ioService.newURI(aURL, aOriginCharset, aBaseURI);
   305   },
   307   _checkAndGetURI:
   308   function WCCR_checkAndGetURI(aURIString, aContentWindow)
   309   {
   310     try {
   311       let baseURI = aContentWindow.document.baseURIObject;
   312       var uri = this._makeURI(aURIString, null, baseURI);
   313     } catch (ex) {
   314       // not supposed to throw according to spec
   315       return; 
   316     }
   318     // For security reasons we reject non-http(s) urls (see bug 354316),
   319     // we may need to revise this once we support more content types
   320     // XXX this should be a "security exception" according to spec, but that
   321     // isn't defined yet.
   322     if (uri.scheme != "http" && uri.scheme != "https")
   323       throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
   325     // We also reject handlers registered from a different host (see bug 402287)
   326     // The pref allows us to test the feature
   327     var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
   328     if ((!pb.prefHasUserValue(PREF_ALLOW_DIFFERENT_HOST) ||
   329          !pb.getBoolPref(PREF_ALLOW_DIFFERENT_HOST)) &&
   330         aContentWindow.location.hostname != uri.host)
   331       throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
   333     // If the uri doesn't contain '%s', it won't be a good handler
   334     if (uri.spec.indexOf("%s") < 0)
   335       throw NS_ERROR_DOM_SYNTAX_ERR; 
   337     return uri;
   338   },
   340   /**
   341    * Determines if a web handler is already registered.
   342    *
   343    * @param aProtocol
   344    *        The scheme of the web handler we are checking for.
   345    * @param aURITemplate
   346    *        The URI template that the handler uses to handle the protocol.
   347    * @return true if it is already registered, false otherwise.
   348    */
   349   _protocolHandlerRegistered:
   350   function WCCR_protocolHandlerRegistered(aProtocol, aURITemplate) {
   351     var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
   352               getService(Ci.nsIExternalProtocolService);
   353     var handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
   354     var handlers =  handlerInfo.possibleApplicationHandlers;
   355     for (let i = 0; i < handlers.length; i++) {
   356       try { // We only want to test web handlers
   357         let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
   358         if (handler.uriTemplate == aURITemplate)
   359           return true;
   360       } catch (e) { /* it wasn't a web handler */ }
   361     }
   362     return false;
   363   },
   365   /**
   366    * See nsIWebContentHandlerRegistrar
   367    */
   368   registerProtocolHandler: 
   369   function WCCR_registerProtocolHandler(aProtocol, aURIString, aTitle, aContentWindow) {
   370     LOG("registerProtocolHandler(" + aProtocol + "," + aURIString + "," + aTitle + ")");
   372     var uri = this._checkAndGetURI(aURIString, aContentWindow);
   374     // If the protocol handler is already registered, just return early.
   375     if (this._protocolHandlerRegistered(aProtocol, uri.spec)) {
   376       return;
   377     }
   379     var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);    
   380     if (PrivateBrowsingUtils.isWindowPrivate(browserWindow)) {
   381       // Inside the private browsing mode, we don't want to alert the user to save
   382       // a protocol handler.  We log it to the error console so that web developers
   383       // would have some way to tell what's going wrong.
   384       Cc["@mozilla.org/consoleservice;1"].
   385       getService(Ci.nsIConsoleService).
   386       logStringMessage("Web page denied access to register a protocol handler inside private browsing mode");
   387       return;
   388     }
   390     // First, check to make sure this isn't already handled internally (we don't
   391     // want to let them take over, say "chrome").
   392     var ios = Cc["@mozilla.org/network/io-service;1"].
   393               getService(Ci.nsIIOService);
   394     var handler = ios.getProtocolHandler(aProtocol);
   395     if (!(handler instanceof Ci.nsIExternalProtocolHandler)) {
   396       // This is handled internally, so we don't want them to register
   397       // XXX this should be a "security exception" according to spec, but that
   398       // isn't defined yet.
   399       throw("Permission denied to add " + aURIString + "as a protocol handler");
   400     }
   402     // check if it is in the black list
   403     var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
   404     var allowed;
   405     try {
   406       allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol);
   407     }
   408     catch (e) {
   409       allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default");
   410     }
   411     if (!allowed) {
   412       // XXX this should be a "security exception" according to spec
   413       throw("Not allowed to register a protocol handler for " + aProtocol);
   414     }
   416     // Now Ask the user and provide the proper callback
   417     var message = this._getFormattedString("addProtocolHandler",
   418                                            [aTitle, uri.host, aProtocol]);
   420     var notificationIcon = uri.prePath + "/favicon.ico";
   421     var notificationValue = "Protocol Registration: " + aProtocol;
   422     var addButton = {
   423       label: this._getString("addProtocolHandlerAddButton"),
   424       accessKey: this._getString("addHandlerAddButtonAccesskey"),
   425       protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle },
   427       callback:
   428         function WCCR_addProtocolHandlerButtonCallback(aNotification, aButtonInfo) {
   429           var protocol = aButtonInfo.protocolInfo.protocol;
   430           var uri      = aButtonInfo.protocolInfo.uri;
   431           var name     = aButtonInfo.protocolInfo.name;
   433           var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
   434                         createInstance(Ci.nsIWebHandlerApp);
   435           handler.name = name;
   436           handler.uriTemplate = uri;
   438           var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
   439                     getService(Ci.nsIExternalProtocolService);
   440           var handlerInfo = eps.getProtocolHandlerInfo(protocol);
   441           handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
   443           // Since the user has agreed to add a new handler, chances are good
   444           // that the next time they see a handler of this type, they're going
   445           // to want to use it.  Reset the handlerInfo to ask before the next
   446           // use.
   447           handlerInfo.alwaysAskBeforeHandling = true;
   449           var hs = Cc["@mozilla.org/uriloader/handler-service;1"].
   450                    getService(Ci.nsIHandlerService);
   451           hs.store(handlerInfo);
   452         }
   453     };
   454     var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow);
   455     var notificationBox = browserWindow.getBrowser().getNotificationBox(browserElement);
   456     notificationBox.appendNotification(message,
   457                                        notificationValue,
   458                                        notificationIcon,
   459                                        notificationBox.PRIORITY_INFO_LOW,
   460                                        [addButton]);
   461   },
   463   /**
   464    * See nsIWebContentHandlerRegistrar
   465    * If a DOM window is provided, then the request came from content, so we
   466    * prompt the user to confirm the registration.
   467    */
   468   registerContentHandler: 
   469   function WCCR_registerContentHandler(aContentType, aURIString, aTitle, aContentWindow) {
   470     LOG("registerContentHandler(" + aContentType + "," + aURIString + "," + aTitle + ")");
   472     // We only support feed types at present.
   473     // XXX this should be a "security exception" according to spec, but that
   474     // isn't defined yet.
   475     var contentType = this._resolveContentType(aContentType);
   476     if (contentType != TYPE_MAYBE_FEED)
   477       return;
   479     if (aContentWindow) {
   480       var uri = this._checkAndGetURI(aURIString, aContentWindow);
   482       var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
   483       var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow);
   484       var notificationBox = browserWindow.getBrowser().getNotificationBox(browserElement);
   485       this._appendFeedReaderNotification(uri, aTitle, notificationBox);
   486     }
   487     else
   488       this._registerContentHandler(contentType, aURIString, aTitle);
   489   },
   491   /**
   492    * Returns the browser chrome window in which the content window is in
   493    */
   494   _getBrowserWindowForContentWindow:
   495   function WCCR__getBrowserWindowForContentWindow(aContentWindow) {
   496     return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   497                          .getInterface(Ci.nsIWebNavigation)
   498                          .QueryInterface(Ci.nsIDocShellTreeItem)
   499                          .rootTreeItem
   500                          .QueryInterface(Ci.nsIInterfaceRequestor)
   501                          .getInterface(Ci.nsIDOMWindow)
   502                          .wrappedJSObject;
   503   },
   505   /**
   506    * Returns the <xul:browser> element associated with the given content
   507    * window.
   508    *
   509    * @param aBrowserWindow
   510    *        The browser window in which the content window is in.
   511    * @param aContentWindow
   512    *        The content window. It's possible to pass a child content window
   513    *        (i.e. the content window of a frame/iframe).
   514    */
   515   _getBrowserForContentWindow:
   516   function WCCR__getBrowserForContentWindow(aBrowserWindow, aContentWindow) {
   517     // This depends on pseudo APIs of browser.js and tabbrowser.xml
   518     aContentWindow = aContentWindow.top;
   519     var browsers = aBrowserWindow.getBrowser().browsers;
   520     for (var i = 0; i < browsers.length; ++i) {
   521       if (browsers[i].contentWindow == aContentWindow)
   522         return browsers[i];
   523     }
   524   },
   526   /**
   527    * Appends a notifcation for the given feed reader details.
   528    *
   529    * The notification could be either a pseudo-dialog which lets
   530    * the user to add the feed reader:
   531    * [ [icon] Add %feed-reader-name% (%feed-reader-host%) as a Feed Reader?  (Add) [x] ]
   532    *
   533    * or a simple message for the case where the feed reader is already registered:
   534    * [ [icon] %feed-reader-name% is already registered as a Feed Reader             [x] ]
   535    *
   536    * A new notification isn't appended if the given notificationbox has a
   537    * notification for the same feed reader.
   538    *
   539    * @param aURI
   540    *        The url of the feed reader as a nsIURI object
   541    * @param aName
   542    *        The feed reader name as it was passed to registerContentHandler
   543    * @param aNotificationBox
   544    *        The notification box to which a notification might be appended
   545    * @return true if a notification has been appended, false otherwise.
   546    */
   547   _appendFeedReaderNotification:
   548   function WCCR__appendFeedReaderNotification(aURI, aName, aNotificationBox) {
   549     var uriSpec = aURI.spec;
   550     var notificationValue = "feed reader notification: " + uriSpec;
   551     var notificationIcon = aURI.prePath + "/favicon.ico";
   553     // Don't append a new notification if the notificationbox
   554     // has a notification for the given feed reader already
   555     if (aNotificationBox.getNotificationWithValue(notificationValue))
   556       return false;
   558     var buttons, message;
   559     if (this.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uriSpec))
   560       message = this._getFormattedString("handlerRegistered", [aName]);
   561     else {
   562       message = this._getFormattedString("addHandler", [aName, aURI.host]);
   563       var self = this;
   564       var addButton = {
   565         _outer: self,
   566         label: self._getString("addHandlerAddButton"),
   567         accessKey: self._getString("addHandlerAddButtonAccesskey"),
   568         feedReaderInfo: { uri: uriSpec, name: aName },
   570         /* static */
   571         callback:
   572         function WCCR__addFeedReaderButtonCallback(aNotification, aButtonInfo) {
   573           var uri = aButtonInfo.feedReaderInfo.uri;
   574           var name = aButtonInfo.feedReaderInfo.name;
   575           var outer = aButtonInfo._outer;
   577           // The reader could have been added from another window mean while
   578           if (!outer.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uri))
   579             outer._registerContentHandler(TYPE_MAYBE_FEED, uri, name);
   581           // avoid reference cycles
   582           aButtonInfo._outer = null;
   584           return false;
   585         }
   586       };
   587       buttons = [addButton];
   588     }
   590     aNotificationBox.appendNotification(message,
   591                                         notificationValue,
   592                                         notificationIcon,
   593                                         aNotificationBox.PRIORITY_INFO_LOW,
   594                                         buttons);
   595     return true;
   596   },
   598   /**
   599    * Save Web Content Handler metadata to persistent preferences. 
   600    * @param   contentType
   601    *          The content Type being handled
   602    * @param   uri
   603    *          The uri of the web service
   604    * @param   title
   605    *          The human readable name of the web service
   606    *
   607    * This data is stored under:
   608    * 
   609    *    browser.contentHandlers.type0 = content/type
   610    *    browser.contentHandlers.uri0 = http://www.foo.com/q=%s
   611    *    browser.contentHandlers.title0 = Foo 2.0alphr
   612    */
   613   _saveContentHandlerToPrefs: 
   614   function WCCR__saveContentHandlerToPrefs(contentType, uri, title) {
   615     var ps = 
   616         Cc["@mozilla.org/preferences-service;1"].
   617         getService(Ci.nsIPrefService);
   618     var i = 0;
   619     var typeBranch = null;
   620     while (true) {
   621       typeBranch = 
   622         ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + i + ".");
   623       try {
   624         typeBranch.getCharPref("type");
   625         ++i;
   626       }
   627       catch (e) {
   628         // No more handlers
   629         break;
   630       }
   631     }
   632     if (typeBranch) {
   633       typeBranch.setCharPref("type", contentType);
   634       var pls = 
   635           Cc["@mozilla.org/pref-localizedstring;1"].
   636           createInstance(Ci.nsIPrefLocalizedString);
   637       pls.data = uri;
   638       typeBranch.setComplexValue("uri", Ci.nsIPrefLocalizedString, pls);
   639       pls.data = title;
   640       typeBranch.setComplexValue("title", Ci.nsIPrefLocalizedString, pls);
   642       ps.savePrefFile(null);
   643     }
   644   },
   646   /**
   647    * Determines if there is a type with a particular uri registered for the 
   648    * specified content type already.
   649    * @param   contentType
   650    *          The content type that the uri handles
   651    * @param   uri
   652    *          The uri of the 
   653    */
   654   _typeIsRegistered: function WCCR__typeIsRegistered(contentType, uri) {
   655     if (!(contentType in this._contentTypes))
   656       return false;
   658     var services = this._contentTypes[contentType];
   659     for (var i = 0; i < services.length; ++i) {
   660       // This uri has already been registered
   661       if (services[i].uri == uri)
   662         return true;
   663     }
   664     return false;
   665   },
   667   /**
   668    * Gets a stream converter contract id for the specified content type.
   669    * @param   contentType
   670    *          The source content type for the conversion.
   671    * @returns A contract id to construct a converter to convert between the 
   672    *          contentType and *\/*.
   673    */
   674   _getConverterContractID: function WCCR__getConverterContractID(contentType) {
   675     const template = "@mozilla.org/streamconv;1?from=%s&to=*/*";
   676     return template.replace(/%s/, contentType);
   677   },
   679   /**
   680    * Register a web service handler for a content type.
   681    * 
   682    * @param   contentType
   683    *          the content type being handled
   684    * @param   uri
   685    *          the URI of the web service
   686    * @param   title
   687    *          the human readable name of the web service
   688    */
   689   _registerContentHandler:
   690   function WCCR__registerContentHandler(contentType, uri, title) {
   691     this._updateContentTypeHandlerMap(contentType, uri, title);
   692     this._saveContentHandlerToPrefs(contentType, uri, title);
   694     if (contentType == TYPE_MAYBE_FEED) {
   695       // Make the new handler the last-selected reader in the preview page
   696       // and make sure the preview page is shown the next time a feed is visited
   697       var pb = Cc["@mozilla.org/preferences-service;1"].
   698                getService(Ci.nsIPrefService).getBranch(null);
   699       pb.setCharPref(PREF_SELECTED_READER, "web");
   701       var supportsString = 
   702         Cc["@mozilla.org/supports-string;1"].
   703         createInstance(Ci.nsISupportsString);
   704         supportsString.data = uri;
   705       pb.setComplexValue(PREF_SELECTED_WEB, Ci.nsISupportsString,
   706                          supportsString);
   707       pb.setCharPref(PREF_SELECTED_ACTION, "ask");
   708       this._setAutoHandler(TYPE_MAYBE_FEED, null);
   709     }
   710   },
   712   /**
   713    * Update the content type -> handler map. This mapping is not persisted, use
   714    * registerContentHandler or _saveContentHandlerToPrefs for that purpose.
   715    * @param   contentType
   716    *          The content Type being handled
   717    * @param   uri
   718    *          The uri of the web service
   719    * @param   title
   720    *          The human readable name of the web service
   721    */
   722   _updateContentTypeHandlerMap: 
   723   function WCCR__updateContentTypeHandlerMap(contentType, uri, title) {
   724     if (!(contentType in this._contentTypes))
   725       this._contentTypes[contentType] = [];
   727     // Avoid adding duplicates
   728     if (this._typeIsRegistered(contentType, uri)) 
   729       return;
   731     this._contentTypes[contentType].push(new ServiceInfo(contentType, uri, title));
   733     if (!(contentType in this._blockedTypes)) {
   734       var converterContractID = this._getConverterContractID(contentType);
   735       var cr = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
   736       cr.registerFactory(WCC_CLASSID, WCC_CLASSNAME, converterContractID, 
   737                          WebContentConverterFactory);
   738     }
   739   },
   741   /**
   742    * See nsIWebContentConverterService
   743    */
   744   getContentHandlers: 
   745   function WCCR_getContentHandlers(contentType, countRef) {
   746     countRef.value = 0;
   747     if (!(contentType in this._contentTypes))
   748       return [];
   750     var handlers = this._contentTypes[contentType];
   751     countRef.value = handlers.length;
   752     return handlers;
   753   },
   755   /**
   756    * See nsIWebContentConverterService
   757    */
   758   resetHandlersForType: 
   759   function WCCR_resetHandlersForType(contentType) {
   760     // currently unused within the tree, so only useful for extensions; previous
   761     // impl. was buggy (and even infinite-looped!), so I argue that this is a
   762     // definite improvement
   763     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   764   },
   766   /**
   767    * Registers a handler from the settings on a preferences branch.
   768    *
   769    * @param branch
   770    *        an nsIPrefBranch containing "type", "uri", and "title" preferences
   771    *        corresponding to the content handler to be registered
   772    */
   773   _registerContentHandlerWithBranch: function(branch) {
   774     /**
   775      * Since we support up to six predefined readers, we need to handle gaps 
   776      * better, since the first branch with user-added values will be .6
   777      * 
   778      * How we deal with that is to check to see if there's no prefs in the 
   779      * branch and stop cycling once that's true.  This doesn't fix the case
   780      * where a user manually removes a reader, but that's not supported yet!
   781      */
   782     var vals = branch.getChildList("");
   783     if (vals.length == 0)
   784       return;
   786     try {
   787       var type = branch.getCharPref("type");
   788       var uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data;
   789       var title = branch.getComplexValue("title",
   790                                          Ci.nsIPrefLocalizedString).data;
   791       this._updateContentTypeHandlerMap(type, uri, title);
   792     }
   793     catch(ex) {
   794       // do nothing, the next branch might have values
   795     }
   796   },
   798   /**
   799    * Load the auto handler, content handler and protocol tables from 
   800    * preferences.
   801    */
   802   _init: function WCCR__init() {
   803     var ps = 
   804         Cc["@mozilla.org/preferences-service;1"].
   805         getService(Ci.nsIPrefService);
   807     var kids = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH)
   808                  .getChildList("");
   810     // first get the numbers of the providers by getting all ###.uri prefs
   811     var nums = [];
   812     for (var i = 0; i < kids.length; i++) {
   813       var match = /^(\d+)\.uri$/.exec(kids[i]);
   814       if (!match)
   815         continue;
   816       else
   817         nums.push(match[1]);
   818     }
   820     // sort them, to get them back in order
   821     nums.sort(function(a, b) {return a - b;});
   823     // now register them
   824     for (var i = 0; i < nums.length; i++) {
   825       var branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + nums[i] + ".");
   826       this._registerContentHandlerWithBranch(branch);
   827     }
   829     // We need to do this _after_ registering all of the available handlers, 
   830     // so that getWebContentHandlerByURI can return successfully.
   831     try {
   832       var autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
   833       var childPrefs = autoBranch.getChildList("");
   834       for (var i = 0; i < childPrefs.length; ++i) {
   835         var type = childPrefs[i];
   836         var uri = autoBranch.getCharPref(type);
   837         if (uri) {
   838           var handler = this.getWebContentHandlerByURI(type, uri);
   839           this._setAutoHandler(type, handler);
   840         }
   841       }
   842     }
   843     catch (e) {
   844       // No auto branch yet, that's fine
   845       //LOG("WCCR.init: There is no auto branch, benign");
   846     }
   847   },
   849   /**
   850    * See nsIObserver
   851    */
   852   observe: function WCCR_observe(subject, topic, data) {
   853     var os = 
   854         Cc["@mozilla.org/observer-service;1"].
   855         getService(Ci.nsIObserverService);
   856     switch (topic) {
   857     case "app-startup":
   858       os.addObserver(this, "browser-ui-startup-complete", false);
   859       break;
   860     case "browser-ui-startup-complete":
   861       os.removeObserver(this, "browser-ui-startup-complete");
   862       this._init();
   863       break;
   864     }
   865   },
   867   /**
   868    * See nsIFactory
   869    */
   870   createInstance: function WCCR_createInstance(outer, iid) {
   871     if (outer != null)
   872       throw Cr.NS_ERROR_NO_AGGREGATION;
   873     return this.QueryInterface(iid);
   874   },
   876   classID: WCCR_CLASSID,
   878   /**
   879    * See nsISupports
   880    */
   881   QueryInterface: XPCOMUtils.generateQI(
   882      [Ci.nsIWebContentConverterService, 
   883       Ci.nsIWebContentHandlerRegistrar,
   884       Ci.nsIObserver,
   885       Ci.nsIFactory]),
   887   _xpcom_categories: [{
   888     category: "app-startup",
   889     service: true
   890   }]
   891 };
   893 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrar]);

mercurial