browser/components/feeds/src/WebContentConverter.js

branch
TOR_BUG_3246
changeset 6
8bccb770b82d
equal deleted inserted replaced
-1:000000000000 0:d5306afb23c6
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/. */
5
6 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
7 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
8
9 const Cc = Components.classes;
10 const Ci = Components.interfaces;
11 const Cr = Components.results;
12
13 function LOG(str) {
14 dump("*** " + str + "\n");
15 }
16
17 const WCCR_CONTRACTID = "@mozilla.org/embeddor.implemented/web-content-handler-registrar;1";
18 const WCCR_CLASSID = Components.ID("{792a7e82-06a0-437c-af63-b2d12e808acc}");
19
20 const WCC_CLASSID = Components.ID("{db7ebf28-cc40-415f-8a51-1b111851df1e}");
21 const WCC_CLASSNAME = "Web Service Handler";
22
23 const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
24 const TYPE_ANY = "*/*";
25
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";
33
34 const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties";
35
36 const NS_ERROR_MODULE_DOM = 2152923136;
37 const NS_ERROR_DOM_SYNTAX_ERR = NS_ERROR_MODULE_DOM + 12;
38
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() { },
46
47 onStartRequest: function WCC_onStartRequest(request, context) {
48 var wccr =
49 Cc[WCCR_CONTRACTID].
50 getService(Ci.nsIWebContentConverterService);
51 wccr.loadPreferredHandler(request);
52 },
53
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 };
62
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 },
69
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 };
77
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 },
90
91 /**
92 * See nsIHandlerApp
93 */
94 equals: function SI_equals(aHandlerApp) {
95 if (!aHandlerApp)
96 throw Cr.NS_ERROR_NULL_POINTER;
97
98 if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo &&
99 aHandlerApp.contentType == this.contentType &&
100 aHandlerApp.uri == this.uri)
101 return true;
102
103 return false;
104 },
105
106 /**
107 * See nsIWebContentHandlerInfo
108 */
109 get contentType() {
110 return this._contentType;
111 },
112
113 /**
114 * See nsIWebContentHandlerInfo
115 */
116 get uri() {
117 return this._uri;
118 },
119
120 /**
121 * See nsIWebContentHandlerInfo
122 */
123 getHandlerURI: function SI_getHandlerURI(uri) {
124 return this._uri.replace(/%s/gi, encodeURIComponent(uri));
125 },
126
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 };
134
135 function WebContentConverterRegistrar() {
136 this._contentTypes = { };
137 this._autoHandleContentTypes = { };
138 }
139
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 },
148
149 _getFormattedString: function WCCR__getFormattedString(key, params) {
150 return this.stringBundle.formatStringFromName(key, params, params.length);
151 },
152
153 _getString: function WCCR_getString(key) {
154 return this.stringBundle.GetStringFromName(key);
155 },
156
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 },
167
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;
175
176 contentType = this._resolveContentType(contentType);
177 this._setAutoHandler(contentType, handler);
178
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);
187
188 ps.savePrefFile(null);
189 },
190
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 },
201
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 },
214
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);
225
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 },
233
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 },
256
257 /**
258 * See nsIWebContentConverterService
259 */
260 removeContentHandler:
261 function WCCR_removeContentHandler(contentType, uri) {
262 function notURI(serviceInfo) {
263 return serviceInfo.uri != uri;
264 }
265
266 if (contentType in this._contentTypes) {
267 this._contentTypes[contentType] =
268 this._contentTypes[contentType].filter(notURI);
269 }
270 },
271
272 /**
273 *
274 */
275 _mappings: {
276 "application/rss+xml": TYPE_MAYBE_FEED,
277 "application/atom+xml": TYPE_MAYBE_FEED,
278 },
279
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 },
288
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 },
300
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 },
306
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 }
317
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");
324
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");
332
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;
336
337 return uri;
338 },
339
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 },
364
365 /**
366 * See nsIWebContentHandlerRegistrar
367 */
368 registerProtocolHandler:
369 function WCCR_registerProtocolHandler(aProtocol, aURIString, aTitle, aContentWindow) {
370 LOG("registerProtocolHandler(" + aProtocol + "," + aURIString + "," + aTitle + ")");
371
372 var uri = this._checkAndGetURI(aURIString, aContentWindow);
373
374 // If the protocol handler is already registered, just return early.
375 if (this._protocolHandlerRegistered(aProtocol, uri.spec)) {
376 return;
377 }
378
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 }
389
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 }
401
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 }
415
416 // Now Ask the user and provide the proper callback
417 var message = this._getFormattedString("addProtocolHandler",
418 [aTitle, uri.host, aProtocol]);
419
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 },
426
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;
432
433 var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
434 createInstance(Ci.nsIWebHandlerApp);
435 handler.name = name;
436 handler.uriTemplate = uri;
437
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);
442
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;
448
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 },
462
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 + ")");
471
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;
478
479 if (aContentWindow) {
480 var uri = this._checkAndGetURI(aURIString, aContentWindow);
481
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 },
490
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 },
504
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 },
525
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";
552
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;
557
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 },
569
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;
576
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);
580
581 // avoid reference cycles
582 aButtonInfo._outer = null;
583
584 return false;
585 }
586 };
587 buttons = [addButton];
588 }
589
590 aNotificationBox.appendNotification(message,
591 notificationValue,
592 notificationIcon,
593 aNotificationBox.PRIORITY_INFO_LOW,
594 buttons);
595 return true;
596 },
597
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);
641
642 ps.savePrefFile(null);
643 }
644 },
645
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;
657
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 },
666
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 },
678
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);
693
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");
700
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 },
711
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] = [];
726
727 // Avoid adding duplicates
728 if (this._typeIsRegistered(contentType, uri))
729 return;
730
731 this._contentTypes[contentType].push(new ServiceInfo(contentType, uri, title));
732
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 },
740
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 [];
749
750 var handlers = this._contentTypes[contentType];
751 countRef.value = handlers.length;
752 return handlers;
753 },
754
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 },
765
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;
785
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 },
797
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);
806
807 var kids = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH)
808 .getChildList("");
809
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 }
819
820 // sort them, to get them back in order
821 nums.sort(function(a, b) {return a - b;});
822
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 }
828
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 },
848
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 },
866
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 },
875
876 classID: WCCR_CLASSID,
877
878 /**
879 * See nsISupports
880 */
881 QueryInterface: XPCOMUtils.generateQI(
882 [Ci.nsIWebContentConverterService,
883 Ci.nsIWebContentHandlerRegistrar,
884 Ci.nsIObserver,
885 Ci.nsIFactory]),
886
887 _xpcom_categories: [{
888 category: "app-startup",
889 service: true
890 }]
891 };
892
893 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrar]);

mercurial