feedOverlay.js
Go to the documentation of this file.
1 /* vim: set sw=2 : */
2 /*
3  *=BEGIN SONGBIRD GPL
4  *
5  * This file is part of the Songbird web player.
6  *
7  * Copyright(c) 2005-2009 POTI, Inc.
8  * http://www.songbirdnest.com
9  *
10  * This file may be licensed under the terms of of the
11  * GNU General Public License Version 2 (the ``GPL'').
12  *
13  * Software distributed under the License is distributed
14  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
15  * express or implied. See the GPL for the specific language
16  * governing rights and limitations.
17  *
18  * You should have received a copy of the GPL along with this
19  * program. If not, go to http://www.gnu.org/licenses/gpl.html
20  * or write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  *
23  *=END SONGBIRD GPL
24  */
25 
26 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
27 Components.utils.import("resource://app/jsmodules/ArrayConverter.jsm");
28 
29 const FeedHandler = {
30  NS_XLINK: "http://www.w3.org/1999/xlink",
31 
32  init: function FeedHandler_init() {
33  window.removeEventListener("load", FeedHandler, false);
34  getBrowser().addEventListener("DOMLinkAdded", this, false);
35  getBrowser().addProgressListener(this,
36  Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
37  Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
38  },
39 
40  handleEvent: function FeedHandler_handleEvent(aEvent) {
41  switch(aEvent.type) {
42  case "load":
43  this.init();
44  break;
45  case "DOMLinkAdded":
46  this.onLinkAdded(aEvent);
47  break;
48  }
49  },
50 
51  onLinkAdded: function FeedHandler_onLinkAdded(aEvent) {
52  /* forked from mozilla/browser/base/content/browser.js DOMLinkHandler */
53  var link = aEvent.originalTarget;
54  var rel = link.rel && link.rel.toLowerCase();
55  if (!link || !link.ownerDocument || !rel || !link.href)
56  return;
57 
58  var iconAdded = false;
59  var searchAdded = false;
60  var relStrings = rel.split(/\s+/);
61  var rels = {};
62  for (let i = 0; i < relStrings.length; i++)
63  rels[relStrings[i]] = true;
64 
65  for (let relVal in rels) {
66  switch (relVal) {
67  case "feed":
68  case "alternate":
69  if (!rels.feed && rels.alternate && rels.stylesheet)
70  break;
71 
72  if (this.isValidFeed(link,
73  link.ownerDocument.nodePrincipal,
74  rels.feed))
75  {
76  this.detectedFeed(link, link.ownerDocument);
77  feedAdded = true;
78  }
79  break;
80  case "icon":
81  if (!iconAdded) {
82  if (!Application.prefs.getValue("browser.chrome.site_icons", true))
83  break;
84 
85  var targetDoc = link.ownerDocument;
86  var uri = makeURI(link.href, targetDoc.characterSet);
87 
88  if (("isFailedIcon" in gBrowser) && gBrowser.isFailedIcon(uri))
89  break;
90 
91  // Verify that the load of this icon is legal.
92  // error pages can load their favicon, to be on the safe side,
93  // only allow chrome:// favicons
94  const aboutNeterr = /^about:neterror\?/;
95  const aboutBlocked = /^about:blocked\?/;
96  const aboutCert = /^about:certerror\?/;
97  if (!(aboutNeterr.test(targetDoc.documentURI) ||
98  aboutBlocked.test(targetDoc.documentURI) ||
99  aboutCert.test(targetDoc.documentURI)) ||
100  !uri.schemeIs("chrome")) {
101  var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].
102  getService(Ci.nsIScriptSecurityManager);
103  try {
104  ssm.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri,
105  Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
106  } catch(e) {
107  break;
108  }
109  }
110 
111  try {
112  var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"].
113  getService(Ci.nsIContentPolicy);
114  } catch(e) {
115  break; // Refuse to load if we can't do a security check.
116  }
117 
118  // Security says okay, now ask content policy
119  if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
120  uri, targetDoc.documentURIObject,
121  link, link.type, null)
122  != Ci.nsIContentPolicy.ACCEPT)
123  break;
124 
125  var browserIndex = gBrowser.getBrowserIndexForDocument(targetDoc);
126  // no browser? no favicon.
127  if (browserIndex == -1)
128  break;
129 
130  var tab = gBrowser.mTabContainer.childNodes[browserIndex];
131  gBrowser.setIcon(tab, link.href);
132  iconAdded = true;
133  }
134  break;
135  case "search":
136  if (!searchAdded) {
137  var type = link.type && link.type.toLowerCase();
138  type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
139 
140  if (type == "application/opensearchdescription+xml" && link.title &&
141  /^(?:https?|ftp):/i.test(link.href)) {
142  var engine = { title: link.title, href: link.href };
143  BrowserSearch.addEngine(engine, link.ownerDocument);
144  searchAdded = true;
145  }
146  }
147  break;
148  }
149  }
150  },
151 
164  isValidFeed: function FeedHandler_isValidFeed(aLink, aPrincipal, aIsFeed) {
165  /* forked from mozilla/browser/base/content/utilityOverlay.js isValidFeed */
166  if (!aLink || !aPrincipal)
167  return null;
168 
169  var type = aLink.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
170  if (!aIsFeed) {
171  aIsFeed = (type == "application/rss+xml" ||
172  type == "application/atom+xml");
173  }
174 
175  if (aIsFeed) {
176  try {
177  urlSecurityCheck(aLink.href, aPrincipal,
178  Components.interfaces.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
179  return type || "application/rss+xml";
180  }
181  catch(ex) {
182  }
183  }
184 
185  return null;
186  },
187 
188  detectedFeed: function FeedHandler_detectedFeed(link, targetDoc) {
189  // find which tab this is for, and set the attribute on the browser
190  var browserForLink = gBrowser.getBrowserForDocument(targetDoc);
191  if (!browserForLink) {
192  // ignore feeds loaded in subframes (see mozbug 305472)
193  return;
194  }
195 
196  var feed = { href: link.href, title: link.title };
197 
198  // grab the feed and see if it's a podcast
199  var ios = Cc["@mozilla.org/network/io-service;1"]
200  .getService(Ci.nsIIOService);
201  var uri = ios.newURI(feed.href, null, null);
202 
203  var processor = Cc["@mozilla.org/feed-processor;1"]
204  .createInstance(Ci.nsIFeedProcessor);
205  processor.listener = {
206  self: this,
207  handleResult: function FeedHandler_handleFeedResult(aResult) {
208  if (aResult.doc.title) {
209  feed.title = aResult.doc.title.plainText();
210  }
211  feed.author = ArrayConverter.JSArray(aResult.doc.authors)
212  .map(function(x) x.QueryInterface(Ci.nsIFeedPerson).name)
213  .join(", ");
214  feed.result = aResult;
215  this.self.addFeed(feed, browserForLink);
216  },
217  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFeedResultListener])
218  };
219 
220  processor.parseAsync(null, uri);
221 
222  var channel = ios.newChannelFromURI(uri);
223  channel.asyncOpen(processor, null);
224  },
225 
226  addFeed: function FeedHandler_addFeed(feed, browserForLink) {
227  if (!browserForLink.feeds)
228  browserForLink.feeds = [];
229 
230  const iTunesNS = "http://www.itunes.com/dtds/podcast-1.0.dtd";
231  let feedElem = feed.result.doc.QueryInterface(Ci.nsIFeed);
232  if (!feedElem.attributes) {
233  // feed has no attributes? not valid.
234  return;
235  }
236  let hasItunesNS = false;
237  for (let i = 0; i < feedElem.attributes.length; ++i) {
238  if (feedElem.attributes.getURI(i) != "http://www.w3.org/2000/xmlns/") {
239  // not a xml namespace
240  continue;
241  }
242  if (feedElem.attributes.getValue(i) == iTunesNS) {
243  hasItunesNS = true;
244  break;
245  }
246  }
247  if (!hasItunesNS) {
248  // not a podcast feed
249  return;
250  }
251 
252  browserForLink.feeds.push(feed);
253 
254  if (browserForLink == gBrowser.mCurrentBrowser) {
255  var feedButton = document.getElementById("feed-button");
256  if (feedButton)
257  feedButton.collapsed = false;
258  }
259  },
260 
265  updateFeeds: function() {
266  var feedButton = document.getElementById("feed-button");
267  if (!this._feedMenuitem)
268  this._feedMenuitem = document.getElementById("subscribeToPageMenuitem");
269  if (!this._feedMenupopup)
270  this._feedMenupopup = document.getElementById("subscribeToPageMenupopup");
271 
272  var feeds = gBrowser.mCurrentBrowser.feeds;
273  if (!feeds || feeds.length == 0) {
274  if (feedButton) {
275  feedButton.collapsed = true;
276  feedButton.removeAttribute("feed");
277  }
278  /*
279  this._feedMenuitem.setAttribute("disabled", "true");
280  this._feedMenupopup.setAttribute("hidden", "true");
281  this._feedMenuitem.removeAttribute("hidden");
282  */
283  } else {
284  if (feedButton)
285  feedButton.collapsed = false;
286 
287  if (feeds.length > 1) {
288  /*
289  this._feedMenuitem.setAttribute("hidden", "true");
290  this._feedMenupopup.removeAttribute("hidden");
291  */
292  if (feedButton)
293  feedButton.removeAttribute("feed");
294  } else {
295  if (feedButton)
296  feedButton.setAttribute("feed", feeds[0].href);
297 
298  /*
299  this._feedMenuitem.setAttribute("feed", feeds[0].href);
300  this._feedMenuitem.removeAttribute("disabled");
301  this._feedMenuitem.removeAttribute("hidden");
302  this._feedMenupopup.setAttribute("hidden", "true");
303  */
304  }
305  }
306  },
307 
308  updateFeedPanel: function FeedHandler_updateFeedPanel(aEvent) {
309  function getElem(s) { return document.getElementById("feed-panel-" + s);}
310 
311  if (aEvent.target != document.getElementById("feed-panel")) {
312  // this is triggered by the inner menulist showing; ignore the event
313  return;
314  }
315 
316  var feeds = getBrowser().selectedBrowser.feeds || [];
317 
318  getElem("listbox").removeAllItems();
319  for (let i = 0; i < feeds.length; ++i) {
320  getElem("listbox").appendItem(feeds[i].title, i, feeds[i].href);
321  }
322  getElem("listbox").collapsed = (feeds.length <= 1);
323 
324  let feed = feeds[0];
325  let feedElem = feed.result.doc.QueryInterface(Ci.nsIFeed);
326  getElem("title").value = feedElem.title.plainText();
327  getElem("author").value = feed.author;
328  var fields = feedElem.fields;
329 
330  // try to find a tag that looks like the description
331  getElem("description").collapsed = true;
332  for each (let key in ["media:description", "itunes:summary", "description"]) {
333  if (fields.hasKey(key)) {
334  getElem("description").collapsed = false;
335  getElem("description").textContent = fields.getProperty(key);
336  break;
337  }
338  }
339  var image = feedElem.image;
340  if ((image instanceof Ci.nsIPropertyBag) && image.hasKey("url")) {
341  getElem("image").setAttributeNS(this.NS_XLINK,
342  "href",
343  image.getProperty("url"));
344  getElem("image").collapsed = false;
345  } else {
346  getElem("image").collapsed = true;
347  }
348  },
349 
350  closeFeedPanel: function FeedHandler_closeFeedPanel() {
351  document.getElementById("feed-panel").hidePopup();
352  },
353 
354  /*** nsIWebProgressListener ***/
355  onStateChange: function FeedHandler_onStateChange(aWebProgress,
356  aRequest,
357  aStateFlags,
358  aStatus)
359  {
360 
361  },
362 
363  onProgressChange: function FeedHanlder_onProgressChange(aWebProgress,
364  aRequest,
365  aCurSelfProgress,
366  aMaxSelfProgress,
367  aCurTotalProgress,
368  aMaxTotalProgress)
369  {
370 
371  },
372 
373  onLocationChange: function FeedHandler_onLocationChange(aWebProgress,
374  aRequest,
375  aLocation)
376  {
377  this.updateFeeds();
378  },
379 
380  onStatusChange: function FeedHandler_onStatusChange(aWebProgress,
381  aRequest,
382  aStatus,
383  aMessage)
384  {
385  this.updateFeeds();
386  },
387 
388  onSecurityChange: function FeedHandler_onSecurityChange(aWebProgress,
389  aRequest,
390  aState)
391  {
392 
393  },
394 
395  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
396  Ci.nsIWebProgressListener])
397 
398 };
399 
400 window.addEventListener("load", FeedHandler, false);
const Cc
var Application
Definition: sbAboutDRM.js:37
sbOSDControlService prototype QueryInterface
function getBrowser() gBrowser
var tab
getService(Ci.sbIFaceplateManager)
let window
_window init
Definition: FeedWriter.js:1144
function makeURI(aURLSpec, aCharset)
Definition: FeedWriter.js:71
Element Properties href
const FeedHandler
Definition: feedOverlay.js:29
function isValidFeed(aLink, aPrincipal, aIsFeed)
return null
Definition: FeedWriter.js:1143
var uri
Definition: FeedWriter.js:1135
const Ci
ContinuingWebProgressListener prototype onStateChange
var ios
Definition: head_feeds.js:5
restoreWindow aState
_getSelectedPageStyle s i