browser/fuel/src/fuelApplication.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/fuel/src/fuelApplication.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,818 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +const Ci = Components.interfaces;
     1.9 +const Cc = Components.classes;
    1.10 +
    1.11 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    1.12 +
    1.13 +const APPLICATION_CID = Components.ID("fe74cf80-aa2d-11db-abbd-0800200c9a66");
    1.14 +const APPLICATION_CONTRACTID = "@mozilla.org/fuel/application;1";
    1.15 +
    1.16 +//=================================================
    1.17 +// Singleton that holds services and utilities
    1.18 +var Utilities = {
    1.19 +  get bookmarks() {
    1.20 +    let bookmarks = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
    1.21 +                    getService(Ci.nsINavBookmarksService);
    1.22 +    this.__defineGetter__("bookmarks", function() bookmarks);
    1.23 +    return this.bookmarks;
    1.24 +  },
    1.25 +
    1.26 +  get bookmarksObserver() {
    1.27 +    let bookmarksObserver = new BookmarksObserver();
    1.28 +    this.__defineGetter__("bookmarksObserver", function() bookmarksObserver);
    1.29 +    return this.bookmarksObserver;
    1.30 +  },
    1.31 +
    1.32 +  get annotations() {
    1.33 +    let annotations = Cc["@mozilla.org/browser/annotation-service;1"].
    1.34 +                      getService(Ci.nsIAnnotationService);
    1.35 +    this.__defineGetter__("annotations", function() annotations);
    1.36 +    return this.annotations;
    1.37 +  },
    1.38 +
    1.39 +  get history() {
    1.40 +    let history = Cc["@mozilla.org/browser/nav-history-service;1"].
    1.41 +                  getService(Ci.nsINavHistoryService);
    1.42 +    this.__defineGetter__("history", function() history);
    1.43 +    return this.history;
    1.44 +  },
    1.45 +
    1.46 +  get windowMediator() {
    1.47 +    let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
    1.48 +                         getService(Ci.nsIWindowMediator);
    1.49 +    this.__defineGetter__("windowMediator", function() windowMediator);
    1.50 +    return this.windowMediator;
    1.51 +  },
    1.52 +
    1.53 +  makeURI: function fuelutil_makeURI(aSpec) {
    1.54 +    if (!aSpec)
    1.55 +      return null;
    1.56 +    var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
    1.57 +    return ios.newURI(aSpec, null, null);
    1.58 +  },
    1.59 +
    1.60 +  free: function fuelutil_free() {
    1.61 +    delete this.bookmarks;
    1.62 +    delete this.bookmarksObserver;
    1.63 +    delete this.annotations;
    1.64 +    delete this.history;
    1.65 +    delete this.windowMediator;
    1.66 +  }
    1.67 +};
    1.68 +
    1.69 +
    1.70 +//=================================================
    1.71 +// Window implementation
    1.72 +
    1.73 +var fuelWindowMap = new WeakMap();
    1.74 +function getWindow(aWindow) {
    1.75 +  let fuelWindow = fuelWindowMap.get(aWindow);
    1.76 +  if (!fuelWindow) {
    1.77 +    fuelWindow = new Window(aWindow);
    1.78 +    fuelWindowMap.set(aWindow, fuelWindow);
    1.79 +  }
    1.80 +  return fuelWindow;
    1.81 +}
    1.82 +
    1.83 +// Don't call new Window() directly; use getWindow instead.
    1.84 +function Window(aWindow) {
    1.85 +  this._window = aWindow;
    1.86 +  this._events = new Events();
    1.87 +
    1.88 +  this._watch("TabOpen");
    1.89 +  this._watch("TabMove");
    1.90 +  this._watch("TabClose");
    1.91 +  this._watch("TabSelect");
    1.92 +}
    1.93 +
    1.94 +Window.prototype = {
    1.95 +  get events() {
    1.96 +    return this._events;
    1.97 +  },
    1.98 +
    1.99 +  get _tabbrowser() {
   1.100 +    return this._window.getBrowser();
   1.101 +  },
   1.102 +
   1.103 +  /*
   1.104 +   * Helper used to setup event handlers on the XBL element. Note that the events
   1.105 +   * are actually dispatched to tabs, so we capture them.
   1.106 +   */
   1.107 +  _watch: function win_watch(aType) {
   1.108 +    this._tabbrowser.tabContainer.addEventListener(aType, this,
   1.109 +                                                   /* useCapture = */ true);
   1.110 +  },
   1.111 +
   1.112 +  handleEvent: function win_handleEvent(aEvent) {
   1.113 +    this._events.dispatch(aEvent.type, getBrowserTab(this, aEvent.originalTarget.linkedBrowser));
   1.114 +  },
   1.115 +
   1.116 +  get tabs() {
   1.117 +    var tabs = [];
   1.118 +    var browsers = this._tabbrowser.browsers;
   1.119 +    for (var i=0; i<browsers.length; i++)
   1.120 +      tabs.push(getBrowserTab(this, browsers[i]));
   1.121 +    return tabs;
   1.122 +  },
   1.123 +
   1.124 +  get activeTab() {
   1.125 +    return getBrowserTab(this, this._tabbrowser.selectedBrowser);
   1.126 +  },
   1.127 +
   1.128 +  open: function win_open(aURI) {
   1.129 +    return getBrowserTab(this, this._tabbrowser.addTab(aURI.spec).linkedBrowser);
   1.130 +  },
   1.131 +
   1.132 +  QueryInterface: XPCOMUtils.generateQI([Ci.fuelIWindow])
   1.133 +};
   1.134 +
   1.135 +//=================================================
   1.136 +// BrowserTab implementation
   1.137 +
   1.138 +var fuelBrowserTabMap = new WeakMap();
   1.139 +function getBrowserTab(aFUELWindow, aBrowser) {
   1.140 +  let fuelBrowserTab = fuelBrowserTabMap.get(aBrowser);
   1.141 +  if (!fuelBrowserTab) {
   1.142 +    fuelBrowserTab = new BrowserTab(aFUELWindow, aBrowser);
   1.143 +    fuelBrowserTabMap.set(aBrowser, fuelBrowserTab);
   1.144 +  }
   1.145 +  else {
   1.146 +    // This tab may have moved to another window, so make sure its cached
   1.147 +    // window is up-to-date.
   1.148 +    fuelBrowserTab._window = aFUELWindow;
   1.149 +  }
   1.150 +
   1.151 +  return fuelBrowserTab;
   1.152 +}
   1.153 +
   1.154 +// Don't call new BrowserTab() directly; call getBrowserTab instead.
   1.155 +function BrowserTab(aFUELWindow, aBrowser) {
   1.156 +  this._window = aFUELWindow;
   1.157 +  this._browser = aBrowser;
   1.158 +  this._events = new Events();
   1.159 +
   1.160 +  this._watch("load");
   1.161 +}
   1.162 +
   1.163 +BrowserTab.prototype = {
   1.164 +  get _tabbrowser() {
   1.165 +    return this._window._tabbrowser;
   1.166 +  },
   1.167 +
   1.168 +  get uri() {
   1.169 +    return this._browser.currentURI;
   1.170 +  },
   1.171 +
   1.172 +  get index() {
   1.173 +    var tabs = this._tabbrowser.tabs;
   1.174 +    for (var i=0; i<tabs.length; i++) {
   1.175 +      if (tabs[i].linkedBrowser == this._browser)
   1.176 +        return i;
   1.177 +    }
   1.178 +    return -1;
   1.179 +  },
   1.180 +
   1.181 +  get events() {
   1.182 +    return this._events;
   1.183 +  },
   1.184 +
   1.185 +  get window() {
   1.186 +    return this._window;
   1.187 +  },
   1.188 +
   1.189 +  get document() {
   1.190 +    return this._browser.contentDocument;
   1.191 +  },
   1.192 +
   1.193 +  /*
   1.194 +   * Helper used to setup event handlers on the XBL element
   1.195 +   */
   1.196 +  _watch: function bt_watch(aType) {
   1.197 +    this._browser.addEventListener(aType, this,
   1.198 +                                   /* useCapture = */ true);
   1.199 +  },
   1.200 +
   1.201 +  handleEvent: function bt_handleEvent(aEvent) {
   1.202 +    if (aEvent.type == "load") {
   1.203 +      if (!(aEvent.originalTarget instanceof Ci.nsIDOMDocument))
   1.204 +        return;
   1.205 +
   1.206 +      if (aEvent.originalTarget.defaultView instanceof Ci.nsIDOMWindow &&
   1.207 +          aEvent.originalTarget.defaultView.frameElement)
   1.208 +        return;
   1.209 +    }
   1.210 +    this._events.dispatch(aEvent.type, this);
   1.211 +  },
   1.212 +  /*
   1.213 +   * Helper used to determine the index offset of the browsertab
   1.214 +   */
   1.215 +  _getTab: function bt_gettab() {
   1.216 +    var tabs = this._tabbrowser.tabs;
   1.217 +    return tabs[this.index] || null;
   1.218 +  },
   1.219 +
   1.220 +  load: function bt_load(aURI) {
   1.221 +    this._browser.loadURI(aURI.spec, null, null);
   1.222 +  },
   1.223 +
   1.224 +  focus: function bt_focus() {
   1.225 +    this._tabbrowser.selectedTab = this._getTab();
   1.226 +    this._tabbrowser.focus();
   1.227 +  },
   1.228 +
   1.229 +  close: function bt_close() {
   1.230 +    this._tabbrowser.removeTab(this._getTab());
   1.231 +  },
   1.232 +
   1.233 +  moveBefore: function bt_movebefore(aBefore) {
   1.234 +    this._tabbrowser.moveTabTo(this._getTab(), aBefore.index);
   1.235 +  },
   1.236 +
   1.237 +  moveToEnd: function bt_moveend() {
   1.238 +    this._tabbrowser.moveTabTo(this._getTab(), this._tabbrowser.browsers.length);
   1.239 +  },
   1.240 +
   1.241 +  QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBrowserTab])
   1.242 +};
   1.243 +
   1.244 +
   1.245 +//=================================================
   1.246 +// Annotations implementation
   1.247 +function Annotations(aId) {
   1.248 +  this._id = aId;
   1.249 +}
   1.250 +
   1.251 +Annotations.prototype = {
   1.252 +  get names() {
   1.253 +    return Utilities.annotations.getItemAnnotationNames(this._id);
   1.254 +  },
   1.255 +
   1.256 +  has: function ann_has(aName) {
   1.257 +    return Utilities.annotations.itemHasAnnotation(this._id, aName);
   1.258 +  },
   1.259 +
   1.260 +  get: function ann_get(aName) {
   1.261 +    if (this.has(aName))
   1.262 +      return Utilities.annotations.getItemAnnotation(this._id, aName);
   1.263 +    return null;
   1.264 +  },
   1.265 +
   1.266 +  set: function ann_set(aName, aValue, aExpiration) {
   1.267 +    Utilities.annotations.setItemAnnotation(this._id, aName, aValue, 0, aExpiration);
   1.268 +  },
   1.269 +
   1.270 +  remove: function ann_remove(aName) {
   1.271 +    if (aName)
   1.272 +      Utilities.annotations.removeItemAnnotation(this._id, aName);
   1.273 +  },
   1.274 +
   1.275 +  QueryInterface: XPCOMUtils.generateQI([Ci.fuelIAnnotations])
   1.276 +};
   1.277 +
   1.278 +
   1.279 +//=================================================
   1.280 +// BookmarksObserver implementation (internal class)
   1.281 +//
   1.282 +// BookmarksObserver is a global singleton which watches the browser's
   1.283 +// bookmarks and sends you events when things change.
   1.284 +//
   1.285 +// You can register three different kinds of event listeners on
   1.286 +// BookmarksObserver, using addListener, addFolderListener, and
   1.287 +// addRootlistener.
   1.288 +//
   1.289 +//  - addListener(aId, aEvent, aListener) lets you listen to a specific
   1.290 +//    bookmark.  You can listen to the "change", "move", and "remove" events.
   1.291 +//
   1.292 +//  - addFolderListener(aId, aEvent, aListener) lets you listen to a specific
   1.293 +//    bookmark folder.  You can listen to "addchild" and "removechild".
   1.294 +//
   1.295 +//  - addRootListener(aEvent, aListener) lets you listen to the root bookmark
   1.296 +//    node.  This lets you hear "add", "remove", and "change" events on all
   1.297 +//    bookmarks.
   1.298 +//
   1.299 +
   1.300 +function BookmarksObserver() {
   1.301 +  this._eventsDict = {};
   1.302 +  this._folderEventsDict = {};
   1.303 +  this._rootEvents = new Events();
   1.304 +  Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true);
   1.305 +}
   1.306 +
   1.307 +BookmarksObserver.prototype = {
   1.308 +  onBeginUpdateBatch: function () {},
   1.309 +  onEndUpdateBatch: function () {},
   1.310 +  onItemVisited: function () {},
   1.311 +
   1.312 +  onItemAdded: function bo_onItemAdded(aId, aFolder, aIndex, aItemType, aURI) {
   1.313 +    this._rootEvents.dispatch("add", aId);
   1.314 +    this._dispatchToEvents("addchild", aId, this._folderEventsDict[aFolder]);
   1.315 +  },
   1.316 +
   1.317 +  onItemRemoved: function bo_onItemRemoved(aId, aFolder, aIndex) {
   1.318 +    this._rootEvents.dispatch("remove", aId);
   1.319 +    this._dispatchToEvents("remove", aId, this._eventsDict[aId]);
   1.320 +    this._dispatchToEvents("removechild", aId, this._folderEventsDict[aFolder]);
   1.321 +  },
   1.322 +
   1.323 +  onItemChanged: function bo_onItemChanged(aId, aProperty, aIsAnnotationProperty, aValue) {
   1.324 +    this._rootEvents.dispatch("change", aProperty);
   1.325 +    this._dispatchToEvents("change", aProperty, this._eventsDict[aId]);
   1.326 +  },
   1.327 +
   1.328 +  onItemMoved: function bo_onItemMoved(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
   1.329 +    this._dispatchToEvents("move", aId, this._eventsDict[aId]);
   1.330 +  },
   1.331 +
   1.332 +  _dispatchToEvents: function bo_dispatchToEvents(aEvent, aData, aEvents) {
   1.333 +    if (aEvents) {
   1.334 +      aEvents.dispatch(aEvent, aData);
   1.335 +    }
   1.336 +  },
   1.337 +
   1.338 +  _addListenerToDict: function bo_addListenerToDict(aId, aEvent, aListener, aDict) {
   1.339 +    var events = aDict[aId];
   1.340 +    if (!events) {
   1.341 +      events = new Events();
   1.342 +      aDict[aId] = events;
   1.343 +    }
   1.344 +    events.addListener(aEvent, aListener);
   1.345 +  },
   1.346 +
   1.347 +  _removeListenerFromDict: function bo_removeListenerFromDict(aId, aEvent, aListener, aDict) {
   1.348 +    var events = aDict[aId];
   1.349 +    if (!events) {
   1.350 +      return;
   1.351 +    }
   1.352 +    events.removeListener(aEvent, aListener);
   1.353 +    if (events._listeners.length == 0) {
   1.354 +      delete aDict[aId];
   1.355 +    }
   1.356 +  },
   1.357 +
   1.358 +  addListener: function bo_addListener(aId, aEvent, aListener) {
   1.359 +    this._addListenerToDict(aId, aEvent, aListener, this._eventsDict);
   1.360 +  },
   1.361 +
   1.362 +  removeListener: function bo_removeListener(aId, aEvent, aListener) {
   1.363 +    this._removeListenerFromDict(aId, aEvent, aListener, this._eventsDict);
   1.364 +  },
   1.365 +
   1.366 +  addFolderListener: function addFolderListener(aId, aEvent, aListener) {
   1.367 +    this._addListenerToDict(aId, aEvent, aListener, this._folderEventsDict);
   1.368 +  },
   1.369 +
   1.370 +  removeFolderListener: function removeFolderListener(aId, aEvent, aListener) {
   1.371 +    this._removeListenerFromDict(aId, aEvent, aListener, this._folderEventsDict);
   1.372 +  },
   1.373 +
   1.374 +  addRootListener: function addRootListener(aEvent, aListener) {
   1.375 +    this._rootEvents.addListener(aEvent, aListener);
   1.376 +  },
   1.377 +
   1.378 +  removeRootListener: function removeRootListener(aEvent, aListener) {
   1.379 +    this._rootEvents.removeListener(aEvent, aListener);
   1.380 +  },
   1.381 +
   1.382 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarksObserver,
   1.383 +                                         Ci.nsISupportsWeakReference])
   1.384 +};
   1.385 +
   1.386 +//=================================================
   1.387 +// Bookmark implementation
   1.388 +//
   1.389 +// Bookmark event listeners are stored in BookmarksObserver, not in the
   1.390 +// Bookmark objects themselves.  Thus, you don't have to hold on to a Bookmark
   1.391 +// object in order for your event listener to stay valid, and Bookmark objects
   1.392 +// not kept alive by the extension can be GC'ed.
   1.393 +//
   1.394 +// A consequence of this is that if you have two different Bookmark objects x
   1.395 +// and y for the same bookmark (i.e., x != y but x.id == y.id), and you do
   1.396 +//
   1.397 +//   x.addListener("foo", fun);
   1.398 +//   y.removeListener("foo", fun);
   1.399 +//
   1.400 +// the second line will in fact remove the listener added in the first line.
   1.401 +//
   1.402 +
   1.403 +function Bookmark(aId, aParent, aType) {
   1.404 +  this._id = aId;
   1.405 +  this._parent = aParent;
   1.406 +  this._type = aType || "bookmark";
   1.407 +  this._annotations = new Annotations(this._id);
   1.408 +
   1.409 +  // Our _events object forwards to bookmarksObserver.
   1.410 +  var self = this;
   1.411 +  this._events = {
   1.412 +    addListener: function bookmarkevents_al(aEvent, aListener) {
   1.413 +      Utilities.bookmarksObserver.addListener(self._id, aEvent, aListener);
   1.414 +    },
   1.415 +    removeListener: function bookmarkevents_rl(aEvent, aListener) {
   1.416 +      Utilities.bookmarksObserver.removeListener(self._id, aEvent, aListener);
   1.417 +    },
   1.418 +    QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
   1.419 +  };
   1.420 +
   1.421 +  // For our onItemMoved listener, which updates this._parent.
   1.422 +  Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true);
   1.423 +}
   1.424 +
   1.425 +Bookmark.prototype = {
   1.426 +  get id() {
   1.427 +    return this._id;
   1.428 +  },
   1.429 +
   1.430 +  get title() {
   1.431 +    return Utilities.bookmarks.getItemTitle(this._id);
   1.432 +  },
   1.433 +
   1.434 +  set title(aTitle) {
   1.435 +    Utilities.bookmarks.setItemTitle(this._id, aTitle);
   1.436 +  },
   1.437 +
   1.438 +  get uri() {
   1.439 +    return Utilities.bookmarks.getBookmarkURI(this._id);
   1.440 +  },
   1.441 +
   1.442 +  set uri(aURI) {
   1.443 +    return Utilities.bookmarks.changeBookmarkURI(this._id, aURI);
   1.444 +  },
   1.445 +
   1.446 +  get description() {
   1.447 +    return this._annotations.get("bookmarkProperties/description");
   1.448 +  },
   1.449 +
   1.450 +  set description(aDesc) {
   1.451 +    this._annotations.set("bookmarkProperties/description", aDesc, Ci.nsIAnnotationService.EXPIRE_NEVER);
   1.452 +  },
   1.453 +
   1.454 +  get keyword() {
   1.455 +    return Utilities.bookmarks.getKeywordForBookmark(this._id);
   1.456 +  },
   1.457 +
   1.458 +  set keyword(aKeyword) {
   1.459 +    Utilities.bookmarks.setKeywordForBookmark(this._id, aKeyword);
   1.460 +  },
   1.461 +
   1.462 +  get type() {
   1.463 +    return this._type;
   1.464 +  },
   1.465 +
   1.466 +  get parent() {
   1.467 +    return this._parent;
   1.468 +  },
   1.469 +
   1.470 +  set parent(aFolder) {
   1.471 +    Utilities.bookmarks.moveItem(this._id, aFolder.id, Utilities.bookmarks.DEFAULT_INDEX);
   1.472 +    // this._parent is updated in onItemMoved
   1.473 +  },
   1.474 +
   1.475 +  get annotations() {
   1.476 +    return this._annotations;
   1.477 +  },
   1.478 +
   1.479 +  get events() {
   1.480 +    return this._events;
   1.481 +  },
   1.482 +
   1.483 +  remove : function bm_remove() {
   1.484 +    Utilities.bookmarks.removeItem(this._id);
   1.485 +  },
   1.486 +
   1.487 +  onBeginUpdateBatch: function () {},
   1.488 +  onEndUpdateBatch: function () {},
   1.489 +  onItemAdded: function () {},
   1.490 +  onItemVisited: function () {},
   1.491 +  onItemRemoved: function () {},
   1.492 +  onItemChanged: function () {},
   1.493 +
   1.494 +  onItemMoved: function(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
   1.495 +    if (aId == this._id) {
   1.496 +      this._parent = new BookmarkFolder(aNewParent, Utilities.bookmarks.getFolderIdForItem(aNewParent));
   1.497 +    }
   1.498 +  },
   1.499 +
   1.500 +  QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmark,
   1.501 +                                         Ci.nsINavBookmarksObserver,
   1.502 +                                         Ci.nsISupportsWeakReference])
   1.503 +};
   1.504 +
   1.505 +
   1.506 +//=================================================
   1.507 +// BookmarkFolder implementation
   1.508 +//
   1.509 +// As with Bookmark, events on BookmarkFolder are handled by the
   1.510 +// BookmarksObserver singleton.
   1.511 +//
   1.512 +
   1.513 +function BookmarkFolder(aId, aParent) {
   1.514 +  this._id = aId;
   1.515 +  this._parent = aParent;
   1.516 +  this._annotations = new Annotations(this._id);
   1.517 +
   1.518 +  // Our event listeners are handled by the BookmarksObserver singleton.  This
   1.519 +  // is a bit complicated because there are three different kinds of events we
   1.520 +  // might want to listen to here:
   1.521 +  //
   1.522 +  //  - If this._parent is null, we're the root bookmark folder, and all our
   1.523 +  //    listeners should be root listeners.
   1.524 +  //
   1.525 +  //  - Otherwise, events ending with "child" (addchild, removechild) are
   1.526 +  //    handled by a folder listener.
   1.527 +  //
   1.528 +  //  - Other events are handled by a vanilla bookmark listener.
   1.529 +
   1.530 +  var self = this;
   1.531 +  this._events = {
   1.532 +    addListener: function bmfevents_al(aEvent, aListener) {
   1.533 +      if (self._parent) {
   1.534 +        if (/child$/.test(aEvent)) {
   1.535 +          Utilities.bookmarksObserver.addFolderListener(self._id, aEvent, aListener);
   1.536 +        }
   1.537 +        else {
   1.538 +          Utilities.bookmarksObserver.addListener(self._id, aEvent, aListener);
   1.539 +        }
   1.540 +      }
   1.541 +      else {
   1.542 +        Utilities.bookmarksObserver.addRootListener(aEvent, aListener);
   1.543 +      }
   1.544 +    },
   1.545 +    removeListener: function bmfevents_rl(aEvent, aListener) {
   1.546 +      if (self._parent) {
   1.547 +        if (/child$/.test(aEvent)) {
   1.548 +          Utilities.bookmarksObserver.removeFolderListener(self._id, aEvent, aListener);
   1.549 +        }
   1.550 +        else {
   1.551 +          Utilities.bookmarksObserver.removeListener(self._id, aEvent, aListener);
   1.552 +        }
   1.553 +      }
   1.554 +      else {
   1.555 +        Utilities.bookmarksObserver.removeRootListener(aEvent, aListener);
   1.556 +      }
   1.557 +    },
   1.558 +    QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
   1.559 +  };
   1.560 +
   1.561 +  // For our onItemMoved listener, which updates this._parent.
   1.562 +  Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true);
   1.563 +}
   1.564 +
   1.565 +BookmarkFolder.prototype = {
   1.566 +  get id() {
   1.567 +    return this._id;
   1.568 +  },
   1.569 +
   1.570 +  get title() {
   1.571 +    return Utilities.bookmarks.getItemTitle(this._id);
   1.572 +  },
   1.573 +
   1.574 +  set title(aTitle) {
   1.575 +    Utilities.bookmarks.setItemTitle(this._id, aTitle);
   1.576 +  },
   1.577 +
   1.578 +  get description() {
   1.579 +    return this._annotations.get("bookmarkProperties/description");
   1.580 +  },
   1.581 +
   1.582 +  set description(aDesc) {
   1.583 +    this._annotations.set("bookmarkProperties/description", aDesc, Ci.nsIAnnotationService.EXPIRE_NEVER);
   1.584 +  },
   1.585 +
   1.586 +  get type() {
   1.587 +    return "folder";
   1.588 +  },
   1.589 +
   1.590 +  get parent() {
   1.591 +    return this._parent;
   1.592 +  },
   1.593 +
   1.594 +  set parent(aFolder) {
   1.595 +    Utilities.bookmarks.moveItem(this._id, aFolder.id, Utilities.bookmarks.DEFAULT_INDEX);
   1.596 +    // this._parent is updated in onItemMoved
   1.597 +  },
   1.598 +
   1.599 +  get annotations() {
   1.600 +    return this._annotations;
   1.601 +  },
   1.602 +
   1.603 +  get events() {
   1.604 +    return this._events;
   1.605 +  },
   1.606 +
   1.607 +  get children() {
   1.608 +    var items = [];
   1.609 +
   1.610 +    var options = Utilities.history.getNewQueryOptions();
   1.611 +    var query = Utilities.history.getNewQuery();
   1.612 +    query.setFolders([this._id], 1);
   1.613 +    var result = Utilities.history.executeQuery(query, options);
   1.614 +    var rootNode = result.root;
   1.615 +    rootNode.containerOpen = true;
   1.616 +    var cc = rootNode.childCount;
   1.617 +    for (var i=0; i<cc; ++i) {
   1.618 +      var node = rootNode.getChild(i);
   1.619 +      if (node.type == node.RESULT_TYPE_FOLDER) {
   1.620 +        var folder = new BookmarkFolder(node.itemId, this._id);
   1.621 +        items.push(folder);
   1.622 +      }
   1.623 +      else if (node.type == node.RESULT_TYPE_SEPARATOR) {
   1.624 +        var separator = new Bookmark(node.itemId, this._id, "separator");
   1.625 +        items.push(separator);
   1.626 +      }
   1.627 +      else {
   1.628 +        var bookmark = new Bookmark(node.itemId, this._id, "bookmark");
   1.629 +        items.push(bookmark);
   1.630 +      }
   1.631 +    }
   1.632 +    rootNode.containerOpen = false;
   1.633 +
   1.634 +    return items;
   1.635 +  },
   1.636 +
   1.637 +  addBookmark: function bmf_addbm(aTitle, aUri) {
   1.638 +    var newBookmarkID = Utilities.bookmarks.insertBookmark(this._id, aUri, Utilities.bookmarks.DEFAULT_INDEX, aTitle);
   1.639 +    var newBookmark = new Bookmark(newBookmarkID, this, "bookmark");
   1.640 +    return newBookmark;
   1.641 +  },
   1.642 +
   1.643 +  addSeparator: function bmf_addsep() {
   1.644 +    var newBookmarkID = Utilities.bookmarks.insertSeparator(this._id, Utilities.bookmarks.DEFAULT_INDEX);
   1.645 +    var newBookmark = new Bookmark(newBookmarkID, this, "separator");
   1.646 +    return newBookmark;
   1.647 +  },
   1.648 +
   1.649 +  addFolder: function bmf_addfolder(aTitle) {
   1.650 +    var newFolderID = Utilities.bookmarks.createFolder(this._id, aTitle, Utilities.bookmarks.DEFAULT_INDEX);
   1.651 +    var newFolder = new BookmarkFolder(newFolderID, this);
   1.652 +    return newFolder;
   1.653 +  },
   1.654 +
   1.655 +  remove: function bmf_remove() {
   1.656 +    Utilities.bookmarks.removeItem(this._id);
   1.657 +  },
   1.658 +
   1.659 +  // observer
   1.660 +  onBeginUpdateBatch: function () {},
   1.661 +  onEndUpdateBatch : function () {},
   1.662 +  onItemAdded : function () {},
   1.663 +  onItemRemoved : function () {},
   1.664 +  onItemChanged : function () {},
   1.665 +
   1.666 +  onItemMoved: function bf_onItemMoved(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
   1.667 +    if (this._id == aId) {
   1.668 +      this._parent = new BookmarkFolder(aNewParent, Utilities.bookmarks.getFolderIdForItem(aNewParent));
   1.669 +    }
   1.670 +  },
   1.671 +
   1.672 +  QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmarkFolder,
   1.673 +                                         Ci.nsINavBookmarksObserver,
   1.674 +                                         Ci.nsISupportsWeakReference])
   1.675 +};
   1.676 +
   1.677 +//=================================================
   1.678 +// BookmarkRoots implementation
   1.679 +function BookmarkRoots() {
   1.680 +}
   1.681 +
   1.682 +BookmarkRoots.prototype = {
   1.683 +  get menu() {
   1.684 +    if (!this._menu)
   1.685 +      this._menu = new BookmarkFolder(Utilities.bookmarks.bookmarksMenuFolder, null);
   1.686 +
   1.687 +    return this._menu;
   1.688 +  },
   1.689 +
   1.690 +  get toolbar() {
   1.691 +    if (!this._toolbar)
   1.692 +      this._toolbar = new BookmarkFolder(Utilities.bookmarks.toolbarFolder, null);
   1.693 +
   1.694 +    return this._toolbar;
   1.695 +  },
   1.696 +
   1.697 +  get tags() {
   1.698 +    if (!this._tags)
   1.699 +      this._tags = new BookmarkFolder(Utilities.bookmarks.tagsFolder, null);
   1.700 +
   1.701 +    return this._tags;
   1.702 +  },
   1.703 +
   1.704 +  get unfiled() {
   1.705 +    if (!this._unfiled)
   1.706 +      this._unfiled = new BookmarkFolder(Utilities.bookmarks.unfiledBookmarksFolder, null);
   1.707 +
   1.708 +    return this._unfiled;
   1.709 +  },
   1.710 +
   1.711 +  QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmarkRoots])
   1.712 +};
   1.713 +
   1.714 +
   1.715 +//=================================================
   1.716 +// Factory - Treat Application as a singleton
   1.717 +// XXX This is required, because we're registered for the 'JavaScript global
   1.718 +// privileged property' category, whose handler always calls createInstance.
   1.719 +// See bug 386535.
   1.720 +var gSingleton = null;
   1.721 +var ApplicationFactory = {
   1.722 +  createInstance: function af_ci(aOuter, aIID) {
   1.723 +    if (aOuter != null)
   1.724 +      throw Components.results.NS_ERROR_NO_AGGREGATION;
   1.725 +
   1.726 +    if (gSingleton == null) {
   1.727 +      gSingleton = new Application();
   1.728 +    }
   1.729 +
   1.730 +    return gSingleton.QueryInterface(aIID);
   1.731 +  }
   1.732 +};
   1.733 +
   1.734 +
   1.735 +#include ../../../toolkit/components/exthelper/extApplication.js
   1.736 +
   1.737 +//=================================================
   1.738 +// Application constructor
   1.739 +function Application() {
   1.740 +  this.initToolkitHelpers();
   1.741 +}
   1.742 +
   1.743 +//=================================================
   1.744 +// Application implementation
   1.745 +function ApplicationPrototype() {
   1.746 +  // for nsIClassInfo + XPCOMUtils
   1.747 +  this.classID = APPLICATION_CID;
   1.748 +
   1.749 +  // redefine the default factory for XPCOMUtils
   1.750 +  this._xpcom_factory = ApplicationFactory;
   1.751 +
   1.752 +  // for nsISupports
   1.753 +  this.QueryInterface = XPCOMUtils.generateQI([
   1.754 +    Ci.fuelIApplication,
   1.755 +    Ci.extIApplication,
   1.756 +    Ci.nsIObserver,
   1.757 +    Ci.nsISupportsWeakReference
   1.758 +  ]);
   1.759 +
   1.760 +  // for nsIClassInfo
   1.761 +  this.classInfo = XPCOMUtils.generateCI({
   1.762 +    classID: APPLICATION_CID,
   1.763 +    contractID: APPLICATION_CONTRACTID,
   1.764 +    interfaces: [
   1.765 +      Ci.fuelIApplication,
   1.766 +      Ci.extIApplication,
   1.767 +      Ci.nsIObserver
   1.768 +    ],
   1.769 +    flags: Ci.nsIClassInfo.SINGLETON
   1.770 +  });
   1.771 +
   1.772 +  // for nsIObserver
   1.773 +  this.observe = function (aSubject, aTopic, aData) {
   1.774 +    // Call the extApplication version of this function first
   1.775 +    var superPrototype = Object.getPrototypeOf(Object.getPrototypeOf(this));
   1.776 +    superPrototype.observe.call(this, aSubject, aTopic, aData);
   1.777 +    if (aTopic == "xpcom-shutdown") {
   1.778 +      this._obs.removeObserver(this, "xpcom-shutdown");
   1.779 +      Utilities.free();
   1.780 +    }
   1.781 +  };
   1.782 +
   1.783 +  Object.defineProperty(this, "bookmarks", {
   1.784 +    get: function bookmarks () {
   1.785 +      let bookmarks = new BookmarkRoots();
   1.786 +      Object.defineProperty(this, "bookmarks", { value: bookmarks });
   1.787 +      return this.bookmarks;
   1.788 +    },
   1.789 +    enumerable: true,
   1.790 +    configurable: true
   1.791 +  });
   1.792 +
   1.793 +  Object.defineProperty(this, "windows", {
   1.794 +    get: function windows() {
   1.795 +      var win = [];
   1.796 +      var browserEnum = Utilities.windowMediator.getEnumerator("navigator:browser");
   1.797 +
   1.798 +      while (browserEnum.hasMoreElements())
   1.799 +        win.push(getWindow(browserEnum.getNext()));
   1.800 +
   1.801 +      return win;
   1.802 +    },
   1.803 +    enumerable: true,
   1.804 +    configurable: true
   1.805 +  });
   1.806 +
   1.807 +  Object.defineProperty(this, "activeWindow", {
   1.808 +    get: () => getWindow(Utilities.windowMediator.getMostRecentWindow("navigator:browser")),
   1.809 +    enumerable: true,
   1.810 +    configurable: true
   1.811 +  });
   1.812 +
   1.813 +};
   1.814 +
   1.815 +// set the proto, defined in extApplication.js
   1.816 +ApplicationPrototype.prototype = extApplication.prototype;
   1.817 +
   1.818 +Application.prototype = new ApplicationPrototype();
   1.819 +
   1.820 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Application]);
   1.821 +

mercurial