sbDisplayPanes.js
Go to the documentation of this file.
1 /*
2  *=BEGIN SONGBIRD GPL
3  *
4  * This file is part of the Songbird web player.
5  *
6  * Copyright(c) 2005-2010 POTI, Inc.
7  * http://www.songbirdnest.com
8  *
9  * This file may be licensed under the terms of of the
10  * GNU General Public License Version 2 (the ``GPL'').
11  *
12  * Software distributed under the License is distributed
13  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
14  * express or implied. See the GPL for the specific language
15  * governing rights and limitations.
16  *
17  * You should have received a copy of the GPL along with this
18  * program. If not, go to http://www.gnu.org/licenses/gpl.html
19  * or write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  *
22  *=END SONGBIRD GPL
23  */
24 
25 const Cc = Components.classes;
26 const Ci = Components.interfaces;
27 const Cu = Components.utils;
28 const CHROME_PREFIX = "chrome://";
29 
30 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
31 Cu.import("resource://app/jsmodules/ArrayConverter.jsm");
32 Cu.import("resource://app/jsmodules/RDFHelper.jsm");
33 Cu.import("resource://app/jsmodules/StringUtils.jsm");
34 
39 function PaneInfo() {};
40 
41 PaneInfo.prototype = {
42  requiredProperties: [ "contentUrl",
43  "contentTitle",
44  "contentIcon",
45  "suggestedContentGroups",
46  "defaultWidth",
47  "defaultHeight" ],
48  optionalProperties: [ "showOnInstall" ],
49 
50  verify: function() {
51  var errorList = [];
52  for (var i = 0; i < this.requiredProperties.length; i++) {
53  var property = this.requiredProperties[i];
54  if (! (typeof(this[property]) == 'string'
55  && this[property].length > 0))
56  {
57  errorList.push("Invalid description. '" + property + "' is a required property.");
58  }
59  }
60  try {
61  this.defaultWidth = parseInt(this.defaultWidth);
62  this.defaultHeight = parseInt(this.defaultHeight);
63  this.showOnInstall = this.showOnInstall == "true";
64  } catch (e) {
65  errorList.push(e.toString());
66  }
67  return(errorList);
68  },
69 
70  QueryInterface: XPCOMUtils.generateQI([Ci.sbIDisplayPaneContentInfo])
71 };
72 
79  //debug("DisplayPaneMetadataReader: ctor\n");
80  this._manager = Cc["@songbirdnest.com/Songbird/DisplayPane/Manager;1"]
81  .getService(Ci.sbIDisplayPaneManager);
82 }
83 DisplayPaneMetadataReader.prototype = {
84  _manager: null,
85 
89  loadPanes: function loadPanes() {
90  //debug("DisplayPaneMetadataReader: loadPanes\n");
91  var addons = RDFHelper.help("rdf:addon-metadata", "urn:songbird:addon:root", RDFHelper.DEFAULT_RDF_NAMESPACES);
92 
93  for (var i = 0; i < addons.length; i++) {
94  // skip addons with no panes.
95  var panes;
96  if (addons[i].displayPanes) {
97  // TODO: remove this some time post 0.5 and before 1.0
98  Cu.reportError(
99  "DisplayPanes: Use of the <displayPanes> element in install.rdf " +
100  "is deprecated. Remove that element and leave the contents as-is."
101  );
102  panes = addons[i].displayPanes[0].displayPane;
103  }
104  else {
105  panes = addons[i].displayPane;
106  }
107 
108  if (!panes) {
109  // no display panes in this addon.
110  continue;
111  }
112 
113  try {
114  for (var j = 0; j < panes.length; j++) {
115  this._registerDisplayPane(addons[i], panes[j])
116  }
117  } catch (e) {
118  this._reportErrors("", [ "An error occurred while processing " +
119  "extension " + addons[i].Value + ". Exception: " + e ]);
120  }
121  }
122  },
123 
127  _registerDisplayPane: function _registerDisplayPane(addon, pane) {
128  // create and validate our pane info
129  var info = new PaneInfo();
130  for (property in pane) {
131  if (pane[property])
132  info[property] = pane[property][0];
133  }
134  var errorList = info.verify();
135 
136  // If errors were encountered, then do not submit
137  // to the Display Pane Manager
138  if (errorList.length > 0) {
139  this._reportErrors(
140  "Ignoring display pane addon in the install.rdf of extension " +
141  addon.Value + " due to these error(s):\n", errorList);
142  return;
143  }
144 
145  // Resolve any localised display pane contentTitle to their actual strings
146  if (info.contentTitle.substr(0,CHROME_PREFIX.length) == CHROME_PREFIX)
147  {
148  var contentTitle = SBString("displaypanes.contenttitle.unnamed");
149  var split = info.contentTitle.split("#", 2);
150  if (split.length == 2) {
151  var bundle = new SBStringBundle(split[0]);
152  contentTitle = bundle.get(split[1], contentTitle);
153  }
154  info.contentTitle = contentTitle;
155  }
156 
157  // Submit description
158  this._manager.registerContent( info.contentUrl,
159  info.contentTitle,
160  info.contentIcon,
161  info.defaultWidth,
162  info.defaultHeight,
163  info.suggestedContentGroups,
164  info.showOnInstall );
165 
166  //debug("DisplayPaneMetadataReader: registered pane " + info.contentTitle
167  // + " from addon " + addon.Value + " \n");
168  },
169 
170 
177  _reportErrors: function _reportErrors(contextMessage, errorList) {
178  var consoleService = Cc["@mozilla.org/consoleservice;1"]
179  .getService(Ci.nsIConsoleService);
180  for (var i = 0; i < errorList.length; i++) {
181  consoleService.logStringMessage("Display Pane Metadata Reader: "
182  + contextMessage + errorList[i]);
183  dump("DisplayPaneMetadataReader: " + contextMessage + errorList[i] + "\n");
184  }
185  }
186 }
187 
196 function DisplayPaneManager() {
197 
198  // Components that implement sbIDisplayPaneContentInfo can indicate they
199  // provide display pane content by adding themselves to the
200  // "display-pane-provider" category.
201  var catMgr = Cc["@mozilla.org/categorymanager;1"]
202  .getService(Ci.nsICategoryManager);
203  var entries = catMgr.enumerateCategory("display-pane-provider");
204  while (entries.hasMoreElements()) {
205  var entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
206  this._registerContentFromCategoryEntry(entry, catMgr);
207  }
208 
209  this._initialized = true;
210 }
211 
212 DisplayPaneManager.prototype = {
213  classDescription: "Songbird Display Pane Manager Service Interface",
214  classID: Components.ID("{6aef120f-d7ad-414d-a93d-3ac945e64301}"),
215  contractID: "@songbirdnest.com/Songbird/DisplayPane/Manager;1",
216  constructor: DisplayPaneManager,
217 
218  LOG: function(str) {
219  var consoleService = Cc['@mozilla.org/consoleservice;1']
220  .getService(Ci.nsIConsoleService);
221  consoleService.logStringMessage(str);
222  },
223 
224  _contentList: [],
225  _instantiatorsList: [],
226  _delayedInstantiations: [],
227  _listenersList: [],
228  _initialized: false,
229 
230  _addonMetadataLoaded: false,
231 
232  _getString: function(aName, aDefault) {
233  if (!this._stringbundle) {
234  var src = "chrome://branding/locale/brand.properties";
235  var stringBundleService = Cc["@mozilla.org/intl/stringbundle;1"]
236  .getService(Ci.nsIStringBundleService);
237  this._stringbundle = stringBundleService.createBundle(src);
238  }
239 
240  try {
241  return this._stringbundle.GetStringFromName(aName);
242  }
243  catch(e) {
244  return aDefault;
245  }
246 
247  },
248 
249  _defaultPaneInfo: null,
250 
251  _cleanupInstantiatorsList: function DPM_cleanupInstantiatorsList() {
252  for (var i = this._instantiatorsList.length - 1; i >= 0; --i) {
253  if (!(this._instantiatorsList[i] instanceof
254  Ci.sbIDisplayPaneInstantiator)) {
255  Cu.reportError("Warning: found bad instantiator; "+
256  "possibly via removal from DOM");
257  this._instantiatorsList.splice(i, 1);
258  }
259  }
260  },
261 
262  get defaultPaneInfo() {
263  if (!this._defaultPaneInfo) {
264  var paneInfo = {
265  contentUrl: this._getString("displaypane.default.url", "chrome://songbird/content/xul/defaultDisplayPane.xul"),
266  contentTitle: null, // automatically set by host depending on where the default pane is instantiated
267  contentIcon: this._getString("displaypane.default.icon", "chrome://branding/content/branding.ico"),
268  defaultWidth: this._getString("displaypane.default.width", "150"),
269  defaultHeight: this._getString("displaypane.default.height", "75"),
270  suggestedContentGroups: this._getString("displaypane.default.groups", "")
271  };
272  this._defaultPaneInfo = paneInfo;
273  }
274  return this._defaultPaneInfo;
275  },
276 
281  ensureAddonMetadataLoaded: function() {
282  if (!this._initialized || this._addonMetadataLoaded) {
283  return;
284  }
285  this._addonMetadataLoaded = true;
286 
287  // Load the addon metadata
288  var metadataReader = new DisplayPaneMetadataReader();
289  metadataReader.loadPanes();
290  },
291 
292 
296  makePaneInfo: function(aContentUrl,
297  aContentTitle,
298  aContentIcon,
299  aSuggestedContentGroups,
300  aDefaultWidth,
301  aDefaultHeight) {
302  var paneInfo = new PaneInfo();
303  paneInfo.contentUrl = aContentUrl;
304  paneInfo.contentTitle = aContentTitle;
305  paneInfo.contentIcon = aContentIcon;
306  paneInfo.suggestedContentGroups = aSuggestedContentGroups;
307  paneInfo.defaultWidth = aDefaultWidth;
308  paneInfo.defaultHeight = aDefaultHeight;
309 
310  return paneInfo;
311  },
312 
316  getPaneInfo: function(aContentUrl) {
317  this.ensureAddonMetadataLoaded();
318 
319  for each (var pane in this._contentList) {
321  if (pane.contentUrl == aContentUrl)
322  return pane;
323  }
324  return null;
325  },
326 
330  getInstantiatorForWindow:
331  function sbDisplayPaneMgr_getInstantiatorForWindow(aWindow) {
332  this._cleanupInstantiatorsList();
333  for each (var instantiator in this._instantiatorsList) {
334  if (instantiator.contentWindow === aWindow) {
335  return instantiator;
336  }
337  }
338  return null;
339  },
340 
344  get contentList() {
345  this.ensureAddonMetadataLoaded();
346  return ArrayConverter.enumerator(this._contentList);
347  },
348 
352  get instantiatorsList() {
353  this._cleanupInstantiatorsList();
354  return ArrayConverter.enumerator(this._instantiatorsList);
355  },
356 
360  registerContent: function(aContentUrl,
361  aContentTitle,
362  aContentIcon,
363  aDefaultWidth,
364  aDefaultHeight,
365  aSuggestedContentGroups,
366  aAutoShow) {
367 
369 
370  var info = this.getPaneInfo(aContentUrl);
371  if (info) {
372  throw Components.results.NS_ERROR_ALREADY_INITIALIZED;
373  }
374  info = this.makePaneInfo(aContentUrl,
375  aContentTitle,
376  aContentIcon,
377  aSuggestedContentGroups,
378  aDefaultWidth,
379  aDefaultHeight);
380  this._contentList.push(info);
381  for each (var listener in this._listenersList) {
382  listener.onRegisterContent(info);
383  }
384  // if we have never seen this pane, show it in its prefered group
385  var SB_NewDataRemote = Components.Constructor("@songbirdnest.com/Songbird/DataRemote;1",
386  "sbIDataRemote",
387  "init");
388  var known = SB_NewDataRemote("displaypane.known." + aContentUrl, null);
389  if (!known.boolValue) {
390  if (aAutoShow) {
391  if (!this.tryInstantiation(info)) {
392  this._delayedInstantiations.push(info);
393  }
394  }
395  // remember we've seen this pane, let the pane hosts reload on their own if they need to
396  known.boolValue = true;
397  }
398  },
399 
403  unregisterContent: function(aContentUrl) {
404  for (var contentIndex = 0; contentIndex < this._contentList.length; contentIndex++) {
405  if (this._contentList[contentIndex].contentUrl != aContentUrl) {
406  continue;
407  }
408 
409  // any instantiator currently hosting this url should be emptied
410  for each (var instantiator in this._instantiatorsList) {
411  if (instantiator.contentUrl == aContentUrl) {
412  instantiator.hide();
413  }
414  }
415  // also remove it from the delayed instantiation list
416  for (instantiatorIndex = this._delayedInstantiations.length - 1; instantiatorIndex >= 0; --instantiatorIndex) {
417  if (this._delayedInstantiations[instantiatorIndex].contentUrl == aContentUrl) {
418  this._delayedInstantiations.splice(instantiatorIndex, 1);
419  }
420  }
421 
422  var [info] = this._contentList.splice(contentIndex, 1);
423 
424  for each (var listener in this._listenersList) {
425  listener.onUnregisterContent(info);
426  }
427  return;
428  }
429  },
430 
434  registerInstantiator: function(aInstantiator) {
435  this.ensureAddonMetadataLoaded();
436 
437  if (this._instantiatorsList.indexOf(aInstantiator) > -1) {
438  Cu.reportError("Attempt to re-register instantiator ignored\n" +
439  (new Error()).stack);
440  return;
441  }
442  this._instantiatorsList.push(aInstantiator);
443  for each (var listener in this._listenersList) {
444  listener.onRegisterInstantiator(aInstantiator);
445  }
446  this.processDelayedInstantiations();
447  },
448 
452  unregisterInstantiator: function(aInstantiator) {
453  var index = this._instantiatorsList.indexOf(aInstantiator);
454  if (index < 0) {
455  // not found
456  return;
457  }
458  this._instantiatorsList.splice(index, 1);
459  for each (var listener in this._listenersList) {
460  listener.onUnregisterInstantiator(aInstantiator);
461  }
462  },
463 
469  getFirstInstantiatorForGroupList: function(aContentGroupList) {
470  var groups = aContentGroupList.toUpperCase().split(";");
471  for each (var group in groups) {
472  for each (var instantiator in this._instantiatorsList) {
473  if (instantiator.contentGroup.toUpperCase() == group) {
474  return instantiator;
475  }
476  }
477  }
478  return null;
479  },
480 
481  processDelayedInstantiations: function() {
482  var table = [];
483  for each (var info in this._delayedInstantiations) {
484  if (!this.isValidPane(info) || this.tryInstantiation(info)) {
485  continue;
486  }
487  table.push(info);
488  }
489  this._delayedInstantiations = table;
490  },
491 
492  tryInstantiation: function(info) {
493  var instantiator = this.getFirstInstantiatorForGroupList(info.suggestedContentGroups);
494  if (instantiator) {
495  instantiator.loadContent(info);
496  return true;
497  }
498  return false;
499  },
500 
501  isValidPane: function(aPane) {
502  this.ensureAddonMetadataLoaded();
503  for each (var pane in this._contentList) {
504  if (pane == aPane) return true;
505  }
506  return false;
507  },
508 
509  showPane: function(aContentUrl) {
510  for each (var instantiator in this._instantiatorsList) {
511  if (instantiator.contentUrl == aContentUrl) {
512  // we already have a pane with this content
513  instantiator.collapsed = false;
514  return;
515  }
516  }
517  var info = this.getPaneInfo(aContentUrl);
518  if (info) {
519  if (!this.tryInstantiation(info)) {
520  this._delayedInstantiations.push(info);
521  }
522  } else {
523  throw new Error("Content URL was not found in list of registered panes");
524  }
525  },
526 
527  addListener: function(aListener) {
528  this._listenersList.push(aListener);
529  },
530 
531  removeListener: function(aListener) {
532  var index = this._listenersList.indexOf(aListener);
533  if (index > -1)
534  this._listenersList.splice(index, 1);
535  },
536 
537  updateContentInfo: function(aContentUrl, aNewContentTitle, aNewContentIcon) {
538  var info = this.getPaneInfo(aContentUrl);
539  if (!info) {
540  throw Components.results.NS_ERROR_NOT_INITIALIZED;
541  }
542 
543  info.contentTitle = aNewContentTitle;
544  info.contentIcon = aNewContentIcon;
545 
546  // change the live title for every instance of this content
547  for each (var instantiator in this._instantiatorsList) {
548  if (instantiator.contentUrl == aContentUrl) {
549  instantiator.contentTitle = aNewContentTitle;
550  instantiator.contentIcon = aNewContentIcon;
551  }
552  }
553  for each (var listener in this._listenersList) {
554  listener.onPaneInfoChanged(info);
555  }
556  },
557 
558  _registerContentFromCategoryEntry: function(aEntry, aCatMgr) {
559  var catMgr = aCatMgr || Cc["@mozilla.org/categorymanager;1"]
560  .getService(Ci.nsICategoryManager);
561  var contractId = catMgr.getCategoryEntry("display-pane-provider", aEntry);
562  var contentInfo = Cc[contractId]
563  .createInstance(Ci.sbIDisplayPaneContentInfo);
564  this.registerContent(contentInfo.contentUrl,
565  contentInfo.contentTitle,
566  contentInfo.contentIcon,
567  contentInfo.defaultWidth,
568  contentInfo.defaultHeight,
569  contentInfo.suggestedContentGroups);
570  },
571 
576  XPCOMUtils.generateQI([Ci.sbIDisplayPaneManager])
577 }; // DisplayPaneManager.prototype
578 
579 function NSGetModule(compMgr, fileSpec) {
580  return XPCOMUtils.generateModule([DisplayPaneManager]);
581 }
582 
classDescription entry
Definition: FeedWriter.js:1427
const Cu
#define LOG(args)
LayoutDescription verify
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
sbOSDControlService prototype QueryInterface
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
function NSGetModule(compMgr, fileSpec)
function SBString(aKey, aDefault, aStringBundle)
Definition: StringUtils.jsm:93
const SB_NewDataRemote
const CHROME_PREFIX
function RDFHelper(aRdf, aDatasource, aResource, aNamespaces)
Definition: RDFHelper.jsm:61
var bundle
const Cc
const Ci
DataRemote prototype constructor
function SBStringBundle(aBundle)
return null
Definition: FeedWriter.js:1143
_updateCookies aName
function PaneInfo()
function DisplayPaneMetadataReader()
sbDeviceFirmwareAutoCheckForUpdate prototype classID
window addListener("unload", function(){window.removeListener("unload", arguments.callee);document.purge();if(Browser.Engine.trident){CollectGarbage();}})
_getSelectedPageStyle s i
var group