Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 7 | Components.utils.import("resource://gre/modules/debug.js"); |
michael@0 | 8 | Components.utils.import("resource://gre/modules/Services.jsm"); |
michael@0 | 9 | |
michael@0 | 10 | const Cc = Components.classes; |
michael@0 | 11 | const Ci = Components.interfaces; |
michael@0 | 12 | const Cr = Components.results; |
michael@0 | 13 | |
michael@0 | 14 | function LOG(str) { |
michael@0 | 15 | dump("*** " + str + "\n"); |
michael@0 | 16 | } |
michael@0 | 17 | |
michael@0 | 18 | const FS_CONTRACTID = "@mozilla.org/browser/feeds/result-service;1"; |
michael@0 | 19 | const FPH_CONTRACTID = "@mozilla.org/network/protocol;1?name=feed"; |
michael@0 | 20 | const PCPH_CONTRACTID = "@mozilla.org/network/protocol;1?name=pcast"; |
michael@0 | 21 | |
michael@0 | 22 | const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed"; |
michael@0 | 23 | const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed"; |
michael@0 | 24 | const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed"; |
michael@0 | 25 | const TYPE_ANY = "*/*"; |
michael@0 | 26 | |
michael@0 | 27 | const PREF_SELECTED_APP = "browser.feeds.handlers.application"; |
michael@0 | 28 | const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice"; |
michael@0 | 29 | const PREF_SELECTED_ACTION = "browser.feeds.handler"; |
michael@0 | 30 | const PREF_SELECTED_READER = "browser.feeds.handler.default"; |
michael@0 | 31 | |
michael@0 | 32 | const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application"; |
michael@0 | 33 | const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice"; |
michael@0 | 34 | const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler"; |
michael@0 | 35 | const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default"; |
michael@0 | 36 | |
michael@0 | 37 | const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application"; |
michael@0 | 38 | const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice"; |
michael@0 | 39 | const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler"; |
michael@0 | 40 | const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default"; |
michael@0 | 41 | |
michael@0 | 42 | function getPrefAppForType(t) { |
michael@0 | 43 | switch (t) { |
michael@0 | 44 | case Ci.nsIFeed.TYPE_VIDEO: |
michael@0 | 45 | return PREF_VIDEO_SELECTED_APP; |
michael@0 | 46 | |
michael@0 | 47 | case Ci.nsIFeed.TYPE_AUDIO: |
michael@0 | 48 | return PREF_AUDIO_SELECTED_APP; |
michael@0 | 49 | |
michael@0 | 50 | default: |
michael@0 | 51 | return PREF_SELECTED_APP; |
michael@0 | 52 | } |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | function getPrefWebForType(t) { |
michael@0 | 56 | switch (t) { |
michael@0 | 57 | case Ci.nsIFeed.TYPE_VIDEO: |
michael@0 | 58 | return PREF_VIDEO_SELECTED_WEB; |
michael@0 | 59 | |
michael@0 | 60 | case Ci.nsIFeed.TYPE_AUDIO: |
michael@0 | 61 | return PREF_AUDIO_SELECTED_WEB; |
michael@0 | 62 | |
michael@0 | 63 | default: |
michael@0 | 64 | return PREF_SELECTED_WEB; |
michael@0 | 65 | } |
michael@0 | 66 | } |
michael@0 | 67 | |
michael@0 | 68 | function getPrefActionForType(t) { |
michael@0 | 69 | switch (t) { |
michael@0 | 70 | case Ci.nsIFeed.TYPE_VIDEO: |
michael@0 | 71 | return PREF_VIDEO_SELECTED_ACTION; |
michael@0 | 72 | |
michael@0 | 73 | case Ci.nsIFeed.TYPE_AUDIO: |
michael@0 | 74 | return PREF_AUDIO_SELECTED_ACTION; |
michael@0 | 75 | |
michael@0 | 76 | default: |
michael@0 | 77 | return PREF_SELECTED_ACTION; |
michael@0 | 78 | } |
michael@0 | 79 | } |
michael@0 | 80 | |
michael@0 | 81 | function getPrefReaderForType(t) { |
michael@0 | 82 | switch (t) { |
michael@0 | 83 | case Ci.nsIFeed.TYPE_VIDEO: |
michael@0 | 84 | return PREF_VIDEO_SELECTED_READER; |
michael@0 | 85 | |
michael@0 | 86 | case Ci.nsIFeed.TYPE_AUDIO: |
michael@0 | 87 | return PREF_AUDIO_SELECTED_READER; |
michael@0 | 88 | |
michael@0 | 89 | default: |
michael@0 | 90 | return PREF_SELECTED_READER; |
michael@0 | 91 | } |
michael@0 | 92 | } |
michael@0 | 93 | |
michael@0 | 94 | function safeGetCharPref(pref, defaultValue) { |
michael@0 | 95 | var prefs = |
michael@0 | 96 | Cc["@mozilla.org/preferences-service;1"]. |
michael@0 | 97 | getService(Ci.nsIPrefBranch); |
michael@0 | 98 | try { |
michael@0 | 99 | return prefs.getCharPref(pref); |
michael@0 | 100 | } |
michael@0 | 101 | catch (e) { |
michael@0 | 102 | } |
michael@0 | 103 | return defaultValue; |
michael@0 | 104 | } |
michael@0 | 105 | |
michael@0 | 106 | function FeedConverter() { |
michael@0 | 107 | } |
michael@0 | 108 | FeedConverter.prototype = { |
michael@0 | 109 | classID: Components.ID("{229fa115-9412-4d32-baf3-2fc407f76fb1}"), |
michael@0 | 110 | |
michael@0 | 111 | /** |
michael@0 | 112 | * This is the downloaded text data for the feed. |
michael@0 | 113 | */ |
michael@0 | 114 | _data: null, |
michael@0 | 115 | |
michael@0 | 116 | /** |
michael@0 | 117 | * This is the object listening to the conversion, which is ultimately the |
michael@0 | 118 | * docshell for the load. |
michael@0 | 119 | */ |
michael@0 | 120 | _listener: null, |
michael@0 | 121 | |
michael@0 | 122 | /** |
michael@0 | 123 | * Records if the feed was sniffed |
michael@0 | 124 | */ |
michael@0 | 125 | _sniffed: false, |
michael@0 | 126 | |
michael@0 | 127 | /** |
michael@0 | 128 | * See nsIStreamConverter.idl |
michael@0 | 129 | */ |
michael@0 | 130 | convert: function FC_convert(sourceStream, sourceType, destinationType, |
michael@0 | 131 | context) { |
michael@0 | 132 | throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 133 | }, |
michael@0 | 134 | |
michael@0 | 135 | /** |
michael@0 | 136 | * See nsIStreamConverter.idl |
michael@0 | 137 | */ |
michael@0 | 138 | asyncConvertData: function FC_asyncConvertData(sourceType, destinationType, |
michael@0 | 139 | listener, context) { |
michael@0 | 140 | this._listener = listener; |
michael@0 | 141 | }, |
michael@0 | 142 | |
michael@0 | 143 | /** |
michael@0 | 144 | * Whether or not the preview page is being forced. |
michael@0 | 145 | */ |
michael@0 | 146 | _forcePreviewPage: false, |
michael@0 | 147 | |
michael@0 | 148 | /** |
michael@0 | 149 | * Release our references to various things once we're done using them. |
michael@0 | 150 | */ |
michael@0 | 151 | _releaseHandles: function FC__releaseHandles() { |
michael@0 | 152 | this._listener = null; |
michael@0 | 153 | this._request = null; |
michael@0 | 154 | this._processor = null; |
michael@0 | 155 | }, |
michael@0 | 156 | |
michael@0 | 157 | /** |
michael@0 | 158 | * See nsIFeedResultListener.idl |
michael@0 | 159 | */ |
michael@0 | 160 | handleResult: function FC_handleResult(result) { |
michael@0 | 161 | // Feeds come in various content types, which our feed sniffer coerces to |
michael@0 | 162 | // the maybe.feed type. However, feeds are used as a transport for |
michael@0 | 163 | // different data types, e.g. news/blogs (traditional feed), video/audio |
michael@0 | 164 | // (podcasts) and photos (photocasts, photostreams). Each of these is |
michael@0 | 165 | // different in that there's a different class of application suitable for |
michael@0 | 166 | // handling feeds of that type, but without a content-type differentiation |
michael@0 | 167 | // it is difficult for us to disambiguate. |
michael@0 | 168 | // |
michael@0 | 169 | // The other problem is that if the user specifies an auto-action handler |
michael@0 | 170 | // for one feed application, the fact that the content type is shared means |
michael@0 | 171 | // that all other applications will auto-load with that handler too, |
michael@0 | 172 | // regardless of the content-type. |
michael@0 | 173 | // |
michael@0 | 174 | // This means that content-type alone is not enough to determine whether |
michael@0 | 175 | // or not a feed should be auto-handled. This means that for feeds we need |
michael@0 | 176 | // to always use this stream converter, even when an auto-action is |
michael@0 | 177 | // specified, not the basic one provided by WebContentConverter. This |
michael@0 | 178 | // converter needs to consume all of the data and parse it, and based on |
michael@0 | 179 | // that determination make a judgment about type. |
michael@0 | 180 | // |
michael@0 | 181 | // Since there are no content types for this content, and I'm not going to |
michael@0 | 182 | // invent any, the upshot is that while a user can set an auto-handler for |
michael@0 | 183 | // generic feed content, the system will prevent them from setting an auto- |
michael@0 | 184 | // handler for other stream types. In those cases, the user will always see |
michael@0 | 185 | // the preview page and have to select a handler. We can guess and show |
michael@0 | 186 | // a client handler, but will not be able to show web handlers for those |
michael@0 | 187 | // types. |
michael@0 | 188 | // |
michael@0 | 189 | // If this is just a feed, not some kind of specialized application, then |
michael@0 | 190 | // auto-handlers can be set and we should obey them. |
michael@0 | 191 | try { |
michael@0 | 192 | var feedService = |
michael@0 | 193 | Cc["@mozilla.org/browser/feeds/result-service;1"]. |
michael@0 | 194 | getService(Ci.nsIFeedResultService); |
michael@0 | 195 | if (!this._forcePreviewPage && result.doc) { |
michael@0 | 196 | var feed = result.doc.QueryInterface(Ci.nsIFeed); |
michael@0 | 197 | var handler = safeGetCharPref(getPrefActionForType(feed.type), "ask"); |
michael@0 | 198 | |
michael@0 | 199 | if (handler != "ask") { |
michael@0 | 200 | if (handler == "reader") |
michael@0 | 201 | handler = safeGetCharPref(getPrefReaderForType(feed.type), "bookmarks"); |
michael@0 | 202 | switch (handler) { |
michael@0 | 203 | case "web": |
michael@0 | 204 | var wccr = |
michael@0 | 205 | Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]. |
michael@0 | 206 | getService(Ci.nsIWebContentConverterService); |
michael@0 | 207 | if ((feed.type == Ci.nsIFeed.TYPE_FEED && |
michael@0 | 208 | wccr.getAutoHandler(TYPE_MAYBE_FEED)) || |
michael@0 | 209 | (feed.type == Ci.nsIFeed.TYPE_VIDEO && |
michael@0 | 210 | wccr.getAutoHandler(TYPE_MAYBE_VIDEO_FEED)) || |
michael@0 | 211 | (feed.type == Ci.nsIFeed.TYPE_AUDIO && |
michael@0 | 212 | wccr.getAutoHandler(TYPE_MAYBE_AUDIO_FEED))) { |
michael@0 | 213 | wccr.loadPreferredHandler(this._request); |
michael@0 | 214 | return; |
michael@0 | 215 | } |
michael@0 | 216 | break; |
michael@0 | 217 | |
michael@0 | 218 | default: |
michael@0 | 219 | LOG("unexpected handler: " + handler); |
michael@0 | 220 | // fall through -- let feed service handle error |
michael@0 | 221 | case "bookmarks": |
michael@0 | 222 | case "client": |
michael@0 | 223 | try { |
michael@0 | 224 | var title = feed.title ? feed.title.plainText() : ""; |
michael@0 | 225 | var desc = feed.subtitle ? feed.subtitle.plainText() : ""; |
michael@0 | 226 | feedService.addToClientReader(result.uri.spec, title, desc, feed.type); |
michael@0 | 227 | return; |
michael@0 | 228 | } catch(ex) { /* fallback to preview mode */ } |
michael@0 | 229 | } |
michael@0 | 230 | } |
michael@0 | 231 | } |
michael@0 | 232 | |
michael@0 | 233 | var ios = |
michael@0 | 234 | Cc["@mozilla.org/network/io-service;1"]. |
michael@0 | 235 | getService(Ci.nsIIOService); |
michael@0 | 236 | var chromeChannel; |
michael@0 | 237 | |
michael@0 | 238 | // If there was no automatic handler, or this was a podcast, |
michael@0 | 239 | // photostream or some other kind of application, show the preview page |
michael@0 | 240 | // if the parser returned a document. |
michael@0 | 241 | if (result.doc) { |
michael@0 | 242 | |
michael@0 | 243 | // Store the result in the result service so that the display |
michael@0 | 244 | // page can access it. |
michael@0 | 245 | feedService.addFeedResult(result); |
michael@0 | 246 | |
michael@0 | 247 | // Now load the actual XUL document. |
michael@0 | 248 | var aboutFeedsURI = ios.newURI("about:feeds", null, null); |
michael@0 | 249 | chromeChannel = ios.newChannelFromURI(aboutFeedsURI, null); |
michael@0 | 250 | chromeChannel.originalURI = result.uri; |
michael@0 | 251 | chromeChannel.owner = |
michael@0 | 252 | Services.scriptSecurityManager.getNoAppCodebasePrincipal(aboutFeedsURI); |
michael@0 | 253 | } else { |
michael@0 | 254 | chromeChannel = ios.newChannelFromURI(result.uri, null); |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | chromeChannel.loadGroup = this._request.loadGroup; |
michael@0 | 258 | chromeChannel.asyncOpen(this._listener, null); |
michael@0 | 259 | } |
michael@0 | 260 | finally { |
michael@0 | 261 | this._releaseHandles(); |
michael@0 | 262 | } |
michael@0 | 263 | }, |
michael@0 | 264 | |
michael@0 | 265 | /** |
michael@0 | 266 | * See nsIStreamListener.idl |
michael@0 | 267 | */ |
michael@0 | 268 | onDataAvailable: function FC_onDataAvailable(request, context, inputStream, |
michael@0 | 269 | sourceOffset, count) { |
michael@0 | 270 | if (this._processor) |
michael@0 | 271 | this._processor.onDataAvailable(request, context, inputStream, |
michael@0 | 272 | sourceOffset, count); |
michael@0 | 273 | }, |
michael@0 | 274 | |
michael@0 | 275 | /** |
michael@0 | 276 | * See nsIRequestObserver.idl |
michael@0 | 277 | */ |
michael@0 | 278 | onStartRequest: function FC_onStartRequest(request, context) { |
michael@0 | 279 | var channel = request.QueryInterface(Ci.nsIChannel); |
michael@0 | 280 | |
michael@0 | 281 | // Check for a header that tells us there was no sniffing |
michael@0 | 282 | // The value doesn't matter. |
michael@0 | 283 | try { |
michael@0 | 284 | var httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); |
michael@0 | 285 | // Make sure to check requestSucceeded before the potentially-throwing |
michael@0 | 286 | // getResponseHeader. |
michael@0 | 287 | if (!httpChannel.requestSucceeded) { |
michael@0 | 288 | // Just give up, but don't forget to cancel the channel first! |
michael@0 | 289 | request.cancel(Cr.NS_BINDING_ABORTED); |
michael@0 | 290 | return; |
michael@0 | 291 | } |
michael@0 | 292 | var noSniff = httpChannel.getResponseHeader("X-Moz-Is-Feed"); |
michael@0 | 293 | } |
michael@0 | 294 | catch (ex) { |
michael@0 | 295 | this._sniffed = true; |
michael@0 | 296 | } |
michael@0 | 297 | |
michael@0 | 298 | this._request = request; |
michael@0 | 299 | |
michael@0 | 300 | // Save and reset the forced state bit early, in case there's some kind of |
michael@0 | 301 | // error. |
michael@0 | 302 | var feedService = |
michael@0 | 303 | Cc["@mozilla.org/browser/feeds/result-service;1"]. |
michael@0 | 304 | getService(Ci.nsIFeedResultService); |
michael@0 | 305 | this._forcePreviewPage = feedService.forcePreviewPage; |
michael@0 | 306 | feedService.forcePreviewPage = false; |
michael@0 | 307 | |
michael@0 | 308 | // Parse feed data as it comes in |
michael@0 | 309 | this._processor = |
michael@0 | 310 | Cc["@mozilla.org/feed-processor;1"]. |
michael@0 | 311 | createInstance(Ci.nsIFeedProcessor); |
michael@0 | 312 | this._processor.listener = this; |
michael@0 | 313 | this._processor.parseAsync(null, channel.URI); |
michael@0 | 314 | |
michael@0 | 315 | this._processor.onStartRequest(request, context); |
michael@0 | 316 | }, |
michael@0 | 317 | |
michael@0 | 318 | /** |
michael@0 | 319 | * See nsIRequestObserver.idl |
michael@0 | 320 | */ |
michael@0 | 321 | onStopRequest: function FC_onStopRequest(request, context, status) { |
michael@0 | 322 | if (this._processor) |
michael@0 | 323 | this._processor.onStopRequest(request, context, status); |
michael@0 | 324 | }, |
michael@0 | 325 | |
michael@0 | 326 | /** |
michael@0 | 327 | * See nsISupports.idl |
michael@0 | 328 | */ |
michael@0 | 329 | QueryInterface: function FC_QueryInterface(iid) { |
michael@0 | 330 | if (iid.equals(Ci.nsIFeedResultListener) || |
michael@0 | 331 | iid.equals(Ci.nsIStreamConverter) || |
michael@0 | 332 | iid.equals(Ci.nsIStreamListener) || |
michael@0 | 333 | iid.equals(Ci.nsIRequestObserver)|| |
michael@0 | 334 | iid.equals(Ci.nsISupports)) |
michael@0 | 335 | return this; |
michael@0 | 336 | throw Cr.NS_ERROR_NO_INTERFACE; |
michael@0 | 337 | }, |
michael@0 | 338 | }; |
michael@0 | 339 | |
michael@0 | 340 | /** |
michael@0 | 341 | * Keeps parsed FeedResults around for use elsewhere in the UI after the stream |
michael@0 | 342 | * converter completes. |
michael@0 | 343 | */ |
michael@0 | 344 | function FeedResultService() { |
michael@0 | 345 | } |
michael@0 | 346 | |
michael@0 | 347 | FeedResultService.prototype = { |
michael@0 | 348 | classID: Components.ID("{2376201c-bbc6-472f-9b62-7548040a61c6}"), |
michael@0 | 349 | |
michael@0 | 350 | /** |
michael@0 | 351 | * A URI spec -> [nsIFeedResult] hash. We have to keep a list as the |
michael@0 | 352 | * value in case the same URI is requested concurrently. |
michael@0 | 353 | */ |
michael@0 | 354 | _results: { }, |
michael@0 | 355 | |
michael@0 | 356 | /** |
michael@0 | 357 | * See nsIFeedResultService.idl |
michael@0 | 358 | */ |
michael@0 | 359 | forcePreviewPage: false, |
michael@0 | 360 | |
michael@0 | 361 | /** |
michael@0 | 362 | * See nsIFeedResultService.idl |
michael@0 | 363 | */ |
michael@0 | 364 | addToClientReader: function FRS_addToClientReader(spec, title, subtitle, feedType) { |
michael@0 | 365 | var prefs = |
michael@0 | 366 | Cc["@mozilla.org/preferences-service;1"]. |
michael@0 | 367 | getService(Ci.nsIPrefBranch); |
michael@0 | 368 | |
michael@0 | 369 | var handler = safeGetCharPref(getPrefActionForType(feedType), "bookmarks"); |
michael@0 | 370 | if (handler == "ask" || handler == "reader") |
michael@0 | 371 | handler = safeGetCharPref(getPrefReaderForType(feedType), "bookmarks"); |
michael@0 | 372 | |
michael@0 | 373 | switch (handler) { |
michael@0 | 374 | case "client": |
michael@0 | 375 | var clientApp = prefs.getComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile); |
michael@0 | 376 | |
michael@0 | 377 | // For the benefit of applications that might know how to deal with more |
michael@0 | 378 | // URLs than just feeds, send feed: URLs in the following format: |
michael@0 | 379 | // |
michael@0 | 380 | // http urls: replace scheme with feed, e.g. |
michael@0 | 381 | // http://foo.com/index.rdf -> feed://foo.com/index.rdf |
michael@0 | 382 | // other urls: prepend feed: scheme, e.g. |
michael@0 | 383 | // https://foo.com/index.rdf -> feed:https://foo.com/index.rdf |
michael@0 | 384 | var ios = |
michael@0 | 385 | Cc["@mozilla.org/network/io-service;1"]. |
michael@0 | 386 | getService(Ci.nsIIOService); |
michael@0 | 387 | var feedURI = ios.newURI(spec, null, null); |
michael@0 | 388 | if (feedURI.schemeIs("http")) { |
michael@0 | 389 | feedURI.scheme = "feed"; |
michael@0 | 390 | spec = feedURI.spec; |
michael@0 | 391 | } |
michael@0 | 392 | else |
michael@0 | 393 | spec = "feed:" + spec; |
michael@0 | 394 | |
michael@0 | 395 | // Retrieving the shell service might fail on some systems, most |
michael@0 | 396 | // notably systems where GNOME is not installed. |
michael@0 | 397 | try { |
michael@0 | 398 | var ss = |
michael@0 | 399 | Cc["@mozilla.org/browser/shell-service;1"]. |
michael@0 | 400 | getService(Ci.nsIShellService); |
michael@0 | 401 | ss.openApplicationWithURI(clientApp, spec); |
michael@0 | 402 | } catch(e) { |
michael@0 | 403 | // If we couldn't use the shell service, fallback to using a |
michael@0 | 404 | // nsIProcess instance |
michael@0 | 405 | var p = |
michael@0 | 406 | Cc["@mozilla.org/process/util;1"]. |
michael@0 | 407 | createInstance(Ci.nsIProcess); |
michael@0 | 408 | p.init(clientApp); |
michael@0 | 409 | p.run(false, [spec], 1); |
michael@0 | 410 | } |
michael@0 | 411 | break; |
michael@0 | 412 | |
michael@0 | 413 | default: |
michael@0 | 414 | // "web" should have been handled elsewhere |
michael@0 | 415 | LOG("unexpected handler: " + handler); |
michael@0 | 416 | // fall through |
michael@0 | 417 | case "bookmarks": |
michael@0 | 418 | var wm = |
michael@0 | 419 | Cc["@mozilla.org/appshell/window-mediator;1"]. |
michael@0 | 420 | getService(Ci.nsIWindowMediator); |
michael@0 | 421 | var topWindow = wm.getMostRecentWindow("navigator:browser"); |
michael@0 | 422 | topWindow.PlacesCommandHook.addLiveBookmark(spec, title, subtitle); |
michael@0 | 423 | break; |
michael@0 | 424 | } |
michael@0 | 425 | }, |
michael@0 | 426 | |
michael@0 | 427 | /** |
michael@0 | 428 | * See nsIFeedResultService.idl |
michael@0 | 429 | */ |
michael@0 | 430 | addFeedResult: function FRS_addFeedResult(feedResult) { |
michael@0 | 431 | NS_ASSERT(feedResult.uri != null, "null URI!"); |
michael@0 | 432 | NS_ASSERT(feedResult.uri != null, "null feedResult!"); |
michael@0 | 433 | var spec = feedResult.uri.spec; |
michael@0 | 434 | if(!this._results[spec]) |
michael@0 | 435 | this._results[spec] = []; |
michael@0 | 436 | this._results[spec].push(feedResult); |
michael@0 | 437 | }, |
michael@0 | 438 | |
michael@0 | 439 | /** |
michael@0 | 440 | * See nsIFeedResultService.idl |
michael@0 | 441 | */ |
michael@0 | 442 | getFeedResult: function RFS_getFeedResult(uri) { |
michael@0 | 443 | NS_ASSERT(uri != null, "null URI!"); |
michael@0 | 444 | var resultList = this._results[uri.spec]; |
michael@0 | 445 | for (var i in resultList) { |
michael@0 | 446 | if (resultList[i].uri == uri) |
michael@0 | 447 | return resultList[i]; |
michael@0 | 448 | } |
michael@0 | 449 | return null; |
michael@0 | 450 | }, |
michael@0 | 451 | |
michael@0 | 452 | /** |
michael@0 | 453 | * See nsIFeedResultService.idl |
michael@0 | 454 | */ |
michael@0 | 455 | removeFeedResult: function FRS_removeFeedResult(uri) { |
michael@0 | 456 | NS_ASSERT(uri != null, "null URI!"); |
michael@0 | 457 | var resultList = this._results[uri.spec]; |
michael@0 | 458 | if (!resultList) |
michael@0 | 459 | return; |
michael@0 | 460 | var deletions = 0; |
michael@0 | 461 | for (var i = 0; i < resultList.length; ++i) { |
michael@0 | 462 | if (resultList[i].uri == uri) { |
michael@0 | 463 | delete resultList[i]; |
michael@0 | 464 | ++deletions; |
michael@0 | 465 | } |
michael@0 | 466 | } |
michael@0 | 467 | |
michael@0 | 468 | // send the holes to the end |
michael@0 | 469 | resultList.sort(); |
michael@0 | 470 | // and trim the list |
michael@0 | 471 | resultList.splice(resultList.length - deletions, deletions); |
michael@0 | 472 | if (resultList.length == 0) |
michael@0 | 473 | delete this._results[uri.spec]; |
michael@0 | 474 | }, |
michael@0 | 475 | |
michael@0 | 476 | createInstance: function FRS_createInstance(outer, iid) { |
michael@0 | 477 | if (outer != null) |
michael@0 | 478 | throw Cr.NS_ERROR_NO_AGGREGATION; |
michael@0 | 479 | return this.QueryInterface(iid); |
michael@0 | 480 | }, |
michael@0 | 481 | |
michael@0 | 482 | QueryInterface: function FRS_QueryInterface(iid) { |
michael@0 | 483 | if (iid.equals(Ci.nsIFeedResultService) || |
michael@0 | 484 | iid.equals(Ci.nsIFactory) || |
michael@0 | 485 | iid.equals(Ci.nsISupports)) |
michael@0 | 486 | return this; |
michael@0 | 487 | throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 488 | }, |
michael@0 | 489 | }; |
michael@0 | 490 | |
michael@0 | 491 | /** |
michael@0 | 492 | * A protocol handler that attempts to deal with the variant forms of feed: |
michael@0 | 493 | * URIs that are actually either http or https. |
michael@0 | 494 | */ |
michael@0 | 495 | function GenericProtocolHandler() { |
michael@0 | 496 | } |
michael@0 | 497 | GenericProtocolHandler.prototype = { |
michael@0 | 498 | _init: function GPH_init(scheme) { |
michael@0 | 499 | var ios = |
michael@0 | 500 | Cc["@mozilla.org/network/io-service;1"]. |
michael@0 | 501 | getService(Ci.nsIIOService); |
michael@0 | 502 | this._http = ios.getProtocolHandler("http"); |
michael@0 | 503 | this._scheme = scheme; |
michael@0 | 504 | }, |
michael@0 | 505 | |
michael@0 | 506 | get scheme() { |
michael@0 | 507 | return this._scheme; |
michael@0 | 508 | }, |
michael@0 | 509 | |
michael@0 | 510 | get protocolFlags() { |
michael@0 | 511 | return this._http.protocolFlags; |
michael@0 | 512 | }, |
michael@0 | 513 | |
michael@0 | 514 | get defaultPort() { |
michael@0 | 515 | return this._http.defaultPort; |
michael@0 | 516 | }, |
michael@0 | 517 | |
michael@0 | 518 | allowPort: function GPH_allowPort(port, scheme) { |
michael@0 | 519 | return this._http.allowPort(port, scheme); |
michael@0 | 520 | }, |
michael@0 | 521 | |
michael@0 | 522 | newURI: function GPH_newURI(spec, originalCharset, baseURI) { |
michael@0 | 523 | // Feed URIs can be either nested URIs of the form feed:realURI (in which |
michael@0 | 524 | // case we create a nested URI for the realURI) or feed://example.com, in |
michael@0 | 525 | // which case we create a nested URI for the real protocol which is http. |
michael@0 | 526 | |
michael@0 | 527 | var scheme = this._scheme + ":"; |
michael@0 | 528 | if (spec.substr(0, scheme.length) != scheme) |
michael@0 | 529 | throw Cr.NS_ERROR_MALFORMED_URI; |
michael@0 | 530 | |
michael@0 | 531 | var prefix = spec.substr(scheme.length, 2) == "//" ? "http:" : ""; |
michael@0 | 532 | var inner = Cc["@mozilla.org/network/io-service;1"]. |
michael@0 | 533 | getService(Ci.nsIIOService).newURI(spec.replace(scheme, prefix), |
michael@0 | 534 | originalCharset, baseURI); |
michael@0 | 535 | var netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil); |
michael@0 | 536 | const URI_INHERITS_SECURITY_CONTEXT = Ci.nsIProtocolHandler |
michael@0 | 537 | .URI_INHERITS_SECURITY_CONTEXT; |
michael@0 | 538 | if (netutil.URIChainHasFlags(inner, URI_INHERITS_SECURITY_CONTEXT)) |
michael@0 | 539 | throw Cr.NS_ERROR_MALFORMED_URI; |
michael@0 | 540 | |
michael@0 | 541 | var uri = netutil.newSimpleNestedURI(inner); |
michael@0 | 542 | uri.spec = inner.spec.replace(prefix, scheme); |
michael@0 | 543 | return uri; |
michael@0 | 544 | }, |
michael@0 | 545 | |
michael@0 | 546 | newChannel: function GPH_newChannel(aUri) { |
michael@0 | 547 | var inner = aUri.QueryInterface(Ci.nsINestedURI).innerURI; |
michael@0 | 548 | var channel = Cc["@mozilla.org/network/io-service;1"]. |
michael@0 | 549 | getService(Ci.nsIIOService).newChannelFromURI(inner, null); |
michael@0 | 550 | if (channel instanceof Components.interfaces.nsIHttpChannel) |
michael@0 | 551 | // Set this so we know this is supposed to be a feed |
michael@0 | 552 | channel.setRequestHeader("X-Moz-Is-Feed", "1", false); |
michael@0 | 553 | channel.originalURI = aUri; |
michael@0 | 554 | return channel; |
michael@0 | 555 | }, |
michael@0 | 556 | |
michael@0 | 557 | QueryInterface: function GPH_QueryInterface(iid) { |
michael@0 | 558 | if (iid.equals(Ci.nsIProtocolHandler) || |
michael@0 | 559 | iid.equals(Ci.nsISupports)) |
michael@0 | 560 | return this; |
michael@0 | 561 | throw Cr.NS_ERROR_NO_INTERFACE; |
michael@0 | 562 | } |
michael@0 | 563 | }; |
michael@0 | 564 | |
michael@0 | 565 | function FeedProtocolHandler() { |
michael@0 | 566 | this._init('feed'); |
michael@0 | 567 | } |
michael@0 | 568 | FeedProtocolHandler.prototype = new GenericProtocolHandler(); |
michael@0 | 569 | FeedProtocolHandler.prototype.classID = Components.ID("{4f91ef2e-57ba-472e-ab7a-b4999e42d6c0}"); |
michael@0 | 570 | |
michael@0 | 571 | function PodCastProtocolHandler() { |
michael@0 | 572 | this._init('pcast'); |
michael@0 | 573 | } |
michael@0 | 574 | PodCastProtocolHandler.prototype = new GenericProtocolHandler(); |
michael@0 | 575 | PodCastProtocolHandler.prototype.classID = Components.ID("{1c31ed79-accd-4b94-b517-06e0c81999d5}"); |
michael@0 | 576 | |
michael@0 | 577 | var components = [FeedConverter, |
michael@0 | 578 | FeedResultService, |
michael@0 | 579 | FeedProtocolHandler, |
michael@0 | 580 | PodCastProtocolHandler]; |
michael@0 | 581 | |
michael@0 | 582 | |
michael@0 | 583 | this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components); |