toolkit/components/exthelper/extApplication.js

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
     6 Components.utils.import("resource://gre/modules/AddonManager.jsm");
     8 //=================================================
     9 // Console constructor
    10 function Console() {
    11   this._console = Components.classes["@mozilla.org/consoleservice;1"]
    12                             .getService(Ci.nsIConsoleService);
    13 }
    15 //=================================================
    16 // Console implementation
    17 Console.prototype = {
    18   log: function cs_log(aMsg) {
    19     this._console.logStringMessage(aMsg);
    20   },
    22   open: function cs_open() {
    23     var wMediator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
    24                               .getService(Ci.nsIWindowMediator);
    25     var console = wMediator.getMostRecentWindow("global:console");
    26     if (!console) {
    27       var wWatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
    28                              .getService(Ci.nsIWindowWatcher);
    29       wWatch.openWindow(null, "chrome://global/content/console.xul", "_blank",
    30                         "chrome,dialog=no,all", null);
    31     } else {
    32       // console was already open
    33       console.focus();
    34     }
    35   },
    37   QueryInterface: XPCOMUtils.generateQI([Ci.extIConsole])
    38 };
    41 //=================================================
    42 // EventItem constructor
    43 function EventItem(aType, aData) {
    44   this._type = aType;
    45   this._data = aData;
    46 }
    48 //=================================================
    49 // EventItem implementation
    50 EventItem.prototype = {
    51   _cancel: false,
    53   get type() {
    54     return this._type;
    55   },
    57   get data() {
    58     return this._data;
    59   },
    61   preventDefault: function ei_pd() {
    62     this._cancel = true;
    63   },
    65   QueryInterface: XPCOMUtils.generateQI([Ci.extIEventItem])
    66 };
    69 //=================================================
    70 // Events constructor
    71 function Events(notifier) {
    72   this._listeners = [];
    73   this._notifier = notifier;
    74 }
    76 //=================================================
    77 // Events implementation
    78 Events.prototype = {
    79   addListener: function evts_al(aEvent, aListener) {
    80     function hasFilter(element) {
    81       return element.event == aEvent && element.listener == aListener;
    82     }
    84     if (this._listeners.some(hasFilter))
    85       return;
    87     this._listeners.push({
    88       event: aEvent,
    89       listener: aListener
    90     });
    92     if (this._notifier) {
    93       this._notifier(aEvent, aListener);
    94     }
    95   },
    97   removeListener: function evts_rl(aEvent, aListener) {
    98     function hasFilter(element) {
    99       return (element.event != aEvent) || (element.listener != aListener);
   100     }
   102     this._listeners = this._listeners.filter(hasFilter);
   103   },
   105   dispatch: function evts_dispatch(aEvent, aEventItem) {
   106     var eventItem = new EventItem(aEvent, aEventItem);
   108     this._listeners.forEach(function(key){
   109       if (key.event == aEvent) {
   110         key.listener.handleEvent ?
   111           key.listener.handleEvent(eventItem) :
   112           key.listener(eventItem);
   113       }
   114     });
   116     return !eventItem._cancel;
   117   },
   119   QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
   120 };
   122 //=================================================
   123 // PreferenceObserver (internal class)
   124 //
   125 // PreferenceObserver is a global singleton which watches the browser's
   126 // preferences and sends you events when things change.
   128 function PreferenceObserver() {
   129   this._observersDict = {};
   130 }
   132 PreferenceObserver.prototype = {
   133   /**
   134    * Add a preference observer.
   135    *
   136    * @param aPrefs the nsIPrefBranch onto which we'll install our listener.
   137    * @param aDomain the domain our listener will watch (a string).
   138    * @param aEvent the event to listen to (you probably want "change").
   139    * @param aListener the function to call back when the event fires.  This
   140    *                  function will receive an EventData argument.
   141    */
   142   addListener: function po_al(aPrefs, aDomain, aEvent, aListener) {
   143     var root = aPrefs.root;
   144     if (!this._observersDict[root]) {
   145       this._observersDict[root] = {};
   146     }
   147     var observer = this._observersDict[root][aDomain];
   149     if (!observer) {
   150       observer = {
   151         events: new Events(),
   152         observe: function po_observer_obs(aSubject, aTopic, aData) {
   153           this.events.dispatch("change", aData);
   154         },
   155         QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
   156                                                Ci.nsISupportsWeakReference])
   157       };
   158       observer.prefBranch = aPrefs;
   159       observer.prefBranch.addObserver(aDomain, observer, /* ownsWeak = */ true);
   161       // Notice that the prefBranch keeps a weak reference to the observer;
   162       // it's this._observersDict which keeps the observer alive.
   163       this._observersDict[root][aDomain] = observer;
   164     }
   165     observer.events.addListener(aEvent, aListener);
   166   },
   168   /**
   169    * Remove a preference observer.
   170    *
   171    * This function's parameters are identical to addListener's.
   172    */
   173   removeListener: function po_rl(aPrefs, aDomain, aEvent, aListener) {
   174     var root = aPrefs.root;
   175     if (!this._observersDict[root] ||
   176         !this._observersDict[root][aDomain]) {
   177       return;
   178     }
   179     var observer = this._observersDict[root][aDomain];
   180     observer.events.removeListener(aEvent, aListener);
   182     if (observer.events._listeners.length == 0) {
   183       // nsIPrefBranch objects are not singletons -- we can have two
   184       // nsIPrefBranch'es for the same branch.  There's no guarantee that
   185       // aPrefs is the same object as observer.prefBranch, so we have to call
   186       // removeObserver on observer.prefBranch.
   187       observer.prefBranch.removeObserver(aDomain, observer);
   188       delete this._observersDict[root][aDomain];
   189       if (Object.keys(this._observersDict[root]).length == 0) {
   190         delete this._observersDict[root];
   191       }
   192     }
   193   }
   194 };
   196 //=================================================
   197 // PreferenceBranch constructor
   198 function PreferenceBranch(aBranch) {
   199   if (!aBranch)
   200     aBranch = "";
   202   this._root = aBranch;
   203   this._prefs = Components.classes["@mozilla.org/preferences-service;1"]
   204                           .getService(Ci.nsIPrefService)
   205                           .QueryInterface(Ci.nsIPrefBranch);
   207   if (aBranch)
   208     this._prefs = this._prefs.getBranch(aBranch);
   210   let prefs = this._prefs;
   211   this._events = {
   212     addListener: function pb_al(aEvent, aListener) {
   213       gPreferenceObserver.addListener(prefs, "", aEvent, aListener);
   214     },
   215     removeListener: function pb_rl(aEvent, aListener) {
   216       gPreferenceObserver.removeListener(prefs, "", aEvent, aListener);
   217     },
   218     QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
   219   };
   220 }
   222 //=================================================
   223 // PreferenceBranch implementation
   224 PreferenceBranch.prototype = {
   225   get root() {
   226     return this._root;
   227   },
   229   get all() {
   230     return this.find({});
   231   },
   233   get events() {
   234     return this._events;
   235   },
   237   // XXX: Disabled until we can figure out the wrapped object issues
   238   // name: "name" or /name/
   239   // path: "foo.bar." or "" or /fo+\.bar/
   240   // type: Boolean, Number, String (getPrefType)
   241   // locked: true, false (prefIsLocked)
   242   // modified: true, false (prefHasUserValue)
   243   find: function prefs_find(aOptions) {
   244     var retVal = [];
   245     var items = this._prefs.getChildList("");
   247     for (var i = 0; i < items.length; i++) {
   248       retVal.push(new Preference(items[i], this));
   249     }
   251     return retVal;
   252   },
   254   has: function prefs_has(aName) {
   255     return (this._prefs.getPrefType(aName) != Ci.nsIPrefBranch.PREF_INVALID);
   256   },
   258   get: function prefs_get(aName) {
   259     return this.has(aName) ? new Preference(aName, this) : null;
   260   },
   262   getValue: function prefs_gv(aName, aValue) {
   263     var type = this._prefs.getPrefType(aName);
   265     switch (type) {
   266       case Ci.nsIPrefBranch.PREF_STRING:
   267         aValue = this._prefs.getComplexValue(aName, Ci.nsISupportsString).data;
   268         break;
   269       case Ci.nsIPrefBranch.PREF_BOOL:
   270         aValue = this._prefs.getBoolPref(aName);
   271         break;
   272       case Ci.nsIPrefBranch.PREF_INT:
   273         aValue = this._prefs.getIntPref(aName);
   274         break;
   275     }
   277     return aValue;
   278   },
   280   setValue: function prefs_sv(aName, aValue) {
   281     var type = aValue != null ? aValue.constructor.name : "";
   283     switch (type) {
   284       case "String":
   285         var str = Components.classes["@mozilla.org/supports-string;1"]
   286                             .createInstance(Ci.nsISupportsString);
   287         str.data = aValue;
   288         this._prefs.setComplexValue(aName, Ci.nsISupportsString, str);
   289         break;
   290       case "Boolean":
   291         this._prefs.setBoolPref(aName, aValue);
   292         break;
   293       case "Number":
   294         this._prefs.setIntPref(aName, aValue);
   295         break;
   296       default:
   297         throw("Unknown preference value specified.");
   298     }
   299   },
   301   reset: function prefs_reset() {
   302     this._prefs.resetBranch("");
   303   },
   305   QueryInterface: XPCOMUtils.generateQI([Ci.extIPreferenceBranch])
   306 };
   309 //=================================================
   310 // Preference constructor
   311 function Preference(aName, aBranch) {
   312   this._name = aName;
   313   this._branch = aBranch;
   315   var self = this;
   316   this._events = {
   317     addListener: function pref_al(aEvent, aListener) {
   318       gPreferenceObserver.addListener(self._branch._prefs, self._name, aEvent, aListener);
   319     },
   320     removeListener: function pref_rl(aEvent, aListener) {
   321       gPreferenceObserver.removeListener(self._branch._prefs, self._name, aEvent, aListener);
   322     },
   323     QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
   324   };
   325 }
   327 //=================================================
   328 // Preference implementation
   329 Preference.prototype = {
   330   get name() {
   331     return this._name;
   332   },
   334   get type() {
   335     var value = "";
   336     var type = this.branch._prefs.getPrefType(this._name);
   338     switch (type) {
   339       case Ci.nsIPrefBranch.PREF_STRING:
   340         value = "String";
   341         break;
   342       case Ci.nsIPrefBranch.PREF_BOOL:
   343         value = "Boolean";
   344         break;
   345       case Ci.nsIPrefBranch.PREF_INT:
   346         value = "Number";
   347         break;
   348     }
   350     return value;
   351   },
   353   get value() {
   354     return this.branch.getValue(this._name, null);
   355   },
   357   set value(aValue) {
   358     return this.branch.setValue(this._name, aValue);
   359   },
   361   get locked() {
   362     return this.branch._prefs.prefIsLocked(this.name);
   363   },
   365   set locked(aValue) {
   366     this.branch._prefs[ aValue ? "lockPref" : "unlockPref" ](this.name);
   367   },
   369   get modified() {
   370     return this.branch._prefs.prefHasUserValue(this.name);
   371   },
   373   get branch() {
   374     return this._branch;
   375   },
   377   get events() {
   378     return this._events;
   379   },
   381   reset: function pref_reset() {
   382     this.branch._prefs.clearUserPref(this.name);
   383   },
   385   QueryInterface: XPCOMUtils.generateQI([Ci.extIPreference])
   386 };
   389 //=================================================
   390 // SessionStorage constructor
   391 function SessionStorage() {
   392   this._storage = {};
   393   this._events = new Events();
   394 }
   396 //=================================================
   397 // SessionStorage implementation
   398 SessionStorage.prototype = {
   399   get events() {
   400     return this._events;
   401   },
   403   has: function ss_has(aName) {
   404     return this._storage.hasOwnProperty(aName);
   405   },
   407   set: function ss_set(aName, aValue) {
   408     this._storage[aName] = aValue;
   409     this._events.dispatch("change", aName);
   410   },
   412   get: function ss_get(aName, aDefaultValue) {
   413     return this.has(aName) ? this._storage[aName] : aDefaultValue;
   414   },
   416   QueryInterface : XPCOMUtils.generateQI([Ci.extISessionStorage])
   417 };
   419 //=================================================
   420 // ExtensionObserver constructor (internal class)
   421 //
   422 // ExtensionObserver is a global singleton which watches the browser's
   423 // extensions and sends you events when things change.
   425 function ExtensionObserver() {
   426   this._eventsDict = {};
   428   AddonManager.addAddonListener(this);
   429   AddonManager.addInstallListener(this);
   430 }
   432 //=================================================
   433 // ExtensionObserver implementation (internal class)
   434 ExtensionObserver.prototype = {
   435   onDisabling: function eo_onDisabling(addon, needsRestart) {
   436     this._dispatchEvent(addon.id, "disable");
   437   },
   439   onEnabling: function eo_onEnabling(addon, needsRestart) {
   440     this._dispatchEvent(addon.id, "enable");
   441   },
   443   onUninstalling: function eo_onUninstalling(addon, needsRestart) {
   444     this._dispatchEvent(addon.id, "uninstall");
   445   },
   447   onOperationCancelled: function eo_onOperationCancelled(addon) {
   448     this._dispatchEvent(addon.id, "cancel");
   449   },
   451   onInstallEnded: function eo_onInstallEnded(install, addon) {
   452     this._dispatchEvent(addon.id, "upgrade");
   453   },
   455   addListener: function eo_al(aId, aEvent, aListener) {
   456     var events = this._eventsDict[aId];
   457     if (!events) {
   458       events = new Events();
   459       this._eventsDict[aId] = events;
   460     }
   461     events.addListener(aEvent, aListener);
   462   },
   464   removeListener: function eo_rl(aId, aEvent, aListener) {
   465     var events = this._eventsDict[aId];
   466     if (!events) {
   467       return;
   468     }
   469     events.removeListener(aEvent, aListener);
   470     if (events._listeners.length == 0) {
   471       delete this._eventsDict[aId];
   472     }
   473   },
   475   _dispatchEvent: function eo_dispatchEvent(aId, aEvent) {
   476     var events = this._eventsDict[aId];
   477     if (events) {
   478       events.dispatch(aEvent, aId);
   479     }
   480   }
   481 };
   483 //=================================================
   484 // Extension constructor
   485 function Extension(aItem) {
   486   this._item = aItem;
   487   this._firstRun = false;
   488   this._prefs = new PreferenceBranch("extensions." + this.id + ".");
   489   this._storage = new SessionStorage();
   491   let id = this.id;
   492   this._events = {
   493     addListener: function ext_events_al(aEvent, aListener) {
   494       gExtensionObserver.addListener(id, aEvent, aListener);
   495     },
   496     removeListener: function ext_events_rl(aEvent, aListener) {
   497       gExtensionObserver.addListener(id, aEvent, aListener);
   498     },
   499     QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
   500   };
   502   var installPref = "install-event-fired";
   503   if (!this._prefs.has(installPref)) {
   504     this._prefs.setValue(installPref, true);
   505     this._firstRun = true;
   506   }
   507 }
   509 //=================================================
   510 // Extension implementation
   511 Extension.prototype = {
   512   get id() {
   513     return this._item.id;
   514   },
   516   get name() {
   517     return this._item.name;
   518   },
   520   get enabled() {
   521     return this._item.isActive;
   522   },
   524   get version() {
   525     return this._item.version;
   526   },
   528   get firstRun() {
   529     return this._firstRun;
   530   },
   532   get storage() {
   533     return this._storage;
   534   },
   536   get prefs() {
   537     return this._prefs;
   538   },
   540   get events() {
   541     return this._events;
   542   },
   544   QueryInterface: XPCOMUtils.generateQI([Ci.extIExtension])
   545 };
   548 //=================================================
   549 // Extensions constructor
   550 function Extensions(addons) {
   551   this._cache = {};
   553   addons.forEach(function (addon) {
   554     this._cache[addon.id] = new Extension(addon);
   555   }, this);
   556 }
   558 //=================================================
   559 // Extensions implementation
   560 Extensions.prototype = {
   561   get all() {
   562     return this.find({});
   563   },
   565   // XXX: Disabled until we can figure out the wrapped object issues
   566   // id: "some@id" or /id/
   567   // name: "name" or /name/
   568   // version: "1.0.1"
   569   // minVersion: "1.0"
   570   // maxVersion: "2.0"
   571   find: function exts_find(aOptions) {
   572     return [e for each (e in this._cache)];
   573   },
   575   has: function exts_has(aId) {
   576     return aId in this._cache;
   577   },
   579   get: function exts_get(aId) {
   580     return this.has(aId) ? this._cache[aId] : null;
   581   },
   583   QueryInterface: XPCOMUtils.generateQI([Ci.extIExtensions])
   584 };
   586 //=================================================
   587 // Application globals
   589 gExtensionObserver = new ExtensionObserver();
   590 gPreferenceObserver = new PreferenceObserver();
   592 //=================================================
   593 // extApplication constructor
   594 function extApplication() {
   595 }
   597 //=================================================
   598 // extApplication implementation
   599 extApplication.prototype = {
   600   initToolkitHelpers: function extApp_initToolkitHelpers() {
   601     XPCOMUtils.defineLazyServiceGetter(this, "_info",
   602                                        "@mozilla.org/xre/app-info;1",
   603                                        "nsIXULAppInfo");
   605     this._obs = Cc["@mozilla.org/observer-service;1"].
   606                 getService(Ci.nsIObserverService);
   607     this._obs.addObserver(this, "xpcom-shutdown", /* ownsWeak = */ true);
   608     this._registered = {"unload": true};
   609   },
   611   classInfo: XPCOMUtils.generateCI({interfaces: [Ci.extIApplication,
   612                                                  Ci.nsIObserver],
   613                                     flags: Ci.nsIClassInfo.SINGLETON}),
   615   // extIApplication
   616   get id() {
   617     return this._info.ID;
   618   },
   620   get name() {
   621     return this._info.name;
   622   },
   624   get version() {
   625     return this._info.version;
   626   },
   628   // for nsIObserver
   629   observe: function app_observe(aSubject, aTopic, aData) {
   630     if (aTopic == "app-startup") {
   631       this.events.dispatch("load", "application");
   632     }
   633     else if (aTopic == "final-ui-startup") {
   634       this.events.dispatch("ready", "application");
   635     }
   636     else if (aTopic == "quit-application-requested") {
   637       // we can stop the quit by checking the return value
   638       if (this.events.dispatch("quit", "application") == false)
   639         aSubject.data = true;
   640     }
   641     else if (aTopic == "xpcom-shutdown") {
   642       this.events.dispatch("unload", "application");
   643       gExtensionObserver = null;
   644       gPreferenceObserver = null;
   645     }
   646   },
   648   get console() {
   649     let console = new Console();
   650     this.__defineGetter__("console", function () console);
   651     return this.console;
   652   },
   654   get storage() {
   655     let storage = new SessionStorage();
   656     this.__defineGetter__("storage", function () storage);
   657     return this.storage;
   658   },
   660   get prefs() {
   661     let prefs = new PreferenceBranch("");
   662     this.__defineGetter__("prefs", function () prefs);
   663     return this.prefs;
   664   },
   666   getExtensions: function(callback) {
   667     AddonManager.getAddonsByTypes(["extension"], function (addons) {
   668       callback.callback(new Extensions(addons));
   669     });
   670   },
   672   get events() {
   674     // This ensures that FUEL only registers for notifications as needed
   675     // by callers. Note that the unload (xpcom-shutdown) event is listened
   676     // for by default, as it's needed for cleanup purposes.
   677     var self = this;
   678     function registerCheck(aEvent) {
   679       var rmap = { "load": "app-startup",
   680                    "ready": "final-ui-startup",
   681                    "quit": "quit-application-requested"};
   682       if (!(aEvent in rmap) || aEvent in self._registered)
   683         return;
   685       self._obs.addObserver(self, rmap[aEvent], /* ownsWeak = */ true);
   686       self._registered[aEvent] = true;
   687     }
   689     let events = new Events(registerCheck);
   690     this.__defineGetter__("events", function () events);
   691     return this.events;
   692   },
   694   // helper method for correct quitting/restarting
   695   _quitWithFlags: function app__quitWithFlags(aFlags) {
   696     let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
   697                                .createInstance(Components.interfaces.nsISupportsPRBool);
   698     let quitType = aFlags & Components.interfaces.nsIAppStartup.eRestart ? "restart" : null;
   699     this._obs.notifyObservers(cancelQuit, "quit-application-requested", quitType);
   700     if (cancelQuit.data)
   701       return false; // somebody canceled our quit request
   703     let appStartup = Components.classes['@mozilla.org/toolkit/app-startup;1']
   704                                .getService(Components.interfaces.nsIAppStartup);
   705     appStartup.quit(aFlags);
   706     return true;
   707   },
   709   quit: function app_quit() {
   710     return this._quitWithFlags(Components.interfaces.nsIAppStartup.eAttemptQuit);
   711   },
   713   restart: function app_restart() {
   714     return this._quitWithFlags(Components.interfaces.nsIAppStartup.eAttemptQuit |
   715                                Components.interfaces.nsIAppStartup.eRestart);
   716   },
   718   QueryInterface: XPCOMUtils.generateQI([Ci.extIApplication, Ci.nsISupportsWeakReference])
   719 };

mercurial