Fri, 16 Jan 2015 18:13:44 +0100
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 };