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;
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");
43 const SP=
"http://songbirdnest.com/rdf/servicepane#";
45 const LOG = DebugUtils.generateLogFunction(
"sbServicePaneService");
49 let caller = Components.stack;
51 caller = caller.caller;
53 caller = caller.caller;
56 while (caller && !caller.filename)
57 caller = caller.caller;
59 let consoleService =
Cc[
"@mozilla.org/consoleservice;1"]
60 .getService(
Ci.nsIConsoleService);
61 let scriptError =
Cc[
"@mozilla.org/scripterror;1"]
62 .createInstance(
Ci.nsIScriptError);
64 caller ? caller.filename :
null,
65 caller ? caller.sourceLine : null,
66 caller ? caller.lineNumber : null,
68 Ci.nsIScriptError.warningFlag,
69 "sbServicePaneService");
70 consoleService.logMessage(scriptError);
74 this._servicePane = servicePane;
75 this._comparisonFunction = comparisonFunction;
76 this._attributes = {__proto__:
null};
77 this._childNodes = [];
78 this._notificationQueue = [];
81 this.wrappedJSObject =
this;
90 _comparisonFunction:
null,
92 _stringBundleURI:
null,
95 _eventListeners:
null,
96 _notificationQueue:
null,
97 _notificationQueueBusy:
false,
105 "be removed in future. All nodes are containers now.");
111 "may be removed in future. Consider using " +
112 "sbIServicePaneNode.className instead.");
118 "may be removed in future. Consider using " +
119 "sbIServicePaneNode.className instead.");
126 if (!name || name[0] !=
"&")
130 if (!this._stringBundle) {
131 let stringbundleURI = this.stringbundle;
133 if (!stringbundleURI) {
135 let contractid = this.contractid;
136 if (contractid && contractid in this._servicePane._modulesByContractId) {
138 let
module = this._servicePane._modulesByContractId[contractid];
139 stringbundleURI = module.stringbundle;
142 Components.utils.reportError(e);
149 this._stringBundleURI = stringbundleURI;
152 LOG(
"sbServicePaneNode.displayName: failed retrieving string bundle " + stringbundleURI);
153 Components.utils.reportError(e);
158 if (this._stringBundle)
159 return this._stringBundle.get(name.substr(1),
name);
164 get isInTree() this._isInTree,
169 if (
aValue == this._isInTree)
174 for each (let child
in this._childNodes)
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);
183 return this._isInTree;
188 hasAttribute:
function(
aName)
aName in this._attributes,
191 if (
aName in this._attributes)
192 return this._attributes[
aName];
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);
202 this._servicePane._registerNode(aName,
aValue,
this);
204 else if (
aName ==
"stringbundle" ||
aName ==
"contractid")
205 delete this._stringBundle;
207 let oldValue = (
aName in this._attributes ? this._attributes[
aName] :
null);
210 delete this._attributes[
aName];
216 let
namespace =
null;
217 if (/(.*):/.test(attr))
218 [
namespace, attr] = [RegExp.$1, RegExp.rightContext];
219 this._notifyMutationListeners(
"attrModified", [
this, attr,
namespace,
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);
230 else if (
aName ==
"stringbundle" ||
aName ==
"contractid")
231 delete this._stringBundle;
233 let oldValue = (
aName in this._attributes ? this._attributes[
aName] :
null);
234 delete this._attributes[
aName];
238 let
namespace =
null;
239 if (/(.*):/.test(attr))
240 [
namespace, attr] = [RegExp.$1, RegExp.rightContext];
241 this._notifyMutationListeners(
"attrModified", [
this, attr,
namespace,
248 hasAttributeNS:
function(aNamespace,
aName)
249 this.hasAttribute(aNamespace +
":" +
aName),
251 getAttributeNS:
function(aNamespace,
aName)
254 setAttributeNS:
function(aNamespace,
aName,
aValue)
257 removeAttributeNS:
function(aNamespace,
aName)
258 this.removeAttribute(aNamespace +
":" + aName),
264 for (let
name in this._attributes)
265 attrNames.push(
name);
266 return ArrayConverter.stringEnumerator(attrNames);
269 get childNodes() ArrayConverter.enumerator(this._childNodes),
272 if (this._childNodes.length)
273 return this._childNodes[0];
279 if (this._childNodes.length)
280 return this._childNodes[this._childNodes.length - 1];
285 get parentNode() this._parentNode,
287 get previousSibling() {
288 let parent = this._parentNode;
292 let index = this._nodeIndex - 1;
294 return parent._childNodes[index];
300 let parent = this._parentNode;
304 let index = this._nodeIndex + 1;
305 if (index < parent._childNodes.length)
306 return parent._childNodes[index];
314 return this.insertBefore(aChild,
null);
317 insertBefore:
function(aChild, aBefore) {
320 aChild = aChild.wrappedJSObject;
322 aBefore = aBefore.wrappedJSObject;
325 throw Ce(
"Node to be inserted/appended is a required parameter");
327 function checkConditions() {
330 if (!this._comparisonFunction && aBefore && aBefore._parentNode !=
this)
331 throw Ce(
"Cannot insert before a node that isn't a child");
333 for (let parent =
this; parent; parent = parent._parentNode)
334 if (parent == aChild)
335 throw Ce(
"Cannot insert/append a node to its child");
338 checkConditions.apply(
this);
340 if (!this._comparisonFunction && aBefore == aChild)
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");
349 checkConditions.apply(
this);
351 if (this._comparisonFunction) {
354 for (; index < this._childNodes.length; index++)
355 if (this._comparisonFunction(aChild, this._childNodes[index]) < 0)
357 if (index < this._childNodes.length)
358 aBefore = this._childNodes[index];
363 let index = aBefore ? aBefore._nodeIndex : this._childNodes.length;
364 for (let
i = index;
i < this._childNodes.length;
i++)
365 this._childNodes[
i]._nodeIndex++;
367 this._childNodes.splice(index, 0, aChild);
368 aChild._nodeIndex = index;
369 aChild._parentNode =
this;
370 aChild.isInTree = this.isInTree;
373 aChild._notifyMutationListeners(
"nodeInserted", [aChild,
this, aBefore]);
381 aChild = aChild.wrappedJSObject;
383 if (!aChild || aChild._parentNode !=
this)
384 throw Ce(
"Cannot remove a node that isn't a child");
386 let wasInTree = aChild.isInTree;
387 let oldParentNode = aChild._parentNode;
389 let index = aChild._nodeIndex;
390 for (let
i = index + 1;
i < this._childNodes.length;
i++)
391 this._childNodes[
i]._nodeIndex--;
393 this._childNodes.splice(index, 1);
394 delete aChild._nodeIndex;
395 delete aChild._parentNode;
396 aChild.isInTree =
null;
399 aChild._notifyMutationListeners(
"nodeRemoved", [aChild,
this], wasInTree, oldParentNode);
404 replaceChild:
function(aChild, aOldChild) {
407 aChild = aChild.wrappedJSObject;
409 aOldChild = aOldChild.wrappedJSObject;
411 if (!aOldChild || aOldChild._parentNode !=
this)
412 throw Ce(
"Cannot replace a node that isn't a child");
414 let insertBefore = aOldChild.nextSibling;
416 this.insertBefore(aChild, insertBefore);
420 if (this._eventListeners ==
null)
421 this._eventListeners = [];
424 for each (let
listener in this._eventListeners)
425 if (listener == aListener)
428 this._eventListeners.push(aListener);
432 if (this._eventListeners ==
null)
435 for (let
i = 0;
i < this._eventListeners.length;
i++)
436 if (this._eventListeners[
i] == aListener)
437 this._eventListeners.splice(
i--, 1);
439 if (this._eventListeners.length == 0)
440 this._eventListeners =
null;
444 for each (let listener
in this._eventListeners)
445 listener.handleEvent(aEventName);
448 addMutationListener:
function(aListener) {
449 if (this._listeners ==
null)
450 this._listeners = [];
453 for each (let listener
in this._listeners)
454 if (listener == aListener)
457 this._listeners.push(aListener);
460 removeMutationListener:
function(aListener) {
461 if (this._listeners ==
null)
464 for (let
i = 0;
i < this._listeners.length;
i++)
465 if (this._listeners[
i] == aListener)
466 this._listeners.splice(
i--, 1);
468 if (this._listeners.length == 0)
469 this._listeners =
null;
472 _notifyMutationListeners:
function(aMethod, aArgs, aIsInTree, aParentNode) {
474 let isInTree = (typeof aIsInTree !=
"undefined" ? aIsInTree : this.isInTree);
481 let notification = [aMethod, aArgs];
484 let parent = (typeof aParentNode !=
"undefined" ? aParentNode : node._parentNode);
486 if (node._listeners !=
null) {
488 node._notificationQueue.push(notification);
493 parent = node._parentNode;
497 for each (let node
in nodes)
498 node._processNotificationQueue();
501 _processNotificationQueue:
function() {
502 if (this._listeners ==
null)
506 if (this._notificationQueueBusy)
508 this._notificationQueueBusy =
true;
512 let listeners = this._listeners.slice();
514 while (this._notificationQueue.length) {
515 let [method,
args] = this._notificationQueue.shift();
516 for each (let listener
in listeners) {
518 listener[method].apply(listener,
args);
526 this._notificationQueueBusy =
false;
555 get searchtype() {
return this.
getAttribute(
"searchtype") ||
"internal"; },
580 attrModified:
function(aNode, aAttrName, aNamespace, aOldValue, aNewValue) {
582 this.
listener.nodePropertyChanged(aNode.id,
583 aNamespace ==
null ? aAttrName : aNamespace + aAttrName);
590 function ServicePaneService () {
591 LOG(
"Service pane initialization started");
595 this._nodesById = {__proto__:
null};
596 this._nodesByUrl = {__proto__:
null};
597 this._nodesByContentPrefix = {__proto__:
null};
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;
606 let name1 = aNode1.displayName;
607 let name2 = aNode2.displayName;
610 else if (name1 > name2)
615 this._root.isInTree =
true;
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
625 moduleEntries.sort();
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");
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);
642 ServicePaneService.prototype = {
644 classID: Components.ID(
"{eb5c665a-bfe2-49f1-a747-cd3554e55606}"),
646 contractID:
"@songbirdnest.com/servicepane/service;1",
649 _modulesByContractId:
null,
650 _categoryEntriesCache:
null,
656 get root()
this._root,
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);
671 case "xpcom-category-entry-removed":
672 if (data ==
"service-pane" && subject instanceof
Ci.nsISupportsCString) {
673 this._removeModule(subject.data);
676 case "quit-application":
682 _loadModule:
function ServicePaneService__loadModule(catMgr, entry) {
683 let contractId = catMgr.getCategoryEntry(
"service-pane", entry);
684 this._categoryEntriesCache[
entry] = contractId;
687 if (contractId in this._modulesByContractId)
690 LOG(
"Trying to load service pane module: " + contractId);
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");
698 LOG(
"Error instantiating service pane module: " + e);
702 _removeModule:
function ServicePaneService__removeModule(entry) {
704 if (!(entry in this._categoryEntriesCache))
706 let contractId = this._categoryEntriesCache[
entry];
707 if (!(contractId in this._modulesByContractId))
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");
718 init:
function ServicePaneService_init() {
720 "longer need to call it.");
723 _clearNodeListeners:
function ServicePaneService__clearNodeListeners(aNode) {
724 if (aNode._eventListeners) {
725 delete aNode._eventListeners;
727 for each (let child
in aNode.childNodes) {
728 this._clearNodeListeners(child);
731 _shutdown:
function ServicePaneService__shutdown() {
732 this._clearNodeListeners(this.root);
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) {
746 dump(
"**********************************************\n");
748 dump(
"**********************************************\n");
752 this._modulesByContractId = {};
755 createNode:
function ServicePaneService_createNode() {
759 _registerNode:
function ServicePaneService__registerNode(
764 table = this._nodesById;
767 table = this._nodesByUrl;
769 case "contentPrefix":
770 table = this._nodesByContentPrefix;
779 table[
aValue].push(aNode);
782 _unregisterNode:
function ServicePaneService__unregisterNode(
787 table = this._nodesById;
790 table = this._nodesByUrl;
792 case "contentPrefix":
793 table = this._nodesByContentPrefix;
802 for (let
i = 0;
i < list.length;
i++)
803 if (list[
i] == aNode)
808 getNode:
function ServicePaneService_getNode(aId) {
809 if (aId in this._nodesById && this._nodesById[aId].length) {
810 if (this._nodesById[aId].length > 1) {
812 let caller = Components.stack.caller;
815 while (caller && !caller.filename)
816 caller = caller.caller;
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,
827 Ci.nsIScriptError.warningFlag,
828 "sbServicePaneService");
829 consoleService.logMessage(scriptError);
831 return this._nodesById[aId][0];
837 getNodeForURL:
function ServicePaneService_getNodeForURL(aUrl, aMatchLevel) {
842 var prefixMatch =
null;
843 if (!(typeof(aMatchLevel) ==
'undefined') &&
844 aMatchLevel ==
Ci.sbIServicePaneService.URL_MATCH_PREFIX)
846 for (let prefix in this._nodesByContentPrefix) {
847 if (aUrl.indexOf(prefix) == 0 &&
848 this._nodesByContentPrefix[prefix].length)
850 prefixMatch = this._nodesByContentPrefix[prefix][0];
859 if (aUrl in this._nodesByUrl && this._nodesByUrl[aUrl].length) {
861 return this._nodesByUrl[aUrl][0];
871 getNodesByAttributeNS:
function ServicePaneService_getNodesByAttributeNS(
873 function findNodeRecursive(aNode, aAttrName,
aValue, aResult) {
874 for each (let child
in aNode._childNodes) {
875 if (child.getAttribute(aAttrName) ==
aValue)
878 findNodeRecursive(child, aAttrName,
aValue, aResult);
883 let attrName = (aNamespace ==
null ?
aName : aNamespace +
":" +
aName);
884 findNodeRecursive(this._root, attrName,
aValue, result);
885 return ArrayConverter.nsIArray(result);
888 addListener:
function ServicePaneService_addListener(aListener) {
890 "and may be removed in future. Consider using " +
891 "root.addMutationListener() instead.");
895 removeListener:
function ServicePaneService_removeListener(aListener) {
897 "and may be removed in future. Consider using " +
898 "root.removeMutationListener() instead.");
901 let listeners = this.root._listeners.slice();
902 for each (let
listener in listeners)
904 this.root.removeMutationListener(listener);
907 fillContextMenu:
function ServicePaneService_fillContextMenu(
908 aNode, aContextMenu, aParentWindow) {
909 for each (let module
in this._modules) {
911 module.fillContextMenu(aNode, aContextMenu, aParentWindow);
913 Components.utils.reportError(ex);
918 fillNewItemMenu:
function ServicePaneService_fillNewItemMenu(
919 aNode, aContextMenu, aParentWindow) {
920 for each (let module
in this._modules) {
922 module.fillNewItemMenu(aNode, aContextMenu, aParentWindow);
924 Components.utils.reportError(ex);
929 onSelectionChanged:
function ServicePaneService_onSelectionChanged(
930 aNode, aContainer, aParentWindow) {
931 for each (let module
in this._modules) {
933 module.onSelectionChanged(aNode, aContainer, aParentWindow);
935 Components.utils.reportError(ex);
944 onBeforeRename:
function ServicePaneService_onBeforeRename(aNode) {
945 if (!aNode || !aNode.editable)
949 if (aNode.contractid && aNode.contractid in
this._modulesByContractId) {
950 let module = this._modulesByContractId[aNode.contractid];
951 module.onBeforeRename(aNode);
959 onRename:
function ServicePaneService_onRename(aNode, aNewName) {
960 if (!aNode || !aNode.editable)
964 if (aNode.contractid && aNode.contractid in
this._modulesByContractId) {
965 let module = this._modulesByContractId[aNode.contractid];
966 module.onRename(aNode, aNewName);
970 addNode:
function ServicePaneService_addNode(aId, aParent, aContainer) {
972 "may be removed in future. Consider using " +
973 "sbIServicePaneService.createNode() instead and " +
974 "adding it to the parent yourself.");
977 throw Ce(
"You need to supply a parent for addNode().");
979 if (aId && this.getNode(aId)) {
984 let
node = this.createNode();
985 aParent.appendChild(node);
986 node.id = (aId !==
null ? aId :
"_generatedNodeId" + ++this._nodeIndex);
988 node.setAttribute(
"isContainer",
"false");
993 removeNode:
function ServicePaneService_removeNode(aNode) {
995 "may be removed in future. Consider using " +
996 "sbIServicePaneNode.removeChild() instead.");
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.");
1003 aNode.parentNode.removeChild(aNode);
1006 sortNode:
function ServicePaneService_sortNode() {
1008 "longer need to call it.");
1011 save:
function ServicePaneService_save() {
1013 "longer need to call it.");
1016 canDrop:
function ServicePaneService_canDrop(
1017 aNode, aDragSession, aOrientation, aWindow) {
1022 LOG(
"canDrop(" + aNode.id +
")");
1025 if (aNode.contractid && aNode.contractid in
this._modulesByContractId) {
1026 let module = this._modulesByContractId[aNode.contractid];
1027 return module.canDrop(aNode, aDragSession, aOrientation, aWindow);
1032 onDrop:
function ServicePaneService_onDrop(
1033 aNode, aDragSession, aOrientation, aWindow) {
1038 LOG(
"onDrop(" + aNode.id +
")");
1041 if (aNode.contractid && aNode.contractid in
this._modulesByContractId) {
1042 let module = this._modulesByContractId[aNode.contractid];
1043 module.onDrop(aNode, aDragSession, aOrientation, aWindow);
1047 onDragGesture:
function ServicePaneService_onDragGesture(aNode, aDataTransfer) {
1052 LOG(
"onDragGesture(" + aNode.id +
")");
1055 Cu.reportError(
new Exception(
"Cannot drag a service pane node without ID"));
1059 let success =
false;
1062 if (aNode.dndDragTypes) {
1063 let
types = aNode.dndDragTypes.split(
',');
1064 for each (let type
in types) {
1065 aDataTransfer.setData(type, aNode.id);
1070 if (aNode.contractid && aNode.contractid in
this._modulesByContractId) {
1071 let module = this._modulesByContractId[aNode.contractid];
1072 if (module.onDragGesture(aNode, aDataTransfer)) {
1077 LOG(
" success=" + success);
1083 var
NSGetModule = XPCOMUtils.generateNSGetModule([ServicePaneService]);
static nsCOMPtr< nsIObserverService > observerService
sbOSDControlService prototype className
imageContainer appendChild(newImage)
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
handlersMenuPopup addEventListener("command", this, false)
function deprecationWarning(msg)
sbOSDControlService prototype QueryInterface
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
function ServicePaneNode(servicePane, comparisonFunction)
function MutationEventTranslator(aListener)
Class translating sbIServicePaneMutationListener calls into sbIServicePaneListener calls...
function SBStringBundle(aBundle)
imageContainer removeChild(oldImage)
menuItem setAttribute("handlerType","client")
return aWindow document documentElement getAttribute(aAttribute)||dimension
sbDeviceFirmwareAutoCheckForUpdate prototype classID
this removeEventListener("load", this.__SS_restore, true)
sbDeviceServicePane prototype nodeInserted
_getSelectedPageStyle s i
sbDeviceServicePane prototype nodeRemoved
sbDeviceFirmwareAutoCheckForUpdate prototype observe
The interface exposed by the service pane service.