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.

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

mercurial