diff options
Diffstat (limited to 'components/fuel/fuelApplication.js')
-rw-r--r-- | components/fuel/fuelApplication.js | 822 |
1 files changed, 822 insertions, 0 deletions
diff --git a/components/fuel/fuelApplication.js b/components/fuel/fuelApplication.js new file mode 100644 index 0000000..bc3a091 --- /dev/null +++ b/components/fuel/fuelApplication.js @@ -0,0 +1,822 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const Ci = Components.interfaces; +const Cc = Components.classes; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Deprecated", + "resource://gre/modules/Deprecated.jsm"); + +const APPLICATION_CID = Components.ID("fe74cf80-aa2d-11db-abbd-0800200c9a66"); +const APPLICATION_CONTRACTID = "@mozilla.org/fuel/application;1"; + +//================================================= +// Singleton that holds services and utilities +var Utilities = { + get bookmarks() { + let bookmarks = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. + getService(Ci.nsINavBookmarksService); + this.__defineGetter__("bookmarks", function() bookmarks); + return this.bookmarks; + }, + + get bookmarksObserver() { + let bookmarksObserver = new BookmarksObserver(); + this.__defineGetter__("bookmarksObserver", function() bookmarksObserver); + return this.bookmarksObserver; + }, + + get annotations() { + let annotations = Cc["@mozilla.org/browser/annotation-service;1"]. + getService(Ci.nsIAnnotationService); + this.__defineGetter__("annotations", function() annotations); + return this.annotations; + }, + + get history() { + let history = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService); + this.__defineGetter__("history", function() history); + return this.history; + }, + + get windowMediator() { + let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator); + this.__defineGetter__("windowMediator", function() windowMediator); + return this.windowMediator; + }, + + makeURI: function fuelutil_makeURI(aSpec) { + if (!aSpec) + return null; + var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + return ios.newURI(aSpec, null, null); + }, + + free: function fuelutil_free() { + delete this.bookmarks; + delete this.bookmarksObserver; + delete this.annotations; + delete this.history; + delete this.windowMediator; + } +}; + + +//================================================= +// Window implementation + +var fuelWindowMap = new WeakMap(); +function getWindow(aWindow) { + let fuelWindow = fuelWindowMap.get(aWindow); + if (!fuelWindow) { + fuelWindow = new Window(aWindow); + fuelWindowMap.set(aWindow, fuelWindow); + } + return fuelWindow; +} + +// Don't call new Window() directly; use getWindow instead. +function Window(aWindow) { + this._window = aWindow; + this._events = new Events(); + + this._watch("TabOpen"); + this._watch("TabMove"); + this._watch("TabClose"); + this._watch("TabSelect"); +} + +Window.prototype = { + get events() { + return this._events; + }, + + get _tabbrowser() { + return this._window.getBrowser(); + }, + + /* + * Helper used to setup event handlers on the XBL element. Note that the events + * are actually dispatched to tabs, so we capture them. + */ + _watch: function win_watch(aType) { + this._tabbrowser.tabContainer.addEventListener(aType, this, + /* useCapture = */ true); + }, + + handleEvent: function win_handleEvent(aEvent) { + this._events.dispatch(aEvent.type, getBrowserTab(this, aEvent.originalTarget.linkedBrowser)); + }, + + get tabs() { + var tabs = []; + var browsers = this._tabbrowser.browsers; + for (var i=0; i<browsers.length; i++) + tabs.push(getBrowserTab(this, browsers[i])); + return tabs; + }, + + get activeTab() { + return getBrowserTab(this, this._tabbrowser.selectedBrowser); + }, + + open: function win_open(aURI) { + return getBrowserTab(this, this._tabbrowser.addTab(aURI.spec).linkedBrowser); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.fuelIWindow]) +}; + +//================================================= +// BrowserTab implementation + +var fuelBrowserTabMap = new WeakMap(); +function getBrowserTab(aFUELWindow, aBrowser) { + let fuelBrowserTab = fuelBrowserTabMap.get(aBrowser); + if (!fuelBrowserTab) { + fuelBrowserTab = new BrowserTab(aFUELWindow, aBrowser); + fuelBrowserTabMap.set(aBrowser, fuelBrowserTab); + } + else { + // This tab may have moved to another window, so make sure its cached + // window is up-to-date. + fuelBrowserTab._window = aFUELWindow; + } + + return fuelBrowserTab; +} + +// Don't call new BrowserTab() directly; call getBrowserTab instead. +function BrowserTab(aFUELWindow, aBrowser) { + this._window = aFUELWindow; + this._browser = aBrowser; + this._events = new Events(); + + this._watch("load"); +} + +BrowserTab.prototype = { + get _tabbrowser() { + return this._window._tabbrowser; + }, + + get uri() { + return this._browser.currentURI; + }, + + get index() { + var tabs = this._tabbrowser.tabs; + for (var i=0; i<tabs.length; i++) { + if (tabs[i].linkedBrowser == this._browser) + return i; + } + return -1; + }, + + get events() { + return this._events; + }, + + get window() { + return this._window; + }, + + get document() { + return this._browser.contentDocument; + }, + + /* + * Helper used to setup event handlers on the XBL element + */ + _watch: function bt_watch(aType) { + this._browser.addEventListener(aType, this, + /* useCapture = */ true); + }, + + handleEvent: function bt_handleEvent(aEvent) { + if (aEvent.type == "load") { + if (!(aEvent.originalTarget instanceof Ci.nsIDOMDocument)) + return; + + if (aEvent.originalTarget.defaultView instanceof Ci.nsIDOMWindow && + aEvent.originalTarget.defaultView.frameElement) + return; + } + this._events.dispatch(aEvent.type, this); + }, + /* + * Helper used to determine the index offset of the browsertab + */ + _getTab: function bt_gettab() { + var tabs = this._tabbrowser.tabs; + return tabs[this.index] || null; + }, + + load: function bt_load(aURI) { + this._browser.loadURI(aURI.spec, null, null); + }, + + focus: function bt_focus() { + this._tabbrowser.selectedTab = this._getTab(); + this._tabbrowser.focus(); + }, + + close: function bt_close() { + this._tabbrowser.removeTab(this._getTab()); + }, + + moveBefore: function bt_movebefore(aBefore) { + this._tabbrowser.moveTabTo(this._getTab(), aBefore.index); + }, + + moveToEnd: function bt_moveend() { + this._tabbrowser.moveTabTo(this._getTab(), this._tabbrowser.browsers.length); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBrowserTab]) +}; + + +//================================================= +// Annotations implementation +function Annotations(aId) { + this._id = aId; +} + +Annotations.prototype = { + get names() { + return Utilities.annotations.getItemAnnotationNames(this._id); + }, + + has: function ann_has(aName) { + return Utilities.annotations.itemHasAnnotation(this._id, aName); + }, + + get: function ann_get(aName) { + if (this.has(aName)) + return Utilities.annotations.getItemAnnotation(this._id, aName); + return null; + }, + + set: function ann_set(aName, aValue, aExpiration) { + Utilities.annotations.setItemAnnotation(this._id, aName, aValue, 0, aExpiration); + }, + + remove: function ann_remove(aName) { + if (aName) + Utilities.annotations.removeItemAnnotation(this._id, aName); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.fuelIAnnotations]) +}; + + +//================================================= +// BookmarksObserver implementation (internal class) +// +// BookmarksObserver is a global singleton which watches the browser's +// bookmarks and sends you events when things change. +// +// You can register three different kinds of event listeners on +// BookmarksObserver, using addListener, addFolderListener, and +// addRootlistener. +// +// - addListener(aId, aEvent, aListener) lets you listen to a specific +// bookmark. You can listen to the "change", "move", and "remove" events. +// +// - addFolderListener(aId, aEvent, aListener) lets you listen to a specific +// bookmark folder. You can listen to "addchild" and "removechild". +// +// - addRootListener(aEvent, aListener) lets you listen to the root bookmark +// node. This lets you hear "add", "remove", and "change" events on all +// bookmarks. +// + +function BookmarksObserver() { + this._eventsDict = {}; + this._folderEventsDict = {}; + this._rootEvents = new Events(); + Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true); +} + +BookmarksObserver.prototype = { + onBeginUpdateBatch: function () {}, + onEndUpdateBatch: function () {}, + onItemVisited: function () {}, + + onItemAdded: function bo_onItemAdded(aId, aFolder, aIndex, aItemType, aURI) { + this._rootEvents.dispatch("add", aId); + this._dispatchToEvents("addchild", aId, this._folderEventsDict[aFolder]); + }, + + onItemRemoved: function bo_onItemRemoved(aId, aFolder, aIndex) { + this._rootEvents.dispatch("remove", aId); + this._dispatchToEvents("remove", aId, this._eventsDict[aId]); + this._dispatchToEvents("removechild", aId, this._folderEventsDict[aFolder]); + }, + + onItemChanged: function bo_onItemChanged(aId, aProperty, aIsAnnotationProperty, aValue) { + this._rootEvents.dispatch("change", aProperty); + this._dispatchToEvents("change", aProperty, this._eventsDict[aId]); + }, + + onItemMoved: function bo_onItemMoved(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) { + this._dispatchToEvents("move", aId, this._eventsDict[aId]); + }, + + _dispatchToEvents: function bo_dispatchToEvents(aEvent, aData, aEvents) { + if (aEvents) { + aEvents.dispatch(aEvent, aData); + } + }, + + _addListenerToDict: function bo_addListenerToDict(aId, aEvent, aListener, aDict) { + var events = aDict[aId]; + if (!events) { + events = new Events(); + aDict[aId] = events; + } + events.addListener(aEvent, aListener); + }, + + _removeListenerFromDict: function bo_removeListenerFromDict(aId, aEvent, aListener, aDict) { + var events = aDict[aId]; + if (!events) { + return; + } + events.removeListener(aEvent, aListener); + if (events._listeners.length == 0) { + delete aDict[aId]; + } + }, + + addListener: function bo_addListener(aId, aEvent, aListener) { + this._addListenerToDict(aId, aEvent, aListener, this._eventsDict); + }, + + removeListener: function bo_removeListener(aId, aEvent, aListener) { + this._removeListenerFromDict(aId, aEvent, aListener, this._eventsDict); + }, + + addFolderListener: function addFolderListener(aId, aEvent, aListener) { + this._addListenerToDict(aId, aEvent, aListener, this._folderEventsDict); + }, + + removeFolderListener: function removeFolderListener(aId, aEvent, aListener) { + this._removeListenerFromDict(aId, aEvent, aListener, this._folderEventsDict); + }, + + addRootListener: function addRootListener(aEvent, aListener) { + this._rootEvents.addListener(aEvent, aListener); + }, + + removeRootListener: function removeRootListener(aEvent, aListener) { + this._rootEvents.removeListener(aEvent, aListener); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarksObserver, + Ci.nsISupportsWeakReference]) +}; + +//================================================= +// Bookmark implementation +// +// Bookmark event listeners are stored in BookmarksObserver, not in the +// Bookmark objects themselves. Thus, you don't have to hold on to a Bookmark +// object in order for your event listener to stay valid, and Bookmark objects +// not kept alive by the extension can be GC'ed. +// +// A consequence of this is that if you have two different Bookmark objects x +// and y for the same bookmark (i.e., x != y but x.id == y.id), and you do +// +// x.addListener("foo", fun); +// y.removeListener("foo", fun); +// +// the second line will in fact remove the listener added in the first line. +// + +function Bookmark(aId, aParent, aType) { + this._id = aId; + this._parent = aParent; + this._type = aType || "bookmark"; + this._annotations = new Annotations(this._id); + + // Our _events object forwards to bookmarksObserver. + var self = this; + this._events = { + addListener: function bookmarkevents_al(aEvent, aListener) { + Utilities.bookmarksObserver.addListener(self._id, aEvent, aListener); + }, + removeListener: function bookmarkevents_rl(aEvent, aListener) { + Utilities.bookmarksObserver.removeListener(self._id, aEvent, aListener); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents]) + }; + + // For our onItemMoved listener, which updates this._parent. + Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true); +} + +Bookmark.prototype = { + get id() { + return this._id; + }, + + get title() { + return Utilities.bookmarks.getItemTitle(this._id); + }, + + set title(aTitle) { + Utilities.bookmarks.setItemTitle(this._id, aTitle); + }, + + get uri() { + return Utilities.bookmarks.getBookmarkURI(this._id); + }, + + set uri(aURI) { + return Utilities.bookmarks.changeBookmarkURI(this._id, aURI); + }, + + get description() { + return this._annotations.get("bookmarkProperties/description"); + }, + + set description(aDesc) { + this._annotations.set("bookmarkProperties/description", aDesc, Ci.nsIAnnotationService.EXPIRE_NEVER); + }, + + get keyword() { + return Utilities.bookmarks.getKeywordForBookmark(this._id); + }, + + set keyword(aKeyword) { + Utilities.bookmarks.setKeywordForBookmark(this._id, aKeyword); + }, + + get type() { + return this._type; + }, + + get parent() { + return this._parent; + }, + + set parent(aFolder) { + Utilities.bookmarks.moveItem(this._id, aFolder.id, Utilities.bookmarks.DEFAULT_INDEX); + // this._parent is updated in onItemMoved + }, + + get annotations() { + return this._annotations; + }, + + get events() { + return this._events; + }, + + remove : function bm_remove() { + Utilities.bookmarks.removeItem(this._id); + }, + + onBeginUpdateBatch: function () {}, + onEndUpdateBatch: function () {}, + onItemAdded: function () {}, + onItemVisited: function () {}, + onItemRemoved: function () {}, + onItemChanged: function () {}, + + onItemMoved: function(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) { + if (aId == this._id) { + this._parent = new BookmarkFolder(aNewParent, Utilities.bookmarks.getFolderIdForItem(aNewParent)); + } + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmark, + Ci.nsINavBookmarksObserver, + Ci.nsISupportsWeakReference]) +}; + + +//================================================= +// BookmarkFolder implementation +// +// As with Bookmark, events on BookmarkFolder are handled by the +// BookmarksObserver singleton. +// + +function BookmarkFolder(aId, aParent) { + this._id = aId; + this._parent = aParent; + this._annotations = new Annotations(this._id); + + // Our event listeners are handled by the BookmarksObserver singleton. This + // is a bit complicated because there are three different kinds of events we + // might want to listen to here: + // + // - If this._parent is null, we're the root bookmark folder, and all our + // listeners should be root listeners. + // + // - Otherwise, events ending with "child" (addchild, removechild) are + // handled by a folder listener. + // + // - Other events are handled by a vanilla bookmark listener. + + var self = this; + this._events = { + addListener: function bmfevents_al(aEvent, aListener) { + if (self._parent) { + if (/child$/.test(aEvent)) { + Utilities.bookmarksObserver.addFolderListener(self._id, aEvent, aListener); + } + else { + Utilities.bookmarksObserver.addListener(self._id, aEvent, aListener); + } + } + else { + Utilities.bookmarksObserver.addRootListener(aEvent, aListener); + } + }, + removeListener: function bmfevents_rl(aEvent, aListener) { + if (self._parent) { + if (/child$/.test(aEvent)) { + Utilities.bookmarksObserver.removeFolderListener(self._id, aEvent, aListener); + } + else { + Utilities.bookmarksObserver.removeListener(self._id, aEvent, aListener); + } + } + else { + Utilities.bookmarksObserver.removeRootListener(aEvent, aListener); + } + }, + QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents]) + }; + + // For our onItemMoved listener, which updates this._parent. + Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true); +} + +BookmarkFolder.prototype = { + get id() { + return this._id; + }, + + get title() { + return Utilities.bookmarks.getItemTitle(this._id); + }, + + set title(aTitle) { + Utilities.bookmarks.setItemTitle(this._id, aTitle); + }, + + get description() { + return this._annotations.get("bookmarkProperties/description"); + }, + + set description(aDesc) { + this._annotations.set("bookmarkProperties/description", aDesc, Ci.nsIAnnotationService.EXPIRE_NEVER); + }, + + get type() { + return "folder"; + }, + + get parent() { + return this._parent; + }, + + set parent(aFolder) { + Utilities.bookmarks.moveItem(this._id, aFolder.id, Utilities.bookmarks.DEFAULT_INDEX); + // this._parent is updated in onItemMoved + }, + + get annotations() { + return this._annotations; + }, + + get events() { + return this._events; + }, + + get children() { + var items = []; + + var options = Utilities.history.getNewQueryOptions(); + var query = Utilities.history.getNewQuery(); + query.setFolders([this._id], 1); + var result = Utilities.history.executeQuery(query, options); + var rootNode = result.root; + rootNode.containerOpen = true; + var cc = rootNode.childCount; + for (var i=0; i<cc; ++i) { + var node = rootNode.getChild(i); + if (node.type == node.RESULT_TYPE_FOLDER) { + var folder = new BookmarkFolder(node.itemId, this._id); + items.push(folder); + } + else if (node.type == node.RESULT_TYPE_SEPARATOR) { + var separator = new Bookmark(node.itemId, this._id, "separator"); + items.push(separator); + } + else { + var bookmark = new Bookmark(node.itemId, this._id, "bookmark"); + items.push(bookmark); + } + } + rootNode.containerOpen = false; + + return items; + }, + + addBookmark: function bmf_addbm(aTitle, aUri) { + var newBookmarkID = Utilities.bookmarks.insertBookmark(this._id, aUri, Utilities.bookmarks.DEFAULT_INDEX, aTitle); + var newBookmark = new Bookmark(newBookmarkID, this, "bookmark"); + return newBookmark; + }, + + addSeparator: function bmf_addsep() { + var newBookmarkID = Utilities.bookmarks.insertSeparator(this._id, Utilities.bookmarks.DEFAULT_INDEX); + var newBookmark = new Bookmark(newBookmarkID, this, "separator"); + return newBookmark; + }, + + addFolder: function bmf_addfolder(aTitle) { + var newFolderID = Utilities.bookmarks.createFolder(this._id, aTitle, Utilities.bookmarks.DEFAULT_INDEX); + var newFolder = new BookmarkFolder(newFolderID, this); + return newFolder; + }, + + remove: function bmf_remove() { + Utilities.bookmarks.removeItem(this._id); + }, + + // observer + onBeginUpdateBatch: function () {}, + onEndUpdateBatch : function () {}, + onItemAdded : function () {}, + onItemRemoved : function () {}, + onItemChanged : function () {}, + + onItemMoved: function bf_onItemMoved(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) { + if (this._id == aId) { + this._parent = new BookmarkFolder(aNewParent, Utilities.bookmarks.getFolderIdForItem(aNewParent)); + } + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmarkFolder, + Ci.nsINavBookmarksObserver, + Ci.nsISupportsWeakReference]) +}; + +//================================================= +// BookmarkRoots implementation +function BookmarkRoots() { +} + +BookmarkRoots.prototype = { + get menu() { + if (!this._menu) + this._menu = new BookmarkFolder(Utilities.bookmarks.bookmarksMenuFolder, null); + + return this._menu; + }, + + get toolbar() { + if (!this._toolbar) + this._toolbar = new BookmarkFolder(Utilities.bookmarks.toolbarFolder, null); + + return this._toolbar; + }, + + get tags() { + if (!this._tags) + this._tags = new BookmarkFolder(Utilities.bookmarks.tagsFolder, null); + + return this._tags; + }, + + get unfiled() { + if (!this._unfiled) + this._unfiled = new BookmarkFolder(Utilities.bookmarks.unfiledBookmarksFolder, null); + + return this._unfiled; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmarkRoots]) +}; + + +//================================================= +// Factory - Treat Application as a singleton +// XXX This is required, because we're registered for the 'JavaScript global +// privileged property' category, whose handler always calls createInstance. +// See bug 386535. +var gSingleton = null; +var ApplicationFactory = { + createInstance: function af_ci(aOuter, aIID) { + if (aOuter != null) + throw Components.results.NS_ERROR_NO_AGGREGATION; + + if (gSingleton == null) { + gSingleton = new Application(); + } + + return gSingleton.QueryInterface(aIID); + } +}; + + +#include ../../../../toolkit/components/exthelper/extApplication.js + +//================================================= +// Application constructor +function Application() { + Deprecated.warning("FUEL is deprecated, you should use the standard Toolkit API instead.", + "https://github.com/MoonchildProductions/UXP/issues/1083"); + this.initToolkitHelpers(); +} + +//================================================= +// Application implementation +function ApplicationPrototype() { + // for nsIClassInfo + XPCOMUtils + this.classID = APPLICATION_CID; + + // redefine the default factory for XPCOMUtils + this._xpcom_factory = ApplicationFactory; + + // for nsISupports + this.QueryInterface = XPCOMUtils.generateQI([ + Ci.fuelIApplication, + Ci.extIApplication, + Ci.nsIObserver, + Ci.nsISupportsWeakReference + ]); + + // for nsIClassInfo + this.classInfo = XPCOMUtils.generateCI({ + classID: APPLICATION_CID, + contractID: APPLICATION_CONTRACTID, + interfaces: [ + Ci.fuelIApplication, + Ci.extIApplication, + Ci.nsIObserver + ], + flags: Ci.nsIClassInfo.SINGLETON + }); + + // for nsIObserver + this.observe = function (aSubject, aTopic, aData) { + // Call the extApplication version of this function first + var superPrototype = Object.getPrototypeOf(Object.getPrototypeOf(this)); + superPrototype.observe.call(this, aSubject, aTopic, aData); + if (aTopic == "xpcom-shutdown") { + this._obs.removeObserver(this, "xpcom-shutdown"); + Utilities.free(); + } + }; + + Object.defineProperty(this, "bookmarks", { + get: function bookmarks () { + let bookmarks = new BookmarkRoots(); + Object.defineProperty(this, "bookmarks", { value: bookmarks }); + return this.bookmarks; + }, + enumerable: true, + configurable: true + }); + + Object.defineProperty(this, "windows", { + get: function windows() { + var win = []; + var browserEnum = Utilities.windowMediator.getEnumerator("navigator:browser"); + + while (browserEnum.hasMoreElements()) + win.push(getWindow(browserEnum.getNext())); + + return win; + }, + enumerable: true, + configurable: true + }); + + Object.defineProperty(this, "activeWindow", { + get: () => getWindow(Utilities.windowMediator.getMostRecentWindow("navigator:browser")), + enumerable: true, + configurable: true + }); + +}; + +// set the proto, defined in extApplication.js +ApplicationPrototype.prototype = extApplication.prototype; + +Application.prototype = new ApplicationPrototype(); + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Application]); + |