bug 411536 - create SMILE (SeaMonkey Interface Library for Extensions), initial part with base work applicable pieces from FUEL, last review comments and nits addressed by Philip Chee, r=Neil
authorJorge Villalobos <jorge.villalobos@gmail.com>
Mon, 10 Aug 2009 15:30:29 +0800
changeset 3269 23414c2c2381c32f02a5083edbe2edfd67f1b519
parent 3268 1bb0e6d704340584e3a729d7eda9039a75cf6ad0
child 3270 54a31dce7f855f3b1901040299d7d4916b2f9a14
push idunknown
push userunknown
push dateunknown
reviewersNeil
bugs411536
bug 411536 - create SMILE (SeaMonkey Interface Library for Extensions), initial part with base work applicable pieces from FUEL, last review comments and nits addressed by Philip Chee, r=Neil
suite/Makefile.in
suite/installer/unix/packages
suite/installer/windows/packages
suite/makefiles.sh
suite/smile/Makefile.in
suite/smile/public/Makefile.in
suite/smile/public/smileIApplication.idl
suite/smile/src/Makefile.in
suite/smile/src/smileApplication.js
suite/smile/test/ContentA.html
suite/smile/test/ContentB.html
suite/smile/test/ContentWithFrames.html
suite/smile/test/Makefile.in
suite/smile/test/browser_Application.js
suite/smile/test/browser_ApplicationPrefs.js
suite/smile/test/browser_ApplicationQuitting.js
suite/smile/test/browser_ApplicationStorage.js
suite/smile/test/browser_Browser.js
suite/smile/test/browser_Extensions.js
--- a/suite/Makefile.in
+++ b/suite/Makefile.in
@@ -57,16 +57,17 @@ PARALLEL_DIRS	+= \
 		feeds/src \
 		locales \
 		modules \
 		themes/classic \
 		themes/modern \
 		profile \
 		security \
 		shell/public \
+		smile \
 		$(NULL)
 
 ifeq ($(OS_ARCH),WINNT)
 PARALLEL_DIRS += shell/src
 ifdef MOZ_INSTALLER
 PARALLEL_DIRS += installer/windows
 endif
 endif
--- a/suite/installer/unix/packages
+++ b/suite/installer/unix/packages
@@ -216,16 +216,17 @@ bin/components/pref.xpt
 bin/components/prefetch.xpt
 bin/components/profile.xpt
 bin/components/proxyObjInst.xpt
 bin/components/rdf.xpt
 bin/components/satchel.xpt
 bin/components/saxparser.xpt
 bin/components/shistory.xpt
 bin/components/shellservice.xpt
+bin/components/smile.xpt
 bin/components/spellchecker.xpt
 bin/components/storage.xpt
 bin/components/suitebrowser.xpt
 bin/components/suitecommon.xpt
 bin/components/suitefeeds.xpt
 bin/components/suitemigration.xpt
 bin/components/toolkitprofile.xpt
 bin/components/toolkitremote.xpt
@@ -293,16 +294,17 @@ bin/components/nsSuiteDownloadManagerUI.
 bin/components/nsSuiteGlue.js
 bin/components/nsTaggingService.js
 bin/components/nsTypeAheadFind.js
 bin/components/nsTryToClose.js
 bin/components/nsUpdateService.js
 bin/components/nsURLFormatter.js
 bin/components/nsWebHandlerApp.js
 bin/components/pluginGlue.js
