|
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]); |