sbServicePaneService.js
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 :miv */
3 /*
4  *=BEGIN SONGBIRD GPL
5  *
6  * This file is part of the Songbird web player.
7  *
8  * Copyright(c) 2005-2010 POTI, Inc.
9  * http://www.songbirdnest.com
10  *
11  * This file may be licensed under the terms of of the
12  * GNU General Public License Version 2 (the ``GPL'').
13  *
14  * Software distributed under the License is distributed
15  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
16  * express or implied. See the GPL for the specific language
17  * governing rights and limitations.
18  *
19  * You should have received a copy of the GPL along with this
20  * program. If not, go to http://www.gnu.org/licenses/gpl.html
21  * or write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23  *
24  *=END SONGBIRD GPL
25  */
26 
32 const Cc = Components.classes;
33 const Ci = Components.interfaces;
34 const Cr = Components.results;
35 const Ce = Components.Exception;
36 const Cu = Components.utils;
37 
38 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
39 Components.utils.import("resource://app/jsmodules/ArrayConverter.jsm");
40 Components.utils.import("resource://app/jsmodules/DebugUtils.jsm");
41 Components.utils.import("resource://app/jsmodules/StringUtils.jsm");
42 
43 const SP="http://songbirdnest.com/rdf/servicepane#";
44 
45 const LOG = DebugUtils.generateLogFunction("sbServicePaneService");
46 
48  // The code that called a deprecated function is two stack frames above us
49  let caller = Components.stack;
50  if (caller)
51  caller = caller.caller;
52  if (caller)
53  caller = caller.caller;
54 
55  // Skip empty frames
56  while (caller && !caller.filename)
57  caller = caller.caller;
58 
59  let consoleService = Cc["@mozilla.org/consoleservice;1"]
60  .getService(Ci.nsIConsoleService);
61  let scriptError = Cc["@mozilla.org/scripterror;1"]
62  .createInstance(Ci.nsIScriptError);
63  scriptError.init(msg,
64  caller ? caller.filename : null,
65  caller ? caller.sourceLine : null,
66  caller ? caller.lineNumber : null,
67  0,
68  Ci.nsIScriptError.warningFlag,
69  "sbServicePaneService");
70  consoleService.logMessage(scriptError);
71 }
72 
73 function ServicePaneNode(servicePane, comparisonFunction) {
74  this._servicePane = servicePane;
75  this._comparisonFunction = comparisonFunction;
76  this._attributes = {__proto__: null};
77  this._childNodes = [];
78  this._notificationQueue = [];
79 
80  // Allow accessing internal properties when we get a wrapped node passed
81  this.wrappedJSObject = this;
82 }
83 
84 ServicePaneNode.prototype = {
85  _attributes: null,
86  _childNodes: null,
87  _parentNode: null,
88  _nodeIndex: null,
89  _servicePane: null,
90  _comparisonFunction: null,
91  _stringBundle: null,
92  _stringBundleURI: null,
93  _isInTree: false,
94  _listeners: null,
95  _eventListeners: null,
96  _notificationQueue: null,
97  _notificationQueueBusy: false,
98 
99  QueryInterface: XPCOMUtils.generateQI([Ci.sbIServicePaneNode]),
100 
101  // Generic properties
102 
103  get isContainer() {
104  deprecationWarning("sbIServicePaneNode.isContainer is deprecated and may " +
105  "be removed in future. All nodes are containers now.");
106  return this.getAttribute("isContainer") != "false";
107  },
108 
109  get properties() {
110  deprecationWarning("sbIServicePaneNode.properties is deprecated and " +
111  "may be removed in future. Consider using " +
112  "sbIServicePaneNode.className instead.");
113  return this.className;
114 
115  },
116  set properties(aValue) {
117  deprecationWarning("sbIServicePaneNode.properties is deprecated and " +
118  "may be removed in future. Consider using " +
119  "sbIServicePaneNode.className instead.");
120  return (this.className = aValue);
121  },
122 
123  get displayName() {
124  // Only names beginning with & should be translated
125  let name = this.name;
126  if (!name || name[0] != "&")
127  return name;
128 
129  // Cache the string bundle so that we don't retrieve it more than once
130  if (!this._stringBundle) {
131  let stringbundleURI = this.stringbundle;
132 
133  if (!stringbundleURI) {
134  // Try module's stringbundle
135  let contractid = this.contractid;
136  if (contractid && contractid in this._servicePane._modulesByContractId) {
137  try {
138  let module = this._servicePane._modulesByContractId[contractid];
139  stringbundleURI = module.stringbundle;
140  }
141  catch (e) {
142  Components.utils.reportError(e);
143  }
144  }
145  }
146 
147  try {
148  this._stringBundle = new SBStringBundle(stringbundleURI);
149  this._stringBundleURI = stringbundleURI;
150  }
151  catch (e) {
152  LOG("sbServicePaneNode.displayName: failed retrieving string bundle " + stringbundleURI);
153  Components.utils.reportError(e);
154  }
155  }
156 
157  // Try to get displayed name from string bundle - fall back to key name
158  if (this._stringBundle)
159  return this._stringBundle.get(name.substr(1), name);
160 
161  return name;
162  },
163 
164  get isInTree() this._isInTree,
165  set isInTree(aValue) {
166  // Convert the parameter to a real boolean value, for sake of comparisons
167  aValue = !!aValue;
168 
169  if (aValue == this._isInTree)
170  return aValue;
171 
172  // Set the value and propagate the change to child nodes
173  this._isInTree = aValue;
174  for each (let child in this._childNodes)
175  child.isInTree = aValue;
176 
177  // Update service pane data
178  let notificationMethod = (aValue ? "_registerNode" : "_unregisterNode");
179  for each (let attr in ["id", "url", "contentPrefix"])
180  if (attr in this._attributes)
181  this._servicePane[notificationMethod](attr, this._attributes[attr], this);
182 
183  return this._isInTree;
184  },
185 
186  // Attribute functions without namespace
187 
188  hasAttribute: function(aName) aName in this._attributes,
189 
190  getAttribute: function(aName) {
191  if (aName in this._attributes)
192  return this._attributes[aName];
193  else
194  return null;
195  },
196 
197  setAttribute: function(aName, aValue) {
198  if (this.isInTree && (aName == "id" || aName == "url" || aName == "contentPrefix")) {
199  if (aName in this._attributes)
200  this._servicePane._unregisterNode(aName, this._attributes[aName], this);
201  if (aValue !== null)
202  this._servicePane._registerNode(aName, aValue, this);
203  }
204  else if (aName == "stringbundle" || aName == "contractid")
205  delete this._stringBundle;
206 
207  let oldValue = (aName in this._attributes ? this._attributes[aName] : null);
208 
209  if (aValue === null)
210  delete this._attributes[aName];
211  else
212  this._attributes[aName] = aValue;
213 
214  // Trigger mutation listeners
215  let attr = aName;
216  let namespace = null;
217  if (/(.*):/.test(attr))
218  [namespace, attr] = [RegExp.$1, RegExp.rightContext];
219  this._notifyMutationListeners("attrModified", [this, attr, namespace,
220  oldValue, aValue]);
221 
222  return aValue;
223  },
224 
225  removeAttribute: function(aName) {
226  if (this.isInTree && (aName == "id" || aName == "url" || aName == "contentPrefix") &&
227  aName in this._attributes) {
228  this._servicePane._unregisterNode(aName, this._attributes[aName], this);
229  }
230  else if (aName == "stringbundle" || aName == "contractid")
231  delete this._stringBundle;
232 
233  let oldValue = (aName in this._attributes ? this._attributes[aName] : null);
234  delete this._attributes[aName];
235 
236  // Trigger mutation listeners
237  let attr = aName;
238  let namespace = null;
239  if (/(.*):/.test(attr))
240  [namespace, attr] = [RegExp.$1, RegExp.rightContext];
241  this._notifyMutationListeners("attrModified", [this, attr, namespace,
242  oldValue, null]);
243  },
244 
245  // Attribute functions with namespace - these simply combine namespace and
246  // attribute name and fall back to the regular attribute functions
247 
248  hasAttributeNS: function(aNamespace, aName)
249  this.hasAttribute(aNamespace + ":" + aName),
250 
251  getAttributeNS: function(aNamespace, aName)
252  this.getAttribute(aNamespace + ":" + aName),
253 
254  setAttributeNS: function(aNamespace, aName, aValue)
255  this.setAttribute(aNamespace + ":" + aName, aValue),
256 
257  removeAttributeNS: function(aNamespace, aName)
258  this.removeAttribute(aNamespace + ":" + aName),
259 
260  // DOM Properties
261 
262  get attributes() {
263  let attrNames = [];
264  for (let name in this._attributes)
265  attrNames.push(name);
266  return ArrayConverter.stringEnumerator(attrNames);
267  },
268 
269  get childNodes() ArrayConverter.enumerator(this._childNodes),
270 
271  get firstChild() {
272  if (this._childNodes.length)
273  return this._childNodes[0];
274  else
275  return null;
276  },
277 
278  get lastChild() {
279  if (this._childNodes.length)
280  return this._childNodes[this._childNodes.length - 1];
281  else
282  return null;
283  },
284 
285  get parentNode() this._parentNode,
286 
287  get previousSibling() {
288  let parent = this._parentNode;
289  if (!parent)
290  return null;
291 
292  let index = this._nodeIndex - 1;
293  if (index >= 0)
294  return parent._childNodes[index];
295  else
296  return null;
297  },
298 
299  get nextSibling() {
300  let parent = this._parentNode;
301  if (!parent)
302  return null;
303 
304  let index = this._nodeIndex + 1;
305  if (index < parent._childNodes.length)
306  return parent._childNodes[index];
307  else
308  return null;
309  },
310 
311  // DOM methods
312 
313  appendChild: function(aChild) {
314  return this.insertBefore(aChild, null);
315  },
316 
317  insertBefore: function(aChild, aBefore) {
318  // Unwrap parameters so that we can access internal properties
319  if (aChild)
320  aChild = aChild.wrappedJSObject;
321  if (aBefore)
322  aBefore = aBefore.wrappedJSObject;
323 
324  if (!aChild)
325  throw Ce("Node to be inserted/appended is a required parameter");
326 
327  function checkConditions() {
328  // Don't check aBefore if we have a custom comparison function, we won't
329  // use it anyway in that case.
330  if (!this._comparisonFunction && aBefore && aBefore._parentNode != this)
331  throw Ce("Cannot insert before a node that isn't a child");
332 
333  for (let parent = this; parent; parent = parent._parentNode)
334  if (parent == aChild)
335  throw Ce("Cannot insert/append a node to its child");
336  }
337 
338  checkConditions.apply(this);
339 
340  if (!this._comparisonFunction && aBefore == aChild)
341  return aChild; // nothing to do
342 
343  if (aChild._parentNode) {
344  aChild._parentNode.removeChild(aChild);
345  if (aChild._parentNode)
346  throw Ce("Mutation listener prevented removal of node from its previous location");
347 
348  // Check conditions again, mutation listeners might have modified the tree
349  checkConditions.apply(this);
350  }
351  if (this._comparisonFunction) {
352  // Use comparison function to determine node position
353  let index = 0;
354  for (; index < this._childNodes.length; index++)
355  if (this._comparisonFunction(aChild, this._childNodes[index]) < 0)
356  break;
357  if (index < this._childNodes.length)
358  aBefore = this._childNodes[index];
359  else
360  aBefore = null;
361  }
362 
363  let index = aBefore ? aBefore._nodeIndex : this._childNodes.length;
364  for (let i = index; i < this._childNodes.length; i++)
365  this._childNodes[i]._nodeIndex++;
366 
367  this._childNodes.splice(index, 0, aChild);
368  aChild._nodeIndex = index;
369  aChild._parentNode = this;
370  aChild.isInTree = this.isInTree;
371 
372  // Trigger mutation listeners
373  aChild._notifyMutationListeners("nodeInserted", [aChild, this, aBefore]);
374 
375  return aChild;
376  },
377 
378  removeChild: function(aChild) {
379  // Unwrap parameters so that we can access internal properties
380  if (aChild)
381  aChild = aChild.wrappedJSObject;
382 
383  if (!aChild || aChild._parentNode != this)
384  throw Ce("Cannot remove a node that isn't a child");
385 
386  let wasInTree = aChild.isInTree;
387  let oldParentNode = aChild._parentNode;
388 
389  let index = aChild._nodeIndex;
390  for (let i = index + 1; i < this._childNodes.length; i++)
391  this._childNodes[i]._nodeIndex--;
392 
393  this._childNodes.splice(index, 1);
394  delete aChild._nodeIndex;
395  delete aChild._parentNode;
396  aChild.isInTree = null;
397 
398  // Trigger mutation listeners
399  aChild._notifyMutationListeners("nodeRemoved", [aChild, this], wasInTree, oldParentNode);
400 
401  return aChild;
402  },
403 
404  replaceChild: function(aChild, aOldChild) {
405  // Unwrap parameters so that we can access internal properties
406  if (aChild)
407  aChild = aChild.wrappedJSObject;
408  if (aOldChild)
409  aOldChild = aOldChild.wrappedJSObject;
410 
411  if (!aOldChild || aOldChild._parentNode != this)
412  throw Ce("Cannot replace a node that isn't a child");
413 
414  let insertBefore = aOldChild.nextSibling;
415  this.removeChild(aOldChild);
416  this.insertBefore(aChild, insertBefore);
417  },
418 
419  addEventListener: function(aListener) {
420  if (this._eventListeners == null)
421  this._eventListeners = [];
422 
423  // Don't add listeners twice
424  for each (let listener in this._eventListeners)
425  if (listener == aListener)
426  return;
427 
428  this._eventListeners.push(aListener);
429  },
430 
431  removeEventListener: function(aListener) {
432  if (this._eventListeners == null)
433  return;
434 
435  for (let i = 0; i < this._eventListeners.length; i++)
436  if (this._eventListeners[i] == aListener)
437  this._eventListeners.splice(i--, 1);
438 
439  if (this._eventListeners.length == 0)
440  this._eventListeners = null;
441  },
442 
443  dispatchEvent: function(aEventName) {
444  for each (let listener in this._eventListeners)
445  listener.handleEvent(aEventName);
446  },
447 
448  addMutationListener: function(aListener) {
449  if (this._listeners == null)
450  this._listeners = [];
451 
452  // Don't add listeners twice
453  for each (let listener in this._listeners)
454  if (listener == aListener)
455  return;
456 
457  this._listeners.push(aListener);
458  },
459 
460  removeMutationListener: function(aListener) {
461  if (this._listeners == null)
462  return;
463 
464  for (let i = 0; i < this._listeners.length; i++)
465  if (this._listeners[i] == aListener)
466  this._listeners.splice(i--, 1);
467 
468  if (this._listeners.length == 0)
469  this._listeners = null;
470  },
471 
472  _notifyMutationListeners: function(aMethod, aArgs, aIsInTree, aParentNode) {
473  // Don't trigger listeners if we haven't been added to the service pane tree
474  let isInTree = (typeof aIsInTree != "undefined" ? aIsInTree : this.isInTree);
475  if (!isInTree)
476  return;
477 
478  // Queue the notification before sending it out, just in case one of the
479  // listeners decides to mess with the DOM structure. Add to the queue
480  // of this node and all of its parent nodes (bubbling).
481  let notification = [aMethod, aArgs];
482  let nodes = [];
483  let node = this;
484  let parent = (typeof aParentNode != "undefined" ? aParentNode : node._parentNode);
485  while (node) {
486  if (node._listeners != null) {
487  nodes.push(node);
488  node._notificationQueue.push(notification);
489  }
490 
491  node = parent;
492  if (node)
493  parent = node._parentNode;
494  }
495 
496  // Now actually trigger listeners for all nodes
497  for each (let node in nodes)
498  node._processNotificationQueue();
499  },
500 
501  _processNotificationQueue: function() {
502  if (this._listeners == null)
503  return;
504 
505  // Protect against reentrance
506  if (this._notificationQueueBusy)
507  return;
508  this._notificationQueueBusy = true;
509 
510  // Create a local copy of the listeners in case the list is modified while
511  // we are calling listeners.
512  let listeners = this._listeners.slice();
513 
514  while (this._notificationQueue.length) {
515  let [method, args] = this._notificationQueue.shift();
516  for each (let listener in listeners) {
517  try {
518  listener[method].apply(listener, args);
519  }
520  catch (e) {
521  Cu.reportError(e);
522  }
523  }
524  }
525 
526  this._notificationQueueBusy = false;
527  },
528 
529  // Attribute shortcuts
530 
531  get id() this.getAttribute("id"),
532  set id(aValue) this.setAttribute("id", aValue),
533  get className() this.getAttribute("class"),
534  set className(aValue) this.setAttribute("class", aValue),
535  get url() this.getAttribute("url"),
536  set url(aValue) this.setAttribute("url", aValue),
537  get contentPrefix() this.getAttribute("contentPrefix"),
538  set contentPrefix(aValue) this.setAttribute("contentPrefix", aValue),
539  get image() this.getAttribute("image"),
540  set image(aValue) this.setAttribute("image", aValue),
541  get name() this.getAttribute("name"),
542  set name(aValue) this.setAttribute("name", aValue),
543  get tooltip() this.getAttribute("tooltip"),
544  set tooltip(aValue) this.setAttribute("tooltip", aValue),
545  get hidden() this.getAttribute("hidden") == "true",
546  set hidden(aValue) {
547  return this.setAttribute("hidden", aValue ? "true" : "false");
548  },
549  get editable() this.getAttribute("editable") == "true",
550  set editable(aValue) this.setAttribute("editable", aValue ? "true" : "false"),
551  get isOpen() this.getAttribute("isOpen") != "false",
552  set isOpen(aValue) this.setAttribute("isOpen", aValue ? "true" : "false"),
553  get contractid() this.getAttribute("contractid"),
554  set contractid(aValue) this.setAttribute("contractid", aValue),
555  get searchtype() { return this.getAttribute("searchtype") || "internal"; },
556  set searchtype(aValue) this.setAttribute("searchtype", aValue),
557  get stringbundle() this.getAttribute("stringbundle"),
558  set stringbundle(aValue) this.setAttribute("stringbundle", aValue),
559  get dndDragTypes() this.getAttribute("dndDragTypes"),
560  set dndDragTypes(aValue) this.setAttribute("dndDragTypes", aValue),
561  get dndAcceptNear() this.getAttribute("dndAcceptNear"),
562  set dndAcceptNear(aValue) this.setAttribute("dndAcceptNear", aValue),
563  get dndAcceptIn() this.getAttribute("dndAcceptIn"),
564  set dndAcceptIn(aValue) this.setAttribute("dndAcceptIn", aValue),
565 };
566 
567 
572 function MutationEventTranslator(aListener) {
573  this.listener = aListener;
574 }
575 MutationEventTranslator.prototype = {
576  listener: null,
577 
578  QueryInterface: XPCOMUtils.generateQI([Ci.sbIServicePaneMutationListener]),
579 
580  attrModified: function(aNode, aAttrName, aNamespace, aOldValue, aNewValue) {
581  if (aNode.id)
582  this.listener.nodePropertyChanged(aNode.id,
583  aNamespace == null ? aAttrName : aNamespace + aAttrName);
584  },
585 
586  nodeInserted: function() {},
587  nodeRemoved: function() {}
588 }
589 
590 function ServicePaneService () {
591  LOG("Service pane initialization started");
592 
593  // Create root node - children are sorted automatically
594 
595  this._nodesById = {__proto__: null};
596  this._nodesByUrl = {__proto__: null};
597  this._nodesByContentPrefix = {__proto__: null};
598  this._root = new ServicePaneNode(this, function(aNode1, aNode2) {
599  // Nodes with lower weight go first
600  let weight1 = parseInt(aNode1.getAttributeNS(SP, 'Weight')) || 0;
601  let weight2 = parseInt(aNode2.getAttributeNS(SP, 'Weight')) || 0;
602  if (weight1 != weight2)
603  return weight1 - weight2;
604 
605  // For equal weight, sort alphabetically
606  let name1 = aNode1.displayName;
607  let name2 = aNode2.displayName;
608  if (name1 < name2)
609  return -1;
610  else if (name1 > name2)
611  return 1;
612 
613  return 0;
614  });
615  this._root.isInTree = true;
616 
617  // Initialize modules
618 
619  let catMgr = Cc["@mozilla.org/categorymanager;1"]
620  .getService(Ci.nsICategoryManager);
621  let enumerator = catMgr.enumerateCategory("service-pane");
622  let moduleEntries = ArrayConverter.JSArray(enumerator).map(
623  function(entry) entry.QueryInterface(Ci.nsISupportsCString).data
624  );
625  moduleEntries.sort();
626 
627  this._modules = [];
628  this._modulesByContractId = {__proto__: null};
629  this._categoryEntriesCache = {__proto__: null};
630  for each (let entry in moduleEntries)
631  this._loadModule(catMgr, entry);
632  LOG("Service pane initialization completed successfully");
633 
634  // Listen for modules being added or removed
635  let observerService = Cc["@mozilla.org/observer-service;1"]
636  .getService(Ci.nsIObserverService);
637  observerService.addObserver(this, "xpcom-category-entry-added", true);
638  observerService.addObserver(this, "xpcom-category-entry-removed", true);
639  observerService.addObserver(this, "quit-application", false);
640 }
641 
642 ServicePaneService.prototype = {
643  // XPCOM component info
644  classID: Components.ID("{eb5c665a-bfe2-49f1-a747-cd3554e55606}"),
645  classDescription: "Songbird Service Pane Service",
646  contractID: "@songbirdnest.com/servicepane/service;1",
647 
648  _modules: null,
649  _modulesByContractId: null,
650  _categoryEntriesCache: null,
651  _root: null,
652  _nodesById: null,
653  _nodesByUrl: null,
654  _nodeIndex: 0,
655 
656  get root() this._root,
657 
658  QueryInterface: XPCOMUtils.generateQI([Ci.sbIServicePaneService,
659  Ci.nsIObserver,
661 
662  observe: function(subject, topic, data) {
663  switch (topic) {
664  case "xpcom-category-entry-added":
665  if (data == "service-pane" && subject instanceof Ci.nsISupportsCString) {
666  let catMgr = Cc["@mozilla.org/categorymanager;1"]
667  .getService(Ci.nsICategoryManager);
668  this._loadModule(catMgr, subject.data);
669  }
670  break;
671  case "xpcom-category-entry-removed":
672  if (data == "service-pane" && subject instanceof Ci.nsISupportsCString) {
673  this._removeModule(subject.data);
674  }
675  break;
676  case "quit-application":
677  this._shutdown();
678  break;
679  }
680  },
681 
682  _loadModule: function ServicePaneService__loadModule(catMgr, entry) {
683  let contractId = catMgr.getCategoryEntry("service-pane", entry);
684  this._categoryEntriesCache[entry] = contractId;
685 
686  // Don't load if already loaded
687  if (contractId in this._modulesByContractId)
688  return;
689 
690  LOG("Trying to load service pane module: " + contractId);
691  try {
692  let module = Cc[contractId].getService(Ci.sbIServicePaneModule);
693  module.servicePaneInit(this);
694  this._modules.push(module);
695  this._modulesByContractId[contractId] = module;
696  LOG("Service pane module successfully initialized");
697  } catch (e) {
698  LOG("Error instantiating service pane module: " + e);
699  }
700  },
701 
702  _removeModule: function ServicePaneService__removeModule(entry) {
703  // Don't bother if we don't know this module
704  if (!(entry in this._categoryEntriesCache))
705  return;
706  let contractId = this._categoryEntriesCache[entry];
707  if (!(contractId in this._modulesByContractId))
708  return;
709 
710  let module = this._modulesByContractId[contractId];
711  for (let i = 0; i < this._modules.length; i++)
712  if (this._modules[i] == module)
713  this._modules.splice(i--, 1);
714  delete this._modulesByContractId[contractId];
715  LOG("Service pane module " + contractId + " removed");
716  },
717 
718  init: function ServicePaneService_init() {
719  deprecationWarning("sbIServicePaneService.init() is deprecated, you no " +
720  "longer need to call it.");
721  },
722 
723  _clearNodeListeners: function ServicePaneService__clearNodeListeners(aNode) {
724  if (aNode._eventListeners) {
725  delete aNode._eventListeners;
726  }
727  for each (let child in aNode.childNodes) {
728  this._clearNodeListeners(child);
729  }
730  },
731  _shutdown: function ServicePaneService__shutdown() {
732  this._clearNodeListeners(this.root);
733 
734  let observerService = Cc["@mozilla.org/observer-service;1"]
735  .getService(Ci.nsIObserverService);
736  observerService.removeObserver(this, "quit-application");
737  observerService.removeObserver(this, "xpcom-category-entry-added");
738  observerService.removeObserver(this, "xpcom-category-entry-removed");
739  for each (let module in this._modules) {
740  try {
741  module.shutdown();
742  } catch (e) {
743  // Components.utils.reportError can't report to anywhere visible at
744  // this point, since to get here we must have closed the Error Console.
745  // Dump to stderr instead.
746  dump("**********************************************\n");
747  dump(e + "\n");
748  dump("**********************************************\n");
749  }
750  }
751  this._modules = [];
752  this._modulesByContractId = {};
753  },
754 
755  createNode: function ServicePaneService_createNode() {
756  return new ServicePaneNode(this, null);
757  },
758 
759  _registerNode: function ServicePaneService__registerNode(
760  aAttr, aValue, aNode) {
761  let table;
762  switch (aAttr) {
763  case "id":
764  table = this._nodesById;
765  break;
766  case "url":
767  table = this._nodesByUrl;
768  break;
769  case "contentPrefix":
770  table = this._nodesByContentPrefix;
771  break;
772  default:
773  return;
774  }
775 
776  if (!(aValue in table))
777  table[aValue] = [];
778 
779  table[aValue].push(aNode);
780  },
781 
782  _unregisterNode: function ServicePaneService__unregisterNode(
783  aAttr, aValue, aNode) {
784  let table;
785  switch (aAttr) {
786  case "id":
787  table = this._nodesById;
788  break;
789  case "url":
790  table = this._nodesByUrl;
791  break;
792  case "contentPrefix":
793  table = this._nodesByContentPrefix;
794  break;
795  default:
796  return;
797  }
798 
799  if (aValue in table)
800  {
801  let list = table[aValue];
802  for (let i = 0; i < list.length; i++)
803  if (list[i] == aNode)
804  list.splice(i--, 1);
805  }
806  },
807 
808  getNode: function ServicePaneService_getNode(aId) {
809  if (aId in this._nodesById && this._nodesById[aId].length) {
810  if (this._nodesById[aId].length > 1) {
811  // Warn if multiple nodes with same ID exist
812  let caller = Components.stack.caller;
813 
814  // Skip empty frames
815  while (caller && !caller.filename)
816  caller = caller.caller;
817 
818  let consoleService = Cc["@mozilla.org/consoleservice;1"]
819  .getService(Ci.nsIConsoleService);
820  let scriptError = Cc["@mozilla.org/scripterror;1"]
821  .createInstance(Ci.nsIScriptError);
822  scriptError.init("Multiple service pane nodes with ID '" + aId + "' exist, only returning one node.",
823  caller ? caller.filename : null,
824  caller ? caller.sourceLine : null,
825  caller ? caller.lineNumber : null,
826  0,
827  Ci.nsIScriptError.warningFlag,
828  "sbServicePaneService");
829  consoleService.logMessage(scriptError);
830  }
831  return this._nodesById[aId][0];
832  }
833  else
834  return null;
835  },
836 
837  getNodeForURL: function ServicePaneService_getNodeForURL(aUrl, aMatchLevel) {
838 
839  // Check to see if aUrl (a string url spec) begins with a node's
840  // contentPrefix. Only do this if the caller specifically requests a
841  // prefix match.
842  var prefixMatch = null;
843  if (!(typeof(aMatchLevel) == 'undefined') &&
844  aMatchLevel == Ci.sbIServicePaneService.URL_MATCH_PREFIX)
845  {
846  for (let prefix in this._nodesByContentPrefix) {
847  if (aUrl.indexOf(prefix) == 0 &&
848  this._nodesByContentPrefix[prefix].length)
849  {
850  prefixMatch = this._nodesByContentPrefix[prefix][0];
851  }
852  }
853  }
854 
855  // Check for an exact url match. This is done for both URL_MATCH_PREFIX and
856  // URL_MATCH_EXACT intentionally so that calls using URL_MATCH_PREFIX will
857  // still find a node if the url attribute is an exact match (even in the
858  // absence of a contentPrefix attribute)
859  if (aUrl in this._nodesByUrl && this._nodesByUrl[aUrl].length) {
860  // We prefer exact matches to prefix matches
861  return this._nodesByUrl[aUrl][0];
862  }
863  else {
864  // We didn't find an exact match. Return a prefix match if we found one.
865  // prefixMatch will always be null if the caller didn't pass in
866  // aMatchLevel or they passed URL_MATCH_EXACT
867  return prefixMatch;
868  }
869  },
870 
871  getNodesByAttributeNS: function ServicePaneService_getNodesByAttributeNS(
872  aNamespace, aName, aValue) {
873  function findNodeRecursive(aNode, aAttrName, aValue, aResult) {
874  for each (let child in aNode._childNodes) {
875  if (child.getAttribute(aAttrName) == aValue)
876  aResult.push(child);
877 
878  findNodeRecursive(child, aAttrName, aValue, aResult);
879  }
880  }
881 
882  let result = [];
883  let attrName = (aNamespace == null ? aName : aNamespace + ":" + aName);
884  findNodeRecursive(this._root, attrName, aValue, result);
885  return ArrayConverter.nsIArray(result);
886  },
887 
888  addListener: function ServicePaneService_addListener(aListener) {
889  deprecationWarning("sbIServicePaneService.addListener() is deprecated " +
890  "and may be removed in future. Consider using " +
891  "root.addMutationListener() instead.");
892  this.root.addMutationListener(new MutationEventTranslator(aListener));
893  },
894 
895  removeListener: function ServicePaneService_removeListener(aListener) {
896  deprecationWarning("sbIServicePaneService.addListener() is deprecated " +
897  "and may be removed in future. Consider using " +
898  "root.removeMutationListener() instead.");
899 
900  // Make a copy of the listeners in case they change while we are in the loop
901  let listeners = this.root._listeners.slice();
902  for each (let listener in listeners)
903  if (listener instanceof MutationEventTranslator && listener.listener == aListener)
904  this.root.removeMutationListener(listener);
905  },
906 
907  fillContextMenu: function ServicePaneService_fillContextMenu(
908  aNode, aContextMenu, aParentWindow) {
909  for each (let module in this._modules) {
910  try {
911  module.fillContextMenu(aNode, aContextMenu, aParentWindow);
912  } catch (ex) {
913  Components.utils.reportError(ex);
914  }
915  }
916  },
917 
918  fillNewItemMenu: function ServicePaneService_fillNewItemMenu(
919  aNode, aContextMenu, aParentWindow) {
920  for each (let module in this._modules) {
921  try {
922  module.fillNewItemMenu(aNode, aContextMenu, aParentWindow);
923  } catch (ex) {
924  Components.utils.reportError(ex);
925  }
926  }
927  },
928 
929  onSelectionChanged: function ServicePaneService_onSelectionChanged(
930  aNode, aContainer, aParentWindow) {
931  for each (let module in this._modules) {
932  try {
933  module.onSelectionChanged(aNode, aContainer, aParentWindow);
934  } catch (ex) {
935  Components.utils.reportError(ex);
936  }
937  }
938  },
939 
944  onBeforeRename: function ServicePaneService_onBeforeRename(aNode) {
945  if (!aNode || !aNode.editable)
946  return;
947 
948  // Pass the message on to the node owner
949  if (aNode.contractid && aNode.contractid in this._modulesByContractId) {
950  let module = this._modulesByContractId[aNode.contractid];
951  module.onBeforeRename(aNode);
952  }
953  },
954 
959  onRename: function ServicePaneService_onRename(aNode, aNewName) {
960  if (!aNode || !aNode.editable)
961  return;
962 
963  // Pass the message on to the node owner
964  if (aNode.contractid && aNode.contractid in this._modulesByContractId) {
965  let module = this._modulesByContractId[aNode.contractid];
966  module.onRename(aNode, aNewName);
967  }
968  },
969 
970  addNode: function ServicePaneService_addNode(aId, aParent, aContainer) {
971  deprecationWarning("sbIServicePaneService.addNode() is deprecated and " +
972  "may be removed in future. Consider using " +
973  "sbIServicePaneService.createNode() instead and " +
974  "adding it to the parent yourself.");
975 
976  if (!aParent)
977  throw Ce("You need to supply a parent for addNode().");
978 
979  if (aId && this.getNode(aId)) {
980  // Original implementation returned null for attempts to duplicate a node
981  return null;
982  }
983 
984  let node = this.createNode();
985  aParent.appendChild(node);
986  node.id = (aId !== null ? aId : "_generatedNodeId" + ++this._nodeIndex);
987  if (!aContainer)
988  node.setAttribute("isContainer", "false");
989 
990  return node;
991  },
992 
993  removeNode: function ServicePaneService_removeNode(aNode) {
994  deprecationWarning("sbIServicePaneService.removeNode() is deprecated and " +
995  "may be removed in future. Consider using " +
996  "sbIServicePaneNode.removeChild() instead.");
997 
998  if (!aNode)
999  throw Ce("You need to supply a node for removeNode().");
1000  if (!aNode.parentNode)
1001  throw Ce("Cannot remove a node that doesn't have a parent.");
1002 
1003  aNode.parentNode.removeChild(aNode);
1004  },
1005 
1006  sortNode: function ServicePaneService_sortNode() {
1007  deprecationWarning("sbIServicePaneService.sortNode() is deprecated, you no " +
1008  "longer need to call it.");
1009  },
1010 
1011  save: function ServicePaneService_save() {
1012  deprecationWarning("sbIServicePaneService.save() is deprecated, you no " +
1013  "longer need to call it.");
1014  },
1015 
1016  canDrop: function ServicePaneService_canDrop(
1017  aNode, aDragSession, aOrientation, aWindow) {
1018  if (!aNode) {
1019  return false;
1020  }
1021 
1022  LOG("canDrop(" + aNode.id + ")");
1023 
1024  // let the module that owns this node handle this
1025  if (aNode.contractid && aNode.contractid in this._modulesByContractId) {
1026  let module = this._modulesByContractId[aNode.contractid];
1027  return module.canDrop(aNode, aDragSession, aOrientation, aWindow);
1028  }
1029  return false;
1030  },
1031 
1032  onDrop: function ServicePaneService_onDrop(
1033  aNode, aDragSession, aOrientation, aWindow) {
1034  if (!aNode) {
1035  return;
1036  }
1037 
1038  LOG("onDrop(" + aNode.id + ")");
1039 
1040  // or let the module that owns this node handle it
1041  if (aNode.contractid && aNode.contractid in this._modulesByContractId) {
1042  let module = this._modulesByContractId[aNode.contractid];
1043  module.onDrop(aNode, aDragSession, aOrientation, aWindow);
1044  }
1045  },
1046 
1047  onDragGesture: function ServicePaneService_onDragGesture(aNode, aDataTransfer) {
1048  if (!aNode) {
1049  return false;
1050  }
1051 
1052  LOG("onDragGesture(" + aNode.id + ")");
1053 
1054  if (!aNode.id) {
1055  Cu.reportError(new Exception("Cannot drag a service pane node without ID"));
1056  return false;
1057  }
1058 
1059  let success = false;
1060 
1061  // get drag types from the node data
1062  if (aNode.dndDragTypes) {
1063  let types = aNode.dndDragTypes.split(',');
1064  for each (let type in types) {
1065  aDataTransfer.setData(type, aNode.id);
1066  success = true;
1067  }
1068  }
1069 
1070  if (aNode.contractid && aNode.contractid in this._modulesByContractId) {
1071  let module = this._modulesByContractId[aNode.contractid];
1072  if (module.onDragGesture(aNode, aDataTransfer)) {
1073  success = true;
1074  }
1075  }
1076 
1077  LOG(" success=" + success);
1078 
1079  return success;
1080  }
1081 };
1082 
1083 var NSGetModule = XPCOMUtils.generateNSGetModule([ServicePaneService]);
classDescription entry
Definition: FeedWriter.js:1427
var args
Definition: alert.js:8
static nsCOMPtr< nsIObserverService > observerService
Definition: UnityProxy.cpp:6
tab dispatchEvent(event)
const Cc
tabData attributes[name]
menuItem id
Definition: FeedWriter.js:971
onPageChanged aValue
Definition: FeedWriter.js:1395
sbOSDControlService prototype className
imageContainer appendChild(newImage)
var NSGetModule
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
handlersMenuPopup addEventListener("command", this, false)
function deprecationWarning(msg)
sbOSDControlService prototype QueryInterface
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
const LOG
const char * types
function ServicePaneNode(servicePane, comparisonFunction)
_window init
Definition: FeedWriter.js:1144
const module
Definition: httpd.js:4659
const Ce
const Ci
const Cu
function MutationEventTranslator(aListener)
Class translating sbIServicePaneMutationListener calls into sbIServicePaneListener calls...
function SBStringBundle(aBundle)
this _document this
Definition: FeedWriter.js:1085
imageContainer removeChild(oldImage)
return null
Definition: FeedWriter.js:1143
menuItem setAttribute("handlerType","client")
let node
const SP
_updateCookies aName
function url(spec)
return aWindow document documentElement getAttribute(aAttribute)||dimension
observe topic
Definition: FeedWriter.js:1326
const Cr
sbDeviceFirmwareAutoCheckForUpdate prototype classID
var hidden
function msg
this removeEventListener("load", this.__SS_restore, true)
observe data
Definition: FeedWriter.js:1329
window addListener("unload", function(){window.removeListener("unload", arguments.callee);document.purge();if(Browser.Engine.trident){CollectGarbage();}})
sbDeviceServicePane prototype nodeInserted
_getSelectedPageStyle s i
sbDeviceServicePane prototype nodeRemoved
sbDeviceFirmwareAutoCheckForUpdate prototype observe
The interface exposed by the service pane service.