+bin/components/smileApplication.js
 bin/components/storage-Legacy.js
 bin/components/storage-mozStorage.js
 bin/components/txEXSLTRegExFunctions.js
 bin/components/WebContentConverter.js
 
 ; Modules
 bin/modules/*
 
--- a/suite/installer/windows/packages
+++ b/suite/installer/windows/packages
@@ -216,16 +216,17 @@ bin\components\pref.xpt
 bin\components\prefetch.xpt
 bin\components\profile.xpt
 bin\components\proxyObject.xpt
 bin\components\rdf.xpt
 bin\components\satchel.xpt
 bin\components\saxparser.xpt
 bin\components\shellservice.xpt
 bin\components\shistory.xpt
+bin\components\smile.xpt
 bin\components\spellchecker.xpt
 bin\components\storage.xpt
 bin\components\suitebrowser.xpt
 bin\components\suitecommon.xpt
 bin\components\suitefeeds.xpt
 bin\components\suitemigration.xpt
 bin\components\toolkitprofile.xpt
 bin\components\txmgr.xpt
@@ -296,16 +297,17 @@ bin\components\nsSuiteDownloadManagerUI.
 bin\components\nsSuiteGlue.js
 bin\components\nsTaggingService.js
 bin\components\nsTypeAheadFind.js
 bin\components\nsTryToClose.js
 bin\components\nsUpdateService.js
 bin\components\nsURLFormatter.js
 bin\components\nsWebHandlerApp.js
 bin\components\pluginGlue.js
+bin\components\smileApplication.js
 bin\components\storage-Legacy.js
 bin\components\storage-mozStorage.js
 bin\components\txEXSLTRegExFunctions.js
 bin\components\WebContentConverter.js
 
 ; Modules
 bin\modules\*
 
--- a/suite/makefiles.sh
+++ b/suite/makefiles.sh
@@ -58,12 +58,13 @@ add_makefiles "
   suite/mailnews/Makefile
   suite/modules/Makefile
   suite/modules/test/Makefile
   suite/profile/Makefile
   suite/profile/migration/public/Makefile
   suite/profile/migration/src/Makefile
   suite/shell/public/Makefile
   suite/shell/src/Makefile
+  suite/smile/Makefile
   suite/themes/modern/Makefile
   suite/themes/classic/Makefile
 "
 fi
new file mode 100644
--- /dev/null
+++ b/suite/smile/Makefile.in
@@ -0,0 +1,50 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is SMILE.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2006
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Jorge Villalobos <jorge.villalobos@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH     = ../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+PARALLEL_DIRS = public src
+
+ifdef ENABLE_TESTS
+PARALLEL_DIRS += test
+endif
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/suite/smile/public/Makefile.in
@@ -0,0 +1,49 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is SMILE.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2006
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Jorge Villalobos <jorge.villalobos@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH     = ../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE        = smile
+XPIDL_MODULE  = smile
+
+XPIDLSRCS = smileIApplication.idl
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/suite/smile/public/smileIApplication.idl
@@ -0,0 +1,167 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is FUEL.
+ *
+ * The Initial Developer of the Original Code is Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Mark Finkle <mfinkle@mozilla.com> (Original Author)
+ *  John Resig  <jresig@mozilla.com> (Original Author)
+ *  Jorge Villalobos <jorge.villalobos@gmail.com> (SeaMonkey port)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+#include "extIApplication.idl"
+
+interface nsIVariant;
+interface nsIURI;
+interface nsIDOMHTMLDocument;
+
+interface smileIBrowserTab;
+
+/**
+ * Interface representing a container for bookmark roots. Roots
+ * are the top level parents for the various types of bookmarks in the system.
+ * Empty as it is only used as the return type for Application.bookmarks. It
+ * will be supported once the Places bookmarks API is supported in SeaMonkey.
+ */
+[scriptable, uuid(1102eec4-f66b-4082-abad-c967ad7d5f76)]
+interface smileIBookmarkRoots : nsISupports
+{
+};
+
+/**
+ * Interface representing a browser window.
+ */
+[scriptable, uuid(1c3002ec-5aaf-4232-ab7d-835a348133fd)]
+interface smileIWindow : nsISupports
+{
+  /**
+   * A collection of browser tabs within the browser window.
+   */
+  readonly attribute nsIVariant tabs;
+
+  /**
+   * The currently-active tab within the browser window.
+   */
+  readonly attribute smileIBrowserTab activeTab;
+
+  /**
+   * Open a new browser tab, pointing to the specified URI.
+   * @param   aURI
+   *          The uri to open the browser tab to
+   */
+  smileIBrowserTab open(in nsIURI aURI);
+
+  /**
+   * The events object for the browser window.
+   * supports: "TabOpen", "TabClose", "TabMove", "TabSelect"
+   */
+  readonly attribute extIEvents events;
+};
+
+/**
+ * Interface representing a browser tab.
+ */
+[scriptable, uuid(9b06c55e-5377-4c71-8bda-8e8750c5a02a)]
+interface smileIBrowserTab : nsISupports
+{
+  /**
+   * The current uri of this tab.
+   */
+  readonly attribute nsIURI uri;
+
+  /**
+   * The current index of this tab in the browser window.
+   */
+  readonly attribute PRInt32 index;
+
+  /**
+   * The browser window that is holding the tab.
+   */
+  readonly attribute smileIWindow window;
+
+  /**
+   * The content document of the browser tab.
+   */
+  readonly attribute nsIDOMHTMLDocument document;
+
+  /**
+   * The events object for the browser tab.
+   * supports: "load"
+   */
+  readonly attribute extIEvents events;
+
+  /**
+   * Load a new URI into this browser tab.
+   * @param   aURI
+   *          The uri to load into the browser tab
+   */
+  void load(in nsIURI aURI);
+
+  /**
+   * Give focus to this browser tab, and bring it to the front.
+   */
+  void focus();
+
+  /**
+   * Close the browser tab. This may not actually close the tab
+   * as script may abort the close operation.
+   */
+  void close();
+
+  /**
+   * Moves this browser tab before another browser tab within the window.
+   * @param   aBefore
+   *          The tab before which the target tab will be moved
+   */
+  void moveBefore(in smileIBrowserTab aBefore);
+
+  /**
+   * Move this browser tab to the last tab within the window.
+   */
+  void moveToEnd();
+};
+
+/**
+ * Interface for managing and accessing the applications systems
+ */
+[scriptable, uuid(c9ba8f65-c936-4ac6-a859-8936832b0c12)]
+interface smileIApplication : extIApplication
+{
+  /**
+   * An array of browser windows within the application.
+   */
+  readonly attribute nsIVariant windows;
+
+  /**
+   * The currently active browser window.
+   */
+  readonly attribute smileIWindow activeWindow;
+};
new file mode 100644
--- /dev/null
+++ b/suite/smile/src/Makefile.in
@@ -0,0 +1,48 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is SMILE.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2006
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Jorge Villalobos <jorge.villalobos@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH     = ../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = smile
+
+EXTRA_PP_COMPONENTS = smileApplication.js
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/suite/smile/src/smileApplication.js
@@ -0,0 +1,386 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is FUEL.
+ *
+ * The Initial Developer of the Original Code is Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Mark Finkle <mfinkle@mozilla.com> (Original Author)
+ *  John Resig  <jresig@mozilla.com> (Original Author)
+ *  Jorge Villalobos <jorge.villalobos@gmail.com> (SeaMonkey port)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+//=================================================
+// Singleton that holds services and utilities
+var Utilities = {
+  _bookmarks : null,
+  get bookmarks() {
+    if (!this._bookmarks) {
+      this._bookmarks =
+        Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]
+                  .getService(Components.interfaces.nsINavBookmarksService);
+    }
+    return this._bookmarks;
+  },
+
+  _livemarks : null,
+  get livemarks() {
+    if (!this._livemarks) {
+      this._livemarks =
+        Components.classes["@mozilla.org/browser/livemark-service;2"]
+                  .getService(Components.interfaces.nsILivemarkService);
+    }
+    return this._livemarks;
+  },
+
+  _annotations : null,
+  get annotations() {
+    if (!this._annotations) {
+      this._annotations =
+        Components.classes["@mozilla.org/browser/annotation-service;1"]
+                  .getService(Components.interfaces.nsIAnnotationService);
+    }
+    return this._annotations;
+  },
+
+  _history : null,
+  get history() {
+    if (!this._history) {
+      this._history =
+        Components.classes["@mozilla.org/browser/nav-history-service;1"]
+                  .getService(Components.interfaces.nsINavHistoryService);
+    }
+    return this._history;
+  },
+
+  _windowMediator : null,
+  get windowMediator() {
+    if (!this._windowMediator) {
+      this._windowMediator =
+        Components.classes["@mozilla.org/appshell/window-mediator;1"]
+                  .getService(Components.interfaces.nsIWindowMediator);
+    }
+    return this._windowMediator;
+  },
+
+  makeURI : function(aSpec) {
+    if (!aSpec)
+      return null;
+    var ios = Components.classes["@mozilla.org/network/io-service;1"]
+                        .getService(Components.interfaces.nsIIOService);
+    return ios.newURI(aSpec, null, null);
+  },
+
+  free : function() {
+    this._bookmarks = null;
+    this._livemarks = null;
+    this._annotations = null;
+    this._history = null;
+    this._windowMediator = null;
+  }
+};
+
+
+//=================================================
+// Window implementation
+function Window(aWindow) {
+  this._window = aWindow;
+  this._tabbrowser = aWindow.getBrowser();
+  this._events = new Events();
+  this._cleanup = {};
+
+  this._watch("TabOpen");
+  this._watch("TabMove");
+  this._watch("TabClose");
+  this._watch("TabSelect");
+
+  var self = this;
+  gShutdown.push(function() { self._shutdown(); });
+}
+
+Window.prototype = {
+  get events() {
+    return this._events;
+  },
+
+  /*
+   * 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) {
+    var self = this;
+    this._tabbrowser.addEventListener(aType,
+      this._cleanup[aType] = function(e){ self._event(e); },
+      true);
+  },
+
+  /*
+   * Helper event callback used to redirect events made on the XBL element
+   */
+  _event : function win_event(aEvent) {
+    this._events.dispatch(aEvent.type, new BrowserTab(this, aEvent.originalTarget));
+  },
+
+  get tabs() {
+    var tabs = [];
+    var mTabs = this._tabbrowser.mTabs;
+    for (var i = 0; i < mTabs.length; i++)
+      tabs.push(new BrowserTab(this, mTabs[i]));
+    return tabs;
+  },
+
+  get activeTab() {
+    return new BrowserTab(this, this._tabbrowser.selectedTab);
+  },
+
+  open : function win_open(aURI) {
+    return new BrowserTab(this, this._tabbrowser.addTab(aURI.spec));
+  },
+
+  _shutdown : function win_shutdown() {
+    for (var type in this._cleanup)
+      this._tabbrowser.removeEventListener(type, this._cleanup[type], true);
+    this._cleanup = null;
+
+    this._window = null;
+    this._tabbrowser = null;
+    this._events = null;
+  },
+
+  QueryInterface : XPCOMUtils.generateQI([Components.interfaces.smileIWindow])
+};
+
+
+//=================================================
+// BrowserTab implementation
+function BrowserTab(aSMILEWindow, aTab) {
+  this._window = aSMILEWindow;
+  this._tabbrowser = aSMILEWindow._tabbrowser;
+  this._browser = aTab.linkedBrowser;
+  this._tab = aTab;
+  this._events = new Events();
+  this._cleanup = {};
+
+  this._watch("load");
+
+  var self = this;
+  gShutdown.push(function() { self._shutdown(); });
+}
+
+BrowserTab.prototype = {
+  get uri() {
+    return this._browser.currentURI;
+  },
+
+  get index() {
+    try {
+      return this._tabbrowser.getTabIndex(this._tab);
+    }
+    catch (e) {
+      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) {
+    var self = this;
+    this._browser.addEventListener(aType,
+      this._cleanup[aType] = function(e){ self._event(e); },
+      true);
+  },
+
+  /*
+   * Helper event callback used to redirect events made on the XBL element
+   */
+  _event : function bt_event(aEvent) {
+    if (aEvent.type == "load") {
+      if (!(aEvent.originalTarget instanceof Components.interfaces.nsIDOMDocument))
+        return;
+
+      if (aEvent.originalTarget.defaultView instanceof Components.interfaces.nsIDOMWindowInternal &&
+          aEvent.originalTarget.defaultView.frameElement)
+        return;
+    }
+    this._events.dispatch(aEvent.type, this);
+  },
+
+  load : function bt_load(aURI) {
+    this._browser.loadURI(aURI.spec, null, null);
+  },
+
+  focus : function bt_focus() {
+    this._tabbrowser.selectedTab = this._tab;
+    this._tabbrowser.focus();
+  },
+
+  close : function bt_close() {
+    this._tabbrowser.removeTab(this._tab);
+  },
+
+  moveBefore : function bt_movebefore(aBefore) {
+    this._tabbrowser.moveTabTo(this._tab, aBefore.index);
+  },
+
+  moveToEnd : function bt_moveend() {
+    this._tabbrowser.moveTabTo(this._tab, this._tabbrowser.browsers.length);
+  },
+
+  _shutdown : function bt_shutdown() {
+    for (var type in this._cleanup)
+      this._browser.removeEventListener(type, this._cleanup[type], true);
+    this._cleanup = null;
+
+    this._window = null;
+    this._tabbrowser = null;
+    this._browser = null;
+    this._tab = null;
+    this._events = null;
+  },
+
+  QueryInterface : XPCOMUtils.generateQI([Components.interfaces.smileIBrowserTab])
+};
+
+
+//=================================================
+// 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);
+  }
+};
+
+
+
+//=================================================
+// Application constructor
+function Application() {
+  this.initToolkitHelpers();
+  this._bookmarks = null;
+}
+
+//=================================================
+// Application implementation
+Application.prototype = {
+  // for nsIClassInfo + XPCOMUtils
+  classDescription: "Application",
+  classID:          Components.ID("c9ba8f65-c936-4ac6-a859-8936832b0c12"),
+  contractID:       "@mozilla.org/smile/application;1",
+
+  // redefine the default factory for XPCOMUtils
+  _xpcom_factory: ApplicationFactory,
+
+  // for nsISupports
+  QueryInterface : XPCOMUtils.generateQI(
+                     [Components.interfaces.smileIApplication,
+                      Components.interfaces.extIApplication,
+                      Components.interfaces.nsIObserver,
+                      Components.interfaces.nsIClassInfo]),
+
+  getInterfaces : function app_gi(aCount) {
+   var interfaces = [Components.interfaces.smileIApplication,
+                     Components.interfaces.extIApplication,
+                     Components.interfaces.nsIObserver,
+                     Components.interfaces.nsIClassInfo];
+    aCount.value = interfaces.length;
+    return interfaces;
+  },
+
+  // for nsIObserver
+  observe: function app_observe(aSubject, aTopic, aData) {
+    // Call the extApplication version of this function first
+    this.__proto__.__proto__.observe.call(this, aSubject, aTopic, aData);
+    if (aTopic == "xpcom-shutdown") {
+      this._bookmarks = null;
+      Utilities.free();
+    }
+  },
+
+  /*
+   Uncomment once Places Bookmarks migration is complete.
+   get bookmarks() {
+
+     if (this._bookmarks == null)
+      this._bookmarks = new BookmarkRoots();
+
+    return this._bookmarks;
+  },*/
+
+  get windows() {
+    var win = [];
+    var enum = Utilities.windowMediator.getEnumerator("navigator:browser");
+
+    while (enum.hasMoreElements())
+      win.push(new Window(enum.getNext()));
+
+    return win;
+  },
+
+  get activeWindow() {
+    return new Window(Utilities.windowMediator.getMostRecentWindow("navigator:browser"));
+  }
+};
+
+//module initialization
+function NSGetModule(aCompMgr, aFileSpec) {
+  // set the proto, defined in extApplication.js
+  Application.prototype.__proto__ = extApplication.prototype;
+  return XPCOMUtils.generateModule([Application]);
+}
+
+#include ../../../mozilla/toolkit/components/exthelper/extApplication.js
new file mode 100644
--- /dev/null
+++ b/suite/smile/test/ContentA.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" >
+<head>
+    <title>Content Page A</title>
+</head>
+<body>
+<h1>Content Page A</h1>
+<div id="desc">This is a simple content page used for testing SMILE Browser API</div>
+<div id="test1">A</div>
+<div id="test2">B</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/suite/smile/test/ContentB.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" >
+<head>
+    <title>Content Page B</title>
+</head>
+<body>
+<h1>Content Page B</h1>
+<div id="desc">This is a simple content page used for testing SMILE Browser API</div>
+<div id="test1">1</div>
+<div id="test2">2</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/suite/smile/test/ContentWithFrames.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" >
+<head>
+    <title>Content Page with Frames</title>
+</head>
+<body>
+<h1>Content Page with Frames</h1>
+<div id="desc">This is a simple framed content page used for testing SMILE Browser API</div>
+<iframe src="ContentA.html"></iframe>
+<iframe src="ContentB.html"></iframe>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/suite/smile/test/Makefile.in
@@ -0,0 +1,60 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is SMILE.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2006
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Mark Finkle <mfinkle@mozilla.com>
+#   John Resig  <jresig@mozilla.com>
+#   Jorge Villalobos <jorge.villalobos@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH          = ../../..
+topsrcdir      = @top_srcdir@
+srcdir         = @srcdir@
+VPATH          = @srcdir@
+relativesrcdir = suite/smile/test
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_BROWSER_FILES =browser_Application.js \
+		browser_ApplicationPrefs.js \
+		browser_ApplicationStorage.js \
+		browser_ApplicationQuitting.js \
+		browser_Browser.js \
+		browser_Extensions.js \
+		ContentA.html \
+		ContentB.html \
+		ContentWithFrames.html \
+		$(NULL)
+
+libs::	$(_BROWSER_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(MOZDEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/suite/smile/test/browser_Application.js
@@ -0,0 +1,86 @@
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+// This listens for the next opened window and checks it is of the right url.
+// opencallback is called when the new window is fully loaded
+// closecallback is called when the window is closed
+function WindowOpenListener(url, opencallback, closecallback) {
+  this.url = url;
+  this.opencallback = opencallback;
+  this.closecallback = closecallback;
+
+  var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+           getService(Ci.nsIWindowMediator);
+  wm.addListener(this);
+}
+
+WindowOpenListener.prototype = {
+  url: null,
+  opencallback: null,
+  closecallback: null,
+  window: null,
+  domwindow: null,
+
+  handleEvent: function(event) {
+    is(this.domwindow.document.location.href, this.url, "Should have opened the correct window");
+
+    this.domwindow.removeEventListener("load", this, false);
+    // Allow any other load handlers to execute
+    var self = this;
+    executeSoon(function() { self.opencallback(self.domwindow); } );
+  },
+
+  onWindowTitleChange: function(window, title) {
+  },
+
+  onOpenWindow: function(window) {
+    if (this.window)
+      return;
+
+    this.window = window;
+    this.domwindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowInternal);
+    this.domwindow.addEventListener("load", this, false);
+  },
+
+  onCloseWindow: function(window) {
+    if (this.window != window)
+      return;
+
+    var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+             getService(Ci.nsIWindowMediator);
+    wm.removeListener(this);
+    this.opencallback = null;
+    this.window = null;
+    this.domwindow = null;
+
+    // Let the window close complete
+    executeSoon(this.closecallback);
+    this.closecallback = null;
+  }
+};
+
+function test() {
+  ok(Application, "Check global access to Application");
+  
+  // I'd test these against a specific value, but that is bound to flucuate
+  ok(Application.id, "Check to see if an ID exists for the Application");
+  ok(Application.name, "Check to see if a name exists for the Application");
+  ok(Application.version, "Check to see if a version exists for the Application");
+  
+  var wMediator = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+  var console = wMediator.getMostRecentWindow("global:console");
+  waitForExplicitFinish();
+  ok(!console, "Console should not already be open");
+
+  new WindowOpenListener("chrome://global/content/console.xul", consoleOpened, consoleClosed);
+  Application.console.open();
+}
+
+function consoleOpened(win) {
+  win.close();
+}
+
+function consoleClosed() {
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/suite/smile/test/browser_ApplicationPrefs.js
@@ -0,0 +1,170 @@
+// The various properties that we'll be testing
+var testdata = {
+  missing: "smile.smile-test-missing",
+  dummy: "smile.smile-test",
+  string: "browser.active_color",
+  integer: "permissions.default.image",
+  boolean: "browser.blink_allowed"
+};
+
+function test() {
+  // test getting non-existing values
+  var itemValue = Application.prefs.getValue(testdata.missing, "default");
+  is(itemValue, "default", "Check 'Application.prefs.getValue' for non-existing item");
+
+  is(Application.prefs.get(testdata.missing), null, "Check 'Application.prefs.get' for non-existing item");
+
+  // test setting and getting a value
+  Application.prefs.setValue(testdata.dummy, "dummy");
+  itemValue = Application.prefs.getValue(testdata.dummy, "default");
+  is(itemValue, "dummy", "Check 'Application.prefs.getValue' for existing item");
+
+  // test for overwriting an existing value
+  Application.prefs.setValue(testdata.dummy, "smarty");
+  itemValue = Application.prefs.getValue(testdata.dummy, "default");
+  is(itemValue, "smarty", "Check 'Application.prefs.getValue' for overwritten item");
+
+  // test setting and getting a value
+  Application.prefs.get(testdata.dummy).value = "dummy2";
+  itemValue = Application.prefs.get(testdata.dummy).value;
+  is(itemValue, "dummy2", "Check 'Application.prefs.get().value' for existing item");
+
+  // test resetting a pref [since there is no default value, the pref should disappear]
+  Application.prefs.get(testdata.dummy).reset();
+  itemValue = Application.prefs.getValue(testdata.dummy, "default");
+  is(itemValue, "default", "Check 'Application.prefs.getValue' for reset pref");
+
+  // test to see if a non-existant property exists
+  ok(!Application.prefs.has(testdata.dummy), "Check non-existant property for existance");
+
+  // PREF: string browser.active_color == #EE0000
+
+  // test to see if an existing string property exists
+  ok(Application.prefs.has(testdata.string), "Check existing string property for existance");
+
+  // test accessing a non-existant string property
+  var val = Application.prefs.getValue(testdata.dummy, "default");
+  is(val, "default", "Check non-existant string property for expected value");
+
+  // test accessing an existing string property
+  var val = Application.prefs.getValue(testdata.string, "default");
+  is(val, "#EE0000", "Check existing string property for expected value");
+
+  // test manipulating an existing string property
+  Application.prefs.setValue(testdata.string, "#EF0000");
+  var val = Application.prefs.getValue(testdata.string, "default");
+  is(val, "#EF0000", "Set existing string property");
+
+  // test getting the type of an existing string property
+  var type = Application.prefs.get(testdata.string).type;
+  is(type, "String", "Check 'Application.prefs.get().type' for string pref");
+
+  // test resetting an existing string property
+  Application.prefs.get(testdata.string).reset();
+  var val = Application.prefs.getValue(testdata.string, "default");
+  is(val, "#EE0000", "Reset existing string property");
+
+  // PREF: integer permissions.default.image == 1
+
+  // test to see if an existing integer property exists
+  ok(Application.prefs.has(testdata.integer), "Check existing integer property for existance");
+
+  // test accessing a non-existant integer property
+  var val = Application.prefs.getValue(testdata.dummy, 0);
+  is(val, 0, "Check non-existant integer property for expected value");
+
+  // test accessing an existing integer property
+  var val = Application.prefs.getValue(testdata.integer, 0);
+  is(val, 1, "Check existing integer property for expected value");
+
+  // test manipulating an existing integer property
+  Application.prefs.setValue(testdata.integer, 0);
+  var val = Application.prefs.getValue(testdata.integer, 1);
+  is(val, 0, "Set existing integer property");
+
+  // test getting the type of an existing integer property
+  var type = Application.prefs.get(testdata.integer).type;
+  is(type, "Number", "Check 'Application.prefs.get().type' for integer pref");
+
+  // test resetting an existing integer property
+  Application.prefs.get(testdata.integer).reset();
+  var val = Application.prefs.getValue(testdata.integer, 0);
+  is(val, 1, "Reset existing integer property");
+
+  // PREF: boolean browser.blink_allowed == true
+
+  // test to see if an existing boolean property exists
+  ok(Application.prefs.has(testdata.boolean), "Check existing boolean property for existance");
+
+  // test accessing a non-existant boolean property
+  var val = Application.prefs.getValue(testdata.dummy, true);
+  ok(val, "Check non-existant boolean property for expected value");
+
+  // test accessing an existing boolean property
+  var val = Application.prefs.getValue(testdata.boolean, false);
+  ok(val, "Check existing boolean property for expected value");
+
+  // test manipulating an existing boolean property
+  Application.prefs.setValue(testdata.boolean, false);
+  var val = Application.prefs.getValue(testdata.boolean, true);
+  ok(!val, "Set existing boolean property");
+
+  // test getting the type of an existing boolean property
+  var type = Application.prefs.get(testdata.boolean).type;
+  is(type, "Boolean", "Check 'Application.prefs.get().type' for boolean pref");
+
+  // test resetting an existing boolean property
+  Application.prefs.get(testdata.boolean).reset();
+  var val = Application.prefs.getValue(testdata.boolean, false);
+  ok(val, "Reset existing string property for expected value");
+
+  // test getting all preferences
+  var allPrefs = Application.prefs.all;
+  ok(allPrefs.length >= 800, "Check 'Application.prefs.all' for the right number of preferences");
+  ok(allPrefs[0].name.length > 0, "Check 'Application.prefs.all' for a valid name in the starting preference");
+
+  // test the value of the preference root
+  is(Application.prefs.root, "", "Check the Application preference root");
+
+  // test for user changed preferences
+  ok(Application.prefs.get("browser.shell.checkDefaultBrowser").modified, "A single preference is marked as modified.");
+  ok(!Application.prefs.get(testdata.string).modified, "A single preference is marked as not modified.");
+
+  // test for a locked preference
+  var pref = Application.prefs.get(testdata.string);
+  ok(!pref.locked, "A single preference should not be locked.");
+
+  pref.locked = true;
+  ok(pref.locked, "A single preference should be locked.");
+
+  try {
+    prev.value = "test value";
+
+    ok(false, "A locked preference should not be able to be modified.");
+  } catch(e){
+    ok(true, "A locked preference should not be able to be modified.");
+  }
+
+  pref.locked = false;
+  ok(!pref.locked, "A single preference should not be locked.");
+
+  // check for change event when setting a value
+  waitForExplicitFinish();
+  Application.prefs.events.addListener("change", onPrefChange);
+  Application.prefs.setValue("smile.smile-test", "change event");
+}
+
+function onPrefChange(evt) {
+  is(evt.data, testdata.dummy, "Check 'Application.prefs.set' fired a change event");
+  Application.prefs.events.removeListener("change", onPrefChange);
+
+  Application.prefs.get("smile.smile-test").events.addListener("change", onPrefChange2);
+  Application.prefs.setValue("smile.smile-test", "change event2");
+}
+
+function onPrefChange2(evt) {
+  is(evt.data, testdata.dummy, "Check 'Application.prefs.set' fired a change event for a single preference");
+  Application.prefs.events.removeListener("change", onPrefChange2);
+
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/suite/smile/test/browser_ApplicationQuitting.js
@@ -0,0 +1,21 @@
+function test() {
+  let quitRequestObserver = {
+    observe: function(aSubject, aTopic, aData) {
+      ok(aTopic == "quit-application-requested" &&
+         aSubject instanceof Components.interfaces.nsISupportsPRBool,
+         "Received a quit request we're going to deny");
+      aSubject.data = true;
+    }
+  };
+  
+  // ensure that we don't accidentally quit
+  let os = Components.classes["@mozilla.org/observer-service;1"]
+                     .getService(Components.interfaces.nsIObserverService);
+  os.addObserver(quitRequestObserver, "quit-application-requested", false);
+  
+  ok(!Application.quit(),    "Tried to quit - and didn't succeed");
+  ok(!Application.restart(), "Tried to restart - and didn't succeed");
+  
+  // clean up
+  os.removeObserver(quitRequestObserver, "quit-application-requested", false);
+}
new file mode 100644
--- /dev/null
+++ b/suite/smile/test/browser_ApplicationStorage.js
@@ -0,0 +1,30 @@
+function test() {
+  // test for existence of values
+  var hasItem = Application.storage.has("smile-test-missing");
+  is(hasItem, false, "Check 'Application.storage.has' for non-existing item");
+  Application.storage.set("smile-test", "dummy");
+  hasItem = Application.storage.has("smile-test");
+  is(hasItem, true, "Check 'Application.storage.has' for existing item");
+
+  // test getting non-existing and existing values
+  var itemValue = Application.storage.get("smile-test-missing", "default");
+  is(itemValue, "default", "Check 'Application.storage.get' for non-existing item");
+  itemValue = Application.storage.get("smile-test", "default");
+  is(itemValue, "dummy", "Check 'Application.storage.get' for existing item");
+
+  // test for overwriting an existing value
+  Application.storage.set("smile-test", "smarty");
+  itemValue = Application.storage.get("smile-test", "default");
+  is(itemValue, "smarty", "Check 'Application.storage.get' for overwritten item");
+
+  // check for change event when setting a value
+  waitForExplicitFinish();
+  Application.storage.events.addListener("change", onStorageChange);
+  Application.storage.set("smile-test", "change event");
+}
+
+function onStorageChange(evt) {
+  is(evt.data, "smile-test", "Check 'Application.storage.set' fired a change event");
+  Application.storage.events.removeListener("change", onStorageChange);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/suite/smile/test/browser_Browser.js
@@ -0,0 +1,159 @@
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+function url(spec) {
+  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+  return ios.newURI(spec, null, null);
+}
+
+var gPageA = null;
+var gPageB = null;
+
+// cached data from events
+var gTabOpenPageA = null;
+var gTabOpenPageB = null;
+var gTabOpenCount = 0;
+var gTabCloseCount = 0;
+var gTabMoveCount = 0;
+var gPageLoadCount = 0;
+
+function test() {
+  var windows = Application.windows;
+  ok(windows, "Check access to browser windows");
+  ok(windows.length, "There should be at least one browser window open");
+
+  var activeWin = Application.activeWindow;
+  activeWin.events.addListener("TabOpen", onTabOpen);
+  activeWin.events.addListener("TabClose", onTabClose);
+  activeWin.events.addListener("TabMove", onTabMove);
+
+  gPageA = activeWin.open(url("chrome://mochikit/content/browser/suite/smile/test/ContentA.html"));
+  gPageA.events.addListener("load", onPageAFirstLoad);
+
+  is(activeWin.tabs.length, 2, "Checking length of 'Browser.tabs' after opening 1 additional tab");
+
+  waitForExplicitFinish();
+
+  function execAfterOpen() {
+    executeSoon(afterOpen);
+  }
+
+  function onPageAFirstLoad(event) {
+    gPageA.events.removeListener("load", onPageAFirstLoad);
+    is(gPageA.uri.spec, event.data.uri.spec, "Checking event browser tab is equal to page A");
+
+    gPageB = activeWin.open(url("chrome://mochikit/content/browser/suite/smile/test/ContentB.html"));
+    gPageB.events.addListener("load", execAfterOpen);
+    gPageB.focus();
+
+    is(activeWin.tabs.length, 3, "Checking length of 'Browser.tabs' after opening a second additional tab");
+    is(activeWin.activeTab.index, gPageB.index, "Checking 'Browser.activeTab' after setting focus");
+  }
+
+  // need to wait for the url's to be refreshed during the load
+  function afterOpen(event) {
+    gPageB.events.removeListener("load", execAfterOpen);
+    // check actuals
+    is(gPageA.uri.spec, "chrome://mochikit/content/browser/suite/smile/test/ContentA.html", "Checking 'BrowserTab.uri' after opening");
+    is(gPageB.uri.spec, "chrome://mochikit/content/browser/suite/smile/test/ContentB.html", "Checking 'BrowserTab.uri' after opening");
+
+    // check cached values from TabOpen event
+    is(gPageA.uri.spec, gTabOpenPageA.uri.spec, "Checking first browser tab open is equal to page A");
+    is(gPageB.uri.spec, gTabOpenPageB.uri.spec, "Checking second browser tab open is equal to page B");
+    // check event
+    is(gTabOpenCount, 2, "Checking event handler for tab open");
+
+    // test document access
+    var test1 = gPageA.document.getElementById("test1");
+    ok(test1, "Checking existence of element in content DOM");
+    is(test1.innerHTML, "A", "Checking content of element in content DOM");
+
+    // test moving tab
+    gPageA.moveToEnd();
+    is(gPageA.index, 2, "Checking index after moving tab");
+
+    // check event
+    is(gTabMoveCount, 1, "Checking event handler for tab move");
+
+    let browser = gBrowser.getBrowserAtIndex(gPageB.index);
+    browser.addProgressListener({
+      onStateChange: function(webProgress, request, stateFlags, status) {
+        const complete = Ci.nsIWebProgressListener.STATE_IS_WINDOW +
+                         Ci.nsIWebProgressListener.STATE_IS_NETWORK +
+                         Ci.nsIWebProgressListener.STATE_STOP;
+        if ((stateFlags & complete) == complete) {
+          browser.removeProgressListener(this);
+          onPageBLoadComplete();
+        }
+      },
+
+      onLocationChange: function() { return 0; },
+      onProgressChange: function() { return 0; },
+      onStatusChange: function() { return 0; },
+      onSecurityChange: function() { return 0; },
+      QueryInterface: function(iid) {
+        if (iid.equals(Ci.nsISupportsWeakReference) ||
+           iid.equals(Ci.nsIWebProgressListener) ||
+           iid.equals(Ci.nsISupports))
+           return this;
+
+        throw Components.results.NS_ERROR_NO_INTERFACE;
+      }
+    });
+
+    // test loading new content with a frame into a tab
+    // the event will be checked in afterClose
+    gPageB.events.addListener("load", onPageBLoadWithFrames);
+    gPageB.load(url("chrome://mochikit/content/browser/suite/smile/test/ContentWithFrames.html"));
+  }
+
+  function onPageBLoadWithFrames(event) {
+    gPageLoadCount++;
+  }
+
+  function onPageBLoadComplete() {
+    gPageB.events.removeListener("load", onPageBLoadWithFrames);
+    // check page load with frame event
+    is(gPageLoadCount, 1, "Checking load count after loading new content with a frame");
+
+    // test loading new content into a tab
+    // the event will be checked in onPageLoad
+    gPageA.events.addListener("load", onPageASecondLoad);
+    gPageA.load(url("chrome://mochikit/content/browser/suite/smile/test/ContentB.html"));
+  }
+
+  function onPageASecondLoad(event) {
+    gPageA.events.removeListener("load", onPageASecondLoad);
+    is(gPageA.uri.spec, "chrome://mochikit/content/browser/suite/smile/test/ContentB.html", "Checking 'BrowserTab.uri' after loading new content");
+
+    // start testing closing tabs
+    // the event will be checked in afterClose
+    // use executeSoon so the onPageASecondLoad
+    // has a chance to finish first
+    gPageA.close();
+    gPageB.close();
+
+    is(gTabCloseCount, 2, "Checking that tabs closed");
+    is(activeWin.tabs.length, 1, "Checking length of 'Browser.tabs' after closing 2 tabs");
+    finish();
+  }
+}
+
+function onTabOpen(event) {
+  gTabOpenCount++;
+
+  // cache these values so we can check them later (after loading completes)
+  if (gTabOpenCount == 1)
+    gTabOpenPageA = event.data;
+
+  if (gTabOpenCount == 2)
+    gTabOpenPageB = event.data;
+}
+
+function onTabClose(event) {
+  gTabCloseCount++;
+}
+
+function onTabMove(event) {
+  gTabMoveCount++;
+}
new file mode 100644
--- /dev/null
+++ b/suite/smile/test/browser_Extensions.js
@@ -0,0 +1,126 @@
+// The various pieces that we'll be testing
+var testdata = {
+  dummyid: "smile-dummy-extension@mozilla.org",
+  dummyname: "Dummy Extension",
+  inspectorid: "inspector@mozilla.org",
+  inspectorname: "DOM Inspector",
+  missing: "smile.smile-test-missing",
+  dummy: "smile.smile-test"
+};
+var gLastEvent = "";
+
+function test() {
+  // test to see if the extensions object is available
+  ok(Application.extensions, "Check for the 'Extensions' object");
+
+  // test to see if a non-existant extension exists
+  ok(!Application.extensions.has(testdata.dummyid), "Check non-existant extension for existance");
+
+  // BUG 420028: Must find a way to add a dummy extension for test suite
+  return;
+
+  // test to see if an extension exists
+  ok(Application.extensions.has(testdata.inspectorid), "Check extension for existance");
+
+  var inspector = Application.extensions.get(testdata.inspectorid);
+  is(inspector.id, testdata.inspectorid, "Check 'Extension.id' for known extension");
+  is(inspector.name, testdata.inspectorname, "Check 'Extension.name' for known extension");
+  // The known version number changes too frequently to hardcode in
+  ok(inspector.version, "Check 'Extension.version' for known extension");
+  ok(inspector.firstRun, "Check 'Extension.firstRun' for known extension");
+  ok(inspector.enabled, "Check 'Extension.enabled' for known extension");
+
+  // test to see if extension find works
+  is(Application.extensions.all.length, 1, "Check a find for all extensions");
+  // STORAGE TESTING
+  // Make sure the we are given the same extension (cached) so things like .storage work right
+  inspector.storage.set("test", "simple check");
+  ok(inspector.storage.has("test"), "Checking that extension storage worked");
+
+  var inspector2 = Application.extensions.get(testdata.inspectorid);
+  is(inspector2.id, testdata.inspectorid, "Check 'Extension.id' for known extension - from cache");
+  ok(inspector.storage.has("test"), "Checking that extension storage worked - from cache");
+  is(inspector2.storage.get("test", "cache"), inspector.storage.get("test", "original"), "Checking that the storage of same extension is correct - from cache");
+
+  inspector.events.addListener("disable", onGenericEvent);
+  inspector.events.addListener("enable", onGenericEvent);
+  inspector.events.addListener("uninstall", onGenericEvent);
+  inspector.events.addListener("cancel", onGenericEvent);
+
+  var extmgr = Components.classes["@mozilla.org/extensions/manager;1"]
+                         .getService(Components.interfaces.nsIExtensionManager);
+
+  extmgr.disableItem(testdata.inspectorid);
+  is(gLastEvent, "disable", "Checking that disable event is fired");
+
+  // enabling after a disable will only fire a 'cancel' event
+  // see - http://mxr.mozilla.org/seamonkey/source/toolkit/mozapps/extensions/src/nsExtensionManager.js.in#5216
+  extmgr.enableItem(testdata.inspectorid);
+  is(gLastEvent, "cancel", "Checking that enable (cancel) event is fired");
+
+  extmgr.uninstallItem(testdata.inspectorid);
+  is(gLastEvent, "uninstall", "Checking that uninstall event is fired");
+
+  extmgr.cancelUninstallItem(testdata.inspectorid);
+  is(gLastEvent, "cancel", "Checking that cancel event is fired");
+
+  // PREF TESTING
+  // Reset the install event preference, so that we can test it again later
+  inspector.prefs.get("install-event-fired").reset();
+
+  // test the value of the preference root
+  is(Application.extensions.all[0].prefs.root, "extensions.inspector@mozilla.org.", "Check an extension preference root");
+
+  // test getting non-existing values
+  var itemValue = inspector.prefs.getValue(testdata.missing, "default");
+  is(itemValue, "default", "Check 'Extension.prefs.getValue' for non-existing item");
+
+  is(inspector.prefs.get(testdata.missing), null, "Check 'Extension.prefs.get' for non-existing item");
+
+  // test setting and getting a value
+  inspector.prefs.setValue(testdata.dummy, "dummy");
+  itemValue = inspector.prefs.getValue(testdata.dummy, "default");
+  is(itemValue, "dummy", "Check 'Extension.prefs.getValue' for existing item");
+
+  // test for overwriting an existing value
+  inspector.prefs.setValue(testdata.dummy, "smarty");
+  itemValue = inspector.prefs.getValue(testdata.dummy, "default");
+  is(itemValue, "smarty", "Check 'Extension.prefs.getValue' for overwritten item");
+
+  // test setting and getting a value
+  inspector.prefs.get(testdata.dummy).value = "dummy2";
+  itemValue = inspector.prefs.get(testdata.dummy).value;
+  is(itemValue, "dummy2", "Check 'Extension.prefs.get().value' for existing item");
+
+  // test resetting a pref [since there is no default value, the pref should disappear]
+  inspector.prefs.get(testdata.dummy).reset();
+  var itemValue = inspector.prefs.getValue(testdata.dummy, "default");
+  is(itemValue, "default", "Check 'Extension.prefs.getValue' for reset pref");
+
+  // test to see if a non-existant property exists
+  ok(!inspector.prefs.has(testdata.dummy), "Check non-existant property for existance");
+
+  waitForExplicitFinish();
+  inspector.prefs.events.addListener("change", onPrefChange);
+  inspector.prefs.setValue("smile.smile-test", "change event");
+}
+
+function onGenericEvent(event) {
+  gLastEvent = event.type;
+}
+
+function onPrefChange(evt) {
+  var inspector3 = Application.extensions.get(testdata.inspectorid);
+
+  is(evt.data, testdata.dummy, "Check 'Extension.prefs.set' fired a change event");
+  inspector3.prefs.events.removeListener("change", onPrefChange);
+
+  inspector3.prefs.get("smile.smile-test").events.addListener("change", onPrefChange2);
+  inspector3.prefs.setValue("smile.smile-test", "change event2");
+}
+
+function onPrefChange2(evt) {
+  is(evt.data, testdata.dummy, "Check 'Extension.prefs.set' fired a change event for a single preference");
+
+  finish();
+}