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 const Ci = Components.interfaces;
6 const Cc = Components.classes;
8 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
10 const APPLICATION_CID = Components.ID("fe74cf80-aa2d-11db-abbd-0800200c9a66");
11 const APPLICATION_CONTRACTID = "@mozilla.org/fuel/application;1";
13 //=================================================
14 // Singleton that holds services and utilities
17 let bookmarks = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
18 getService(Ci.nsINavBookmarksService);
19 this.__defineGetter__("bookmarks", function() bookmarks);
20 return this.bookmarks;
23 get bookmarksObserver() {
24 let bookmarksObserver = new BookmarksObserver();
25 this.__defineGetter__("bookmarksObserver", function() bookmarksObserver);
26 return this.bookmarksObserver;
30 let annotations = Cc["@mozilla.org/browser/annotation-service;1"].
31 getService(Ci.nsIAnnotationService);
32 this.__defineGetter__("annotations", function() annotations);
33 return this.annotations;
37 let history = Cc["@mozilla.org/browser/nav-history-service;1"].
38 getService(Ci.nsINavHistoryService);
39 this.__defineGetter__("history", function() history);
43 get windowMediator() {
44 let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
45 getService(Ci.nsIWindowMediator);
46 this.__defineGetter__("windowMediator", function() windowMediator);
47 return this.windowMediator;
50 makeURI: function fuelutil_makeURI(aSpec) {
53 var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
54 return ios.newURI(aSpec, null, null);
57 free: function fuelutil_free() {
58 delete this.bookmarks;
59 delete this.bookmarksObserver;
60 delete this.annotations;
62 delete this.windowMediator;
67 //=================================================
68 // Window implementation
70 var fuelWindowMap = new WeakMap();
71 function getWindow(aWindow) {
72 let fuelWindow = fuelWindowMap.get(aWindow);
74 fuelWindow = new Window(aWindow);
75 fuelWindowMap.set(aWindow, fuelWindow);
80 // Don't call new Window() directly; use getWindow instead.
81 function Window(aWindow) {
82 this._window = aWindow;
83 this._events = new Events();
85 this._watch("TabOpen");
86 this._watch("TabMove");
87 this._watch("TabClose");
88 this._watch("TabSelect");
97 return this._window.gBrowser;
101 * Helper used to setup event handlers on the XBL element. Note that the events
102 * are actually dispatched to tabs, so we capture them.
104 _watch: function win_watch(aType) {
105 this._tabbrowser.tabContainer.addEventListener(aType, this,
106 /* useCapture = */ true);
109 handleEvent: function win_handleEvent(aEvent) {
110 this._events.dispatch(aEvent.type, getBrowserTab(this, aEvent.originalTarget.linkedBrowser));
115 var browsers = this._tabbrowser.browsers;
116 for (var i=0; i<browsers.length; i++)
117 tabs.push(getBrowserTab(this, browsers[i]));
122 return getBrowserTab(this, this._tabbrowser.selectedBrowser);
125 open: function win_open(aURI) {
126 return getBrowserTab(this, this._tabbrowser.addTab(aURI.spec).linkedBrowser);
129 QueryInterface: XPCOMUtils.generateQI([Ci.fuelIWindow])
132 //=================================================
133 // BrowserTab implementation
135 var fuelBrowserTabMap = new WeakMap();
136 function getBrowserTab(aFUELWindow, aBrowser) {
137 let fuelBrowserTab = fuelBrowserTabMap.get(aBrowser);
138 if (!fuelBrowserTab) {
139 fuelBrowserTab = new BrowserTab(aFUELWindow, aBrowser);
140 fuelBrowserTabMap.set(aBrowser, fuelBrowserTab);
143 // This tab may have moved to another window, so make sure its cached
144 // window is up-to-date.
145 fuelBrowserTab._window = aFUELWindow;
148 return fuelBrowserTab;
151 // Don't call new BrowserTab() directly; call getBrowserTab instead.
152 function BrowserTab(aFUELWindow, aBrowser) {
153 this._window = aFUELWindow;
154 this._browser = aBrowser;
155 this._events = new Events();
160 BrowserTab.prototype = {
162 return this._window._tabbrowser;
166 return this._browser.currentURI;
170 var tabs = this._tabbrowser.tabs;
171 for (var i=0; i<tabs.length; i++) {
172 if (tabs[i].linkedBrowser == this._browser)
187 return this._browser.contentDocument;
191 * Helper used to setup event handlers on the XBL element
193 _watch: function bt_watch(aType) {
194 this._browser.addEventListener(aType, this,
195 /* useCapture = */ true);
198 handleEvent: function bt_handleEvent(aEvent) {
199 if (aEvent.type == "load") {
200 if (!(aEvent.originalTarget instanceof Ci.nsIDOMDocument))
203 if (aEvent.originalTarget.defaultView instanceof Ci.nsIDOMWindow &&
204 aEvent.originalTarget.defaultView.frameElement)
207 this._events.dispatch(aEvent.type, this);
210 * Helper used to determine the index offset of the browsertab
212 _getTab: function bt_gettab() {
213 var tabs = this._tabbrowser.tabs;
214 return tabs[this.index] || null;
217 load: function bt_load(aURI) {
218 this._browser.loadURI(aURI.spec, null, null);
221 focus: function bt_focus() {
222 this._tabbrowser.selectedTab = this._getTab();
223 this._tabbrowser.focus();
226 close: function bt_close() {
227 this._tabbrowser.removeTab(this._getTab());
230 moveBefore: function bt_movebefore(aBefore) {
231 this._tabbrowser.moveTabTo(this._getTab(), aBefore.index);
234 moveToEnd: function bt_moveend() {
235 this._tabbrowser.moveTabTo(this._getTab(), this._tabbrowser.browsers.length);
238 QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBrowserTab])
242 //=================================================
243 // Annotations implementation
244 function Annotations(aId) {
248 Annotations.prototype = {
250 return Utilities.annotations.getItemAnnotationNames(this._id);
253 has: function ann_has(aName) {
254 return Utilities.annotations.itemHasAnnotation(this._id, aName);
257 get: function ann_get(aName) {
259 return Utilities.annotations.getItemAnnotation(this._id, aName);
263 set: function ann_set(aName, aValue, aExpiration) {
264 Utilities.annotations.setItemAnnotation(this._id, aName, aValue, 0, aExpiration);
267 remove: function ann_remove(aName) {
269 Utilities.annotations.removeItemAnnotation(this._id, aName);
272 QueryInterface: XPCOMUtils.generateQI([Ci.fuelIAnnotations])
276 //=================================================
277 // BookmarksObserver implementation (internal class)
279 // BookmarksObserver is a global singleton which watches the browser's
280 // bookmarks and sends you events when things change.
282 // You can register three different kinds of event listeners on
283 // BookmarksObserver, using addListener, addFolderListener, and
286 // - addListener(aId, aEvent, aListener) lets you listen to a specific
287 // bookmark. You can listen to the "change", "move", and "remove" events.
289 // - addFolderListener(aId, aEvent, aListener) lets you listen to a specific
290 // bookmark folder. You can listen to "addchild" and "removechild".
292 // - addRootListener(aEvent, aListener) lets you listen to the root bookmark
293 // node. This lets you hear "add", "remove", and "change" events on all
297 function BookmarksObserver() {
298 this._eventsDict = {};
299 this._folderEventsDict = {};
300 this._rootEvents = new Events();
301 Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true);
304 BookmarksObserver.prototype = {
305 onBeginUpdateBatch: function () {},
306 onEndUpdateBatch: function () {},
307 onItemVisited: function () {},
309 onItemAdded: function bo_onItemAdded(aId, aFolder, aIndex, aItemType, aURI) {
310 this._rootEvents.dispatch("add", aId);
311 this._dispatchToEvents("addchild", aId, this._folderEventsDict[aFolder]);
314 onItemRemoved: function bo_onItemRemoved(aId, aFolder, aIndex) {
315 this._rootEvents.dispatch("remove", aId);
316 this._dispatchToEvents("remove", aId, this._eventsDict[aId]);
317 this._dispatchToEvents("removechild", aId, this._folderEventsDict[aFolder]);
320 onItemChanged: function bo_onItemChanged(aId, aProperty, aIsAnnotationProperty, aValue) {
321 this._rootEvents.dispatch("change", aProperty);
322 this._dispatchToEvents("change", aProperty, this._eventsDict[aId]);
325 onItemMoved: function bo_onItemMoved(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
326 this._dispatchToEvents("move", aId, this._eventsDict[aId]);
329 _dispatchToEvents: function bo_dispatchToEvents(aEvent, aData, aEvents) {
331 aEvents.dispatch(aEvent, aData);
335 _addListenerToDict: function bo_addListenerToDict(aId, aEvent, aListener, aDict) {
336 var events = aDict[aId];
338 events = new Events();
341 events.addListener(aEvent, aListener);
344 _removeListenerFromDict: function bo_removeListenerFromDict(aId, aEvent, aListener, aDict) {
345 var events = aDict[aId];
349 events.removeListener(aEvent, aListener);
350 if (events._listeners.length == 0) {
355 addListener: function bo_addListener(aId, aEvent, aListener) {
356 this._addListenerToDict(aId, aEvent, aListener, this._eventsDict);
359 removeListener: function bo_removeListener(aId, aEvent, aListener) {
360 this._removeListenerFromDict(aId, aEvent, aListener, this._eventsDict);
363 addFolderListener: function addFolderListener(aId, aEvent, aListener) {
364 this._addListenerToDict(aId, aEvent, aListener, this._folderEventsDict);
367 removeFolderListener: function removeFolderListener(aId, aEvent, aListener) {
368 this._removeListenerFromDict(aId, aEvent, aListener, this._folderEventsDict);
371 addRootListener: function addRootListener(aEvent, aListener) {
372 this._rootEvents.addListener(aEvent, aListener);
375 removeRootListener: function removeRootListener(aEvent, aListener) {
376 this._rootEvents.removeListener(aEvent, aListener);
379 QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver,
380 Ci.nsISupportsWeakReference])
383 //=================================================
384 // Bookmark implementation
386 // Bookmark event listeners are stored in BookmarksObserver, not in the
387 // Bookmark objects themselves. Thus, you don't have to hold on to a Bookmark
388 // object in order for your event listener to stay valid, and Bookmark objects
389 // not kept alive by the extension can be GC'ed.
391 // A consequence of this is that if you have two different Bookmark objects x
392 // and y for the same bookmark (i.e., x != y but x.id == y.id), and you do
394 // x.addListener("foo", fun);
395 // y.removeListener("foo", fun);
397 // the second line will in fact remove the listener added in the first line.
400 function Bookmark(aId, aParent, aType) {
402 this._parent = aParent;
403 this._type = aType || "bookmark";
404 this._annotations = new Annotations(this._id);
406 // Our _events object forwards to bookmarksObserver.
409 addListener: function bookmarkevents_al(aEvent, aListener) {
410 Utilities.bookmarksObserver.addListener(self._id, aEvent, aListener);
412 removeListener: function bookmarkevents_rl(aEvent, aListener) {
413 Utilities.bookmarksObserver.removeListener(self._id, aEvent, aListener);
415 QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
418 // For our onItemMoved listener, which updates this._parent.
419 Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true);
422 Bookmark.prototype = {
428 return Utilities.bookmarks.getItemTitle(this._id);
432 Utilities.bookmarks.setItemTitle(this._id, aTitle);
436 return Utilities.bookmarks.getBookmarkURI(this._id);
440 return Utilities.bookmarks.changeBookmarkURI(this._id, aURI);
444 return this._annotations.get("bookmarkProperties/description");
447 set description(aDesc) {
448 this._annotations.set("bookmarkProperties/description", aDesc, Ci.nsIAnnotationService.EXPIRE_NEVER);
452 return Utilities.bookmarks.getKeywordForBookmark(this._id);
455 set keyword(aKeyword) {
456 Utilities.bookmarks.setKeywordForBookmark(this._id, aKeyword);
467 set parent(aFolder) {
468 Utilities.bookmarks.moveItem(this._id, aFolder.id, Utilities.bookmarks.DEFAULT_INDEX);
469 // this._parent is updated in onItemMoved
473 return this._annotations;
480 remove : function bm_remove() {
481 Utilities.bookmarks.removeItem(this._id);
484 onBeginUpdateBatch: function () {},
485 onEndUpdateBatch: function () {},
486 onItemAdded: function () {},
487 onItemVisited: function () {},
488 onItemRemoved: function () {},
489 onItemChanged: function () {},
491 onItemMoved: function(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
492 if (aId == this._id) {
493 this._parent = new BookmarkFolder(aNewParent, Utilities.bookmarks.getFolderIdForItem(aNewParent));
497 QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmark,
498 Ci.nsINavBookmarkObserver,
499 Ci.nsISupportsWeakReference])
503 //=================================================
504 // BookmarkFolder implementation
506 // As with Bookmark, events on BookmarkFolder are handled by the
507 // BookmarksObserver singleton.
510 function BookmarkFolder(aId, aParent) {
512 this._parent = aParent;
513 this._annotations = new Annotations(this._id);
515 // Our event listeners are handled by the BookmarksObserver singleton. This
516 // is a bit complicated because there are three different kinds of events we
517 // might want to listen to here:
519 // - If this._parent is null, we're the root bookmark folder, and all our
520 // listeners should be root listeners.
522 // - Otherwise, events ending with "child" (addchild, removechild) are
523 // handled by a folder listener.
525 // - Other events are handled by a vanilla bookmark listener.
529 addListener: function bmfevents_al(aEvent, aListener) {
531 if (/child$/.test(aEvent)) {
532 Utilities.bookmarksObserver.addFolderListener(self._id, aEvent, aListener);
535 Utilities.bookmarksObserver.addListener(self._id, aEvent, aListener);
539 Utilities.bookmarksObserver.addRootListener(aEvent, aListener);
542 removeListener: function bmfevents_rl(aEvent, aListener) {
544 if (/child$/.test(aEvent)) {
545 Utilities.bookmarksObserver.removeFolderListener(self._id, aEvent, aListener);
548 Utilities.bookmarksObserver.removeListener(self._id, aEvent, aListener);
552 Utilities.bookmarksObserver.removeRootListener(aEvent, aListener);
555 QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
558 // For our onItemMoved listener, which updates this._parent.
559 Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true);
562 BookmarkFolder.prototype = {
568 return Utilities.bookmarks.getItemTitle(this._id);
572 Utilities.bookmarks.setItemTitle(this._id, aTitle);
576 return this._annotations.get("bookmarkProperties/description");
579 set description(aDesc) {
580 this._annotations.set("bookmarkProperties/description", aDesc, Ci.nsIAnnotationService.EXPIRE_NEVER);
591 set parent(aFolder) {
592 Utilities.bookmarks.moveItem(this._id, aFolder.id, Utilities.bookmarks.DEFAULT_INDEX);
593 // this._parent is updated in onItemMoved
597 return this._annotations;
607 var options = Utilities.history.getNewQueryOptions();
608 var query = Utilities.history.getNewQuery();
609 query.setFolders([this._id], 1);
610 var result = Utilities.history.executeQuery(query, options);
611 var rootNode = result.root;
612 rootNode.containerOpen = true;
613 var cc = rootNode.childCount;
614 for (var i=0; i<cc; ++i) {
615 var node = rootNode.getChild(i);
616 if (node.type == node.RESULT_TYPE_FOLDER) {
617 var folder = new BookmarkFolder(node.itemId, this._id);
620 else if (node.type == node.RESULT_TYPE_SEPARATOR) {
621 var separator = new Bookmark(node.itemId, this._id, "separator");
622 items.push(separator);
625 var bookmark = new Bookmark(node.itemId, this._id, "bookmark");
626 items.push(bookmark);
629 rootNode.containerOpen = false;
634 addBookmark: function bmf_addbm(aTitle, aUri) {
635 var newBookmarkID = Utilities.bookmarks.insertBookmark(this._id, aUri, Utilities.bookmarks.DEFAULT_INDEX, aTitle);
636 var newBookmark = new Bookmark(newBookmarkID, this, "bookmark");
640 addSeparator: function bmf_addsep() {
641 var newBookmarkID = Utilities.bookmarks.insertSeparator(this._id, Utilities.bookmarks.DEFAULT_INDEX);
642 var newBookmark = new Bookmark(newBookmarkID, this, "separator");
646 addFolder: function bmf_addfolder(aTitle) {
647 var newFolderID = Utilities.bookmarks.createFolder(this._id, aTitle, Utilities.bookmarks.DEFAULT_INDEX);
648 var newFolder = new BookmarkFolder(newFolderID, this);
652 remove: function bmf_remove() {
653 Utilities.bookmarks.removeItem(this._id);
657 onBeginUpdateBatch: function () {},
658 onEndUpdateBatch : function () {},
659 onItemAdded : function () {},
660 onItemRemoved : function () {},
661 onItemChanged : function () {},
663 onItemMoved: function bf_onItemMoved(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
664 if (this._id == aId) {
665 this._parent = new BookmarkFolder(aNewParent, Utilities.bookmarks.getFolderIdForItem(aNewParent));
669 QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmarkFolder,
670 Ci.nsINavBookmarkObserver,
671 Ci.nsISupportsWeakReference])
674 //=================================================
675 // BookmarkRoots implementation
676 function BookmarkRoots() {
679 BookmarkRoots.prototype = {
682 this._menu = new BookmarkFolder(Utilities.bookmarks.bookmarksMenuFolder, null);
689 this._toolbar = new BookmarkFolder(Utilities.bookmarks.toolbarFolder, null);
691 return this._toolbar;
696 this._tags = new BookmarkFolder(Utilities.bookmarks.tagsFolder, null);
703 this._unfiled = new BookmarkFolder(Utilities.bookmarks.unfiledBookmarksFolder, null);
705 return this._unfiled;
708 QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmarkRoots])
712 //=================================================
713 // Factory - Treat Application as a singleton
714 // XXX This is required, because we're registered for the 'JavaScript global
715 // privileged property' category, whose handler always calls createInstance.
717 var gSingleton = null;
718 var ApplicationFactory = {
719 createInstance: function af_ci(aOuter, aIID) {
721 throw Components.results.NS_ERROR_NO_AGGREGATION;
723 if (gSingleton == null) {
724 gSingleton = new Application();
727 return gSingleton.QueryInterface(aIID);
732 #include ../../toolkit/components/exthelper/extApplication.js
734 //=================================================
735 // Application constructor
736 function Application() {
737 this.initToolkitHelpers();
740 //=================================================
741 // Application implementation
742 function ApplicationPrototype() {
743 // for nsIClassInfo + XPCOMUtils
744 this.classID = APPLICATION_CID;
746 // redefine the default factory for XPCOMUtils
747 this._xpcom_factory = ApplicationFactory;
750 this.QueryInterface = XPCOMUtils.generateQI([
754 Ci.nsISupportsWeakReference
758 this.classInfo = XPCOMUtils.generateCI({
759 classID: APPLICATION_CID,
760 contractID: APPLICATION_CONTRACTID,
766 flags: Ci.nsIClassInfo.SINGLETON
770 this.observe = function (aSubject, aTopic, aData) {
771 // Call the extApplication version of this function first
772 var superPrototype = Object.getPrototypeOf(Object.getPrototypeOf(this));
773 superPrototype.observe.call(this, aSubject, aTopic, aData);
774 if (aTopic == "xpcom-shutdown") {
775 this._obs.removeObserver(this, "xpcom-shutdown");
780 Object.defineProperty(this, "bookmarks", {
781 get: function bookmarks () {
782 let bookmarks = new BookmarkRoots();
783 Object.defineProperty(this, "bookmarks", { value: bookmarks });
784 return this.bookmarks;
790 Object.defineProperty(this, "windows", {
791 get: function windows() {
793 var browserEnum = Utilities.windowMediator.getEnumerator("navigator:browser");
795 while (browserEnum.hasMoreElements())
796 win.push(getWindow(browserEnum.getNext()));
804 Object.defineProperty(this, "activeWindow", {
805 get: () => getWindow(Utilities.windowMediator.getMostRecentWindow("navigator:browser")),
812 // set the proto, defined in extApplication.js
813 ApplicationPrototype.prototype = extApplication.prototype;
815 Application.prototype = new ApplicationPrototype();
817 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Application]);