WebContentConverter.js 30 KB

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