Merge from mozilla-central.
authorDavid Anderson <danderson@mozilla.com>
Wed, 18 Jan 2012 13:39:48 -0800
changeset 112440 8eb8ea0086b6506d8f33aaf83aac3efdba31e149
parent 112439 49bc8d35a21ec182c2e0a421bac7045d6049500d (current diff)
parent 87610 58e933465c3624555701e202085c87d024a3bf41 (diff)
child 112441 1d622f8be014c0960261ecbed1fb9873a5fee622
push id239
push userakeybl@mozilla.com
push dateThu, 03 Jan 2013 21:54:43 +0000
treeherdermozilla-release@3a7b66445659 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone12.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge from mozilla-central.
b2g/installer/package-manifest.in
browser/devtools/highlighter/Makefile.in
browser/devtools/highlighter/test/Makefile.in
browser/devtools/highlighter/test/browser_inspector_bug_665880.js
browser/devtools/highlighter/test/browser_inspector_bug_674871.js
browser/devtools/highlighter/test/browser_inspector_editor.js
browser/devtools/highlighter/test/browser_inspector_highlighter.js
browser/devtools/highlighter/test/browser_inspector_iframeTest.js
browser/devtools/highlighter/test/browser_inspector_initialization.js
browser/devtools/highlighter/test/browser_inspector_registertools.js
browser/devtools/highlighter/test/browser_inspector_scrolling.js
browser/devtools/highlighter/test/browser_inspector_treePanel_click.js
browser/devtools/highlighter/test/browser_inspector_treeSelection.js
browser/devtools/jar.mn
browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
config/autoconf.mk.in
configure.in
content/base/src/nsXMLHttpRequest.cpp
content/base/src/nsXMLHttpRequest.h
content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
content/canvas/test/test_canvas.html
content/html/content/src/nsHTMLTableElement.cpp
content/html/content/src/nsHTMLTableSectionElement.cpp
dom/Makefile.in
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/dom-config.mk
dom/plugins/base/nsNPAPIPlugin.cpp
dom/plugins/base/nsNPAPIPluginInstance.cpp
dom/plugins/base/nsNPAPIPluginInstance.h
dom/plugins/base/nsPluginInstanceOwner.cpp
dom/sms/src/SmsServiceFactory.cpp
dom/sms/src/SmsServiceFactory.h
dom/workers/RuntimeService.cpp
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerScope.cpp
embedding/android/AndroidManifest.xml.in
embedding/android/GeckoApp.java
embedding/android/GeckoAppShell.java
embedding/android/Makefile.in
gfx/2d/ScaledFontSkia.cpp
gfx/2d/ScaledFontSkia.h
gfx/gl/GLContextProviderCGL.mm
gfx/layers/d3d9/CanvasLayerD3D9.cpp
gfx/layers/opengl/LayerManagerOGL.cpp
gfx/thebes/gfxFont.h
gfx/thebes/gfxPlatform.cpp
gfx/thebes/gfxPlatform.h
gfx/thebes/gfxPlatformMac.cpp
gfx/thebes/gfxPlatformMac.h
gfx/thebes/gfxQuartzSurface.cpp
gfx/thebes/gfxQuartzSurface.h
gfx/thebes/gfxWindowsPlatform.cpp
image/src/imgFrame.cpp
intl/uconv/src/nsCharsetConverterManager.cpp
js/src/jsanalyze.cpp
js/src/jsanalyze.h
js/src/jsarray.cpp
js/src/jscntxt.cpp
js/src/jsfun.cpp
js/src/jsinfer.cpp
js/src/methodjit/BaseAssembler.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/FastArithmetic.cpp
js/src/methodjit/FastOps.cpp
js/src/methodjit/LoopState.cpp
js/src/methodjit/PolyIC.cpp
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
js/xpconnect/src/XPCJSRuntime.cpp
layout/base/nsLayoutUtils.cpp
layout/build/Makefile.in
layout/build/nsLayoutModule.cpp
layout/build/nsLayoutStatics.cpp
layout/generic/nsObjectFrame.cpp
layout/reftests/canvas/reftest.list
layout/reftests/reftest-sanity/reftest.list
layout/reftests/svg/as-image/reftest.list
layout/tools/reftest/reftest.js
mobile/android/base/AndroidManifest.xml.in
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/Makefile.in
mobile/xul/app/mobile.js
mobile/xul/installer/package-manifest.in
modules/libpref/src/init/all.js
mozglue/android/APKOpen.cpp
netwerk/base/src/nsInputStreamPump.cpp
netwerk/cache/nsCacheEntryDescriptor.cpp
netwerk/protocol/http/nsHttpChannel.cpp
parser/html/nsHtml5TreeOpExecutor.cpp
storage/src/mozStorageConnection.cpp
storage/src/mozStorageStatement.cpp
toolkit/components/places/nsAnnotationService.cpp
toolkit/components/places/nsNavBookmarks.cpp
uriloader/exthandler/mac/nsOSHelperAppService.mm
view/src/nsViewManager.cpp
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidJNI.cpp
widget/android/nsAppShell.cpp
widget/android/nsWindow.cpp
widget/cocoa/nsAppShell.mm
widget/cocoa/nsChildView.mm
widget/xpwidgets/GfxInfoX11.cpp
xpcom/build/nsXPComInit.cpp
xpcom/ds/nsIRecyclingAllocator.idl
xpcom/ds/nsRecyclingAllocator.cpp
xpcom/ds/nsRecyclingAllocator.h
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -151,16 +151,17 @@
 @BINPATH@/components/dom_system_b2g.xpt
 #endif
 @BINPATH@/components/dom_battery.xpt
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_geolocation.xpt
+@BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_indexeddb.xpt
 @BINPATH@/components/dom_offline.xpt
 @BINPATH@/components/dom_json.xpt
 @BINPATH@/components/dom_range.xpt
 @BINPATH@/components/dom_sidebar.xpt
 @BINPATH@/components/dom_sms.xpt
@@ -518,19 +519,17 @@
 @BINPATH@/res/entityTables/*
 #ifdef XP_MACOSX
 @BINPATH@/res/MainMenu.nib/
 #endif
 
 ; svg
 @BINPATH@/res/svg.css
 @BINPATH@/components/dom_svg.xpt
-#ifdef MOZ_SMIL
 @BINPATH@/components/dom_smil.xpt
-#endif
 
 ; [Personal Security Manager]
 ;
 @BINPATH@/@DLL_PREFIX@nssckbi@DLL_SUFFIX@
 @BINPATH@/components/pipboot.xpt
 @BINPATH@/components/pipnss.xpt
 @BINPATH@/components/pippki.xpt
 @BINPATH@/@DLL_PREFIX@nss3@DLL_SUFFIX@
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -40,17 +40,17 @@ tabbrowser {
 }
 
 .tabbrowser-tab:not([pinned]):not([fadein]) {
   max-width: 0.1px;
   min-width: 0.1px;
   opacity: 0 !important;
   -moz-transition: min-width 200ms ease-out,
                    max-width 250ms ease-out,
-                   opacity 50ms ease-out 100ms /* hide the tab for the last 100ms of the max-width transition */;
+                   opacity 50ms ease-out 180ms /* hide the tab for the last 20ms of the max-width transition */;
 }
 
 .tab-throbber:not([fadein]):not([pinned]),
 .tab-label:not([fadein]):not([pinned]),
 .tab-icon-image:not([fadein]):not([pinned]),
 .tab-close-button:not([fadein]):not([pinned]) {
   display: none;
 }
--- a/browser/components/preferences/main.js
+++ b/browser/components/preferences/main.js
@@ -366,17 +366,17 @@ var gMainPane = {
       // With 3.0, a new desktop folder - 'Downloads' was introduced for
       // platforms and versions that don't support a default system downloads
       // folder. See nsDownloadManager for details. 
       downloadFolder.label = bundlePreferences.getString("downloadsFolderName");
       iconUrlSpec = fph.getURLSpecFromFile(this._indexToFolder(1));
     } else {
       // 'Desktop'
       downloadFolder.label = bundlePreferences.getString("desktopFolderName");
-      iconUrlSpec = fph.getURLSpecFromFile(desk);
+      iconUrlSpec = fph.getURLSpecFromFile(this._getDownloadsFolder("Desktop"));
     }
     downloadFolder.image = "moz-icon://" + iconUrlSpec + "?size=16";
     
     // don't override the preference's value in UI
     return undefined;
   },
 
   /**
--- a/browser/devtools/highlighter/Makefile.in
+++ b/browser/devtools/highlighter/Makefile.in
@@ -44,15 +44,16 @@ VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 EXTRA_JS_MODULES = \
 	inspector.jsm \
 	domplate.jsm \
 	InsideOutBox.jsm \
 	TreePanel.jsm \
+	highlighter.jsm \
 	$(NULL)
 
 ifdef ENABLE_TESTS
  	DIRS += test
 endif
 
 include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/highlighter/TreePanel.jsm
+++ b/browser/devtools/highlighter/TreePanel.jsm
@@ -356,17 +356,17 @@ TreePanel.prototype = {
     if (node) {
       if (hitTwisty) {
         this.ioBox.toggleObject(node);
       } else {
         if (this.IUI.inspecting) {
           this.IUI.stopInspecting(true);
         } else {
           this.IUI.select(node, true, false);
-          this.IUI.highlighter.highlightNode(node);
+          this.IUI.highlighter.highlight(node);
         }
       }
     }
   },
 
   /**
    * Handle double-click events in the html tree panel.
    * (double-clicking an attribute value allows it to be edited)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/highlighter.jsm
@@ -0,0 +1,878 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* ***** 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 the Mozilla Highlighter Module.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Rob Campbell <rcampbell@mozilla.com> (original author)
+ *   Mihai Șucan <mihai.sucan@gmail.com>
+ *   Julian Viereck <jviereck@mozilla.com>
+ *   Paul Rouget <paul@mozilla.com>
+ *   Kyle Simpson <ksimpson@mozilla.com>
+ *   Johan Charlez <johan.charlez@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 ***** */
+
+const Cu = Components.utils;
+Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
+
+var EXPORTED_SYMBOLS = ["Highlighter"];
+
+const INSPECTOR_INVISIBLE_ELEMENTS = {
+  "head": true,
+  "base": true,
+  "basefont": true,
+  "isindex": true,
+  "link": true,
+  "meta": true,
+  "script": true,
+  "style": true,
+  "title": true,
+};
+
+/**
+ * A highlighter mechanism.
+ *
+ * The highlighter is built dynamically into the browser element.
+ * The caller is in charge of destroying the highlighter (ie, the highlighter
+ * won't be destroyed if a new tab is selected for example).
+ *
+ * API:
+ *
+ *   // Constructor and destructor.
+ *   // @param aWindow - browser.xul window.
+ *   Highlighter(aWindow); 
+ *   void destroy();
+ *
+ *   // Highlight a node.
+ *   // @param aNode - node to highlight
+ *   // @param aScroll - scroll to ensure the node is visible
+ *   void highlight(aNode, aScroll);
+ *
+ *   // Get the selected node.
+ *   DOMNode getNode();
+ *
+ *   // Lock and unlock the select node.
+ *   void lock();
+ *   void unlock();
+ *
+ *   // Show and hide the highlighter
+ *   void show();
+ *   void hide();
+ *   boolean isHidden();
+ *
+ *   // Redraw the highlighter if the visible portion of the node has changed.
+ *   void invalidateSize(aScroll);
+ *
+ *   // Is a node highlightable.
+ *   boolean isNodeHighlightable(aNode);
+ *
+ *   // Add/Remove lsiteners
+ *   // @param aEvent - event name
+ *   // @param aListener - function callback
+ *   void addListener(aEvent, aListener);
+ *   void removeListener(aEvent, aListener);
+ *
+ * Events:
+ *
+ *   "closed" - Highlighter is closing
+ *   "nodeselected" - A new node has been selected
+ *   "highlighting" - Highlighter is highlighting
+ *   "locked" - The selected node has been locked
+ *   "unlocked" - The selected ndoe has been unlocked
+ *
+ * Structure:
+ *
+ *   <stack id="highlighter-container">
+ *     <vbox id="highlighter-veil-container">...</vbox>
+ *     <box id="highlighter-controls>...</vbox>
+ *   </stack>
+ *
+ */
+
+
+/**
+ * Constructor.
+ *
+ * @param object aWindow
+ */
+function Highlighter(aWindow)
+{
+  this.chromeWin = aWindow;
+  this.tabbrowser = aWindow.gBrowser;
+  this.chromeDoc = aWindow.document;
+  this.browser = aWindow.gBrowser.selectedBrowser;
+  this.events = {};
+
+  this._init();
+}
+
+Highlighter.prototype = {
+  _init: function Highlighter__init()
+  {
+    let stack = this.browser.parentNode;
+    this.win = this.browser.contentWindow;
+    this._highlighting = false;
+
+    this.highlighterContainer = this.chromeDoc.createElement("stack");
+    this.highlighterContainer.id = "highlighter-container";
+
+    this.veilContainer = this.chromeDoc.createElement("vbox");
+    this.veilContainer.id = "highlighter-veil-container";
+
+    // The controlsBox will host the different interactive
+    // elements of the highlighter (buttons, toolbars, ...).
+    let controlsBox = this.chromeDoc.createElement("box");
+    controlsBox.id = "highlighter-controls";
+    this.highlighterContainer.appendChild(this.veilContainer);
+    this.highlighterContainer.appendChild(controlsBox);
+
+    stack.appendChild(this.highlighterContainer);
+
+    // The veil will make the whole page darker except
+    // for the region of the selected box.
+    this.buildVeil(this.veilContainer);
+
+    this.buildInfobar(controlsBox);
+
+    this.transitionDisabler = null;
+
+    this.computeZoomFactor();
+    this.unlock();
+    this.hide();
+  },
+
+  /**
+   * Destroy the nodes. Remove listeners.
+   */
+  destroy: function Highlighter_destroy()
+  {
+    this.detachKeysListeners();
+    this.detachMouseListeners();
+    this.detachPageListeners();
+
+    this.chromeWin.clearTimeout(this.transitionDisabler);
+    this.boundCloseEventHandler = null;
+    this._contentRect = null;
+    this._highlightRect = null;
+    this._highlighting = false;
+    this.veilTopBox = null;
+    this.veilLeftBox = null;
+    this.veilMiddleBox = null;
+    this.veilTransparentBox = null;
+    this.veilContainer = null;
+    this.node = null;
+    this.nodeInfo = null;
+    this.highlighterContainer.parentNode.removeChild(this.highlighterContainer);
+    this.highlighterContainer = null;
+    this.win = null
+    this.browser = null;
+    this.chromeDoc = null;
+    this.chromeWin = null;
+    this.tabbrowser = null;
+
+    this.emitEvent("closed");
+    this.removeAllListeners();
+  },
+
+  /**
+   * Show the veil, and select a node.
+   * If no node is specified, the previous selected node is highlighted if any.
+   * If no node was selected, the root element is selected.
+   *
+   * @param aNode [optional] - The node to be selected.
+   * @param aScroll [optional] boolean
+   *        Should we scroll to ensure that the selected node is visible.
+   */
+  highlight: function Highlighter_highlight(aNode, aScroll)
+  {
+    if (this.hidden)
+      this.show();
+
+    let oldNode = this.node;
+
+    if (!aNode) {
+      if (!this.node)
+        this.node = this.win.document.documentElement;
+    } else {
+      this.node = aNode;
+    }
+
+    if (oldNode !== this.node) {
+      this.updateInfobar();
+    }
+
+    this.invalidateSize(!!aScroll);
+
+    if (oldNode !== this.node) {
+      this.emitEvent("nodeselected");
+    }
+  },
+
+  /**
+   * Update the highlighter size and position.
+   */
+  invalidateSize: function Highlighter_invalidateSize(aScroll)
+  {
+    let rect = null;
+
+    if (this.node && this.isNodeHighlightable(this.node)) {
+
+      if (aScroll &&
+          this.node.scrollIntoView) { // XUL elements don't have such method
+        this.node.scrollIntoView();
+      }
+      let clientRect = this.node.getBoundingClientRect();
+      rect = LayoutHelpers.getDirtyRect(this.node);
+    }
+
+    this.highlightRectangle(rect);
+
+    this.moveInfobar();
+
+    if (this._highlighting) {
+      this.emitEvent("highlighting");
+    }
+  },
+
+  /**
+   * Returns the selected node.
+   *
+   * @returns node
+   */
+  getNode: function() {
+    return this.node;
+  },
+
+  /**
+   * Show the highlighter if it has been hidden.
+   */
+  show: function() {
+    if (!this.hidden) return;
+    this.veilContainer.removeAttribute("hidden");
+    this.nodeInfo.container.removeAttribute("hidden");
+    this.attachKeysListeners();
+    this.attachPageListeners();
+    this.invalidateSize();
+    this.hidden = false;
+  },
+
+  /**
+   * Hide the highlighter, the veil and the infobar.
+   */
+  hide: function() {
+    if (this.hidden) return;
+    this.veilContainer.setAttribute("hidden", "true");
+    this.nodeInfo.container.setAttribute("hidden", "true");
+    this.detachKeysListeners();
+    this.detachPageListeners();
+    this.hidden = true;
+  },
+
+  /**
+   * Is the highlighter visible?
+   *
+   * @return boolean
+   */
+  isHidden: function() {
+    return this.hidden;
+  },
+
+  /**
+   * Lock a node. Stops the inspection.
+   */
+  lock: function() {
+    if (this.locked === true) return;
+    this.veilContainer.setAttribute("locked", "true");
+    this.nodeInfo.container.setAttribute("locked", "true");
+    this.detachMouseListeners();
+    this.locked = true;
+    this.emitEvent("locked");
+  },
+
+  /**
+   * Start inspecting.
+   * Unlock the current node (if any), and select any node being hovered.
+   */
+  unlock: function() {
+    if (this.locked === false) return;
+    this.veilContainer.removeAttribute("locked");
+    this.nodeInfo.container.removeAttribute("locked");
+    this.attachMouseListeners();
+    this.locked = false;
+    this.emitEvent("unlocked");
+  },
+
+  /**
+   * Is the specified node highlightable?
+   *
+   * @param nsIDOMNode aNode
+   *        the DOM element in question
+   * @returns boolean
+   *          True if the node is highlightable or false otherwise.
+   */
+  isNodeHighlightable: function Highlighter_isNodeHighlightable(aNode)
+  {
+    if (aNode.nodeType != aNode.ELEMENT_NODE) {
+      return false;
+    }
+    let nodeName = aNode.nodeName.toLowerCase();
+    return !INSPECTOR_INVISIBLE_ELEMENTS[nodeName];
+  },
+  /**
+   * Build the veil:
+   *
+   * <vbox id="highlighter-veil-container">
+   *   <box id="highlighter-veil-topbox" class="highlighter-veil"/>
+   *   <hbox id="highlighter-veil-middlebox">
+   *     <box id="highlighter-veil-leftbox" class="highlighter-veil"/>
+   *     <box id="highlighter-veil-transparentbox"/>
+   *     <box id="highlighter-veil-rightbox" class="highlighter-veil"/>
+   *   </hbox>
+   *   <box id="highlighter-veil-bottombox" class="highlighter-veil"/>
+   * </vbox>
+   *
+   * @param nsIDOMElement aParent
+   *        The container of the veil boxes.
+   */
+
+  buildVeil: function Highlighter_buildVeil(aParent)
+  {
+    // We will need to resize these boxes to surround a node.
+    // See highlightRectangle().
+
+    this.veilTopBox = this.chromeDoc.createElement("box");
+    this.veilTopBox.id = "highlighter-veil-topbox";
+    this.veilTopBox.className = "highlighter-veil";
+
+    this.veilMiddleBox = this.chromeDoc.createElement("hbox");
+    this.veilMiddleBox.id = "highlighter-veil-middlebox";
+
+    this.veilLeftBox = this.chromeDoc.createElement("box");
+    this.veilLeftBox.id = "highlighter-veil-leftbox";
+    this.veilLeftBox.className = "highlighter-veil";
+
+    this.veilTransparentBox = this.chromeDoc.createElement("box");
+    this.veilTransparentBox.id = "highlighter-veil-transparentbox";
+
+    // We don't need any references to veilRightBox and veilBottomBox.
+    // These boxes are automatically resized (flex=1)
+
+    let veilRightBox = this.chromeDoc.createElement("box");
+    veilRightBox.id = "highlighter-veil-rightbox";
+    veilRightBox.className = "highlighter-veil";
+
+    let veilBottomBox = this.chromeDoc.createElement("box");
+    veilBottomBox.id = "highlighter-veil-bottombox";
+    veilBottomBox.className = "highlighter-veil";
+
+    this.veilMiddleBox.appendChild(this.veilLeftBox);
+    this.veilMiddleBox.appendChild(this.veilTransparentBox);
+    this.veilMiddleBox.appendChild(veilRightBox);
+
+    aParent.appendChild(this.veilTopBox);
+    aParent.appendChild(this.veilMiddleBox);
+    aParent.appendChild(veilBottomBox);
+  },
+
+  /**
+   * Build the node Infobar.
+   *
+   * <box id="highlighter-nodeinfobar-container">
+   *   <box id="Highlighter-nodeinfobar-arrow-top"/>
+   *   <vbox id="highlighter-nodeinfobar">
+   *     <label id="highlighter-nodeinfobar-tagname"/>
+   *     <label id="highlighter-nodeinfobar-id"/>
+   *     <vbox id="highlighter-nodeinfobar-classes"/>
+   *   </vbox>
+   *   <box id="Highlighter-nodeinfobar-arrow-bottom"/>
+   * </box>
+   *
+   * @param nsIDOMElement aParent
+   *        The container of the infobar.
+   */
+  buildInfobar: function Highlighter_buildInfobar(aParent)
+  {
+    let container = this.chromeDoc.createElement("box");
+    container.id = "highlighter-nodeinfobar-container";
+    container.setAttribute("position", "top");
+    container.setAttribute("disabled", "true");
+
+    let nodeInfobar = this.chromeDoc.createElement("hbox");
+    nodeInfobar.id = "highlighter-nodeinfobar";
+
+    let arrowBoxTop = this.chromeDoc.createElement("box");
+    arrowBoxTop.className = "highlighter-nodeinfobar-arrow";
+    arrowBoxTop.id = "highlighter-nodeinfobar-arrow-top";
+
+    let arrowBoxBottom = this.chromeDoc.createElement("box");
+    arrowBoxBottom.className = "highlighter-nodeinfobar-arrow";
+    arrowBoxBottom.id = "highlighter-nodeinfobar-arrow-bottom";
+
+    let tagNameLabel = this.chromeDoc.createElement("label");
+    tagNameLabel.id = "highlighter-nodeinfobar-tagname";
+    tagNameLabel.className = "plain";
+
+    let idLabel = this.chromeDoc.createElement("label");
+    idLabel.id = "highlighter-nodeinfobar-id";
+    idLabel.className = "plain";
+
+    let classesBox = this.chromeDoc.createElement("hbox");
+    classesBox.id = "highlighter-nodeinfobar-classes";
+
+    nodeInfobar.appendChild(tagNameLabel);
+    nodeInfobar.appendChild(idLabel);
+    nodeInfobar.appendChild(classesBox);
+    container.appendChild(arrowBoxTop);
+    container.appendChild(nodeInfobar);
+    container.appendChild(arrowBoxBottom);
+
+    aParent.appendChild(container);
+
+    let barHeight = container.getBoundingClientRect().height;
+
+    this.nodeInfo = {
+      tagNameLabel: tagNameLabel,
+      idLabel: idLabel,
+      classesBox: classesBox,
+      container: container,
+      barHeight: barHeight,
+    };
+  },
+
+  /**
+   * Highlight a rectangular region.
+   *
+   * @param object aRect
+   *        The rectangle region to highlight.
+   * @returns boolean
+   *          True if the rectangle was highlighted, false otherwise.
+   */
+  highlightRectangle: function Highlighter_highlightRectangle(aRect)
+  {
+    if (!aRect) {
+      this.unhighlight();
+      return;
+    }
+
+    let oldRect = this._contentRect;
+
+    if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left &&
+        aRect.width == oldRect.width && aRect.height == oldRect.height) {
+      return; // same rectangle
+    }
+
+    let aRectScaled = LayoutHelpers.getZoomedRect(this.win, aRect);
+
+    if (aRectScaled.left >= 0 && aRectScaled.top >= 0 &&
+        aRectScaled.width > 0 && aRectScaled.height > 0) {
+
+      this.veilTransparentBox.style.visibility = "visible";
+
+      // The bottom div and the right div are flexibles (flex=1).
+      // We don't need to resize them.
+      this.veilTopBox.style.height = aRectScaled.top + "px";
+      this.veilLeftBox.style.width = aRectScaled.left + "px";
+      this.veilMiddleBox.style.height = aRectScaled.height + "px";
+      this.veilTransparentBox.style.width = aRectScaled.width + "px";
+
+      this._highlighting = true;
+    } else {
+      this.unhighlight();
+    }
+
+    this._contentRect = aRect; // save orig (non-scaled) rect
+    this._highlightRect = aRectScaled; // and save the scaled rect.
+
+    return;
+  },
+
+  /**
+   * Clear the highlighter surface.
+   */
+  unhighlight: function Highlighter_unhighlight()
+  {
+    this._highlighting = false;
+    this.veilMiddleBox.style.height = 0;
+    this.veilTransparentBox.style.width = 0;
+    this.veilTransparentBox.style.visibility = "hidden";
+  },
+
+  /**
+   * Update node information (tagName#id.class) 
+   */
+  updateInfobar: function Highlighter_updateInfobar()
+  {
+    // Tag name
+    this.nodeInfo.tagNameLabel.textContent = this.node.tagName;
+
+    // ID
+    this.nodeInfo.idLabel.textContent = this.node.id ? "#" + this.node.id : "";
+
+    // Classes
+    let classes = this.nodeInfo.classesBox;
+    while (classes.hasChildNodes()) {
+      classes.removeChild(classes.firstChild);
+    }
+
+    if (this.node.className) {
+      let fragment = this.chromeDoc.createDocumentFragment();
+      for (let i = 0; i < this.node.classList.length; i++) {
+        let classLabel = this.chromeDoc.createElement("label");
+        classLabel.className = "highlighter-nodeinfobar-class plain";
+        classLabel.textContent = "." + this.node.classList[i];
+        fragment.appendChild(classLabel);
+      }
+      classes.appendChild(fragment);
+    }
+  },
+
+  /**
+   * Move the Infobar to the right place in the highlighter.
+   */
+  moveInfobar: function Highlighter_moveInfobar()
+  {
+    if (this._highlightRect) {
+      let winHeight = this.win.innerHeight * this.zoom;
+      let winWidth = this.win.innerWidth * this.zoom;
+
+      let rect = {top: this._highlightRect.top,
+                  left: this._highlightRect.left,
+                  width: this._highlightRect.width,
+                  height: this._highlightRect.height};
+
+      rect.top = Math.max(rect.top, 0);
+      rect.left = Math.max(rect.left, 0);
+      rect.width = Math.max(rect.width, 0);
+      rect.height = Math.max(rect.height, 0);
+
+      rect.top = Math.min(rect.top, winHeight);
+      rect.left = Math.min(rect.left, winWidth);
+
+      this.nodeInfo.container.removeAttribute("disabled");
+      // Can the bar be above the node?
+      if (rect.top < this.nodeInfo.barHeight) {
+        // No. Can we move the toolbar under the node?
+        if (rect.top + rect.height +
+            this.nodeInfo.barHeight > winHeight) {
+          // No. Let's move it inside.
+          this.nodeInfo.container.style.top = rect.top + "px";
+          this.nodeInfo.container.setAttribute("position", "overlap");
+        } else {
+          // Yes. Let's move it under the node.
+          this.nodeInfo.container.style.top = rect.top + rect.height + "px";
+          this.nodeInfo.container.setAttribute("position", "bottom");
+        }
+      } else {
+        // Yes. Let's move it on top of the node.
+        this.nodeInfo.container.style.top =
+          rect.top - this.nodeInfo.barHeight + "px";
+        this.nodeInfo.container.setAttribute("position", "top");
+      }
+
+      let barWidth = this.nodeInfo.container.getBoundingClientRect().width;
+      let left = rect.left + rect.width / 2 - barWidth / 2;
+
+      // Make sure the whole infobar is visible
+      if (left < 0) {
+        left = 0;
+        this.nodeInfo.container.setAttribute("hide-arrow", "true");
+      } else {
+        if (left + barWidth > winWidth) {
+          left = winWidth - barWidth;
+          this.nodeInfo.container.setAttribute("hide-arrow", "true");
+        } else {
+          this.nodeInfo.container.removeAttribute("hide-arrow");
+        }
+      }
+      this.nodeInfo.container.style.left = left + "px";
+    } else {
+      this.nodeInfo.container.style.left = "0";
+      this.nodeInfo.container.style.top = "0";
+      this.nodeInfo.container.setAttribute("position", "top");
+      this.nodeInfo.container.setAttribute("hide-arrow", "true");
+    }
+  },
+
+  /**
+   * Store page zoom factor.
+   */
+  computeZoomFactor: function Highlighter_computeZoomFactor() {
+    this.zoom =
+      this.win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+      .getInterface(Components.interfaces.nsIDOMWindowUtils)
+      .screenPixelsPerCSSPixel;
+  },
+
+  /////////////////////////////////////////////////////////////////////////
+  //// Event Emitter Mechanism
+
+  addListener: function Highlighter_addListener(aEvent, aListener)
+  {
+    if (!(aEvent in this.events))
+      this.events[aEvent] = [];
+    this.events[aEvent].push(aListener);
+  },
+
+  removeListener: function Highlighter_removeListener(aEvent, aListener)
+  {
+    if (!(aEvent in this.events))
+      return;
+    let idx = this.events[aEvent].indexOf(aListener);
+    if (idx > -1)
+      this.events[aEvent].splice(idx, 1);
+  },
+
+  emitEvent: function Highlighter_emitEvent(aEvent, aArgv)
+  {
+    if (!(aEvent in this.events))
+      return;
+
+    let listeners = this.events[aEvent];
+    let highlighter = this;
+    listeners.forEach(function(aListener) {
+      try {
+        aListener.apply(highlighter, aArgv);
+      } catch(e) {}
+    });
+  },
+
+  removeAllListeners: function Highlighter_removeAllIsteners()
+  {
+    for (let event in this.events) {
+      delete this.events[event];
+    }
+  },
+
+  /////////////////////////////////////////////////////////////////////////
+  //// Event Handling
+
+  attachMouseListeners: function Highlighter_attachMouseListeners()
+  {
+    this.browser.addEventListener("mousemove", this, true);
+    this.browser.addEventListener("click", this, true);
+    this.browser.addEventListener("dblclick", this, true);
+    this.browser.addEventListener("mousedown", this, true);
+    this.browser.addEventListener("mouseup", this, true);
+  },
+
+  detachMouseListeners: function Highlighter_detachMouseListeners()
+  {
+    this.browser.removeEventListener("mousemove", this, true);
+    this.browser.removeEventListener("click", this, true);
+    this.browser.removeEventListener("dblclick", this, true);
+    this.browser.removeEventListener("mousedown", this, true);
+    this.browser.removeEventListener("mouseup", this, true);
+  },
+
+  attachPageListeners: function Highlighter_attachPageListeners()
+  {
+    this.browser.addEventListener("resize", this, true);
+    this.browser.addEventListener("scroll", this, true);
+  },
+
+  detachPageListeners: function Highlighter_detachPageListeners()
+  {
+    this.browser.removeEventListener("resize", this, true);
+    this.browser.removeEventListener("scroll", this, true);
+  },
+
+  attachKeysListeners: function Highlighter_attachKeysListeners()
+  {
+    this.browser.addEventListener("keypress", this, true);
+    this.highlighterContainer.addEventListener("keypress", this, true);
+  },
+
+  detachKeysListeners: function Highlighter_detachKeysListeners()
+  {
+    this.browser.removeEventListener("keypress", this, true);
+    this.highlighterContainer.removeEventListener("keypress", this, true);
+  },
+
+  /**
+   * Generic event handler.
+   *
+   * @param nsIDOMEvent aEvent
+   *        The DOM event object.
+   */
+  handleEvent: function Highlighter_handleEvent(aEvent)
+  {
+    switch (aEvent.type) {
+      case "click":
+        this.handleClick(aEvent);
+        break;
+      case "mousemove":
+        this.handleMouseMove(aEvent);
+        break;
+      case "resize":
+      case "scroll":
+        this.computeZoomFactor();
+        this.brieflyDisableTransitions();
+        this.invalidateSize();
+        break;
+      case "dblclick":
+      case "mousedown":
+      case "mouseup":
+        aEvent.stopPropagation();
+        aEvent.preventDefault();
+        break;
+        break;
+      case "keypress":
+        switch (aEvent.keyCode) {
+          case this.chromeWin.KeyEvent.DOM_VK_RETURN:
+            this.locked ? this.unlock() : this.lock();
+            aEvent.preventDefault();
+            aEvent.stopPropagation();
+            break;
+          case this.chromeWin.KeyEvent.DOM_VK_LEFT:
+            let node;
+            if (this.node) {
+              node = this.node.parentNode;
+            } else {
+              node = this.defaultSelection;
+            }
+            if (node && this.isNodeHighlightable(node)) {
+              this.highlight(node);
+            }
+            aEvent.preventDefault();
+            aEvent.stopPropagation();
+            break;
+          case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
+            if (this.node) {
+              // Find the first child that is highlightable.
+              for (let i = 0; i < this.node.childNodes.length; i++) {
+                node = this.node.childNodes[i];
+                if (node && this.isNodeHighlightable(node)) {
+                  break;
+                }
+              }
+            } else {
+              node = this.defaultSelection;
+            }
+            if (node && this.isNodeHighlightable(node)) {
+              this.highlight(node, true);
+            }
+            aEvent.preventDefault();
+            aEvent.stopPropagation();
+            break;
+          case this.chromeWin.KeyEvent.DOM_VK_UP:
+            if (this.node) {
+              // Find a previous sibling that is highlightable.
+              node = this.node.previousSibling;
+              while (node && !this.isNodeHighlightable(node)) {
+                node = node.previousSibling;
+              }
+            } else {
+              node = this.defaultSelection;
+            }
+            if (node && this.isNodeHighlightable(node)) {
+              this.highlight(node, true);
+            }
+            aEvent.preventDefault();
+            aEvent.stopPropagation();
+            break;
+          case this.chromeWin.KeyEvent.DOM_VK_DOWN:
+            if (this.node) {
+              // Find a next sibling that is highlightable.
+              node = this.node.nextSibling;
+              while (node && !this.isNodeHighlightable(node)) {
+                node = node.nextSibling;
+              }
+            } else {
+              node = this.defaultSelection;
+            }
+            if (node && this.isNodeHighlightable(node)) {
+              this.highlight(node, true);
+            }
+            aEvent.preventDefault();
+            aEvent.stopPropagation();
+            break;
+        }
+    }
+  },
+
+  /**
+   * Disable the CSS transitions for a short time to avoid laggy animations
+   * during scrolling or resizing.
+   */
+  brieflyDisableTransitions: function Highlighter_brieflyDisableTransitions()
+  {
+   if (this.transitionDisabler) {
+     this.chromeWin.clearTimeout(this.transitionDisabler);
+   } else {
+     this.veilContainer.setAttribute("disable-transitions", "true");
+     this.nodeInfo.container.setAttribute("disable-transitions", "true");
+   }
+   this.transitionDisabler =
+     this.chromeWin.setTimeout(function() {
+       this.veilContainer.removeAttribute("disable-transitions");
+       this.nodeInfo.container.removeAttribute("disable-transitions");
+       this.transitionDisabler = null;
+     }.bind(this), 500);
+  },
+
+  /**
+   * Handle clicks.
+   *
+   * @param nsIDOMEvent aEvent
+   *        The DOM event.
+   */
+  handleClick: function Highlighter_handleClick(aEvent)
+  {
+    // Stop inspection when the user clicks on a node.
+    if (aEvent.button == 0) {
+      let win = aEvent.target.ownerDocument.defaultView;
+      this.lock();
+      win.focus();
+    }
+    aEvent.preventDefault();
+    aEvent.stopPropagation();
+  },
+
+  /**
+   * Handle mousemoves in panel.
+   *
+   * @param nsiDOMEvent aEvent
+   *        The MouseEvent triggering the method.
+   */
+  handleMouseMove: function Highlighter_handleMouseMove(aEvent)
+  {
+    let element = LayoutHelpers.getElementFromPoint(aEvent.target.ownerDocument,
+      aEvent.clientX, aEvent.clientY);
+    if (element && element != this.node) {
+      this.highlight(element);
+    }
+  },
+};
+
+///////////////////////////////////////////////////////////////////////////
+
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -47,37 +47,21 @@ const Ci = Components.interfaces;
 const Cr = Components.results;
 
 var EXPORTED_SYMBOLS = ["InspectorUI"];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/TreePanel.jsm");
 Cu.import("resource:///modules/devtools/CssRuleView.jsm");
-
-const INSPECTOR_INVISIBLE_ELEMENTS = {
-  "head": true,
-  "base": true,
-  "basefont": true,
-  "isindex": true,
-  "link": true,
-  "meta": true,
-  "script": true,
-  "style": true,
-  "title": true,
-};
+Cu.import("resource:///modules/highlighter.jsm");
+Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
 
 // Inspector notifications dispatched through the nsIObserverService.
 const INSPECTOR_NOTIFICATIONS = {
-  // Fires once the Inspector highlights an element in the page.
-  HIGHLIGHTING: "inspector-highlighting",
-
-  // Fires once the Inspector stops highlighting any element.
-  UNHIGHLIGHTING: "inspector-unhighlighting",
-
   // Fires once the Inspector completes the initialization and opens up on
   // screen.
   OPENED: "inspector-opened",
 
   // Fires once the Inspector is closed.
   CLOSED: "inspector-closed",
 
   // Fires once the Inspector is destroyed. Not fired on tab switch.
@@ -94,693 +78,16 @@ const INSPECTOR_NOTIFICATIONS = {
 
   // Event notifications for the attribute-value editor
   EDITOR_OPENED: "inspector-editor-opened",
   EDITOR_CLOSED: "inspector-editor-closed",
   EDITOR_SAVED: "inspector-editor-saved",
 };
 
 ///////////////////////////////////////////////////////////////////////////
-//// Highlighter
-
-/**
- * A highlighter mechanism.
- *
- * The highlighter is built dynamically once the Inspector is invoked:
- * <stack id="highlighter-container">
- *   <vbox id="highlighter-veil-container">...</vbox>
- *   <box id="highlighter-controls>...</vbox>
- * </stack>
- *
- * @param object aInspector
- *        The InspectorUI instance.
- */
-function Highlighter(aInspector)
-{
-  this.IUI = aInspector;
-  this._init();
-}
-
-Highlighter.prototype = {
-  _init: function Highlighter__init()
-  {
-    this.browser = this.IUI.browser;
-    this.chromeDoc = this.IUI.chromeDoc;
-
-    let stack = this.browser.parentNode;
-    this.win = this.browser.contentWindow;
-    this._highlighting = false;
-
-    this.highlighterContainer = this.chromeDoc.createElement("stack");
-    this.highlighterContainer.id = "highlighter-container";
-
-    this.veilContainer = this.chromeDoc.createElement("vbox");
-    this.veilContainer.id = "highlighter-veil-container";
-
-    // The controlsBox will host the different interactive
-    // elements of the highlighter (buttons, toolbars, ...).
-    let controlsBox = this.chromeDoc.createElement("box");
-    controlsBox.id = "highlighter-controls";
-    this.highlighterContainer.appendChild(this.veilContainer);
-    this.highlighterContainer.appendChild(controlsBox);
-
-    stack.appendChild(this.highlighterContainer);
-
-    // The veil will make the whole page darker except
-    // for the region of the selected box.
-    this.buildVeil(this.veilContainer);
-
-    this.buildInfobar(controlsBox);
-
-    if (!this.IUI.store.getValue(this.winID, "inspecting")) {
-      this.veilContainer.setAttribute("locked", true);
-      this.nodeInfo.container.setAttribute("locked", true);
-    }
-
-    this.browser.addEventListener("resize", this, true);
-    this.browser.addEventListener("scroll", this, true);
-
-    this.transitionDisabler = null;
-
-    this.computeZoomFactor();
-    this.handleResize();
-  },
-
-  /**
-   * Build the veil:
-   *
-   * <vbox id="highlighter-veil-container">
-   *   <box id="highlighter-veil-topbox" class="highlighter-veil"/>
-   *   <hbox id="highlighter-veil-middlebox">
-   *     <box id="highlighter-veil-leftbox" class="highlighter-veil"/>
-   *     <box id="highlighter-veil-transparentbox"/>
-   *     <box id="highlighter-veil-rightbox" class="highlighter-veil"/>
-   *   </hbox>
-   *   <box id="highlighter-veil-bottombox" class="highlighter-veil"/>
-   * </vbox>
-   *
-   * @param nsIDOMElement aParent
-   *        The container of the veil boxes.
-   */
-  buildVeil: function Highlighter_buildVeil(aParent)
-  {
-    // We will need to resize these boxes to surround a node.
-    // See highlightRectangle().
-
-    this.veilTopBox = this.chromeDoc.createElement("box");
-    this.veilTopBox.id = "highlighter-veil-topbox";
-    this.veilTopBox.className = "highlighter-veil";
-
-    this.veilMiddleBox = this.chromeDoc.createElement("hbox");
-    this.veilMiddleBox.id = "highlighter-veil-middlebox";
-
-    this.veilLeftBox = this.chromeDoc.createElement("box");
-    this.veilLeftBox.id = "highlighter-veil-leftbox";
-    this.veilLeftBox.className = "highlighter-veil";
-
-    this.veilTransparentBox = this.chromeDoc.createElement("box");
-    this.veilTransparentBox.id = "highlighter-veil-transparentbox";
-
-    // We don't need any references to veilRightBox and veilBottomBox.
-    // These boxes are automatically resized (flex=1)
-
-    let veilRightBox = this.chromeDoc.createElement("box");
-    veilRightBox.id = "highlighter-veil-rightbox";
-    veilRightBox.className = "highlighter-veil";
-
-    let veilBottomBox = this.chromeDoc.createElement("box");
-    veilBottomBox.id = "highlighter-veil-bottombox";
-    veilBottomBox.className = "highlighter-veil";
-
-    this.veilMiddleBox.appendChild(this.veilLeftBox);
-    this.veilMiddleBox.appendChild(this.veilTransparentBox);
-    this.veilMiddleBox.appendChild(veilRightBox);
-
-    aParent.appendChild(this.veilTopBox);
-    aParent.appendChild(this.veilMiddleBox);
-    aParent.appendChild(veilBottomBox);
-  },
-
-  /**
-   * Build the node Infobar.
-   *
-   * <box id="highlighter-nodeinfobar-container">
-   *   <box id="Highlighter-nodeinfobar-arrow-top"/>
-   *   <vbox id="highlighter-nodeinfobar">
-   *     <label id="highlighter-nodeinfobar-tagname"/>
-   *     <label id="highlighter-nodeinfobar-id"/>
-   *     <vbox id="highlighter-nodeinfobar-classes"/>
-   *   </vbox>
-   *   <box id="Highlighter-nodeinfobar-arrow-bottom"/>
-   * </box>
-   *
-   * @param nsIDOMElement aParent
-   *        The container of the infobar.
-   */
-  buildInfobar: function Highlighter_buildInfobar(aParent)
-  {
-    let container = this.chromeDoc.createElement("box");
-    container.id = "highlighter-nodeinfobar-container";
-    container.setAttribute("position", "top");
-    container.setAttribute("disabled", "true");
-
-    let nodeInfobar = this.chromeDoc.createElement("hbox");
-    nodeInfobar.id = "highlighter-nodeinfobar";
-
-    let arrowBoxTop = this.chromeDoc.createElement("box");
-    arrowBoxTop.className = "highlighter-nodeinfobar-arrow";
-    arrowBoxTop.id = "highlighter-nodeinfobar-arrow-top";
-
-    let arrowBoxBottom = this.chromeDoc.createElement("box");
-    arrowBoxBottom.className = "highlighter-nodeinfobar-arrow";
-    arrowBoxBottom.id = "highlighter-nodeinfobar-arrow-bottom";
-
-    let tagNameLabel = this.chromeDoc.createElement("label");
-    tagNameLabel.id = "highlighter-nodeinfobar-tagname";
-    tagNameLabel.className = "plain";
-
-    let idLabel = this.chromeDoc.createElement("label");
-    idLabel.id = "highlighter-nodeinfobar-id";
-    idLabel.className = "plain";
-
-    let classesBox = this.chromeDoc.createElement("hbox");
-    classesBox.id = "highlighter-nodeinfobar-classes";
-
-    nodeInfobar.appendChild(tagNameLabel);
-    nodeInfobar.appendChild(idLabel);
-    nodeInfobar.appendChild(classesBox);
-    container.appendChild(arrowBoxTop);
-    container.appendChild(nodeInfobar);
-    container.appendChild(arrowBoxBottom);
-
-    aParent.appendChild(container);
-
-    let barHeight = container.getBoundingClientRect().height;
-
-    this.nodeInfo = {
-      tagNameLabel: tagNameLabel,
-      idLabel: idLabel,
-      classesBox: classesBox,
-      container: container,
-      barHeight: barHeight,
-    };
-  },
-
-  /**
-   * Destroy the nodes.
-   */
-  destroy: function Highlighter_destroy()
-  {
-    this.IUI.win.clearTimeout(this.transitionDisabler);
-    this.browser.removeEventListener("scroll", this, true);
-    this.browser.removeEventListener("resize", this, true);
-    this.boundCloseEventHandler = null;
-    this._contentRect = null;
-    this._highlightRect = null;
-    this._highlighting = false;
-    this.veilTopBox = null;
-    this.veilLeftBox = null;
-    this.veilMiddleBox = null;
-    this.veilTransparentBox = null;
-    this.veilContainer = null;
-    this.node = null;
-    this.nodeInfo = null;
-    this.highlighterContainer.parentNode.removeChild(this.highlighterContainer);
-    this.highlighterContainer = null;
-    this.win = null
-    this.browser = null;
-    this.chromeDoc = null;
-    this.IUI = null;
-  },
-
-  /**
-   * Is the highlighter highlighting? Public method for querying the state
-   * of the highlighter.
-   */
-  get isHighlighting() {
-    return this._highlighting;
-  },
-
-  /**
-   * Highlight this.node, unhilighting first if necessary.
-   *
-   * @param boolean aScroll
-   *        Boolean determining whether to scroll or not.
-   */
-  highlight: function Highlighter_highlight(aScroll)
-  {
-    let rect = null;
-
-    if (this.node && this.isNodeHighlightable(this.node)) {
-
-      if (aScroll) {
-        this.node.scrollIntoView();
-      }
-
-      let clientRect = this.node.getBoundingClientRect();
-
-      // Go up in the tree of frames to determine the correct rectangle.
-      // clientRect is read-only, we need to be able to change properties.
-      rect = {top: clientRect.top,
-              left: clientRect.left,
-              width: clientRect.width,
-              height: clientRect.height};
-
-      let frameWin = this.node.ownerDocument.defaultView;
-
-      // We iterate through all the parent windows.
-      while (true) {
-
-        // Does the selection overflow on the right of its window?
-        let diffx = frameWin.innerWidth - (rect.left + rect.width);
-        if (diffx < 0) {
-          rect.width += diffx;
-        }
-
-        // Does the selection overflow on the bottom of its window?
-        let diffy = frameWin.innerHeight - (rect.top + rect.height);
-        if (diffy < 0) {
-          rect.height += diffy;
-        }
-
-        // Does the selection overflow on the left of its window?
-        if (rect.left < 0) {
-          rect.width += rect.left;
-          rect.left = 0;
-        }
-
-        // Does the selection overflow on the top of its window?
-        if (rect.top < 0) {
-          rect.height += rect.top;
-          rect.top = 0;
-        }
-
-        // Selection has been clipped to fit in its own window.
-
-        // Are we in the top-level window?
-        if (frameWin.parent === frameWin || !frameWin.frameElement) {
-          break;
-        }
-
-        // We are in an iframe.
-        // We take into account the parent iframe position and its
-        // offset (borders and padding).
-        let frameRect = frameWin.frameElement.getBoundingClientRect();
-
-        let [offsetTop, offsetLeft] =
-          this.IUI.getIframeContentOffset(frameWin.frameElement);
-
-        rect.top += frameRect.top + offsetTop;
-        rect.left += frameRect.left + offsetLeft;
-
-        frameWin = frameWin.parent;
-      }
-    }
-
-    this.highlightRectangle(rect);
-
-    this.moveInfobar();
-
-    if (this._highlighting) {
-      Services.obs.notifyObservers(null,
-        INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, null);
-    }
-  },
-
-  /**
-   * Highlight the given node.
-   *
-   * @param nsIDOMNode aNode
-   *        a DOM element to be highlighted
-   * @param object aParams
-   *        extra parameters object
-   */
-  highlightNode: function Highlighter_highlightNode(aNode, aParams)
-  {
-    this.node = aNode;
-    this.updateInfobar();
-    this.highlight(aParams && aParams.scroll);
-  },
-
-  /**
-   * Highlight a rectangular region.
-   *
-   * @param object aRect
-   *        The rectangle region to highlight.
-   * @returns boolean
-   *          True if the rectangle was highlighted, false otherwise.
-   */
-  highlightRectangle: function Highlighter_highlightRectangle(aRect)
-  {
-    if (!aRect) {
-      this.unhighlight();
-      return;
-    }
-
-    let oldRect = this._contentRect;
-
-    if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left &&
-        aRect.width == oldRect.width && aRect.height == oldRect.height) {
-      return this._highlighting; // same rectangle
-    }
-
-    // adjust rect for zoom scaling
-    let aRectScaled = {};
-    for (let prop in aRect) {
-      aRectScaled[prop] = aRect[prop] * this.zoom;
-    }
-
-    if (aRectScaled.left >= 0 && aRectScaled.top >= 0 &&
-        aRectScaled.width > 0 && aRectScaled.height > 0) {
-
-      this.veilTransparentBox.style.visibility = "visible";
-
-      // The bottom div and the right div are flexibles (flex=1).
-      // We don't need to resize them.
-      this.veilTopBox.style.height = aRectScaled.top + "px";
-      this.veilLeftBox.style.width = aRectScaled.left + "px";
-      this.veilMiddleBox.style.height = aRectScaled.height + "px";
-      this.veilTransparentBox.style.width = aRectScaled.width + "px";
-
-      this._highlighting = true;
-    } else {
-      this.unhighlight();
-    }
-
-    this._contentRect = aRect; // save orig (non-scaled) rect
-    this._highlightRect = aRectScaled; // and save the scaled rect.
-
-    return this._highlighting;
-  },
-
-  /**
-   * Clear the highlighter surface.
-   */
-  unhighlight: function Highlighter_unhighlight()
-  {
-    this._highlighting = false;
-    this.veilMiddleBox.style.height = 0;
-    this.veilTransparentBox.style.width = 0;
-    this.veilTransparentBox.style.visibility = "hidden";
-    Services.obs.notifyObservers(null,
-      INSPECTOR_NOTIFICATIONS.UNHIGHLIGHTING, null);
-  },
-
-  /**
-   * Update node information (tagName#id.class) 
-   */
-  updateInfobar: function Highlighter_updateInfobar()
-  {
-    // Tag name
-    this.nodeInfo.tagNameLabel.textContent = this.node.tagName;
-
-    // ID
-    this.nodeInfo.idLabel.textContent = this.node.id ? "#" + this.node.id : "";
-
-    // Classes
-    let classes = this.nodeInfo.classesBox;
-    while (classes.hasChildNodes()) {
-      classes.removeChild(classes.firstChild);
-    }
-
-    if (this.node.className) {
-      let fragment = this.chromeDoc.createDocumentFragment();
-      for (let i = 0; i < this.node.classList.length; i++) {
-        let classLabel = this.chromeDoc.createElement("label");
-        classLabel.className = "highlighter-nodeinfobar-class plain";
-        classLabel.textContent = "." + this.node.classList[i];
-        fragment.appendChild(classLabel);
-      }
-      classes.appendChild(fragment);
-    }
-  },
-
-  /**
-   * Move the Infobar to the right place in the highlighter.
-   */
-  moveInfobar: function Highlighter_moveInfobar()
-  {
-    if (this._highlightRect) {
-      let winHeight = this.win.innerHeight * this.zoom;
-      let winWidth = this.win.innerWidth * this.zoom;
-
-      let rect = {top: this._highlightRect.top,
-                  left: this._highlightRect.left,
-                  width: this._highlightRect.width,
-                  height: this._highlightRect.height};
-
-      rect.top = Math.max(rect.top, 0);
-      rect.left = Math.max(rect.left, 0);
-      rect.width = Math.max(rect.width, 0);
-      rect.height = Math.max(rect.height, 0);
-
-      rect.top = Math.min(rect.top, winHeight);
-      rect.left = Math.min(rect.left, winWidth);
-
-      this.nodeInfo.container.removeAttribute("disabled");
-      // Can the bar be above the node?
-      if (rect.top < this.nodeInfo.barHeight) {
-        // No. Can we move the toolbar under the node?
-        if (rect.top + rect.height +
-            this.nodeInfo.barHeight > winHeight) {
-          // No. Let's move it inside.
-          this.nodeInfo.container.style.top = rect.top + "px";
-          this.nodeInfo.container.setAttribute("position", "overlap");
-        } else {
-          // Yes. Let's move it under the node.
-          this.nodeInfo.container.style.top = rect.top + rect.height + "px";
-          this.nodeInfo.container.setAttribute("position", "bottom");
-        }
-      } else {
-        // Yes. Let's move it on top of the node.
-        this.nodeInfo.container.style.top =
-          rect.top - this.nodeInfo.barHeight + "px";
-        this.nodeInfo.container.setAttribute("position", "top");
-      }
-
-      let barWidth = this.nodeInfo.container.getBoundingClientRect().width;
-      let left = rect.left + rect.width / 2 - barWidth / 2;
-
-      // Make sure the whole infobar is visible
-      if (left < 0) {
-        left = 0;
-        this.nodeInfo.container.setAttribute("hide-arrow", "true");
-      } else {
-        if (left + barWidth > winWidth) {
-          left = winWidth - barWidth;
-          this.nodeInfo.container.setAttribute("hide-arrow", "true");
-        } else {
-          this.nodeInfo.container.removeAttribute("hide-arrow");
-        }
-      }
-      this.nodeInfo.container.style.left = left + "px";
-    } else {
-      this.nodeInfo.container.style.left = "0";
-      this.nodeInfo.container.style.top = "0";
-      this.nodeInfo.container.setAttribute("position", "top");
-      this.nodeInfo.container.setAttribute("hide-arrow", "true");
-    }
-  },
-
-  /**
-   * Return the midpoint of a line from pointA to pointB.
-   *
-   * @param object aPointA
-   *        An object with x and y properties.
-   * @param object aPointB
-   *        An object with x and y properties.
-   * @returns object
-   *          An object with x and y properties.
-   */
-  midPoint: function Highlighter_midPoint(aPointA, aPointB)
-  {
-    let pointC = { };
-    pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
-    pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
-    return pointC;
-  },
-
-  /**
-   * Return the node under the highlighter rectangle. Useful for testing.
-   * Calculation based on midpoint of diagonal from top left to bottom right
-   * of panel.
-   *
-   * @returns nsIDOMNode|null
-   *          Returns the node under the current highlighter rectangle. Null is
-   *          returned if there is no node highlighted.
-   */
-  get highlitNode()
-  {
-    // Not highlighting? Bail.
-    if (!this._highlighting || !this._contentRect) {
-      return null;
-    }
-
-    let a = {
-      x: this._contentRect.left,
-      y: this._contentRect.top
-    };
-
-    let b = {
-      x: a.x + this._contentRect.width,
-      y: a.y + this._contentRect.height
-    };
-
-    // Get midpoint of diagonal line.
-    let midpoint = this.midPoint(a, b);
-
-    return this.IUI.elementFromPoint(this.win.document, midpoint.x,
-      midpoint.y);
-  },
-
-  /**
-   * Is the specified node highlightable?
-   *
-   * @param nsIDOMNode aNode
-   *        the DOM element in question
-   * @returns boolean
-   *          True if the node is highlightable or false otherwise.
-   */
-  isNodeHighlightable: function Highlighter_isNodeHighlightable(aNode)
-  {
-    if (aNode.nodeType != aNode.ELEMENT_NODE) {
-      return false;
-    }
-    let nodeName = aNode.nodeName.toLowerCase();
-    return !INSPECTOR_INVISIBLE_ELEMENTS[nodeName];
-  },
-
-  /**
-   * Store page zoom factor.
-   */
-  computeZoomFactor: function Highlighter_computeZoomFactor() {
-    this.zoom =
-      this.win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-      .getInterface(Components.interfaces.nsIDOMWindowUtils)
-      .screenPixelsPerCSSPixel;
-  },
-
-  /////////////////////////////////////////////////////////////////////////
-  //// Event Handling
-
-  attachInspectListeners: function Highlighter_attachInspectListeners()
-  {
-    this.browser.addEventListener("mousemove", this, true);
-    this.browser.addEventListener("click", this, true);
-    this.browser.addEventListener("dblclick", this, true);
-    this.browser.addEventListener("mousedown", this, true);
-    this.browser.addEventListener("mouseup", this, true);
-  },
-
-  detachInspectListeners: function Highlighter_detachInspectListeners()
-  {
-    this.browser.removeEventListener("mousemove", this, true);
-    this.browser.removeEventListener("click", this, true);
-    this.browser.removeEventListener("dblclick", this, true);
-    this.browser.removeEventListener("mousedown", this, true);
-    this.browser.removeEventListener("mouseup", this, true);
-  },
-
-
-  /**
-   * Generic event handler.
-   *
-   * @param nsIDOMEvent aEvent
-   *        The DOM event object.
-   */
-  handleEvent: function Highlighter_handleEvent(aEvent)
-  {
-    switch (aEvent.type) {
-      case "click":
-        this.handleClick(aEvent);
-        break;
-      case "mousemove":
-        this.handleMouseMove(aEvent);
-        break;
-      case "resize":
-        this.computeZoomFactor();
-        this.brieflyDisableTransitions();
-        this.handleResize(aEvent);
-        break;
-      case "dblclick":
-      case "mousedown":
-      case "mouseup":
-        aEvent.stopPropagation();
-        aEvent.preventDefault();
-        break;
-      case "scroll":
-        this.brieflyDisableTransitions();
-        this.highlight();
-        break;
-    }
-  },
-
-  /**
-   * Disable the CSS transitions for a short time to avoid laggy animations
-   * during scrolling or resizing.
-   */
-  brieflyDisableTransitions: function Highlighter_brieflyDisableTransitions()
-  {
-   if (this.transitionDisabler) {
-     this.IUI.win.clearTimeout(this.transitionDisabler);
-   } else {
-     this.veilContainer.setAttribute("disable-transitions", "true");
-     this.nodeInfo.container.setAttribute("disable-transitions", "true");
-   }
-   this.transitionDisabler =
-     this.IUI.win.setTimeout(function() {
-       this.veilContainer.removeAttribute("disable-transitions");
-       this.nodeInfo.container.removeAttribute("disable-transitions");
-       this.transitionDisabler = null;
-     }.bind(this), 500);
-  },
-
-  /**
-   * Handle clicks.
-   *
-   * @param nsIDOMEvent aEvent
-   *        The DOM event.
-   */
-  handleClick: function Highlighter_handleClick(aEvent)
-  {
-    // Stop inspection when the user clicks on a node.
-    if (aEvent.button == 0) {
-      let win = aEvent.target.ownerDocument.defaultView;
-      this.IUI.stopInspecting();
-      win.focus();
-    }
-    aEvent.preventDefault();
-    aEvent.stopPropagation();
-  },
-
-  /**
-   * Handle mousemoves in panel when InspectorUI.inspecting is true.
-   *
-   * @param nsiDOMEvent aEvent
-   *        The MouseEvent triggering the method.
-   */
-  handleMouseMove: function Highlighter_handleMouseMove(aEvent)
-  {
-    let element = this.IUI.elementFromPoint(aEvent.target.ownerDocument,
-      aEvent.clientX, aEvent.clientY);
-    if (element && element != this.node) {
-      this.IUI.inspectNode(element);
-    }
-  },
-
-  /**
-   * Handle window resize events.
-   */
-  handleResize: function Highlighter_handleResize()
-  {
-    this.highlight();
-  },
-};
-
-///////////////////////////////////////////////////////////////////////////
 //// InspectorUI
 
 /**
  * Main controller class for the Inspector.
  *
  * @constructor
  * @param nsIDOMWindow aWindow
  *        The chrome window for which the Inspector instance is created.
@@ -970,18 +277,21 @@ InspectorUI.prototype = {
 
     // initialize the HTML Breadcrumbs
     this.breadcrumbs = new HTMLBreadcrumbs(this);
 
     this.isDirty = false;
 
     this.progressListener = new InspectorProgressListener(this);
 
+    this.chromeWin.addEventListener("keypress", this, false);
+
     // initialize the highlighter
-    this.initializeHighlighter();
+    this.highlighter = new Highlighter(this.chromeWin);
+    this.highlighterReady();
   },
 
   /**
    * Register the Rule View in the Sidebar.
    */
   registerRuleView: function IUI_registerRuleView()
   {
     let isOpen = this.isRuleViewOpen.bind(this);
@@ -1008,27 +318,16 @@ InspectorUI.prototype = {
    * Register and initialize any included tools.
    */
   initTools: function IUI_initTools()
   {
     // Extras go here.
   },
 
   /**
-   * Initialize highlighter.
-   */
-  initializeHighlighter: function IUI_initializeHighlighter()
-  {
-    this.highlighter = new Highlighter(this);
-    this.browser.addEventListener("keypress", this, true);
-    this.highlighter.highlighterContainer.addEventListener("keypress", this, true);
-    this.highlighterReady();
-  },
-
-  /**
    * Initialize the InspectorStore.
    */
   initializeStore: function IUI_initializeStore()
   {
     // First time opened, add the TabSelect listener
     if (this.store.isEmpty()) {
       this.tabbrowser.tabContainer.addEventListener("TabSelect", this, false);
     }
@@ -1091,31 +390,29 @@ InspectorUI.prototype = {
       this.store.setValue(this.winID, "inspecting", this.inspecting);
       this.store.setValue(this.winID, "isDirty", this.isDirty);
     }
 
     if (this.store.isEmpty()) {
       this.tabbrowser.tabContainer.removeEventListener("TabSelect", this, false);
     }
 
+    this.chromeWin.removeEventListener("keypress", this, false);
+
     this.stopInspecting();
-    this.browser.removeEventListener("keypress", this, true);
 
     this.saveToolState(this.winID);
     this.toolsDo(function IUI_toolsHide(aTool) {
       this.unregisterTool(aTool);
     }.bind(this));
 
     // close the sidebar
     this.hideSidebar();
 
     if (this.highlighter) {
-      this.highlighter.highlighterContainer.removeEventListener("keypress",
-                                                                this,
-                                                                true);
       this.highlighter.destroy();
       this.highlighter = null;
     }
 
     if (this.breadcrumbs) {
       this.breadcrumbs.destroy();
       this.breadcrumbs = null;
     }
@@ -1142,52 +439,44 @@ InspectorUI.prototype = {
   startInspecting: function IUI_startInspecting()
   {
     // if currently editing an attribute value, starting
     // "live inspection" mode closes the editor
     if (this.treePanel && this.treePanel.editingContext)
       this.treePanel.closeEditor();
 
     this.inspectToolbutton.checked = true;
-    this.highlighter.attachInspectListeners();
 
     this.inspecting = true;
     this.toolsDim(true);
-    this.highlighter.veilContainer.removeAttribute("locked");
-    this.highlighter.nodeInfo.container.removeAttribute("locked");
+    this.highlighter.unlock();
   },
 
   /**
    * Stop inspecting webpage, detach page listeners, disable highlighter
    * event listeners.
    * @param aPreventScroll
    *        Prevent scroll in the HTML tree?
    */
   stopInspecting: function IUI_stopInspecting(aPreventScroll)
   {
     if (!this.inspecting) {
       return;
     }
 
     this.inspectToolbutton.checked = false;
-    // Detach event listeners from content window and child windows to disable
-    // highlighting. We still want to be notified if the user presses "ESCAPE"
-    // to close the inspector, or "RETURN" to unlock the node, so we don't 
-    // remove the "keypress" event until the highlighter is removed.
-    this.highlighter.detachInspectListeners();
 
     this.inspecting = false;
     this.toolsDim(false);
-    if (this.highlighter.node) {
-      this.select(this.highlighter.node, true, true, !aPreventScroll);
+    if (this.highlighter.getNode()) {
+      this.select(this.highlighter.getNode(), true, true, !aPreventScroll);
     } else {
       this.select(null, true, true);
     }
-    this.highlighter.veilContainer.setAttribute("locked", true);
-    this.highlighter.nodeInfo.container.setAttribute("locked", true);
+    this.highlighter.lock();
   },
 
   /**
    * Select an object in the tree view.
    * @param aNode
    *        node to inspect
    * @param forceUpdate
    *        force an update?
@@ -1202,17 +491,17 @@ InspectorUI.prototype = {
       this.treePanel.closeEditor();
 
     if (!aNode)
       aNode = this.defaultSelection;
 
     if (forceUpdate || aNode != this.selection) {
       this.selection = aNode;
       if (!this.inspecting) {
-        this.highlighter.highlightNode(this.selection);
+        this.highlighter.highlight(this.selection);
       }
     }
 
     this.breadcrumbs.update();
     this.chromeWin.Tilt.update(aNode);
 
     this.toolsSelect(aScroll);
   },
@@ -1221,37 +510,53 @@ InspectorUI.prototype = {
    * Called when the highlighted node is changed by a tool.
    *
    * @param object aUpdater
    *        The tool that triggered the update (if any), that tool's
    *        onChanged will not be called.
    */
   nodeChanged: function IUI_nodeChanged(aUpdater)
   {
-    this.highlighter.highlight();
+    this.highlighter.invalidateSize();
     this.toolsOnChanged(aUpdater);
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Event Handling
 
   highlighterReady: function IUI_highlighterReady()
   {
     // Setup the InspectorStore or restore state
     this.initializeStore();
 
+    let self = this;
+
+    this.highlighter.addListener("locked", function() {
+      self.stopInspecting();
+    });
+
+    this.highlighter.addListener("unlocked", function() {
+      self.startInspecting();
+    });
+
+    this.highlighter.addListener("nodeselected", function() {
+      self.select(self.highlighter.getNode(), false, false);
+    });
+
     if (this.store.getValue(this.winID, "inspecting")) {
       this.startInspecting();
     }
 
     this.restoreToolState(this.winID);
 
     this.win.focus();
     Services.obs.notifyObservers({wrappedJSObject: this},
                                  INSPECTOR_NOTIFICATIONS.OPENED, null);
+
+    this.highlighter.highlight();
   },
 
   /**
    * Main callback handler for events.
    *
    * @param event
    *        The event to be handled.
    */
@@ -1308,84 +613,16 @@ InspectorUI.prototype = {
         break;
       case "keypress":
         switch (event.keyCode) {
           case this.chromeWin.KeyEvent.DOM_VK_ESCAPE:
             this.closeInspectorUI(false);
             event.preventDefault();
             event.stopPropagation();
             break;
-          case this.chromeWin.KeyEvent.DOM_VK_RETURN:
-            this.toggleInspection();
-            event.preventDefault();
-            event.stopPropagation();
-            break;
-          case this.chromeWin.KeyEvent.DOM_VK_LEFT:
-            let node;
-            if (this.selection) {
-              node = this.selection.parentNode;
-            } else {
-              node = this.defaultSelection;
-            }
-            if (node && this.highlighter.isNodeHighlightable(node)) {
-              this.inspectNode(node, true);
-            }
-            event.preventDefault();
-            event.stopPropagation();
-            break;
-          case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
-            if (this.selection) {
-              // Find the first child that is highlightable.
-              for (let i = 0; i < this.selection.childNodes.length; i++) {
-                node = this.selection.childNodes[i];
-                if (node && this.highlighter.isNodeHighlightable(node)) {
-                  break;
-                }
-              }
-            } else {
-              node = this.defaultSelection;
-            }
-            if (node && this.highlighter.isNodeHighlightable(node)) {
-              this.inspectNode(node, true);
-            }
-            event.preventDefault();
-            event.stopPropagation();
-            break;
-          case this.chromeWin.KeyEvent.DOM_VK_UP:
-            if (this.selection) {
-              // Find a previous sibling that is highlightable.
-              node = this.selection.previousSibling;
-              while (node && !this.highlighter.isNodeHighlightable(node)) {
-                node = node.previousSibling;
-              }
-            } else {
-              node = this.defaultSelection;
-            }
-            if (node && this.highlighter.isNodeHighlightable(node)) {
-              this.inspectNode(node, true);
-            }
-            event.preventDefault();
-            event.stopPropagation();
-            break;
-          case this.chromeWin.KeyEvent.DOM_VK_DOWN:
-            if (this.selection) {
-              // Find a next sibling that is highlightable.
-              node = this.selection.nextSibling;
-              while (node && !this.highlighter.isNodeHighlightable(node)) {
-                node = node.nextSibling;
-              }
-            } else {
-              node = this.defaultSelection;
-            }
-            if (node && this.highlighter.isNodeHighlightable(node)) {
-              this.inspectNode(node, true);
-            }
-            event.preventDefault();
-            event.stopPropagation();
-            break;
         }
         break;
     }
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// CssRuleView methods
 
@@ -1498,88 +735,23 @@ InspectorUI.prototype = {
    * @param aNode
    *        the element in the document to inspect
    * @param aScroll
    *        force scroll?
    */
   inspectNode: function IUI_inspectNode(aNode, aScroll)
   {
     this.select(aNode, true, true);
-    this.highlighter.highlightNode(aNode, { scroll: aScroll });
-  },
-
-  /**
-   * Find an element from the given coordinates. This method descends through
-   * frames to find the element the user clicked inside frames.
-   *
-   * @param DOMDocument aDocument the document to look into.
-   * @param integer aX
-   * @param integer aY
-   * @returns Node|null the element node found at the given coordinates.
-   */
-  elementFromPoint: function IUI_elementFromPoint(aDocument, aX, aY)
-  {
-    let node = aDocument.elementFromPoint(aX, aY);
-    if (node && node.contentDocument) {
-      if (node instanceof Ci.nsIDOMHTMLIFrameElement) {
-        let rect = node.getBoundingClientRect();
-
-        // Gap between the iframe and its content window.
-        let [offsetTop, offsetLeft] = this.getIframeContentOffset(node);
-
-        aX -= rect.left + offsetLeft;
-        aY -= rect.top + offsetTop;
-
-        if (aX < 0 || aY < 0) {
-          // Didn't reach the content document, still over the iframe.
-          return node;
-        }
-      }
-      if (node instanceof Ci.nsIDOMHTMLIFrameElement ||
-          node instanceof Ci.nsIDOMHTMLFrameElement) {
-        let subnode = this.elementFromPoint(node.contentDocument, aX, aY);
-        if (subnode) {
-          node = subnode;
-        }
-      }
-    }
-    return node;
+    this.highlighter.highlight(aNode, aScroll);
   },
 
   ///////////////////////////////////////////////////////////////////////////
   //// Utility functions
 
   /**
-   * Returns iframe content offset (iframe border + padding).
-   * Note: this function shouldn't need to exist, had the platform provided a
-   * suitable API for determining the offset between the iframe's content and
-   * its bounding client rect. Bug 626359 should provide us with such an API.
-   *
-   * @param aIframe
-   *        The iframe.
-   * @returns array [offsetTop, offsetLeft]
-   *          offsetTop is the distance from the top of the iframe and the
-   *            top of the content document.
-   *          offsetLeft is the distance from the left of the iframe and the
-   *            left of the content document.
-   */
-  getIframeContentOffset: function IUI_getIframeContentOffset(aIframe)
-  {
-    let style = aIframe.contentWindow.getComputedStyle(aIframe, null);
-
-    let paddingTop = parseInt(style.getPropertyValue("padding-top"));
-    let paddingLeft = parseInt(style.getPropertyValue("padding-left"));
-
-    let borderTop = parseInt(style.getPropertyValue("border-top-width"));
-    let borderLeft = parseInt(style.getPropertyValue("border-left-width"));
-
-    return [borderTop + paddingTop, borderLeft + paddingLeft];
-  },
-
-  /**
    * Retrieve the unique ID of a window object.
    *
    * @param nsIDOMWindow aWindow
    * @returns integer ID
    */
   getWindowID: function IUI_getWindowID(aWindow)
   {
     if (!aWindow) {
@@ -1924,16 +1096,22 @@ InspectorUI.prototype = {
         }
       }.bind(this));
       this.sidebarTools.forEach(function(tool) {
         if (tool != activeSidebarTool)
           this.chromeDoc.getElementById(
             this.getToolbarButtonId(tool.id)).removeAttribute("checked");
       }.bind(this));
     }
+    if (this.store.getValue(this.winID, "inspecting")) {
+      this.highlighter.unlock();
+    } else {
+      this.highlighter.lock();
+    }
+
     Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null);
   },
 
   /**
    * For each tool in the tools collection select the current node that is
    * selected in the highlighter
    * @param aScroll boolean
    *        Do you want to scroll the treepanel?
--- a/browser/devtools/highlighter/test/Makefile.in
+++ b/browser/devtools/highlighter/test/Makefile.in
@@ -61,18 +61,19 @@ include $(topsrcdir)/config/rules.mk
 		browser_inspector_bug_566084_location_changed.js \
 		browser_inspector_infobar.js \
 		browser_inspector_bug_690361.js \
 		browser_inspector_bug_672902_keyboard_shortcuts.js \
 		browser_inspector_keybindings.js \
 		browser_inspector_breadcrumbs.html \
 		browser_inspector_breadcrumbs.js \
 		browser_inspector_bug_699308_iframe_navigation.js \
-        browser_inspector_changes.js \
-        browser_inspector_ruleviewstore.js \
-        browser_inspector_duplicate_ruleview.js \
+		browser_inspector_changes.js \
+		browser_inspector_ruleviewstore.js \
+		browser_inspector_duplicate_ruleview.js \
+		head.js \
 		$(NULL)
 
 # Disabled due to constant failures
 # 		browser_inspector_treePanel_click.js \
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
--- a/browser/devtools/highlighter/test/browser_inspector_breadcrumbs.js
+++ b/browser/devtools/highlighter/test/browser_inspector_breadcrumbs.js
@@ -43,32 +43,29 @@ function test()
 
   function runTests()
   {
     Services.obs.removeObserver(runTests,
       InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
 
     cursor = 0;
     executeSoon(function() {
-      Services.obs.addObserver(nodeSelected,
-        InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
-
+      InspectorUI.highlighter.addListener("nodeselected", nodeSelected);
       InspectorUI.inspectNode(nodes[0].node);
     });
   }
 
   function nodeSelected()
   {
     executeSoon(function() {
       performTest();
       cursor++;
       if (cursor >= nodes.length) {
 
-        Services.obs.removeObserver(nodeSelected,
-          InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+        InspectorUI.highlighter.removeListener("nodeselected", nodeSelected);
         Services.obs.addObserver(finishUp,
           InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
 
         executeSoon(function() {
           InspectorUI.closeInspectorUI();
         });
       } else {
         let node = nodes[cursor].node;
--- a/browser/devtools/highlighter/test/browser_inspector_bug_665880.js
+++ b/browser/devtools/highlighter/test/browser_inspector_bug_665880.js
@@ -29,27 +29,25 @@ function test()
   }
 
   function runObjectInspectionTest()
   {
     Services.obs.removeObserver(runObjectInspectionTest,
       InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
 
     executeSoon(function() {
-      Services.obs.addObserver(performTestComparison,
-        InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+      InspectorUI.highlighter.addListener("nodeselected", performTestComparison);
 
       InspectorUI.inspectNode(objectNode);
     });
   }
 
   function performTestComparison()
   {
-    Services.obs.removeObserver(performTestComparison,
-      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+    InspectorUI.highlighter.removeListener("nodeselected", performTestComparison);
 
     is(InspectorUI.selection, objectNode, "selection matches node");
 
     Services.obs.addObserver(finishUp,
       InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
     InspectorUI.closeInspectorUI();
   }
 
--- a/browser/devtools/highlighter/test/browser_inspector_bug_672902_keyboard_shortcuts.js
+++ b/browser/devtools/highlighter/test/browser_inspector_bug_672902_keyboard_shortcuts.js
@@ -33,94 +33,79 @@ function test()
   }
 
   function findAndHighlightNode()
   {
     Services.obs.removeObserver(findAndHighlightNode,
                                 InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
 
     executeSoon(function() {
-      Services.obs.addObserver(highlightBodyNode,
-                               InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING,
-                               false);
+      InspectorUI.highlighter.addListener("nodeselected", highlightBodyNode);
       // Test that navigating around without a selected node gets us to the
       // body element.
       node = doc.querySelector("body");
       EventUtils.synthesizeKey("VK_RIGHT", { });
     });
   }
 
   function highlightBodyNode()
   {
-    Services.obs.removeObserver(highlightBodyNode,
-                                InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+    InspectorUI.highlighter.removeListener("nodeselected", highlightBodyNode);
     is(InspectorUI.selection, node, "selected body element");
 
     executeSoon(function() {
-      Services.obs.addObserver(highlightHeaderNode,
-                               InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING,
-                               false);
+      InspectorUI.highlighter.addListener("nodeselected", highlightHeaderNode);
       // Test that moving to the child works.
       node = doc.querySelector("h1");
       EventUtils.synthesizeKey("VK_RIGHT", { });
     });
   }
 
   function highlightHeaderNode()
   {
-    Services.obs.removeObserver(highlightHeaderNode,
-                                InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+    InspectorUI.highlighter.removeListener("nodeselected", highlightHeaderNode);
     is(InspectorUI.selection, node, "selected h1 element");
 
     executeSoon(function() {
-      Services.obs.addObserver(highlightParagraphNode,
-                               InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING,
-                               false);
+      InspectorUI.highlighter.addListener("nodeselected", highlightParagraphNode);
       // Test that moving to the next sibling works.
       node = doc.querySelector("p");
       EventUtils.synthesizeKey("VK_DOWN", { });
     });
   }
 
   function highlightParagraphNode()
   {
-    Services.obs.removeObserver(highlightParagraphNode,
-                                InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+    InspectorUI.highlighter.removeListener("nodeselected", highlightParagraphNode);
     is(InspectorUI.selection, node, "selected p element");
 
     executeSoon(function() {
-      Services.obs.addObserver(highlightHeaderNodeAgain,
-                               InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING,
-                               false);
+      InspectorUI.highlighter.addListener("nodeselected", highlightHeaderNodeAgain);
       // Test that moving to the previous sibling works.
       node = doc.querySelector("h1");
       EventUtils.synthesizeKey("VK_UP", { });
     });
   }
 
   function highlightHeaderNodeAgain()
   {
-    Services.obs.removeObserver(highlightHeaderNodeAgain,
-                                InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+    InspectorUI.highlighter.removeListener("nodeselected", highlightHeaderNodeAgain);
     is(InspectorUI.selection, node, "selected h1 element");
 
     executeSoon(function() {
-      Services.obs.addObserver(highlightParentNode,
-                               InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING,
-                               false);
+      InspectorUI.highlighter.addListener("nodeselected", highlightParentNode);
       // Test that moving to the parent works.
       node = doc.querySelector("body");
       EventUtils.synthesizeKey("VK_LEFT", { });
     });
   }
 
   function highlightParentNode()
   {
-    Services.obs.removeObserver(highlightParentNode,
-                                InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+    InspectorUI.highlighter.removeListener("nodeselected", highlightParentNode);
     is(InspectorUI.selection, node, "selected body element");
 
     // Test that locking works.
     EventUtils.synthesizeKey("VK_RETURN", { });
     executeSoon(isTheNodeLocked);
   }
 
   function isTheNodeLocked()
--- a/browser/devtools/highlighter/test/browser_inspector_bug_674871.js
+++ b/browser/devtools/highlighter/test/browser_inspector_bug_674871.js
@@ -52,45 +52,39 @@ function test()
   }
 
   function runTests()
   {
     Services.obs.removeObserver(runTests,
       InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
 
     executeSoon(function() {
-      Services.obs.addObserver(isTheIframeSelected,
-        InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+      InspectorUI.highlighter.addListener("nodeselected", isTheIframeSelected);
 
       moveMouseOver(iframeNode, 1, 1);
     });
   }
 
   function isTheIframeSelected()
   {
-    Services.obs.removeObserver(isTheIframeSelected,
-      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+    InspectorUI.highlighter.removeListener("nodeselected", isTheIframeSelected);
 
     is(InspectorUI.selection, iframeNode, "selection matches node");
     iframeNode.style.marginBottom = doc.defaultView.innerHeight + "px";
     doc.defaultView.scrollBy(0, 40);
 
     executeSoon(function() {
-      Services.obs.addObserver(isTheIframeContentSelected,
-        InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
-
+      InspectorUI.highlighter.addListener("nodeselected", isTheIframeContentSelected);
       moveMouseOver(iframeNode, 40, 40);
     });
   }
 
   function isTheIframeContentSelected()
   {
-    Services.obs.removeObserver(isTheIframeContentSelected,
-      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
-
+    InspectorUI.highlighter.removeListener("nodeselected", isTheIframeContentSelected);
     is(InspectorUI.selection, iframeBodyNode, "selection matches node");
     // 184 == 200 + 11(border) + 13(padding) - 40(scroll)
     is(InspectorUI.highlighter._highlightRect.height, 184,
       "highlighter height");
 
     Services.obs.addObserver(finishUp,
       InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
     InspectorUI.closeInspectorUI();
--- a/browser/devtools/highlighter/test/browser_inspector_editor.js
+++ b/browser/devtools/highlighter/test/browser_inspector_editor.js
@@ -53,17 +53,17 @@ function runEditorTests()
 
   // start the tests
   doNextStep();
 }
 
 function highlighterTrap()
 {
   // bug 696107
-  Services.obs.removeObserver(highlighterTrap, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+  InspectorUI.highlighter.removeListener("nodeselected", highlighterTrap);
   ok(false, "Highlighter moved. Shouldn't be here!");
   finishUp();
 }
 
 function doEditorTestSteps()
 {
   let treePanel = InspectorUI.treePanel;
   let editor = treePanel.treeBrowserDocument.getElementById("attribute-editor");
@@ -110,32 +110,31 @@ function doEditorTestSteps()
 
   is(treePanel.editingContext.repObj, div, "editor session has correct reference to div");
   is(treePanel.editingContext.attrObj, attrValNode_id, "editor session has correct reference to `id` attribute-value node in HTML panel");
   is(treePanel.editingContext.attrName, "id", "editor session knows correct attribute-name");
 
   editorInput.value = "Hello World";
   editorInput.focus();
 
-  Services.obs.addObserver(highlighterTrap,
-      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+  InspectorUI.highlighter.addListener("nodeselected", highlighterTrap);
 
   // hit <enter> to save the textbox value
   executeSoon(function() {
     // Extra key to test that keyboard handlers have been removed. bug 696107.
     EventUtils.synthesizeKey("VK_LEFT", {}, attrValNode_id.ownerDocument.defaultView);
     EventUtils.synthesizeKey("VK_RETURN", {}, attrValNode_id.ownerDocument.defaultView);
   });
 
   // two `yield` statements, to trap both the "SAVED" and "CLOSED" events that will be triggered
   yield;
   yield; // End of Step 2
 
   // remove this from previous step
-  Services.obs.removeObserver(highlighterTrap, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+  InspectorUI.highlighter.removeListener("nodeselected", highlighterTrap);
 
   // Step 3: validate that the previous editing session saved correctly, then open editor on `class` attribute value
   ok(!treePanel.editingContext, "Step 3: editor session ended");
   editorVisible = editor.classList.contains("editing");
   ok(!editorVisible, "editor popup hidden");
   attrValNodeHighlighted = attrValNode_id.classList.contains("editingAttributeValue");
   ok(!attrValNodeHighlighted, "`id` attribute-value node is no longer editor-highlighted");
   is(div.getAttribute("id"), "Hello World", "`id` attribute-value successfully updated");
--- a/browser/devtools/highlighter/test/browser_inspector_highlighter.js
+++ b/browser/devtools/highlighter/test/browser_inspector_highlighter.js
@@ -98,47 +98,43 @@ function runSelectionTests(subject)
 {
   Services.obs.removeObserver(runSelectionTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   is(subject.wrappedJSObject, InspectorUI,
      "InspectorUI accessible in the observer");
 
   executeSoon(function() {
-    Services.obs.addObserver(performTestComparisons,
-      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.highlighter.addListener("nodeselected", performTestComparisons);
     EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
   });
 }
 
 function performTestComparisons(evt)
 {
-  Services.obs.removeObserver(performTestComparisons,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+  InspectorUI.highlighter.removeListener("nodeselected", performTestComparisons);
 
   InspectorUI.stopInspecting();
-  ok(InspectorUI.highlighter.isHighlighting, "highlighter is highlighting");
-  is(InspectorUI.highlighter.highlitNode, h1, "highlighter matches selection")
+  ok(isHighlighting(), "highlighter is highlighting");
+  is(getHighlitNode(), h1, "highlighter matches selection")
   is(InspectorUI.selection, h1, "selection matches node");
-  is(InspectorUI.selection, InspectorUI.highlighter.highlitNode, "selection matches highlighter");
+  is(InspectorUI.selection, getHighlitNode(), "selection matches highlighter");
 
 
   div = doc.querySelector("div#checkOutThisWickedSpread");
 
   executeSoon(function() {
-    Services.obs.addObserver(finishTestComparisons,
-        InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.highlighter.addListener("nodeselected", finishTestComparisons);
     InspectorUI.inspectNode(div);
   });
 }
 
 function finishTestComparisons()
 {
-  Services.obs.removeObserver(finishTestComparisons,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+  InspectorUI.highlighter.removeListener("nodeselected", finishTestComparisons);
 
   // get dimensions of div element
   let divDims = div.getBoundingClientRect();
   let divWidth = divDims.width;
   let divHeight = divDims.height;
 
   // get dimensions of transparent veil box over element
   let veilBoxDims = 
--- a/browser/devtools/highlighter/test/browser_inspector_iframeTest.js
+++ b/browser/devtools/highlighter/test/browser_inspector_iframeTest.js
@@ -90,44 +90,42 @@ function setupIframeTests()
   InspectorUI.openInspectorUI();
 }
 
 function runIframeTests()
 {
   Services.obs.removeObserver(runIframeTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
-  Services.obs.addObserver(performTestComparisons1,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
-  executeSoon(moveMouseOver.bind(this, div1));
+  executeSoon(function() {
+    InspectorUI.highlighter.addListener("nodeselected", performTestComparisons1);
+    moveMouseOver(div1)
+  });
 }
 
 function performTestComparisons1()
 {
-  Services.obs.removeObserver(performTestComparisons1,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+  InspectorUI.highlighter.removeListener("nodeselected", performTestComparisons1);
 
   is(InspectorUI.selection, div1, "selection matches div1 node");
-  is(InspectorUI.highlighter.highlitNode, div1, "highlighter matches selection");
+  is(getHighlitNode(), div1, "highlighter matches selection");
 
   executeSoon(function() {
-    Services.obs.addObserver(performTestComparisons2,
-      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.highlighter.addListener("nodeselected", performTestComparisons2);
     moveMouseOver(div2);
   });
 }
 
 function performTestComparisons2()
 {
-  Services.obs.removeObserver(performTestComparisons2,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+  InspectorUI.highlighter.removeListener("nodeselected", performTestComparisons2);
 
   is(InspectorUI.selection, div2, "selection matches div2 node");
-  is(InspectorUI.highlighter.highlitNode, div2, "highlighter matches selection");
+  is(getHighlitNode(), div2, "highlighter matches selection");
 
   finish();
 }
 
 function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
--- a/browser/devtools/highlighter/test/browser_inspector_initialization.js
+++ b/browser/devtools/highlighter/test/browser_inspector_initialization.js
@@ -167,29 +167,29 @@ function openInspectorForContextTest()
     InspectorUI.openInspectorUI(salutation);
   });
 }
 
 function inspectNodesFromContextTestWhileOpen()
 {
   Services.obs.removeObserver(inspectNodesFromContextTestWhileOpen, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
   Services.obs.addObserver(inspectNodesFromContextTestTrap, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
-  Services.obs.addObserver(inspectNodesFromContextTestHighlight, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+  InspectorUI.highlighter.addListener("nodeselected", inspectNodesFromContextTestHighlight);
   is(InspectorUI.selection, salutation, "Inspector is highlighting salutation");
   closing = doc.getElementById("closing");
   ok(closing, "we have the closing statement");
   executeSoon(function() {
     InspectorUI.openInspectorUI(closing);
   });
 }
 
 function inspectNodesFromContextTestHighlight()
 {
   winId = InspectorUI.winID;
-  Services.obs.removeObserver(inspectNodesFromContextTestHighlight, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+  InspectorUI.highlighter.removeListener("nodeselected", inspectNodesFromContextTestHighlight);
   Services.obs.addObserver(finishInspectorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.DESTROYED, false);
   is(InspectorUI.selection, closing, "InspectorUI.selection is header");
   executeSoon(function() {
     InspectorUI.closeInspectorUI();
   });
 }
 
 function inspectNodesFromContextTestTrap()
--- a/browser/devtools/highlighter/test/browser_inspector_keybindings.js
+++ b/browser/devtools/highlighter/test/browser_inspector_keybindings.js
@@ -27,28 +27,24 @@ function test()
   }
 
   function highlightNode()
   {
     Services.obs.removeObserver(highlightNode,
       InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
 
     executeSoon(function() {
-      Services.obs.addObserver(lockNode,
-        InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
-
+      InspectorUI.highlighter.addListener("nodeselected", lockNode);
       InspectorUI.inspectNode(node);
     });
   }
 
   function lockNode()
   {
-    Services.obs.removeObserver(lockNode,
-      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
-
+    InspectorUI.highlighter.removeListener("nodeselected", lockNode);
     EventUtils.synthesizeKey("VK_RETURN", { });
 
     executeSoon(isTheNodeLocked);
   }
 
   function isTheNodeLocked()
   {
     is(InspectorUI.selection, node, "selection matches node");
--- a/browser/devtools/highlighter/test/browser_inspector_registertools.js
+++ b/browser/devtools/highlighter/test/browser_inspector_registertools.js
@@ -91,23 +91,23 @@ function setupHighlighterTests()
 
 function inspectorOpen()
 {
   info("we received the inspector-opened notification");
   Services.obs.removeObserver(inspectorOpen, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
   toolsLength = InspectorUI.tools.length;
   toolEvents = InspectorUI.toolEvents.length;
   info("tools registered");
-  Services.obs.addObserver(startToolTests, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+  InspectorUI.highlighter.addListener("nodeselected", startToolTests);
   InspectorUI.inspectNode(h1);
 }
 
 function startToolTests(evt)
 {
-  Services.obs.removeObserver(startToolTests, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+  InspectorUI.highlighter.removeListener("nodeselected", startToolTests);
   InspectorUI.stopInspecting();
   info("Getting InspectorUI.tools");
   let tools = InspectorUI.tools;
 
   tool1 = InspectorUI.tools["tool_1"];
   tool2 = InspectorUI.tools["tool_2"];
   tool3 = InspectorUI.tools["tool_3"];
 
--- a/browser/devtools/highlighter/test/browser_inspector_scrolling.js
+++ b/browser/devtools/highlighter/test/browser_inspector_scrolling.js
@@ -68,32 +68,33 @@ function toggleInspector()
   Services.obs.addObserver(inspectNode, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.toggleInspectorUI();
 }
 
 function inspectNode()
 {
   Services.obs.removeObserver(inspectNode,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
-  Services.obs.addObserver(performScrollingTest,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+
+  InspectorUI.highlighter.addListener("nodeselected", performScrollingTest);
 
   executeSoon(function() {
     InspectorUI.inspectNode(div);
   });
 }
 
 function performScrollingTest()
 {
-  Services.obs.removeObserver(performScrollingTest,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+  InspectorUI.highlighter.removeListener("nodeselected", performScrollingTest);
 
-  EventUtils.synthesizeMouseScroll(div, 10, 10,
-    {axis:"vertical", delta:50, type:"MozMousePixelScroll"},
-    iframe.contentWindow);
+  executeSoon(function() {
+    EventUtils.synthesizeMouseScroll(div, 10, 10,
+      {axis:"vertical", delta:50, type:"MozMousePixelScroll"},
+      iframe.contentWindow);
+  });
 
   gBrowser.selectedBrowser.addEventListener("scroll", function() {
     gBrowser.selectedBrowser.removeEventListener("scroll", arguments.callee,
       false);
 
     is(iframe.contentDocument.body.scrollTop, 50, "inspected iframe scrolled");
 
     div = iframe = doc = null;
--- a/browser/devtools/highlighter/test/browser_inspector_treePanel_click.js
+++ b/browser/devtools/highlighter/test/browser_inspector_treePanel_click.js
@@ -29,34 +29,31 @@ function test() {
   function runTests() {
     Services.obs.removeObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
     Services.obs.addObserver(testNode1, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
     InspectorUI.select(node1, true, true, true);
     InspectorUI.openTreePanel();
   }
 
   function testNode1() {
-    dump("testNode1\n");
     Services.obs.removeObserver(testNode1, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
     is(InspectorUI.selection, node1, "selection matches node");
-    is(InspectorUI.highlighter.node, node1, "selection matches node");
+    is(getHighlitNode(), node1, "selection matches node");
     testNode2();
   }
 
   function testNode2() {
-    dump("testNode2\n")
-    Services.obs.addObserver(testHighlightingNode2, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.highlighter.addListener("nodeselected", testHighlightingNode2);
     InspectorUI.treePanelSelect("node2");
   }
 
   function testHighlightingNode2() {
-    dump("testHighlightingNode2\n")
-    Services.obs.removeObserver(testHighlightingNode2, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+    InspectorUI.highlighter.removeListener("nodeselected", testHighlightingNode2);
     is(InspectorUI.selection, node2, "selection matches node");
-    is(InspectorUI.highlighter.node, node2, "selection matches node");
+    is(getHighlitNode(), node2, "selection matches node");
     Services.obs.addObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
     InspectorUI.closeInspectorUI();
   }
 
   function finishUp() {
     Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
     doc = node1 = node2 = null;
     gBrowser.removeCurrentTab();
--- a/browser/devtools/highlighter/test/browser_inspector_treeSelection.js
+++ b/browser/devtools/highlighter/test/browser_inspector_treeSelection.js
@@ -71,31 +71,30 @@ function setupSelectionTests()
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.openInspectorUI();
 }
 
 function runSelectionTests()
 {
   Services.obs.removeObserver(runSelectionTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
-  Services.obs.addObserver(performTestComparisons,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+
   executeSoon(function() {
+    InspectorUI.highlighter.addListener("nodeselected", performTestComparisons);
     InspectorUI.inspectNode(h1);
   });
 }
 
 function performTestComparisons(evt)
 {
-  Services.obs.removeObserver(performTestComparisons,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+  InspectorUI.highlighter.removeListener("nodeselected", performTestComparisons);
 
   is(h1, InspectorUI.selection, "selection matches node");
-  ok(InspectorUI.highlighter.isHighlighting, "highlighter is highlighting");
-  is(InspectorUI.highlighter.highlitNode, h1, "highlighter highlighting correct node");
+  ok(isHighlighting(), "highlighter is highlighting");
+  is(getHighlitNode(), h1, "highlighter highlighting correct node");
 
   finishUp();
 }
 
 function finishUp() {
   InspectorUI.closeInspectorUI();
   doc = h1 = null;
   gBrowser.removeCurrentTab();
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/test/head.js
@@ -0,0 +1,78 @@
+/* ***** 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 DevTools test code.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ *
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Paul Rouget <paul@mozilla.com> (Original author)
+ *
+ * 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 Cu = Components.utils;
+Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
+
+function isHighlighting()
+{
+  let veil = InspectorUI.highlighter.veilTransparentBox;
+  return !(veil.style.visibility == "hidden");
+}
+
+function getHighlitNode()
+{
+  let h = InspectorUI.highlighter;
+  if (!isHighlighting() || !h._contentRect)
+    return null;
+
+  let a = {
+    x: h._contentRect.left,
+    y: h._contentRect.top
+  };
+
+  let b = {
+    x: a.x + h._contentRect.width,
+    y: a.y + h._contentRect.height
+  };
+
+  // Get midpoint of diagonal line.
+  let midpoint = midPoint(a, b);
+
+  return LayoutHelpers.getElementFromPoint(h.win.document, midpoint.x,
+    midpoint.y);
+}
+
+
+function midPoint(aPointA, aPointB)
+{
+  let pointC = { };
+  pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
+  pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
+  return pointC;
+}
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -7,9 +7,10 @@ browser.jar:
     content/browser/splitview.css                 (styleeditor/splitview.css)
     content/browser/styleeditor.css               (styleeditor/styleeditor.css)
     content/browser/devtools/csshtmltree.xul      (styleinspector/csshtmltree.xul)
     content/browser/devtools/cssruleview.xul      (styleinspector/cssruleview.xul)
     content/browser/devtools/styleinspector.css   (styleinspector/styleinspector.css)
     content/browser/orion.js                      (sourceeditor/orion/orion.js)
     content/browser/orion.css                     (sourceeditor/orion/orion.css)
     content/browser/orion-mozilla.css             (sourceeditor/orion/mozilla.css)
+    content/browser/source-editor-overlay.xul     (sourceeditor/source-editor-overlay.xul)
 
--- a/browser/devtools/scratchpad/scratchpad.xul
+++ b/browser/devtools/scratchpad/scratchpad.xul
@@ -40,29 +40,31 @@
    - ***** END LICENSE BLOCK ***** -->
 #endif
 <!DOCTYPE window [
 <!ENTITY % scratchpadDTD SYSTEM "chrome://browser/locale/devtools/scratchpad.dtd" >
  %scratchpadDTD;
 ]>
 <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/source-editor-overlay.xul"?>
 
 <window id="main-window"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="&window.title;"
         windowtype="devtools:scratchpad"
         screenX="4" screenY="4"
         width="640" height="480"
         persist="screenX screenY width height sizemode">
 
 <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
 <script type="application/javascript" src="chrome://browser/content/scratchpad.js"/>
 
 <commandset id="editMenuCommands"/>
+<commandset id="sourceEditorCommands"/>
 
 <commandset id="sp-commandset">
   <command id="sp-cmd-newWindow" oncommand="Scratchpad.openScratchpad();"/>
   <command id="sp-cmd-openFile" oncommand="Scratchpad.openFile();"/>
   <command id="sp-cmd-save" oncommand="Scratchpad.saveFile();"/>
   <command id="sp-cmd-saveas" oncommand="Scratchpad.saveFileAs();"/>
 
   <!-- TODO: bug 650340 - implement printFile()
@@ -137,16 +139,42 @@
   <key id="sp-key-errorConsole"
        key="&errorConsoleCmd.commandkey;"
        command="sp-cmd-errorConsole"
        modifiers="accel,shift"/>
   <key id="sp-key-webConsole"
        key="&webConsoleCmd.commandkey;"
        command="sp-cmd-webConsole"
        modifiers="accel,shift"/>
+  <key id="key_find"
+       key="&findCmd.key;"
+       command="cmd_find"
+       modifiers="accel"/>
+#ifdef XP_MACOSX
+  <key id="key_findAgain"
+       key="&findAgainCmd.key;"
+       command="cmd_findAgain"
+       modifiers="accel"/>
+  <key id="key_findPrevious"
+       key="&findPreviousCmd.key;"
+       command="cmd_findPrevious"
+       modifiers="accel,shift"/>
+#else
+  <key id="key_findAgain"
+       keycode="VK_F3"
+       command="cmd_findAgain"/>
+  <key id="key_findPrevious"
+       keycode="VK_F3"
+       command="cmd_findPrevious"
+       modifiers="shift"/>
+#endif
+  <key id="key_gotoLine"
+       key="&gotoLineCmd.key;"
+       command="cmd_gotoLine"
+       modifiers="accel"/>
 </keyset>
 
 
 <menubar id="sp-menubar">
   <menu id="sp-file-menu" label="&fileMenu.label;"
         accesskey="&fileMenu.accesskey;">
     <menupopup id="sp-menu-filepopup">
       <menuitem id="sp-menu-newscratchpad"
@@ -218,33 +246,33 @@
                 accesskey="&pasteCmd.accesskey;"
                 command="cmd_paste"/>
       <menuseparator/>
       <menuitem id="sp-menu-selectAll"
                 label="&selectAllCmd.label;"
                 key="key_selectAll"
                 accesskey="&selectAllCmd.accesskey;"
                 command="cmd_selectAll"/>
-
-      <!-- TODO: bug 650345 - implement search and replace
+      <menuseparator/>
       <menuitem id="sp-menu-find"
-                label="&findOnCmd.label;"
-                accesskey="&findOnCmd.accesskey;"
+                label="&findCmd.label;"
+                accesskey="&findCmd.accesskey;"
                 key="key_find"
-                disabled="true"
                 command="cmd_find"/>
       <menuitem id="sp-menu-findAgain"
                 label="&findAgainCmd.label;"
                 accesskey="&findAgainCmd.accesskey;"
                 key="key_findAgain"
-                disabled="true"
                 command="cmd_findAgain"/>
-      <menuseparator id="sp-execute-separator"/>
-      -->
-
+      <menuseparator/>
+      <menuitem id="sp-menu-gotoLine"
+                label="&gotoLineCmd.label;"
+                accesskey="&gotoLineCmd.accesskey;"
+                key="key_gotoLine"
+                command="cmd_gotoLine"/>
     </menupopup>
   </menu>
 
   <menu id="sp-execute-menu" label="&executeMenu.label;"
         accesskey="&executeMenu.accesskey;">
     <menupopup id="sp-menu_executepopup">
       <menuitem id="sp-text-run"
                 label="&run.label;"
--- a/browser/devtools/scratchpad/test/Makefile.in
+++ b/browser/devtools/scratchpad/test/Makefile.in
@@ -53,15 +53,17 @@ include $(topsrcdir)/config/rules.mk
 		browser_scratchpad_ui.js \
 		browser_scratchpad_bug_646070_chrome_context_pref.js \
 		browser_scratchpad_bug_660560_tab.js \
 		browser_scratchpad_open.js \
 		browser_scratchpad_restore.js \
 		browser_scratchpad_bug_679467_falsy.js \
 		browser_scratchpad_bug_699130_edit_ui_updates.js \
 		browser_scratchpad_bug_669612_unsaved.js \
-		head.js \
 		browser_scratchpad_bug_653427_confirm_close.js \
 		browser_scratchpad_bug684546_reset_undo.js \
 		browser_scratchpad_bug690552_display_outputs_errors.js \
+		browser_scratchpad_bug650345_find_ui.js \
+		browser_scratchpad_bug714942_goto_line_ui.js \
+		head.js \
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug650345_find_ui.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function browserLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", browserLoad, true);
+    openScratchpad(runTests);
+  }, true);
+
+  content.location = "data:text/html,<p>test the Find feature in Scratchpad";
+}
+
+function runTests(aWindow, aScratchpad)
+{
+  let editor = aScratchpad.editor;
+  let text = "foobar bug650345\nBug650345 bazbaz\nfoobar omg\ntest";
+  editor.setText(text);
+
+  let needle = "foobar";
+  editor.setSelection(0, needle.length);
+
+  let oldPrompt = Services.prompt;
+  Services.prompt = {
+    prompt: function() { return true; },
+  };
+
+  let findKey = "F";
+  info("test Ctrl/Cmd-" + findKey + " (find)");
+  EventUtils.synthesizeKey(findKey, {accelKey: true}, aWindow);
+  let selection = editor.getSelection();
+  let newIndex = text.indexOf(needle, needle.length);
+  is(selection.start, newIndex, "selection.start is correct");
+  is(selection.end, newIndex + needle.length, "selection.end is correct");
+
+  info("test cmd_find");
+  aWindow.goDoCommand("cmd_find");
+  selection = editor.getSelection();
+  is(selection.start, 0, "selection.start is correct");
+  is(selection.end, needle.length, "selection.end is correct");
+
+  let findNextKey = Services.appinfo.OS == "Darwin" ? "G" : "VK_F3";
+  let findNextKeyOptions = Services.appinfo.OS == "Darwin" ?
+                           {accelKey: true} : {};
+
+  info("test " + findNextKey + " (findNext)");
+  EventUtils.synthesizeKey(findNextKey, findNextKeyOptions, aWindow);
+  selection = editor.getSelection();
+  is(selection.start, newIndex, "selection.start is correct");
+  is(selection.end, newIndex + needle.length, "selection.end is correct");
+
+  info("test cmd_findAgain");
+  aWindow.goDoCommand("cmd_findAgain");
+  selection = editor.getSelection();
+  is(selection.start, 0, "selection.start is correct");
+  is(selection.end, needle.length, "selection.end is correct");
+
+  let findPreviousKey = Services.appinfo.OS == "Darwin" ? "G" : "VK_F3";
+  let findPreviousKeyOptions = Services.appinfo.OS == "Darwin" ?
+                           {accelKey: true, shiftKey: true} : {shiftKey: true};
+
+  info("test " + findPreviousKey + " (findPrevious)");
+  EventUtils.synthesizeKey(findPreviousKey, findPreviousKeyOptions, aWindow);
+  selection = editor.getSelection();
+  is(selection.start, newIndex, "selection.start is correct");
+  is(selection.end, newIndex + needle.length, "selection.end is correct");
+
+  info("test cmd_findPrevious");
+  aWindow.goDoCommand("cmd_findPrevious");
+  selection = editor.getSelection();
+  is(selection.start, 0, "selection.start is correct");
+  is(selection.end, needle.length, "selection.end is correct");
+
+  needle = "BAZbaz";
+  newIndex = text.toLowerCase().indexOf(needle.toLowerCase());
+
+  Services.prompt = {
+    prompt: function(aWindow, aTitle, aMessage, aValue) {
+      aValue.value = needle;
+      return true;
+    },
+  };
+
+  info("test Ctrl/Cmd-" + findKey + " (find) with a custom value");
+  EventUtils.synthesizeKey(findKey, {accelKey: true}, aWindow);
+  selection = editor.getSelection();
+  is(selection.start, newIndex, "selection.start is correct");
+  is(selection.end, newIndex + needle.length, "selection.end is correct");
+
+  Services.prompt = oldPrompt;
+
+  finish();
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug714942_goto_line_ui.js
@@ -0,0 +1,45 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function browserLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", browserLoad, true);
+    openScratchpad(runTests);
+  }, true);
+
+  content.location = "data:text/html,<p>test the 'Jump to line' feature in Scratchpad";
+}
+
+function runTests(aWindow, aScratchpad)
+{
+  let editor = aScratchpad.editor;
+  let text = "foobar bug650345\nBug650345 bazbaz\nfoobar omg\ntest";
+  editor.setText(text);
+  editor.setCaretOffset(0);
+
+  let oldPrompt = Services.prompt;
+  let desiredValue = null;
+  Services.prompt = {
+    prompt: function(aWindow, aTitle, aMessage, aValue) {
+      aValue.value = desiredValue;
+      return true;
+    },
+  };
+
+  desiredValue = 3;
+  EventUtils.synthesizeKey("J", {accelKey: true}, aWindow);
+  is(editor.getCaretOffset(), 34, "caret offset is correct");
+
+  desiredValue = 2;
+  aWindow.goDoCommand("cmd_gotoLine")
+  is(editor.getCaretOffset(), 17, "caret offset is correct (again)");
+
+  Services.prompt = oldPrompt;
+
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/LayoutHelpers.jsm
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* ***** 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 the Mozilla LayoutHelpers Module.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Rob Campbell <rcampbell@mozilla.com> (original author)
+ *   Mihai Șucan <mihai.sucan@gmail.com>
+ *   Julian Viereck <jviereck@mozilla.com>
+ *   Paul Rouget <paul@mozilla.com>
+ *   Kyle Simpson <ksimpson@mozilla.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 ***** */
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+var EXPORTED_SYMBOLS = ["LayoutHelpers"];
+
+LayoutHelpers = {
+
+  /**
+   * Compute the position and the dimensions for the visible portion
+   * of a node, relativalely to the root window.
+   *
+   * @param nsIDOMNode aNode
+   *        a DOM element to be highlighted
+   */
+  getDirtyRect: function LH_getDirectyRect(aNode) {
+    let frameWin = aNode.ownerDocument.defaultView;
+    let clientRect = aNode.getBoundingClientRect();
+
+    // Go up in the tree of frames to determine the correct rectangle.
+    // clientRect is read-only, we need to be able to change properties.
+    rect = {top: clientRect.top,
+            left: clientRect.left,
+            width: clientRect.width,
+            height: clientRect.height};
+
+    // We iterate through all the parent windows.
+    while (true) {
+
+      // Does the selection overflow on the right of its window?
+      let diffx = frameWin.innerWidth - (rect.left + rect.width);
+      if (diffx < 0) {
+        rect.width += diffx;
+      }
+
+      // Does the selection overflow on the bottom of its window?
+      let diffy = frameWin.innerHeight - (rect.top + rect.height);
+      if (diffy < 0) {
+        rect.height += diffy;
+      }
+
+      // Does the selection overflow on the left of its window?
+      if (rect.left < 0) {
+        rect.width += rect.left;
+        rect.left = 0;
+      }
+
+      // Does the selection overflow on the top of its window?
+      if (rect.top < 0) {
+        rect.height += rect.top;
+        rect.top = 0;
+      }
+
+      // Selection has been clipped to fit in its own window.
+
+      // Are we in the top-level window?
+      if (frameWin.parent === frameWin || !frameWin.frameElement) {
+        break;
+      }
+
+      // We are in an iframe.
+      // We take into account the parent iframe position and its
+      // offset (borders and padding).
+      let frameRect = frameWin.frameElement.getBoundingClientRect();
+
+      let [offsetTop, offsetLeft] =
+        this.getIframeContentOffset(frameWin.frameElement);
+
+      rect.top += frameRect.top + offsetTop;
+      rect.left += frameRect.left + offsetLeft;
+
+      frameWin = frameWin.parent;
+    }
+
+    return rect;
+  },
+
+  /**
+   * Returns iframe content offset (iframe border + padding).
+   * Note: this function shouldn't need to exist, had the platform provided a
+   * suitable API for determining the offset between the iframe's content and
+   * its bounding client rect. Bug 626359 should provide us with such an API.
+   *
+   * @param aIframe
+   *        The iframe.
+   * @returns array [offsetTop, offsetLeft]
+   *          offsetTop is the distance from the top of the iframe and the
+   *            top of the content document.
+   *          offsetLeft is the distance from the left of the iframe and the
+   *            left of the content document.
+   */
+  getIframeContentOffset: function LH_getIframeContentOffset(aIframe) {
+    let style = aIframe.contentWindow.getComputedStyle(aIframe, null);
+
+    let paddingTop = parseInt(style.getPropertyValue("padding-top"));
+    let paddingLeft = parseInt(style.getPropertyValue("padding-left"));
+
+    let borderTop = parseInt(style.getPropertyValue("border-top-width"));
+    let borderLeft = parseInt(style.getPropertyValue("border-left-width"));
+
+    return [borderTop + paddingTop, borderLeft + paddingLeft];
+  },
+
+  /**
+   * Apply the page zoom factor.
+   */
+  getZoomedRect: function LH_getZoomedRect(aWin, aRect) {
+    // get page zoom factor, if any
+    let zoom =
+      aWin.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+        .getInterface(Components.interfaces.nsIDOMWindowUtils)
+        .screenPixelsPerCSSPixel;
+
+    // adjust rect for zoom scaling
+    let aRectScaled = {};
+    for (let prop in aRect) {
+      aRectScaled[prop] = aRect[prop] * zoom;
+    }
+
+    return aRectScaled;
+  },
+
+
+  /**
+   * Find an element from the given coordinates. This method descends through
+   * frames to find the element the user clicked inside frames.
+   *
+   * @param DOMDocument aDocument the document to look into.
+   * @param integer aX
+   * @param integer aY
+   * @returns Node|null the element node found at the given coordinates.
+   */
+  getElementFromPoint: function LH_elementFromPoint(aDocument, aX, aY)
+  {
+    let node = aDocument.elementFromPoint(aX, aY);
+    if (node && node.contentDocument) {
+      if (node instanceof Ci.nsIDOMHTMLIFrameElement) {
+        let rect = node.getBoundingClientRect();
+
+        // Gap between the iframe and its content window.
+        let [offsetTop, offsetLeft] = LayoutHelpers.getIframeContentOffset(node);
+
+        aX -= rect.left + offsetLeft;
+        aY -= rect.top + offsetTop;
+
+        if (aX < 0 || aY < 0) {
+          // Didn't reach the content document, still over the iframe.
+          return node;
+        }
+      }
+      if (node instanceof Ci.nsIDOMHTMLIFrameElement ||
+          node instanceof Ci.nsIDOMHTMLFrameElement) {
+        let subnode = this.getElementFromPoint(node.contentDocument, aX, aY);
+        if (subnode) {
+          node = subnode;
+        }
+      }
+    }
+    return node;
+  },
+};
--- a/browser/devtools/shared/Makefile.in
+++ b/browser/devtools/shared/Makefile.in
@@ -49,8 +49,9 @@ ifdef ENABLE_TESTS
 	DIRS += test
 endif
 
 include $(topsrcdir)/config/rules.mk
 
 libs::
 	$(NSINSTALL) $(srcdir)/Templater.jsm $(FINAL_TARGET)/modules/devtools
 	$(NSINSTALL) $(srcdir)/Promise.jsm $(FINAL_TARGET)/modules/devtools
+	$(NSINSTALL) $(srcdir)/LayoutHelpers.jsm $(FINAL_TARGET)/modules/devtools
--- a/browser/devtools/sourceeditor/Makefile.in
+++ b/browser/devtools/sourceeditor/Makefile.in
@@ -47,11 +47,12 @@ include $(DEPTH)/config/autoconf.mk
 ifdef ENABLE_TESTS
 	DIRS += test
 endif
 
 EXTRA_JS_MODULES = \
 	source-editor.jsm \
 	source-editor-orion.jsm \
 	source-editor-textarea.jsm \
+	source-editor-ui.jsm \
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/sourceeditor/source-editor-orion.jsm
+++ b/browser/devtools/sourceeditor/source-editor-orion.jsm
@@ -39,16 +39,17 @@
 
 "use strict";
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/source-editor-ui.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
                                    "@mozilla.org/widget/clipboardhelper;1",
                                    "nsIClipboardHelper");
 
 const ORION_SCRIPT = "chrome://browser/content/orion.js";
 const ORION_IFRAME = "data:text/html;charset=utf8,<!DOCTYPE html>" +
   "<html style='height:100%' dir='ltr'>" +
@@ -121,16 +122,18 @@ function SourceEditor() {
   // Update the SourceEditor defaults from user preferences.
 
   SourceEditor.DEFAULTS.TAB_SIZE =
     Services.prefs.getIntPref(SourceEditor.PREFS.TAB_SIZE);
   SourceEditor.DEFAULTS.EXPAND_TAB =
     Services.prefs.getBoolPref(SourceEditor.PREFS.EXPAND_TAB);
 
   this._onOrionSelection = this._onOrionSelection.bind(this);
+
+  this.ui = new SourceEditorUI(this);
 }
 
 SourceEditor.prototype = {
   _view: null,
   _iframe: null,
   _model: null,
   _undoStack: null,
   _linesRuler: null,
@@ -139,16 +142,23 @@ SourceEditor.prototype = {
   _annotationModel: null,
   _dragAndDrop: null,
   _mode: null,
   _expandTab: null,
   _tabSize: null,
   _iframeWindow: null,
 
   /**
+   * The Source Editor user interface manager.
+   * @type object
+   *       An instance of the SourceEditorUI.
+   */
+  ui: null,
+
+  /**
    * The editor container element.
    * @type nsIDOMElement
    */
   parentElement: null,
 
   /**
    * Initialize the editor.
    *
@@ -199,16 +209,17 @@ SourceEditor.prototype = {
     this._iframe.addEventListener("load", onIframeLoad, true);
 
     this._iframe.setAttribute("src", ORION_IFRAME);
 
     aElement.appendChild(this._iframe);
     this.parentElement = aElement;
     this._config = aConfig;
     this._onReadyCallback = aCallback;
+    this.ui.init();
   },
 
   /**
    * The editor iframe load event handler.
    * @private
    */
   _onIframeLoad: function SE__onIframeLoad()
   {
@@ -267,21 +278,32 @@ SourceEditor.prototype = {
 
     this.setMode(config.mode || SourceEditor.DEFAULTS.MODE);
 
     this._undoStack = new UndoStack(this._view,
       config.undoLimit || SourceEditor.DEFAULTS.UNDO_LIMIT);
 
     this._dragAndDrop = new TextDND(this._view, this._undoStack);
 
-    this._view.setAction("undo", this.undo.bind(this));
-    this._view.setAction("redo", this.redo.bind(this));
-    this._view.setAction("tab", this._doTab.bind(this));
-    this._view.setAction("Unindent Lines", this._doUnindentLines.bind(this));
-    this._view.setAction("enter", this._doEnter.bind(this));
+    let actions = {
+      "undo": [this.undo, this],
+      "redo": [this.redo, this],
+      "tab": [this._doTab, this],
+      "Unindent Lines": [this._doUnindentLines, this],
+      "enter": [this._doEnter, this],
+      "Find...": [this.ui.find, this.ui],
+      "Find Next Occurrence": [this.ui.findNext, this.ui],
+      "Find Previous Occurrence": [this.ui.findPrevious, this.ui],
+      "Goto Line...": [this.ui.gotoLine, this.ui],
+    };
+
+    for (let name in actions) {
+      let action = actions[name];
+      this._view.setAction(name, action[0].bind(action[1]));
+    }
 
     let keys = (config.keys || []).concat(DEFAULT_KEYBINDINGS);
     keys.forEach(function(aKey) {
       let binding = new KeyBinding(aKey.code, aKey.accel, aKey.shift, aKey.alt);
       this._view.setKeyBinding(binding, aKey.action);
 
       if (aKey.callback) {
         this._view.setAction(aKey.action, aKey.callback);
@@ -291,16 +313,17 @@ SourceEditor.prototype = {
 
   /**
    * The Orion "Load" event handler. This is called when the Orion editor
    * completes the initialization.
    * @private
    */
   _onOrionLoad: function SE__onOrionLoad()
   {
+    this.ui.onReady();
     if (this._onReadyCallback) {
       this._onReadyCallback(this);
       this._onReadyCallback = null;
     }
   },
 
   /**
    * The "tab" editor action implementation. This adds support for expanded tabs
@@ -878,23 +901,27 @@ SourceEditor.prototype = {
   destroy: function SE_destroy()
   {
     if (Services.appinfo.OS == "Linux") {
       this._view.removeEventListener("Selection", this._onOrionSelection);
     }
     this._onOrionSelection = null;
 
     this._view.destroy();
+    this.ui.destroy();
+    this.ui = null;
+
     this.parentElement.removeChild(this._iframe);
     this.parentElement = null;
     this._iframeWindow = null;
     this._iframe = null;
     this._undoStack = null;
     this._styler = null;
     this._linesRuler = null;
     this._dragAndDrop = null;
     this._annotationModel = null;
     this._annotationStyler = null;
     this._view = null;
     this._model = null;
     this._config = null;
+    this._lastFind = null;
   },
 };
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/source-editor-overlay.xul
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!-- ***** 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 Source Editor.
+   -
+   - The Initial Developer of the Original Code is
+   - The Mozilla Foundation.
+   - Portions created by the Initial Developer are Copyright (C) 2012
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Mihai Sucan <mihai.sucan@gmail.com> (original author)
+   -
+   - 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 ***** -->
+
+<overlay id="sourceEditorOverlay"
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <commandset id="sourceEditorCommands">
+    <command id="cmd_find" oncommand="goDoCommand('cmd_find')"/>
+    <command id="cmd_findAgain" oncommand="goDoCommand('cmd_findAgain')" disabled="true"/>
+    <command id="cmd_findPrevious" oncommand="goDoCommand('cmd_findPrevious')" disabled="true"/>
+    <command id="cmd_gotoLine" oncommand="goDoCommand('cmd_gotoLine')"/>
+  </commandset>
+</overlay>
--- a/browser/devtools/sourceeditor/source-editor-textarea.jsm
+++ b/browser/devtools/sourceeditor/source-editor-textarea.jsm
@@ -39,19 +39,33 @@
 
 "use strict";
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/source-editor-ui.jsm");
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
+/**
+ * Default key bindings in the textarea editor.
+ */
+const DEFAULT_KEYBINDINGS = [
+  {
+    _action: "_doTab",
+    keyCode: Ci.nsIDOMKeyEvent.DOM_VK_TAB,
+    shiftKey: false,
+    accelKey: false,
+    altKey: false,
+  },
+];
+
 var EXPORTED_SYMBOLS = ["SourceEditor"];
 
 /**
  * The SourceEditor object constructor. The SourceEditor component allows you to
  * provide users with an editor tailored to the specific needs of editing source
  * code, aimed primarily at web developers.
  *
  * The editor used here is a simple textarea. This is used as a fallback
@@ -64,28 +78,37 @@ function SourceEditor() {
 
   SourceEditor.DEFAULTS.TAB_SIZE =
     Services.prefs.getIntPref(SourceEditor.PREFS.TAB_SIZE);
   SourceEditor.DEFAULTS.EXPAND_TAB =
     Services.prefs.getBoolPref(SourceEditor.PREFS.EXPAND_TAB);
 
   this._listeners = {};
   this._lastSelection = {};
+
+  this.ui = new SourceEditorUI(this);
 }
 
 SourceEditor.prototype = {
   _textbox: null,
   _editor: null,
   _listeners: null,
   _lineDelimiter: null,
   _editActionListener: null,
   _expandTab: null,
   _tabSize: null,
 
   /**
+   * The Source Editor user interface manager.
+   * @type object
+   *       An instance of the SourceEditorUI.
+   */
+  ui: null,
+
+  /**
    * The editor container element.
    * @type nsIDOMElement
    */
   parentElement: null,
 
   /**
    * Initialize the editor.
    *
@@ -131,17 +154,17 @@ SourceEditor.prototype = {
     this._textbox.style.MozTabSize = this._tabSize;
 
     this._textbox.setAttribute("value", aConfig.placeholderText || "");
     this._textbox.setAttribute("class", "monospace");
     this._textbox.style.direction = "ltr";
     this._textbox.readOnly = aConfig.readOnly;
 
     // Make sure that the SourceEditor Selection events are fired properly.
-    // Also make sure that the Tab key inserts spaces when expandTab is true.
+    // Also make sure that the configured keyboard bindings work.
     this._textbox.addEventListener("select", this._onSelect.bind(this), false);
     this._textbox.addEventListener("keypress", this._onKeyPress.bind(this), false);
     this._textbox.addEventListener("keyup", this._onSelect.bind(this), false);
     this._textbox.addEventListener("click", this._onSelect.bind(this), false);
 
     // Mimic the mode change.
     this.setMode(aConfig.mode || SourceEditor.DEFAULTS.MODE);
 
@@ -156,39 +179,71 @@ SourceEditor.prototype = {
     this._editActionListener = new EditActionListener(this);
     this._editor.addEditActionListener(this._editActionListener);
 
     this._lineDelimiter = win.navigator.platform.indexOf("Win") > -1 ?
                           "\r\n" : "\n";
 
     this._config = aConfig;
 
+    for each (let key in DEFAULT_KEYBINDINGS) {
+      for (let prop in key) {
+        if (prop == "accelKey") {
+          let newProp = Services.appinfo.OS == "Darwin" ? "metaKey" : "ctrlKey";
+          key[newProp] = key[prop];
+          delete key[prop];
+          break;
+        }
+      }
+    }
+
+    this.ui.init();
+    this.ui.onReady();
+
     if (aCallback) {
       aCallback(this);
     }
   },
 
   /**
-   * The textbox keypress event handler allows users to indent code using the
-   * Tab key.
+   * The textbox keypress event handler calls the configured action for keyboard
+   * event.
    *
    * @private
    * @param nsIDOMEvent aEvent
    *        The DOM object for the event.
+   * @see DEFAULT_KEYBINDINGS
    */
   _onKeyPress: function SE__onKeyPress(aEvent)
   {
-    if (aEvent.keyCode != aEvent.DOM_VK_TAB || aEvent.shiftKey ||
-        aEvent.metaKey || aEvent.ctrlKey || aEvent.altKey) {
-      return;
+    for each (let key in DEFAULT_KEYBINDINGS) {
+      let matched = true;
+      for (let prop in key) {
+        if (prop.charAt(0) != "_" && aEvent[prop] !== key[prop]) {
+          matched = false;
+          break;
+        }
+      }
+      if (matched) {
+        let context = key._context ? this[key._context] : this;
+        context[key._action].call(context);
+        aEvent.preventDefault();
+        break;
+      }
     }
+  },
 
-    aEvent.preventDefault();
-
-    let caret = this.getCaretOffset();
+  /**
+   * The Tab keypress event handler. This allows the user to indent the code
+   * with spaces, when expandTab is true.
+   */
+  _doTab: function SE__doTab()
+  {
+    let selection = this.getSelection();
+    let caret = selection.start;
     let indent = "\t";
 
     if (this._expandTab) {
       let text = this._textbox.value;
       let lineStart = caret;
       while (lineStart > 0) {
         let c = text.charAt(lineStart - 1);
         if (c == "\r" || c == "\n") {
@@ -196,18 +251,18 @@ SourceEditor.prototype = {
         }
         lineStart--;
       }
       let offset = caret - lineStart;
       let spaces = this._tabSize - (offset % this._tabSize);
       indent = (new Array(spaces + 1)).join(" ");
     }
 
-    this.setText(indent, caret, caret);
-    this.setCaretOffset(caret + indent.length);
+    this.setText(indent, selection.start, selection.end);
+    this.setCaretOffset(selection.start + indent.length);
   },
 
   /**
    * The textbox keyup, click and select event handler tracks selection
    * changes. This method invokes the SourceEditor Selection event handlers.
    *
    * @see SourceEditor.EVENTS.SELECTION
    * @private
@@ -611,18 +666,18 @@ SourceEditor.prototype = {
    * @param number [aColumn=0]
    *        Optional. The new caret column location. Columns start from 0.
    */
   setCaretPosition: function SE_setCaretPosition(aLine, aColumn)
   {
     aColumn = aColumn || 0;
 
     let text = this._textbox.value;
-    let i = 0, n = text.length, c0, c1;
-    let line = 0, col = 0;
+    let i = -1, n = text.length, c0, c1;
+    let line = 0, col = -1;
     while (i < n) {
       c1 = text.charAt(i++);
       if (line < aLine && (c1 == "\r" || (c0 != "\r" && c1 == "\n"))) {
         // Count lines and reset the column only until we reach the desired line
         // such that if the desired column is out of boundaries we will stop
         // after the given number of characters from the line start.
         line++;
         col = 0;
@@ -704,24 +759,29 @@ SourceEditor.prototype = {
         if (aListener.domType) {
           aListener.target.removeEventListener(aListener.domType,
                                                aListener.handler, false);
         }
       }, this);
     }
 
     this._editor.removeEditActionListener(this._editActionListener);
+
+    this.ui.destroy();
+    this.ui = null;
+
     this.parentElement.removeChild(this._textbox);
     this.parentElement = null;
     this._editor = null;
     this._textbox = null;
     this._config = null;
     this._listeners = null;
     this._lastSelection = null;
     this._editActionListener = null;
+    this._lastFind = null;
   },
 };
 
 /**
  * The nsIEditActionListener for the nsIEditor of the xul:textbox used by the
  * SourceEditor. This listener traces text changes such that SourceEditor
  * TextChanged event handlers get their events.
  *
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/source-editor-ui.jsm
@@ -0,0 +1,288 @@
+/* vim:set ts=2 sw=2 sts=2 et tw=80:
+ * ***** 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 the Source Editor component.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mihai Sucan <mihai.sucan@gmail.com> (original author)
+ *
+ * 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 *****/
+
+"use strict";
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+var EXPORTED_SYMBOLS = ["SourceEditorUI"];
+
+/**
+ * The Source Editor component user interface.
+ */
+function SourceEditorUI(aEditor)
+{
+  this.editor = aEditor;
+}
+
+SourceEditorUI.prototype = {
+  /**
+   * Initialize the user interface. This is called by the SourceEditor.init()
+   * method.
+   */
+  init: function SEU_init()
+  {
+    this._ownerWindow = this.editor.parentElement.ownerDocument.defaultView;
+  },
+
+  /**
+   * The UI onReady function is executed once the Source Editor completes
+   * initialization and it is ready for usage. Currently this code sets up the
+   * nsIController.
+   */
+  onReady: function SEU_onReady()
+  {
+    if (this._ownerWindow.controllers) {
+      this._controller = new SourceEditorController(this.editor);
+      this._ownerWindow.controllers.insertControllerAt(0, this._controller);
+    }
+  },
+
+  /**
+   * The "go to line" command UI. This displays a prompt that allows the user to
+   * input the line number to jump to.
+   */
+  gotoLine: function SEU_gotoLine()
+  {
+    let oldLine = this.editor.getCaretPosition ?
+                  this.editor.getCaretPosition().line : null;
+    let newLine = {value: oldLine !== null ? oldLine + 1 : ""};
+
+    let result = Services.prompt.prompt(this._ownerWindow,
+      SourceEditorUI.strings.GetStringFromName("gotoLineCmd.promptTitle"),
+      SourceEditorUI.strings.GetStringFromName("gotoLineCmd.promptMessage"),
+      newLine, null, {});
+
+    newLine.value = parseInt(newLine.value);
+    if (result && !isNaN(newLine.value) && --newLine.value != oldLine) {
+      if (this.editor.getLineCount) {
+        let lines = this.editor.getLineCount() - 1;
+        this.editor.setCaretPosition(Math.max(0, Math.min(lines, newLine.value)));
+      } else {
+        this.editor.setCaretPosition(Math.max(0, newLine.value));
+      }
+    }
+
+    return true;
+  },
+
+  /**
+   * The "find" command UI. This displays a prompt that allows the user to input
+   * the string to search for in the code. By default the current selection is
+   * used as a search string, or the last search string.
+   */
+  find: function SEU_find()
+  {
+    let str = {value: this.editor.getSelectedText()};
+    if (!str.value && this.editor.lastFind) {
+      str.value = this.editor.lastFind.str;
+    }
+
+    let result = Services.prompt.prompt(this._ownerWindow,
+      SourceEditorUI.strings.GetStringFromName("findCmd.promptTitle"),
+      SourceEditorUI.strings.GetStringFromName("findCmd.promptMessage"),
+      str, null, {});
+
+    if (result && str.value) {
+      let start = this.editor.getSelection().end;
+      let pos = this.editor.find(str.value, {ignoreCase: true, start: start});
+      if (pos == -1) {
+        this.editor.find(str.value, {ignoreCase: true});
+      }
+      this._onFind();
+    }
+
+    return true;
+  },
+
+  /**
+   * Find the next occurrence of the last search string.
+   */
+  findNext: function SEU_findNext()
+  {
+    let lastFind = this.editor.lastFind;
+    if (lastFind) {
+      this.editor.findNext(true);
+      this._onFind();
+    }
+
+    return true;
+  },
+
+  /**
+   * Find the previous occurrence of the last search string.
+   */
+  findPrevious: function SEU_findPrevious()
+  {
+    let lastFind = this.editor.lastFind;
+    if (lastFind) {
+      this.editor.findPrevious(true);
+      this._onFind();
+    }
+
+    return true;
+  },
+
+  /**
+   * This executed after each find/findNext/findPrevious operation.
+   * @private
+   */
+  _onFind: function SEU__onFind()
+  {
+    let lastFind = this.editor.lastFind;
+    if (lastFind && lastFind.index > -1) {
+      this.editor.setSelection(lastFind.index, lastFind.index + lastFind.str.length);
+    }
+
+    if (this._ownerWindow.goUpdateCommand) {
+      this._ownerWindow.goUpdateCommand("cmd_findAgain");
+      this._ownerWindow.goUpdateCommand("cmd_findPrevious");
+    }
+  },
+
+  /**
+   * Destroy the SourceEditorUI instance. This is called by the
+   * SourceEditor.destroy() method.
+   */
+  destroy: function SEU_destroy()
+  {
+    this._ownerWindow = null;
+    this.editor = null;
+    this._controller = null;
+  },
+};
+
+/**
+ * The Source Editor nsIController implements features that need to be available
+ * from XUL commands.
+ *
+ * @constructor
+ * @param object aEditor
+ *        SourceEditor object instance for which the controller is instanced.
+ */
+function SourceEditorController(aEditor)
+{
+  this._editor = aEditor;
+}
+
+SourceEditorController.prototype = {
+  /**
+   * Check if a command is supported by the controller.
+   *
+   * @param string aCommand
+   *        The command name you want to check support for.
+   * @return boolean
+   *         True if the command is supported, false otherwise.
+   */
+  supportsCommand: function SEC_supportsCommand(aCommand)
+  {
+    let result;
+
+    switch (aCommand) {
+      case "cmd_find":
+      case "cmd_findAgain":
+      case "cmd_findPrevious":
+      case "cmd_gotoLine":
+        result = true;
+        break;
+      default:
+        result = false;
+        break;
+    }
+
+    return result;
+  },
+
+  /**
+   * Check if a command is enabled or not.
+   *
+   * @param string aCommand
+   *        The command name you want to check if it is enabled or not.
+   * @return boolean
+   *         True if the command is enabled, false otherwise.
+   */
+  isCommandEnabled: function SEC_isCommandEnabled(aCommand)
+  {
+    let result;
+
+    switch (aCommand) {
+      case "cmd_find":
+      case "cmd_gotoLine":
+        result = true;
+        break;
+      case "cmd_findAgain":
+      case "cmd_findPrevious":
+        result = this._editor.lastFind && this._editor.lastFind.lastFound != -1;
+        break;
+      default:
+        result = false;
+        break;
+    }
+
+    return result;
+  },
+
+  /**
+   * Perform a command.
+   *
+   * @param string aCommand
+   *        The command name you want to execute.
+   * @return void
+   */
+  doCommand: function SEC_doCommand(aCommand)
+  {
+    switch (aCommand) {
+      case "cmd_find":
+        this._editor.ui.find();
+        break;
+      case "cmd_findAgain":
+        this._editor.ui.findNext();
+        break;
+      case "cmd_findPrevious":
+        this._editor.ui.findPrevious();
+        break;
+      case "cmd_gotoLine":
+        this._editor.ui.gotoLine();
+        break;
+    }
+  },
+
+  onEvent: function() { }
+};
--- a/browser/devtools/sourceeditor/source-editor.jsm
+++ b/browser/devtools/sourceeditor/source-editor.jsm
@@ -36,22 +36,28 @@
  *
  * ***** END LICENSE BLOCK *****/
 
 "use strict";
 
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/source-editor-ui.jsm");
 
 const PREF_EDITOR_COMPONENT = "devtools.editor.component";
+const SOURCEEDITOR_L10N = "chrome://browser/locale/devtools/sourceeditor.properties";
 
 var component = Services.prefs.getCharPref(PREF_EDITOR_COMPONENT);
 var obj = {};
 try {
+  if (component == "ui") {
+    throw new Error("The UI editor component is not available.");
+  }
   Cu.import("resource:///modules/source-editor-" + component + ".jsm", obj);
 } catch (ex) {
   Cu.reportError(ex);
   Cu.reportError("SourceEditor component failed to load: " + component);
 
   // If the component does not exist, clear the user pref back to the default.
   Services.prefs.clearUserPref(PREF_EDITOR_COMPONENT);
 
@@ -61,16 +67,20 @@ try {
 }
 
 // Export the SourceEditor.
 var SourceEditor = obj.SourceEditor;
 var EXPORTED_SYMBOLS = ["SourceEditor"];
 
 // Add the constants used by all SourceEditors.
 
+XPCOMUtils.defineLazyGetter(SourceEditorUI, "strings", function() {
+  return Services.strings.createBundle(SOURCEEDITOR_L10N);
+});
+
 /**
  * Known SourceEditor preferences.
  */
 SourceEditor.PREFS = {
   TAB_SIZE: "devtools.editor.tabsize",
   EXPAND_TAB: "devtools.editor.expandtab",
   COMPONENT: PREF_EDITOR_COMPONENT,
 };
@@ -137,8 +147,166 @@ SourceEditor.EVENTS = {
    * object properties:
    *   - oldValue - the old selection range.
    *   - newValue - the new selection range.
    * Both ranges are objects which hold two properties: start and end.
    */
   SELECTION: "Selection",
 };
 
+/**
+ * Extend a destination object with properties from a source object.
+ *
+ * @param object aDestination
+ * @param object aSource
+ */
+function extend(aDestination, aSource)
+{
+  for (let name in aSource) {
+    if (!aDestination.hasOwnProperty(name)) {
+      aDestination[name] = aSource[name];
+    }
+  }
+}
+
+/**
+ * Add methods common to all components.
+ */
+extend(SourceEditor.prototype, {
+  _lastFind: null,
+
+  /**
+   * Find a string in the editor.
+   *
+   * @param string aString
+   *        The string you want to search for. If |aString| is not given the
+   *        currently selected text is used.
+   * @param object [aOptions]
+   *        Optional find options:
+   *        - start: (integer) offset to start searching from. Default: 0 if
+   *        backwards is false. If backwards is true then start = text.length.
+   *        - ignoreCase: (boolean) tells if you want the search to be case
+   *        insensitive or not. Default: false.
+   *        - backwards: (boolean) tells if you want the search to go backwards
+   *        from the given |start| offset. Default: false.
+   * @return integer
+   *        The offset where the string was found.
+   */
+  find: function SE_find(aString, aOptions)
+  {
+    if (typeof(aString) != "string") {
+      return -1;
+    }
+
+    aOptions = aOptions || {};
+
+    let str = aOptions.ignoreCase ? aString.toLowerCase() : aString;
+
+    let text = this.getText();
+    if (aOptions.ignoreCase) {
+      text = text.toLowerCase();
+    }
+
+    let index = aOptions.backwards ?
+                text.lastIndexOf(str, aOptions.start) :
+                text.indexOf(str, aOptions.start);
+
+    let lastFoundIndex = index;
+    if (index == -1 && this.lastFind && this.lastFind.index > -1 &&
+        this.lastFind.str === aString &&
+        this.lastFind.ignoreCase === !!aOptions.ignoreCase) {
+      lastFoundIndex = this.lastFind.index;
+    }
+
+    this._lastFind = {
+      str: aString,
+      index: index,
+      lastFound: lastFoundIndex,
+      ignoreCase: !!aOptions.ignoreCase,
+    };
+
+    return index;
+  },
+
+  /**
+   * Find the next occurrence of the last search operation.
+   *
+   * @param boolean aWrap
+   *        Tells if you want to restart the search from the beginning of the
+   *        document if the string is not found.
+   * @return integer
+   *        The offset where the string was found.
+   */
+  findNext: function SE_findNext(aWrap)
+  {
+    if (!this.lastFind && this.lastFind.lastFound == -1) {
+      return -1;
+    }
+
+    let options = {
+      start: this.lastFind.lastFound + this.lastFind.str.length,
+      ignoreCase: this.lastFind.ignoreCase,
+    };
+
+    let index = this.find(this.lastFind.str, options);
+    if (index == -1 && aWrap) {
+      options.start = 0;
+      index = this.find(this.lastFind.str, options);
+    }
+
+    return index;
+  },
+
+  /**
+   * Find the previous occurrence of the last search operation.
+   *
+   * @param boolean aWrap
+   *        Tells if you want to restart the search from the end of the
+   *        document if the string is not found.
+   * @return integer
+   *        The offset where the string was found.
+   */
+  findPrevious: function SE_findPrevious(aWrap)
+  {
+    if (!this.lastFind && this.lastFind.lastFound == -1) {
+      return -1;
+    }
+
+    let options = {
+      start: this.lastFind.lastFound - this.lastFind.str.length,
+      ignoreCase: this.lastFind.ignoreCase,
+      backwards: true,
+    };
+
+    let index;
+    if (options.start > 0) {
+      index = this.find(this.lastFind.str, options);
+    } else {
+      index = this._lastFind.index = -1;
+    }
+
+    if (index == -1 && aWrap) {
+      options.start = this.getCharCount() - 1;
+      index = this.find(this.lastFind.str, options);
+    }
+
+    return index;
+  },
+});
+
+/**
+ * Retrieve the last find operation result. This object holds the following
+ * properties:
+ *   - str: the last search string.
+ *   - index: stores the result of the most recent find operation. This is the
+ *   index in the text where |str| was found or -1 otherwise.
+ *   - lastFound: tracks the index where |str| was last found, throughout
+ *   multiple find operations. This can be -1 if |str| was never found in the
+ *   document.
+ *   - ignoreCase: tells if the search was case insensitive or not.
+ * @type object
+ */
+Object.defineProperty(SourceEditor.prototype, "lastFind", {
+  get: function() { return this._lastFind; },
+  enumerable: true,
+  configurable: true,
+});
+
--- a/browser/devtools/sourceeditor/test/Makefile.in
+++ b/browser/devtools/sourceeditor/test/Makefile.in
@@ -48,12 +48,13 @@ include $(topsrcdir)/config/rules.mk
 		browser_sourceeditor_initialization.js \
 		browser_bug684862_paste_html.js \
 		browser_bug687573_vscroll.js \
 		browser_bug687568_pagescroll.js \
 		browser_bug687580_drag_and_drop.js \
 		browser_bug684546_reset_undo.js \
 		browser_bug695035_middle_click_paste.js \
 		browser_bug687160_line_api.js \
+		browser_bug650345_find.js \
 		head.js \
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_bug650345_find.js
@@ -0,0 +1,147 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource:///modules/source-editor.jsm");
+
+let testWin;
+let editor;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  const windowUrl = "data:text/xml,<?xml version='1.0'?>" +
+    "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
+    " title='test for bug 650345' width='600' height='500'><hbox flex='1'/></window>";
+  const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+  testWin = Services.ww.openWindow(null, windowUrl, "_blank", windowFeatures, null);
+  testWin.addEventListener("load", function onWindowLoad() {
+    testWin.removeEventListener("load", onWindowLoad, false);
+    waitForFocus(initEditor, testWin);
+  }, false);
+}
+
+function initEditor()
+{
+  let hbox = testWin.document.querySelector("hbox");
+  editor = new SourceEditor();
+  editor.init(hbox, {}, editorLoaded);
+}
+
+function editorLoaded()
+{
+  editor.focus();
+
+  let text = "foobar bug650345\nBug650345 bazbaz\nfoobar omg\ntest";
+  editor.setText(text);
+
+  let needle = "foobar";
+  is(editor.find(), -1, "find() works");
+  ok(!editor.lastFind, "no editor.lastFind yet");
+
+  is(editor.find(needle), 0, "find('" + needle + "') works");
+  is(editor.lastFind.str, needle, "lastFind.str is correct");
+  is(editor.lastFind.index, 0, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, 0, "lastFind.lastFound is correct");
+  is(editor.lastFind.ignoreCase, false, "lastFind.ignoreCase is correct");
+
+  let newIndex = text.indexOf(needle, needle.length);
+  is(editor.findNext(), newIndex, "findNext() works");
+  is(editor.lastFind.str, needle, "lastFind.str is correct");
+  is(editor.lastFind.index, newIndex, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, newIndex, "lastFind.lastFound is correct");
+  is(editor.lastFind.ignoreCase, false, "lastFind.ignoreCase is correct");
+
+  is(editor.findNext(), -1, "findNext() works again");
+  is(editor.lastFind.index, -1, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, newIndex, "lastFind.lastFound is correct");
+
+  is(editor.findPrevious(), 0, "findPrevious() works");
+  is(editor.lastFind.index, 0, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, 0, "lastFind.lastFound is correct");
+
+  is(editor.findPrevious(), -1, "findPrevious() works again");
+  is(editor.lastFind.index, -1, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, 0, "lastFind.lastFound is correct");
+
+  is(editor.findNext(), newIndex, "findNext() works");
+  is(editor.lastFind.index, newIndex, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, newIndex, "lastFind.lastFound is correct");
+
+  is(editor.findNext(true), 0, "findNext(true) works");
+  is(editor.lastFind.index, 0, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, 0, "lastFind.lastFound is correct");
+
+  is(editor.findNext(true), newIndex, "findNext(true) works again");
+  is(editor.lastFind.index, newIndex, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, newIndex, "lastFind.lastFound is correct");
+
+  is(editor.findPrevious(true), 0, "findPrevious(true) works");
+  is(editor.lastFind.index, 0, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, 0, "lastFind.lastFound is correct");
+
+  is(editor.findPrevious(true), newIndex, "findPrevious(true) works again");
+  is(editor.lastFind.index, newIndex, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, newIndex, "lastFind.lastFound is correct");
+
+  needle = "error";
+  is(editor.find(needle), -1, "find('" + needle + "') works");
+  is(editor.lastFind.str, needle, "lastFind.str is correct");
+  is(editor.lastFind.index, -1, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, -1, "lastFind.lastFound is correct");
+  is(editor.lastFind.ignoreCase, false, "lastFind.ignoreCase is correct");
+
+  is(editor.findNext(), -1, "findNext() works");
+  is(editor.lastFind.str, needle, "lastFind.str is correct");
+  is(editor.lastFind.index, -1, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, -1, "lastFind.lastFound is correct");
+  is(editor.findNext(true), -1, "findNext(true) works");
+
+  is(editor.findPrevious(), -1, "findPrevious() works");
+  is(editor.findPrevious(true), -1, "findPrevious(true) works");
+
+  needle = "bug650345";
+  newIndex = text.indexOf(needle);
+
+  is(editor.find(needle), newIndex, "find('" + needle + "') works");
+  is(editor.findNext(), -1, "findNext() works");
+  is(editor.findNext(true), newIndex, "findNext(true) works");
+  is(editor.findPrevious(), -1, "findPrevious() works");
+  is(editor.findPrevious(true), newIndex, "findPrevious(true) works");
+  is(editor.lastFind.index, newIndex, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, newIndex, "lastFind.lastFound is correct");
+
+  is(editor.find(needle, {ignoreCase: 1}), newIndex,
+     "find('" + needle + "', {ignoreCase: 1}) works");
+  is(editor.lastFind.ignoreCase, true, "lastFind.ignoreCase is correct");
+
+  let newIndex2 = text.toLowerCase().indexOf(needle, newIndex + needle.length);
+  is(editor.findNext(), newIndex2, "findNext() works");
+  is(editor.findNext(), -1, "findNext() works");
+  is(editor.lastFind.index, -1, "lastFind.index is correct");
+  is(editor.lastFind.lastFound, newIndex2, "lastFind.lastFound is correct");
+
+  is(editor.findNext(true), newIndex, "findNext(true) works");
+
+  is(editor.findPrevious(), -1, "findPrevious() works");
+  is(editor.findPrevious(true), newIndex2, "findPrevious(true) works");
+  is(editor.findPrevious(), newIndex, "findPrevious() works again");
+
+  needle = "foobar";
+  newIndex = text.indexOf(needle, 2);
+  is(editor.find(needle, {start: 2}), newIndex,
+     "find('" + needle + "', {start:2}) works");
+  is(editor.findNext(), -1, "findNext() works");
+  is(editor.findNext(true), 0, "findNext(true) works");
+
+  editor.destroy();
+
+  testWin.close();
+  testWin = editor = null;
+
+  waitForFocus(finish, window);
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
@@ -87,29 +87,27 @@ function setupHighlighterTests()
 }
 
 function runSelectionTests()
 {
   Services.obs.removeObserver(runSelectionTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   executeSoon(function() {
-    Services.obs.addObserver(performTestComparisons,
-      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.highlighter.addListener("nodeselected", performTestComparisons);
     EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
   });
 }
 
 function performTestComparisons(evt)
 {
-  Services.obs.removeObserver(performTestComparisons,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+  InspectorUI.highlighter.removeListener("nodeselected", performTestComparisons);
 
   InspectorUI.stopInspecting();
-  ok(InspectorUI.highlighter.isHighlighting, "highlighter is highlighting");
+  is(InspectorUI.highlighter.node, h1, "node selected");
   is(InspectorUI.selection, h1, "selection matches node");
 
   HUDService.activateHUDForContext(gBrowser.selectedTab);
   let hudId = HUDService.getHudIdByWindow(content);
   let hud = HUDService.hudReferences[hudId];
   let jsterm = hud.jsterm;
   outputNode = hud.outputNode;
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -1,10 +1,15 @@
 ; Package file for the Firefox build. 
 ;
+; Packaging manifest is used to copy files from dist/bin
+; to the staging directory.
+; Some other files are built in the staging directory directly,
+; so they will be implicitly packaged too.
+;
 ; File format:
 ;
 ; [] designates a toplevel component. Example: [xpcom]
 ; - in front of a file specifies it to be removed from the destination
 ; * wildcard support to recursively copy the entire directory
 ; ; file comment
 ;
 
@@ -141,16 +146,17 @@
 @BINPATH@/components/dom_system_b2g.xpt
 #endif
 @BINPATH@/components/dom_battery.xpt
 @BINPATH@/components/dom_canvas.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_geolocation.xpt
+@BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_indexeddb.xpt
 @BINPATH@/components/dom_offline.xpt
 @BINPATH@/components/dom_json.xpt
 @BINPATH@/components/dom_range.xpt
 @BINPATH@/components/dom_sidebar.xpt
 @BINPATH@/components/dom_sms.xpt
@@ -496,46 +502,39 @@
 
 ; svg
 @BINPATH@/res/svg.css
 @BINPATH@/components/dom_svg.xpt
 @BINPATH@/components/dom_smil.xpt
 
 ; [Personal Security Manager]
 ;
+; NSS libraries are signed in the staging directory,
+; meaning their .chk files are created there directly.
+;
+@BINPATH@/@DLL_PREFIX@freebl3@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@nss3@DLL_SUFFIX@
 @BINPATH@/@DLL_PREFIX@nssckbi@DLL_SUFFIX@
-@BINPATH@/components/pipboot.xpt
-@BINPATH@/components/pipnss.xpt
-@BINPATH@/components/pippki.xpt
-@BINPATH@/@DLL_PREFIX@nss3@DLL_SUFFIX@
+#ifndef NSS_DISABLE_DBM
+@BINPATH@/@DLL_PREFIX@nssdbm3@DLL_SUFFIX@
+#endif
 @BINPATH@/@DLL_PREFIX@nssutil3@DLL_SUFFIX@
 @BINPATH@/@DLL_PREFIX@smime3@DLL_SUFFIX@
 @BINPATH@/@DLL_PREFIX@softokn3@DLL_SUFFIX@
-@BINPATH@/@DLL_PREFIX@freebl3@DLL_SUFFIX@
 @BINPATH@/@DLL_PREFIX@ssl3@DLL_SUFFIX@
-#ifndef CROSS_COMPILE
-@BINPATH@/@DLL_PREFIX@freebl3.chk
-@BINPATH@/@DLL_PREFIX@softokn3.chk
-#endif
-#ifndef NSS_DISABLE_DBM
-@BINPATH@/@DLL_PREFIX@nssdbm3@DLL_SUFFIX@
-#ifndef CROSS_COMPILE
-@BINPATH@/@DLL_PREFIX@nssdbm3.chk
-#endif
-#endif
 @BINPATH@/chrome/pippki@JAREXT@
 @BINPATH@/chrome/pippki.manifest
+@BINPATH@/components/pipboot.xpt
+@BINPATH@/components/pipnss.xpt
+@BINPATH@/components/pippki.xpt
 
 ; for Solaris SPARC
 #ifdef SOLARIS
-bin/libfreebl_32fpu_3.chk
 bin/libfreebl_32fpu_3.so
-bin/libfreebl_32int_3.chk
 bin/libfreebl_32int_3.so
-bin/libfreebl_32int64_3.chk
 bin/libfreebl_32int64_3.so
 #endif
 
 ; [Updater]
 ;
 #ifdef XP_MACOSX
 @BINPATH@/updater.app/
 #else
--- a/browser/installer/windows/nsis/maintenanceservice_installer.nsi
+++ b/browser/installer/windows/nsis/maintenanceservice_installer.nsi
@@ -116,19 +116,18 @@ ShowUnInstDetails nevershow
 !define MUI_UNICON setup.ico
 !define MUI_WELCOMEPAGE_TITLE_3LINES
 !define MUI_UNWELCOMEFINISHPAGE_BITMAP wizWatermark.bmp
 
 ;Interface Settings
 !define MUI_ABORTWARNING
 
 ; Uninstaller Pages
-!insertmacro MUI_UNPAGE_WELCOME
+!insertmacro MUI_UNPAGE_CONFIRM
 !insertmacro MUI_UNPAGE_INSTFILES
-!insertmacro MUI_UNPAGE_FINISH
 
 ################################################################################
 # Language
 
 !insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
 !verbose push
 !verbose 3
 !include "overrideLocale.nsh"
@@ -252,16 +251,18 @@ FunctionEnd
 Section "Uninstall"
   ; Delete the service so that no updates will be attempted
   nsExec::Exec '"$INSTDIR\maintenanceservice.exe" uninstall'
 
   Push "$INSTDIR\maintenanceservice.exe"
   Call un.RenameDelete
   Push "$INSTDIR\maintenanceservice_tmp.exe"
   Call un.RenameDelete
+  Push "$INSTDIR\maintenanceservice.old"
+  Call un.RenameDelete
   Push "$INSTDIR\Uninstall.exe"
   Call un.RenameDelete
   RMDir /REBOOTOK "$INSTDIR"
 
   DeleteRegKey HKLM "${MaintUninstallKey}"
 
   SetRegView 64
   DeleteRegValue HKLM "Software\Mozilla\MaintenanceService" "Installed"
--- a/browser/locales/en-US/chrome/browser/devtools/scratchpad.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/scratchpad.dtd
@@ -62,16 +62,36 @@
 <!ENTITY pasteCmd.label               "Paste">
 <!ENTITY pasteCmd.key                 "V">
 <!ENTITY pasteCmd.accesskey           "P">
 
 <!ENTITY selectAllCmd.label           "Select All">
 <!ENTITY selectAllCmd.key             "A">
 <!ENTITY selectAllCmd.accesskey       "A">
 
+<!ENTITY findCmd.label                "Find…">
+<!ENTITY findCmd.key                  "F">
+<!ENTITY findCmd.accesskey            "F">
+
+<!ENTITY findAgainCmd.label           "Find Again…">
+<!-- LOCALIZATION NOTE (findAgainCmd.key): This key is used only on Macs.
+  -  Windows and Linux builds use the F3 key which is not localizable on purpose.
+  -->
+<!ENTITY findAgainCmd.key             "G">
+<!ENTITY findAgainCmd.accesskey       "g">
+<!-- LOCALIZATION NOTE (findPreviousCmd.key): This key is used only on Macs.
+  -  Windows and Linux builds use the Shift-F3 key which is not localizable on
+  -  purpose.
+  -->
+<!ENTITY findPreviousCmd.key          "G">
+
+<!ENTITY gotoLineCmd.label            "Jump to line…">
+<!ENTITY gotoLineCmd.key              "J">
+<!ENTITY gotoLineCmd.accesskey        "J">
+
 <!ENTITY run.label                    "Run">
 <!ENTITY run.accesskey                "R">
 <!ENTITY run.key                      "r">
 
 <!ENTITY inspect.label                "Inspect">
 <!ENTITY inspect.accesskey            "I">
 <!ENTITY inspect.key                  "i">
 
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/sourceeditor.properties
@@ -0,0 +1,30 @@
+# LOCALIZATION NOTE These strings are used inside the Source Editor component.
+# This component is used whenever source code is displayed for the purpose of
+# being edited, inside the Firefox developer tools - current examples are the
+# Scratchpad and the Style Editor tools.
+
+# LOCALIZATION NOTE The correct localization of this file might be to keep it
+# in English, or another language commonly spoken among web developers.
+# You want to make that choice consistent across the developer tools.
+# A good criteria is the language in which you'd find the best documentation
+# on web development on the web.
+
+# LOCALIZATION NOTE  (findCmd.promptTitle): This is the dialog title used
+# when the user wants to search for a string in the code. You can
+# access this feature by pressing Ctrl-F on Windows/Linux or Cmd-F on Mac.
+findCmd.promptTitle=Find…
+
+# LOCALIZATION NOTE  (gotoLineCmd.promptMessage): This is the message shown when
+# the user wants to search for a string in the code. You can
+# access this feature by pressing Ctrl-F on Windows/Linux or Cmd-F on Mac.
+findCmd.promptMessage=Search for:
+
+# LOCALIZATION NOTE  (gotoLineCmd.promptTitle): This is the dialog title used
+# when the user wants to jump to a specific line number in the code. You can
+# access this feature by pressing Ctrl-J on Windows/Linux or Cmd-J on Mac.
+gotoLineCmd.promptTitle=Go to line…
+
+# LOCALIZATION NOTE  (gotoLineCmd.promptMessage): This is the message shown when
+# the user wants to jump to a specific line number in the code. You can
+# access this feature by pressing Ctrl-J on Windows/Linux or Cmd-J on Mac.
+gotoLineCmd.promptMessage=Jump to line number:
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -22,16 +22,17 @@
     locale/browser/devtools/tilt.properties           (%chrome/browser/devtools/tilt.properties)
     locale/browser/devtools/scratchpad.properties     (%chrome/browser/devtools/scratchpad.properties)
     locale/browser/devtools/scratchpad.dtd            (%chrome/browser/devtools/scratchpad.dtd)
     locale/browser/devtools/styleeditor.properties    (%chrome/browser/devtools/styleeditor.properties)
     locale/browser/devtools/styleeditor.dtd           (%chrome/browser/devtools/styleeditor.dtd)
     locale/browser/devtools/styleinspector.properties (%chrome/browser/devtools/styleinspector.properties)
     locale/browser/devtools/styleinspector.dtd        (%chrome/browser/devtools/styleinspector.dtd)
     locale/browser/devtools/webConsole.dtd            (%chrome/browser/devtools/webConsole.dtd)
+    locale/browser/devtools/sourceeditor.properties   (%chrome/browser/devtools/sourceeditor.properties)
     locale/browser/openLocation.dtd                (%chrome/browser/openLocation.dtd)
     locale/browser/openLocation.properties         (%chrome/browser/openLocation.properties)
 *   locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
     locale/browser/pageInfo.properties             (%chrome/browser/pageInfo.properties)
     locale/browser/quitDialog.properties           (%chrome/browser/quitDialog.properties)
 *   locale/browser/safeMode.dtd                    (%chrome/browser/safeMode.dtd)
     locale/browser/sanitize.dtd                    (%chrome/browser/sanitize.dtd)
     locale/browser/search.properties               (%chrome/browser/search.properties)
--- a/browser/themes/pinstripe/browser.css
+++ b/browser/themes/pinstripe/browser.css
@@ -1697,22 +1697,20 @@ toolbarbutton.chevron > .toolbarbutton-m
 .tab-throbber[progress] {
   list-style-image: url("chrome://browser/skin/tabbrowser/loading.png");
 }
 
 .tabbrowser-tab:not(:hover) > .tab-stack > .tab-content > .tab-icon-image:not([selected="true"]) {
   opacity: .8;
 }
 
-/* Prevent overlapping of tabs during the close animation */
-.tab-label:not([fadein]):not([pinned]) {
-  margin-left: -16px;
-  margin-right: -16px;
-  -moz-transition: opacity 100ms ease-out,
-                   margin 30ms ease-out 80ms;
+.tabbrowser-tab:not([pinned]):not([fadein]) {
+  -moz-transition: min-width 200ms ease-out /* copied from browser/base/content/browser.css */,
+                   max-width 250ms ease-out /* copied from browser/base/content/browser.css */,
+                   opacity 50ms ease-out 100ms /* hide the tab for the last 100ms of the max-width transition */;
 }
 
 .tab-stack {
   /* ensure stable tab height with and without toolbarbuttons on the tab bar */
   height: 26px;
 }
 
 .tabbrowser-tab,
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -141,16 +141,17 @@ MOZ_INSTALLER	= @MOZ_INSTALLER@
 MOZ_MAINTENANCE_SERVICE	= @MOZ_MAINTENANCE_SERVICE@
 MOZ_UPDATER	= @MOZ_UPDATER@
 MOZ_UPDATE_CHANNEL	= @MOZ_UPDATE_CHANNEL@
 MOZ_UPDATE_PACKAGING	= @MOZ_UPDATE_PACKAGING@
 MOZ_DISABLE_PARENTAL_CONTROLS = @MOZ_DISABLE_PARENTAL_CONTROLS@
 NS_ENABLE_TSF = @NS_ENABLE_TSF@
 MOZ_SPELLCHECK = @MOZ_SPELLCHECK@
 MOZ_ANDROID_HISTORY = @MOZ_ANDROID_HISTORY@
+MOZ_WEBSMS_BACKEND = @MOZ_WEBSMS_BACKEND@
 MOZ_JAVA_COMPOSITOR = @MOZ_JAVA_COMPOSITOR@
 MOZ_PROFILELOCKING = @MOZ_PROFILELOCKING@
 MOZ_FEEDS = @MOZ_FEEDS@
 MOZ_TOOLKIT_SEARCH = @MOZ_TOOLKIT_SEARCH@
 MOZ_PLACES = @MOZ_PLACES@
 MOZ_SAFE_BROWSING = @MOZ_SAFE_BROWSING@
 MOZ_URL_CLASSIFIER = @MOZ_URL_CLASSIFIER@
 MOZ_ZIPWRITER = @MOZ_ZIPWRITER@
--- a/configure.in
+++ b/configure.in
@@ -4624,16 +4624,17 @@ MOZ_DISABLE_DOMCRYPTO=
 NSS_DISABLE_DBM=
 NECKO_WIFI=1
 NECKO_COOKIES=1
 NECKO_PROTOCOLS_DEFAULT="about data file ftp http res viewsource websocket wyciwyg device"
 USE_ARM_KUSER=
 BUILD_CTYPES=1
 MOZ_USE_NATIVE_POPUP_WINDOWS=
 MOZ_ANDROID_HISTORY=
+MOZ_WEBSMS_BACKEND=1
 MOZ_GRAPHITE=1
 
 case "${target}" in
 *darwin*)
     ACCESSIBILITY=
     ;;
 *)
     ACCESSIBILITY=1
@@ -5460,16 +5461,28 @@ AC_SUBST(MOZ_DBUS_GLIB_LIBS)
 dnl ========================================================
 dnl = Enable Android History instead of Places
 dnl ========================================================
 if test -n "$MOZ_ANDROID_HISTORY"; then
      dnl Do this if defined in confvars.sh
      AC_DEFINE(MOZ_ANDROID_HISTORY)
 fi
 
+dnl ========================================================
+dnl = Disable WebSMS backend
+dnl ========================================================
+MOZ_ARG_DISABLE_BOOL(websms-backend,
+[  --disable-websms-backend
+                           Disable WebSMS backend],
+    MOZ_WEBSMS_BACKEND=,
+    MOZ_WEBSMS_BACKEND=1)
+
+if test $MOZ_WEBSMS_BACKEND -eq 1; then
+    AC_DEFINE(MOZ_WEBSMS_BACKEND)
+fi
 
 dnl ========================================================
 dnl = Build Personal Security Manager
 dnl ========================================================
 MOZ_ARG_DISABLE_BOOL(crypto,
 [  --disable-crypto        Disable crypto support (Personal Security Manager)],
     MOZ_PSM=,
     MOZ_PSM=1 )
@@ -8440,16 +8453,17 @@ AC_SUBST(MOZ_DIRECTX_SDK_PATH)
 AC_SUBST(MOZ_DIRECTX_SDK_CPU_SUFFIX)
 AC_SUBST(MOZ_D3DX9_VERSION)
 AC_SUBST(MOZ_D3DX9_CAB)
 AC_SUBST(MOZ_D3DCOMPILER_CAB)
 AC_SUBST(MOZ_D3DX9_DLL)
 AC_SUBST(MOZ_D3DCOMPILER_DLL)
 
 AC_SUBST(MOZ_ANDROID_HISTORY)
+AC_SUBST(MOZ_WEBSMS_BACKEND)
 AC_SUBST(ENABLE_STRIP)
 AC_SUBST(PKG_SKIP_STRIP)
 AC_SUBST(USE_ELF_DYNSTR_GC)
 AC_SUBST(USE_ELF_HACK)
 AC_SUBST(INCREMENTAL_LINKER)
 AC_SUBST(MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS)
 AC_SUBST(MOZ_COMPONENT_NSPR_LIBS)
 
--- a/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
@@ -1160,18 +1160,20 @@ nsCanvasRenderingContext2DAzure::Redraw(
 
   mIsEntireFrameInvalid = true;
 
   if (!mCanvasElement) {
     NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
     return NS_OK;
   }
 
-  if (mThebesSurface)
-      mThebesSurface->MarkDirty();
+  if (!mThebesSurface)
+    mThebesSurface =
+      gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget);
+  mThebesSurface->MarkDirty();
 
   nsSVGEffects::InvalidateDirectRenderingObservers(HTMLCanvasElement());
 
   HTMLCanvasElement()->InvalidateCanvasContent(nsnull);
 
   return NS_OK;
 }
 
@@ -1190,18 +1192,20 @@ nsCanvasRenderingContext2DAzure::Redraw(
     return;
   }
 
   if (!mCanvasElement) {
     NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
     return;
   }
 
-  if (mThebesSurface)
-      mThebesSurface->MarkDirty();
+  if (!mThebesSurface)
+    mThebesSurface =
+      gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget);
+  mThebesSurface->MarkDirty();
 
   nsSVGEffects::InvalidateDirectRenderingObservers(HTMLCanvasElement());
 
   gfxRect tmpR = ThebesRect(r);
   HTMLCanvasElement()->InvalidateCanvasContent(&tmpR);
 
   return;
 }
--- a/content/canvas/test/test_canvas.html
+++ b/content/canvas/test/test_canvas.html
@@ -13,16 +13,33 @@ function IsD2DEnabled() {
     try {
         netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
         enabled = Components.classes["@mozilla.org/gfx/info;1"].getService(Components.interfaces.nsIGfxInfo).D2DEnabled;
     } catch(e) {}
     
     return enabled;
 }
 
+function IsMacOSX10_5orOlder() {
+    var is105orOlder = false;
+
+    if (navigator.platform.indexOf("Mac") == 0) {
+        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+        var version = Components.classes["@mozilla.org/system-info;1"]
+                            .getService(Components.interfaces.nsIPropertyBag2)
+                            .getProperty("version");
+        // the next line is correct: Mac OS 10.6 corresponds to Darwin version 10 !
+        // Mac OS 10.5 would be Darwin version 9. the |version| string we've got here
+        // is the Darwin version.
+        is105orOlder = (parseFloat(version) < 10.0);
+    }
+    return is105orOlder;
+}
+
+
 function IsAzureEnabled() {
   var enabled = false;
 
   try {
     netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
     enabled = Components.classes["@mozilla.org/gfx/info;1"].getService(Components.interfaces.nsIGfxInfo).AzureEnabled;
   } catch (e) { }
 
@@ -5773,20 +5790,22 @@ isPixel(ctx, 80,25, 0,255,0,255, 2);
 <script>
 
 
 function test_2d_gradient_interpolate_overlap() {
 
 var canvas = document.getElementById('c215');
 var ctx = canvas.getContext('2d');
 
-if (!IsD2DEnabled()) {
-    // Only run this on non-D2D. On D2D the different nature of how gradients
+if (!IsD2DEnabled() && !IsMacOSX10_5orOlder()) {
+    // On D2D the different nature of how gradients
     // are drawn makes it so we cannot guarantee these stops are completely
     // hard.
+
+    // On OS X 10.5 quartz is confused by the overlapping stops: Bug #715235
     canvas.width = 200;
     var g = ctx.createLinearGradient(0, 0, 200, 0);
     g.addColorStop(0, '#f00');
     g.addColorStop(0, '#ff0');
     g.addColorStop(0.25, '#00f');
     g.addColorStop(0.25, '#0f0');
     g.addColorStop(0.25, '#0f0');
     g.addColorStop(0.25, '#0f0');
@@ -5830,22 +5849,25 @@ for (var p = 0; p < ps.length; ++p)
 {
         g.addColorStop(ps[p], '#0f0');
         for (var i = 0; i < 15; ++i)
                 g.addColorStop(ps[p], '#f00');
         g.addColorStop(ps[p], '#0f0');
 }
 ctx.fillStyle = g;
 ctx.fillRect(0, 0, 100, 50);
-isPixel(ctx, 1,25, 0,255,0,255, 0);
-isPixel(ctx, 30,25, 0,255,0,255, 0);
-isPixel(ctx, 40,25, 0,255,0,255, 0);
-isPixel(ctx, 60,25, 0,255,0,255, 0);
-isPixel(ctx, 80,25, 0,255,0,255, 0);
-
+
+if (!IsMacOSX10_5orOlder()) {
+    // On OS X 10.5 quartz is confused by the overlapping stops: Bug #715235
+    isPixel(ctx, 1,25, 0,255,0,255, 0);
+    isPixel(ctx, 30,25, 0,255,0,255, 0);
+    isPixel(ctx, 40,25, 0,255,0,255, 0);
+    isPixel(ctx, 60,25, 0,255,0,255, 0);
+    isPixel(ctx, 80,25, 0,255,0,255, 0);
+}
 
 }
 </script>
 
 <!-- [[[ test_2d.gradient.interpolate.solid.html ]]] -->
 
 <p>Canvas test: 2d.gradient.interpolate.solid</p>
 <canvas id="c217" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
--- a/content/html/content/src/nsHTMLTableElement.cpp
+++ b/content/html/content/src/nsHTMLTableElement.cpp
@@ -731,22 +731,24 @@ nsHTMLTableElement::InsertRow(PRInt32 aI
     if (newRow) {
       nsCOMPtr<nsIDOMNode> newRowNode(do_QueryInterface(newRow));
       nsCOMPtr<nsIDOMNode> retChild;
 
       // If index is -1 or equal to the number of rows, the new row
       // is appended.
       if (aIndex == -1 || PRUint32(aIndex) == rowCount) {
         rv = parent->AppendChild(newRowNode, getter_AddRefs(retChild));
+        NS_ENSURE_SUCCESS(rv, rv);
       }
       else
       {
         // insert the new row before the reference row we found above
         rv = parent->InsertBefore(newRowNode, refRow,
                                   getter_AddRefs(retChild));
+        NS_ENSURE_SUCCESS(rv, rv);
       }
 
       if (retChild) {
         CallQueryInterface(retChild, aValue);
       }
     }
   } else {
     // the row count was 0, so 
@@ -773,16 +775,17 @@ nsHTMLTableElement::InsertRow(PRInt32 aI
       nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::tbody,
                                   getter_AddRefs(nodeInfo));
 
       nsCOMPtr<nsIContent> newRowGroup =
         NS_NewHTMLTableSectionElement(nodeInfo.forget());
 
       if (newRowGroup) {
         rv = AppendChildTo(newRowGroup, true);
+        NS_ENSURE_SUCCESS(rv, rv);
 
         rowGroup = do_QueryInterface(newRowGroup);
       }
     }
 
     if (rowGroup) {
       nsCOMPtr<nsINodeInfo> nodeInfo;
       nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::tr,
--- a/content/html/content/src/nsHTMLTableSectionElement.cpp
+++ b/content/html/content/src/nsHTMLTableSectionElement.cpp
@@ -186,18 +186,20 @@ nsHTMLTableSectionElement::InsertRow(PRI
   nsCOMPtr<nsIDOMNode> retChild;
 
   nsresult rv;
   if (doInsert) {
     nsCOMPtr<nsIDOMNode> refRow;
     rows->Item(aIndex, getter_AddRefs(refRow));
 
     rv = InsertBefore(rowNode, refRow, getter_AddRefs(retChild));
+    NS_ENSURE_SUCCESS(rv, rv);
   } else {
     rv = AppendChild(rowNode, getter_AddRefs(retChild));
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (retChild) {
     CallQueryInterface(retChild, aValue);
   }
 
   return NS_OK;
 }
--- a/dom/Makefile.in
+++ b/dom/Makefile.in
@@ -70,16 +70,17 @@ DIRS = \
   $(NULL)
 
 DIRS += \
   base \
   battery \
   sms \
   src \
   locales \
+  network \
   plugins/base \
   plugins/ipc \
   indexedDB \
   system \
   ipc \
   workers \
   $(NULL)
 
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -69,16 +69,17 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "BatteryManager.h"
 #include "SmsManager.h"
 #include "nsISmsService.h"
 #include "mozilla/Hal.h"
 #include "nsIWebNavigation.h"
 #include "mozilla/ClearOnShutdown.h"
+#include "Connection.h"
 
 #ifdef MOZ_B2G_RIL
 #include "TelephonyFactory.h"
 #endif
 
 // This should not be in the namespace.
 DOMCI_DATA(Navigator, mozilla::dom::Navigator)
 
@@ -125,16 +126,17 @@ NS_INTERFACE_MAP_BEGIN(Navigator)
   NS_INTERFACE_MAP_ENTRY(nsIDOMClientInformation)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorGeolocation)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozNavigatorBattery)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorDesktopNotification)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozNavigatorSms)
 #ifdef MOZ_B2G_RIL
   NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorTelephony)
 #endif
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMozNavigatorNetwork)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Navigator)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(Navigator)
 NS_IMPL_RELEASE(Navigator)
 
 void
 Navigator::Invalidate()
@@ -167,16 +169,21 @@ Navigator::Invalidate()
     mSmsManager = nsnull;
   }
 
 #ifdef MOZ_B2G_RIL
   if (mTelephony) {
     mTelephony = nsnull;
   }
 #endif
+
+  if (mConnection) {
+    mConnection->Shutdown();
+    mConnection = nsnull;
+  }
 }
 
 nsPIDOMWindow *
 Navigator::GetWindow()
 {
   nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mWindow));
 
   return win;
@@ -989,23 +996,27 @@ Navigator::IsSmsAllowed() const
 
   // The current page hasn't been whitelisted.
   return false;
 }
 
 bool
 Navigator::IsSmsSupported() const
 {
-  nsCOMPtr<nsISmsService> smsService = do_GetService(SMSSERVICE_CONTRACTID);
+#ifdef MOZ_WEBSMS_BACKEND
+  nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(smsService, false);
 
   bool result = false;
   smsService->HasSupport(&result);
 
   return result;
+#else
+  return false;
+#endif
 }
 
 NS_IMETHODIMP
 Navigator::GetMozSms(nsIDOMMozSmsManager** aSmsManager)
 {
   *aSmsManager = nsnull;
 
   if (!mSmsManager) {
@@ -1054,16 +1065,43 @@ Navigator::GetMozTelephony(nsIDOMTelepho
   }
 
   telephony.forget(aTelephony);
   return NS_OK;
 }
 
 #endif // MOZ_B2G_RIL
 
+//*****************************************************************************
+//    Navigator::nsIDOMNavigatorNetwork
+//*****************************************************************************
+
+NS_IMETHODIMP
+Navigator::GetMozConnection(nsIDOMMozConnection** aConnection)
+{
+  *aConnection = nsnull;
+
+  if (!mConnection) {
+    nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+    NS_ENSURE_TRUE(window && window->GetDocShell(), NS_OK);
+
+    nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(window);
+    NS_ENSURE_TRUE(sgo, NS_OK);
+
+    nsIScriptContext* scx = sgo->GetContext();
+    NS_ENSURE_TRUE(scx, NS_OK);
+
+    mConnection = new network::Connection();
+    mConnection->Init(window, scx);
+  }
+
+  NS_ADDREF(*aConnection = mConnection);
+  return NS_OK;
+}
+
 PRInt64
 Navigator::SizeOf() const
 {
   PRInt64 size = sizeof(*this);
 
   // TODO: add SizeOf() to nsMimeTypeArray, bug 674113.
   size += mMimeTypes ? sizeof(*mMimeTypes.get()) : 0;
   // TODO: add SizeOf() to nsPluginArray, bug 674114.
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -44,24 +44,26 @@
 #define mozilla_dom_Navigator_h
 
 #include "nsIDOMNavigator.h"
 #include "nsIDOMNavigatorGeolocation.h"
 #include "nsIDOMNavigatorDesktopNotification.h"
 #include "nsIDOMClientInformation.h"
 #include "nsIDOMNavigatorBattery.h"
 #include "nsIDOMNavigatorSms.h"
+#include "nsIDOMNavigatorNetwork.h"
 #include "nsAutoPtr.h"
 #include "nsWeakReference.h"
 
 class nsPluginArray;
 class nsMimeTypeArray;
 class nsGeolocation;
 class nsDesktopNotificationCenter;
 class nsPIDOMWindow;
+class nsIDOMMozConnection;
 
 #ifdef MOZ_B2G_RIL
 #include "nsIDOMNavigatorTelephony.h"
 class nsIDOMTelephony;
 #endif
 
 //*****************************************************************************
 // Navigator: Script "navigator" object
@@ -73,41 +75,46 @@ namespace dom {
 namespace battery {
 class BatteryManager;
 } // namespace battery
 
 namespace sms {
 class SmsManager;
 } // namespace sms
 
-class Navigator : public nsIDOMNavigator,
-                  public nsIDOMClientInformation,
-                  public nsIDOMNavigatorGeolocation,
-                  public nsIDOMNavigatorDesktopNotification,
-                  public nsIDOMMozNavigatorBattery,
-                  public nsIDOMMozNavigatorSms
+namespace network {
+class Connection;
+} // namespace Connection;
+
+class Navigator : public nsIDOMNavigator
+                , public nsIDOMClientInformation
+                , public nsIDOMNavigatorGeolocation
+                , public nsIDOMNavigatorDesktopNotification
+                , public nsIDOMMozNavigatorBattery
+                , public nsIDOMMozNavigatorSms
 #ifdef MOZ_B2G_RIL
                 , public nsIDOMNavigatorTelephony
 #endif
+                , public nsIDOMMozNavigatorNetwork
 {
 public:
   Navigator(nsPIDOMWindow *aInnerWindow);
   virtual ~Navigator();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMNAVIGATOR
   NS_DECL_NSIDOMCLIENTINFORMATION
   NS_DECL_NSIDOMNAVIGATORGEOLOCATION
   NS_DECL_NSIDOMNAVIGATORDESKTOPNOTIFICATION
   NS_DECL_NSIDOMMOZNAVIGATORBATTERY
   NS_DECL_NSIDOMMOZNAVIGATORSMS
-
 #ifdef MOZ_B2G_RIL
   NS_DECL_NSIDOMNAVIGATORTELEPHONY
 #endif
+  NS_DECL_NSIDOMMOZNAVIGATORNETWORK
 
   static void Init();
 
   void Invalidate();
   nsPIDOMWindow *GetWindow();
 
   void RefreshMIMEArray();
 
@@ -128,16 +135,17 @@ private:
   nsRefPtr<nsPluginArray> mPlugins;
   nsRefPtr<nsGeolocation> mGeolocation;
   nsRefPtr<nsDesktopNotificationCenter> mNotification;
   nsRefPtr<battery::BatteryManager> mBatteryManager;
   nsRefPtr<sms::SmsManager> mSmsManager;
 #ifdef MOZ_B2G_RIL
   nsCOMPtr<nsIDOMTelephony> mTelephony;
 #endif
+  nsRefPtr<network::Connection> mConnection;
   nsWeakPtr mWindow;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 nsresult NS_GetNavigatorUserAgent(nsAString& aUserAgent);
 nsresult NS_GetNavigatorPlatform(nsAString& aPlatform);
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -34,16 +34,21 @@
  * 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 "mozilla/Util.h"
+#include "SmsFilter.h" // On top because it includes basictypes.h.
+
+#ifdef XP_WIN
+#undef GetClassName
+#endif
 
 // JavaScript includes
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "jsprvtd.h"    // we are using private JS typedefs...
 #include "jsdbgapi.h"
 #include "WrapperFactory.h"
 #include "AccessCheck.h"
@@ -159,21 +164,16 @@
 #include "nsIDOMHTMLSelectElement.h"
 
 // HTMLEmbed/ObjectElement helper includes
 #include "nsNPAPIPluginInstance.h"
 #include "nsIObjectFrame.h"
 #include "nsIObjectLoadingContent.h"
 #include "nsIPluginHost.h"
 
-// Oh, did I mention that I hate Microsoft for doing this to me?
-#ifdef XP_WIN
-#undef GetClassName
-#endif
-
 // HTMLOptionsCollection includes
 #include "nsIDOMHTMLOptionElement.h"
 #include "nsIDOMHTMLOptionsCollection.h"
 
 // ContentList includes
 #include "nsContentList.h"
 #include "nsGenericElement.h"
 
@@ -509,17 +509,22 @@
 #include "nsWrapperCacheInlines.h"
 #include "dombindings.h"
 
 #include "nsIDOMBatteryManager.h"
 #include "BatteryManager.h"
 #include "nsIDOMSmsManager.h"
 #include "nsIDOMSmsMessage.h"
 #include "nsIDOMSmsEvent.h"
+#include "nsIDOMSmsRequest.h"
+#include "nsIDOMSmsFilter.h"
+#include "nsIDOMSmsCursor.h"
 #include "nsIPrivateDOMEvent.h"
+#include "nsIDOMConnection.h"
+#include "mozilla/dom/network/Utils.h"
 
 #ifdef MOZ_B2G_RIL
 #include "Telephony.h"
 #include "TelephonyCall.h"
 #include "CallEvent.h"
 #endif
 
 using namespace mozilla;
@@ -1406,16 +1411,28 @@ static nsDOMClassInfoData sClassInfoData
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(MozSmsMessage, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(MozSmsEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
+  NS_DEFINE_CLASSINFO_DATA(MozSmsRequest, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+  NS_DEFINE_CLASSINFO_DATA(MozSmsFilter, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+  NS_DEFINE_CLASSINFO_DATA(MozSmsCursor, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
+  NS_DEFINE_CLASSINFO_DATA(MozConnection, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
   NS_DEFINE_CLASSINFO_DATA(CSSFontFaceRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(CSSFontFaceStyleDecl, nsCSSStyleDeclSH,
                            ARRAY_SCRIPTABLE_FLAGS)
 
 #if defined(MOZ_MEDIA)
   NS_DEFINE_CLASSINFO_DATA(HTMLVideoElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
@@ -1632,16 +1649,17 @@ static const nsConstructorFuncMapData kC
   NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(Event)
   NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(CustomEvent)
   NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(PopStateEvent)
   NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(HashChangeEvent)
   NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(PageTransitionEvent)
   NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(CloseEvent)
   NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(UIEvent)
   NS_DEFINE_EVENT_CONSTRUCTOR_FUNC_DATA(MouseEvent)
+  NS_DEFINE_CONSTRUCTOR_FUNC_DATA(MozSmsFilter, sms::SmsFilter::NewSmsFilter)
 };
 
 nsIXPConnect *nsDOMClassInfo::sXPConnect = nsnull;
 nsIScriptSecurityManager *nsDOMClassInfo::sSecMan = nsnull;
 bool nsDOMClassInfo::sIsInitialized = false;
 bool nsDOMClassInfo::sDisableDocumentAllSupport = false;
 bool nsDOMClassInfo::sDisableGlobalScopePollutionSupport = false;
 
@@ -2354,16 +2372,18 @@ nsDOMClassInfo::Init()
                                         Navigator::HasDesktopNotificationSupport())
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMClientInformation)
     DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsIDOMMozNavigatorBattery,
                                         battery::BatteryManager::HasSupport())
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozNavigatorSms)
 #ifdef MOZ_B2G_RIL
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorTelephony)
 #endif
+    DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsIDOMMozNavigatorNetwork,
+                                        network::IsAPIEnabled())
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(Plugin, nsIDOMPlugin)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMPlugin)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(PluginArray, nsIDOMPluginArray)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMPluginArray)
@@ -3981,16 +4001,33 @@ nsDOMClassInfo::Init()
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozSmsMessage)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(MozSmsEvent, nsIDOMMozSmsEvent)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozSmsEvent)
      DOM_CLASSINFO_EVENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
+  DOM_CLASSINFO_MAP_BEGIN(MozSmsRequest, nsIDOMMozSmsRequest)
+     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozSmsRequest)
+     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
+  DOM_CLASSINFO_MAP_END
+
+  DOM_CLASSINFO_MAP_BEGIN(MozSmsFilter, nsIDOMMozSmsFilter)
+     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozSmsFilter)
+  DOM_CLASSINFO_MAP_END
+
+  DOM_CLASSINFO_MAP_BEGIN(MozSmsCursor, nsIDOMMozSmsCursor)
+     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozSmsCursor)
+  DOM_CLASSINFO_MAP_END
+
+  DOM_CLASSINFO_MAP_BEGIN(MozConnection, nsIDOMMozConnection)
+     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozConnection)
+  DOM_CLASSINFO_MAP_END
+
   DOM_CLASSINFO_MAP_BEGIN(CSSFontFaceRule, nsIDOMCSSFontFaceRule)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSFontFaceRule)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(CSSFontFaceStyleDecl,
                                       nsIDOMCSSStyleDeclaration)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMCSSStyleDeclaration)
   DOM_CLASSINFO_MAP_END
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -428,16 +428,21 @@ DOMCI_CLASS(GeoPositionCoords)
 DOMCI_CLASS(GeoPositionAddress)
 DOMCI_CLASS(GeoPositionError)
 
 DOMCI_CLASS(MozBatteryManager)
 
 DOMCI_CLASS(MozSmsManager)
 DOMCI_CLASS(MozSmsMessage)
 DOMCI_CLASS(MozSmsEvent)
+DOMCI_CLASS(MozSmsRequest)
+DOMCI_CLASS(MozSmsFilter)
+DOMCI_CLASS(MozSmsCursor)
+
+DOMCI_CLASS(MozConnection)
 
 // @font-face in CSS
 DOMCI_CLASS(CSSFontFaceRule)
 DOMCI_CLASS(CSSFontFaceStyleDecl)
 
 #if defined(MOZ_MEDIA)
 // WhatWG Video Element
 DOMCI_CLASS(HTMLVideoElement)
--- a/dom/dom-config.mk
+++ b/dom/dom-config.mk
@@ -1,11 +1,12 @@
 DOM_SRCDIRS = \
   dom/base \
   dom/battery \
+  dom/network/src \
   dom/sms/src \
   dom/src/events \
   dom/src/storage \
   dom/src/offline \
   dom/src/geolocation \
   dom/src/notification \
   dom/workers \
   content/xbl/src \
new file mode 100644
--- /dev/null
+++ b/dom/network/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 mozilla.org build system.
+#
+# The Initial Developer of the Original Code is Mozilla Foundation
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+#
+# 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 = interfaces src
+
+ifdef ENABLE_TESTS
+DIRS += tests
+endif
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/network/interfaces/Makefile.in
@@ -0,0 +1,53 @@
+# ***** 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 mozilla.org build system.
+#
+# The Initial Developer of the Original Code is Mozilla Foundation
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+#
+# 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
+
+XPIDL_MODULE = dom_network
+
+include $(topsrcdir)/dom/dom-config.mk
+
+XPIDLSRCS = \
+  nsIDOMNavigatorNetwork.idl \
+  nsIDOMConnection.idl \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMConnection.idl
@@ -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 mozilla.org.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * 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"
+
+interface nsIDOMEventListener;
+
+[scriptable, uuid(8c6b574d-1135-4387-a6e3-6d8ba38d79a1)]
+interface nsIDOMMozConnection : nsISupports
+{
+  readonly attribute double  bandwidth;
+  readonly attribute boolean metered;
+
+           attribute nsIDOMEventListener onchange;
+};
new file mode 100644
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMNavigatorNetwork.idl
@@ -0,0 +1,45 @@
+/* ***** 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 mozilla.org.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * 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"
+
+interface nsIDOMMozConnection;
+
+[scriptable, uuid(1dd6773e-30dc-419b-9766-b05458fd96c8)]
+interface nsIDOMMozNavigatorNetwork : nsISupports
+{
+  readonly attribute nsIDOMMozConnection mozConnection;
+};
new file mode 100644
--- /dev/null
+++ b/dom/network/src/Connection.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 <limits>
+#include "mozilla/Hal.h"
+#include "Connection.h"
+#include "nsIDOMClassInfo.h"
+#include "mozilla/Preferences.h"
+#include "nsDOMEvent.h"
+#include "Constants.h"
+
+/**
+ * We have to use macros here because our leak analysis tool things we are
+ * leaking strings when we have |static const nsString|. Sad :(
+ */
+#define CHANGE_EVENT_NAME NS_LITERAL_STRING("change")
+
+DOMCI_DATA(MozConnection, mozilla::dom::network::Connection)
+
+namespace mozilla {
+namespace dom {
+namespace network {
+
+const char* Connection::sMeteredPrefName     = "dom.network.metered";
+const bool  Connection::sMeteredDefaultValue = false;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Connection)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Connection,
+                                                  nsDOMEventTargetWrapperCache)
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(change)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Connection,
+                                                nsDOMEventTargetWrapperCache)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(change)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Connection)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMozConnection)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMMozConnection)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozConnection)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetWrapperCache)
+
+NS_IMPL_ADDREF_INHERITED(Connection, nsDOMEventTargetWrapperCache)
+NS_IMPL_RELEASE_INHERITED(Connection, nsDOMEventTargetWrapperCache)
+
+Connection::Connection()
+  : mCanBeMetered(kDefaultCanBeMetered)
+  , mBandwidth(kDefaultBandwidth)
+{
+}
+
+void
+Connection::Init(nsPIDOMWindow *aWindow, nsIScriptContext* aScriptContext)
+{
+  // Those vars come from nsDOMEventTargetHelper.
+  mOwner = aWindow;
+  mScriptContext = aScriptContext;
+
+  hal::RegisterNetworkObserver(this);
+
+  hal::NetworkInformation networkInfo;
+  hal::GetCurrentNetworkInformation(&networkInfo);
+
+  UpdateFromNetworkInfo(networkInfo);
+}
+
+void
+Connection::Shutdown()
+{
+  hal::UnregisterNetworkObserver(this);
+}
+
+NS_IMETHODIMP
+Connection::GetBandwidth(double* aBandwidth)
+{
+  if (mBandwidth == kDefaultBandwidth) {
+    *aBandwidth = std::numeric_limits<double>::infinity();
+    return NS_OK;
+  }
+
+  *aBandwidth = mBandwidth;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::GetMetered(bool* aMetered)
+{
+  if (!mCanBeMetered) {
+    *aMetered = false;
+    return NS_OK;
+  }
+
+  *aMetered = Preferences::GetBool(sMeteredPrefName,
+                                   sMeteredDefaultValue);
+  return NS_OK;
+}
+
+NS_IMPL_EVENT_HANDLER(Connection, change)
+
+nsresult
+Connection::DispatchTrustedEventToSelf(const nsAString& aEventName)
+{
+  nsRefPtr<nsDOMEvent> event = new nsDOMEvent(nsnull, nsnull);
+  nsresult rv = event->InitEvent(aEventName, false, false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = event->SetTrusted(PR_TRUE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool dummy;
+  rv = DispatchEvent(event, &dummy);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+void
+Connection::UpdateFromNetworkInfo(const hal::NetworkInformation& aNetworkInfo)
+{
+  mBandwidth = aNetworkInfo.bandwidth();
+  mCanBeMetered = aNetworkInfo.canBeMetered();
+}
+
+void
+Connection::Notify(const hal::NetworkInformation& aNetworkInfo)
+{
+  double previousBandwidth = mBandwidth;
+  bool previousCanBeMetered = mCanBeMetered;
+
+  UpdateFromNetworkInfo(aNetworkInfo);
+
+  if (previousBandwidth == mBandwidth &&
+      previousCanBeMetered == mCanBeMetered) {
+    return;
+  }
+
+  DispatchTrustedEventToSelf(CHANGE_EVENT_NAME);
+}
+
+} // namespace network
+} // namespace dom
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/dom/network/src/Connection.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef mozilla_dom_network_Connection_h
+#define mozilla_dom_network_Connection_h
+
+#include "nsIDOMConnection.h"
+#include "nsDOMEventTargetWrapperCache.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Observer.h"
+#include "Types.h"
+
+namespace mozilla {
+
+namespace hal {
+class NetworkInformation;
+} // namespace hal
+
+namespace dom {
+namespace network {
+
+class Connection : public nsDOMEventTargetWrapperCache
+                 , public nsIDOMMozConnection
+                 , public NetworkObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMMOZCONNECTION
+
+  NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetWrapperCache::)
+
+  Connection();
+
+  void Init(nsPIDOMWindow *aWindow, nsIScriptContext* aScriptContext);
+  void Shutdown();
+
+  // For IObserver
+  void Notify(const hal::NetworkInformation& aNetworkInfo);
+
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Connection,
+                                           nsDOMEventTargetWrapperCache)
+
+private:
+  /**
+   * Dispatch a trusted non-cancellable and non-bubbling event to itself.
+   */
+  nsresult DispatchTrustedEventToSelf(const nsAString& aEventName);
+
+  /**
+   * Update the connection information stored in the object using a
+   * NetworkInformation object.
+   */
+  void UpdateFromNetworkInfo(const hal::NetworkInformation& aNetworkInfo);
+
+  /**
+   * If the connection is of a type that can be metered.
+   */
+  bool mCanBeMetered;
+
+  /**
+   * The connection bandwidth.
+   */
+  double mBandwidth;
+
+  NS_DECL_EVENT_HANDLER(change)
+
+  static const char* sMeteredPrefName;
+  static const bool  sMeteredDefaultValue;
+};
+
+} // namespace network
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_network_Connection_h
new file mode 100644
--- /dev/null
+++ b/dom/network/src/Constants.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef mozilla_dom_network_Constants_h__
+#define mozilla_dom_network_Constants_h__
+
+/**
+ * A set of constants to be used by network backends.
+ */
+namespace mozilla {
+namespace dom {
+namespace network {
+
+  static const double kDefaultBandwidth    = -1.0;
+  static const bool   kDefaultCanBeMetered = false;
+
+} // namespace network
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_network_Constants_h__
new file mode 100644
--- /dev/null
+++ b/dom/network/src/Makefile.in
@@ -0,0 +1,69 @@
+# ***** 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 mozilla.org build system.
+#
+# The Initial Developer of the Original Code is Mozilla Foundation
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+#
+# 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
+
+LIBRARY_NAME     = dom_network_s
+LIBXUL_LIBRARY   = 1
+FORCE_STATIC_LIB = 1
+
+include $(topsrcdir)/dom/dom-config.mk
+
+EXPORTS_NAMESPACES = mozilla/dom/network
+
+EXPORTS_mozilla/dom/network = \
+  Utils.h \
+  Types.h \
+  Constants.h \
+  $(NULL)
+
+CPPSRCS = \
+  Connection.cpp \
+  Utils.cpp \
+  $(NULL)
+
+LOCAL_INCLUDES = \
+  -I$(topsrcdir)/content/events/src \
+  $(NULL)
+
+include $(topsrcdir)/config/config.mk
+include $(topsrcdir)/ipc/chromium/chromium-config.mk
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/network/src/Types.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef mozilla_dom_network_Types_h
+#define mozilla_dom_network_Types_h
+
+namespace mozilla {
+namespace hal {
+class NetworkInformation;
+} // namespace hal
+
+template <class T>
+class Observer;
+
+typedef Observer<hal::NetworkInformation> NetworkObserver;
+
+} // namespace mozilla
+
+#endif // mozilla_dom_network_Types_h
new file mode 100644
--- /dev/null
+++ b/dom/network/src/Utils.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 "Utils.h"
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+namespace dom {
+namespace network {
+
+/* extern */ bool
+IsAPIEnabled()
+{
+  return Preferences::GetBool("dom.network.enabled", true);
+}
+
+} // namespace network
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/network/src/Utils.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef mozilla_dom_network_Utils_h
+#define mozilla_dom_network_Utils_h
+
+namespace mozilla {
+namespace dom {
+namespace network {
+
+/**
+ * Returns whether the Network API is enabled.
+ * @return whether the Network API is enabled.
+ */
+extern bool IsAPIEnabled();
+
+} // namespace network
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_network_Utils_h
+
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/Makefile.in
@@ -0,0 +1,62 @@
+# ***** 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 mozilla.org build system.
+#
+# The Initial Developer of the Original Code is Mozilla Foundation
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+#
+# 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   = dom/network/tests
+
+include $(DEPTH)/config/autoconf.mk
+
+DIRS = \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
+
+_TEST_FILES = \
+  test_network_basics.html \
+  $(NULL)
+
+_CHROME_TEST_FILES = \
+  $(NULL)
+
+libs:: $(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
+
+#libs:: $(_CHROME_TEST_FILES)
+#	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/test_network_basics.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Network API</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Network API **/
+
+function checkInterface(aInterface) {
+  ok(!(aInterface in window), aInterface + " should be prefixed");
+  ok(("Moz" + aInterface) in window, aInterface + " should be prefixed");
+}
+
+ok('mozConnection' in navigator, "navigator.mozConnection should exist");
+
+ok(navigator.mozConnection, "navigator.mozConnection returns an object");
+
+ok(navigator.mozConnection instanceof MozConnection,
+   "navigator.mozConnection is a MozConnection object");
+
+checkInterface("Connection");
+
+ok('bandwidth' in navigator.mozConnection,
+   "bandwidth should be a Connection attribute");
+is(navigator.mozConnection.bandwidth, Infinity,
+   "By default connection.bandwidth is equals to Infinity");
+
+ok('metered' in navigator.mozConnection,
+   "metered should be a Connection attribute");
+is(navigator.mozConnection.metered, false,
+   "By default the connection is not metered");
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/plugins/base/nsNPAPIPlugin.cpp
+++ b/dom/plugins/base/nsNPAPIPlugin.cpp
@@ -2502,17 +2502,17 @@ NPError NP_CALLBACK
       else {
         return NPERR_GENERIC_ERROR;
       }
     }
 #endif
 #ifdef MOZ_WIDGET_ANDROID
   case kRequestDrawingModel_ANPSetValue:
     if (inst)
-      inst->SetDrawingModel(NS_PTR_TO_INT32(result));
+      inst->SetANPDrawingModel(NS_PTR_TO_INT32(result));
     return NPERR_NO_ERROR;
   case kAcceptEvents_ANPSetValue:
     return NPERR_NO_ERROR;
 #endif
     default:
       return NPERR_GENERIC_ERROR;
   }
 }
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -84,17 +84,17 @@ nsNPAPIPluginInstance::nsNPAPIPluginInst
 #ifdef NP_NO_QUICKDRAW
     mDrawingModel(NPDrawingModelCoreGraphics),
 #else
     mDrawingModel(NPDrawingModelQuickDraw),
 #endif
 #endif
 #ifdef MOZ_WIDGET_ANDROID
     mSurface(nsnull),
-    mDrawingModel(0),
+    mANPDrawingModel(0),
 #endif
     mRunning(NOT_STARTED),
     mWindowless(false),
     mWindowlessLocal(false),
     mTransparent(false),
     mCached(false),
     mUsesDOMForCursor(false),
     mInPluginInitCall(false),
@@ -726,20 +726,21 @@ void nsNPAPIPluginInstance::SetEventMode
     return;
   }
 
   owner->SetEventModel(aModel);
 }
 #endif
 
 #if defined(MOZ_WIDGET_ANDROID)
-void nsNPAPIPluginInstance::SetDrawingModel(PRUint32 aModel)
+void nsNPAPIPluginInstance::SetANPDrawingModel(PRUint32 aModel)
 {
-  mDrawingModel = aModel;
+  mANPDrawingModel = aModel;
 }
+
 class SurfaceGetter : public nsRunnable {
 public:
   SurfaceGetter(nsNPAPIPluginInstance* aInstance, NPPluginFuncs* aPluginFunctions, NPP_t aNPP) : 
     mInstance(aInstance), mPluginFunctions(aPluginFunctions), mNPP(aNPP) {
   }
   ~SurfaceGetter() {
   }
   nsresult Run() {
@@ -755,17 +756,17 @@ private:
   nsNPAPIPluginInstance* mInstance;
   NPP_t mNPP;
   NPPluginFuncs* mPluginFunctions;
 };
 
 
 void* nsNPAPIPluginInstance::GetJavaSurface()
 {
-  if (mDrawingModel != kSurface_ANPDrawingModel)
+  if (mANPDrawingModel != kSurface_ANPDrawingModel)
     return nsnull;
   
   return mSurface;
 }
 
 void nsNPAPIPluginInstance::SetJavaSurface(void* aSurface)
 {
   mSurface = aSurface;
@@ -780,17 +781,17 @@ void nsNPAPIPluginInstance::RequestJavaS
 
   ((SurfaceGetter*)mSurfaceGetter.get())->RequestSurface();
 }
 
 #endif
 
 nsresult nsNPAPIPluginInstance::GetDrawingModel(PRInt32* aModel)
 {
-#if defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID)
+#if defined(XP_MACOSX)
   *aModel = (PRInt32)mDrawingModel;
   return NS_OK;
 #else
   return NS_ERROR_FAILURE;
 #endif
 }
 
 nsresult nsNPAPIPluginInstance::IsRemoteDrawingCoreAnimation(bool* aDrawing)
--- a/dom/plugins/base/nsNPAPIPluginInstance.h
+++ b/dom/plugins/base/nsNPAPIPluginInstance.h
@@ -145,17 +145,18 @@ public:
   bool UsesDOMForCursor();
 
 #ifdef XP_MACOSX
   void SetDrawingModel(NPDrawingModel aModel);
   void SetEventModel(NPEventModel aModel);
 #endif
 
 #ifdef MOZ_WIDGET_ANDROID
-  void SetDrawingModel(PRUint32 aModel);
+  PRUint32 GetANPDrawingModel() { return mANPDrawingModel; }
+  void SetANPDrawingModel(PRUint32 aModel);
   void* GetJavaSurface();
   void SetJavaSurface(void* aSurface);
   void RequestJavaSurface();
 #endif
 
   nsresult NewStreamListener(const char* aURL, void* notifyData,
                              nsIPluginStreamListener** listener);
 
@@ -224,17 +225,17 @@ protected:
   // the browser.
   NPP_t mNPP;
 
 #ifdef XP_MACOSX
   NPDrawingModel mDrawingModel;
 #endif
 
 #ifdef MOZ_WIDGET_ANDROID
-  PRUint32 mDrawingModel;
+  PRUint32 mANPDrawingModel;
   nsCOMPtr<nsIRunnable> mSurfaceGetter;
 #endif
 
   enum {
     NOT_STARTED,
     RUNNING,
     DESTROYING,
     DESTROYED
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -2863,18 +2863,17 @@ void nsPluginInstanceOwner::Paint(const 
 
 void nsPluginInstanceOwner::Paint(gfxContext* aContext,
                                   const gfxRect& aFrameRect,
                                   const gfxRect& aDirtyRect)
 {
   if (!mInstance || !mObjectFrame)
     return;
 
-  PRInt32 model;
-  mInstance->GetDrawingModel(&model);
+  PRInt32 model = mInstance->GetANPDrawingModel();
 
   if (model == kSurface_ANPDrawingModel) {
     if (!AddPluginView(aFrameRect)) {
       NPRect rect;
       rect.left = rect.top = 0;
       rect.right = aFrameRect.width;
       rect.bottom = aFrameRect.height;
       InvalidateRect(&rect);
--- a/dom/sms/interfaces/Makefile.in
+++ b/dom/sms/interfaces/Makefile.in
@@ -46,11 +46,15 @@ XPIDL_MODULE = dom_sms
 include $(topsrcdir)/dom/dom-config.mk
 
 XPIDLSRCS = \
   nsIDOMNavigatorSms.idl \
   nsIDOMSmsManager.idl \
   nsISmsService.idl \
   nsIDOMSmsMessage.idl \
   nsIDOMSmsEvent.idl \
+  nsISmsDatabaseService.idl \
+  nsIDOMSmsRequest.idl \
+  nsIDOMSmsFilter.idl \
+  nsIDOMSmsCursor.idl \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/sms/interfaces/nsIDOMSmsCursor.idl
@@ -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 mozilla.org.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * 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"
+
+interface nsIDOMMozSmsFilter;
+interface nsIDOMMozSmsMessage;
+
+[scriptable, function, uuid(77b41d7e-ccb1-4480-8322-2af7bc437a3c)]
+interface nsIDOMMozSmsCursor : nsISupports
+{
+  // Can be null if there is no more results.
+  readonly attribute nsIDOMMozSmsMessage message;
+                                    void continue();
+};
--- a/dom/sms/interfaces/nsIDOMSmsEvent.idl
+++ b/dom/sms/interfaces/nsIDOMSmsEvent.idl
@@ -34,13 +34,14 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 #include "nsIDOMEvent.idl"
 
 interface nsIDOMMozSmsMessage;
 
-[scriptable, uuid(34dda4c3-4683-4323-9ee3-2a7bfef7df3b)]
+[scriptable, uuid(fa8d1c86-85b1-4e5b-978c-12dd296cd1cc)]
 interface nsIDOMMozSmsEvent : nsIDOMEvent
 {
+  [binaryname(MessageMoz)]
   readonly attribute nsIDOMMozSmsMessage message;
 };
new file mode 100644
--- /dev/null
+++ b/dom/sms/interfaces/nsIDOMSmsFilter.idl
@@ -0,0 +1,57 @@
+/* ***** 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 mozilla.org.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * 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"
+
+[scriptable, builtinclass, uuid(7da08e45-ee81-4293-912b-2f2fea5b6935)]
+interface nsIDOMMozSmsFilter : nsISupports
+{
+  // A date that can return null.
+  [implicit_jscontext]
+  attribute jsval startDate;
+
+  // A date that can return null.
+  [implicit_jscontext]
+  attribute jsval endDate;
+
+  // An array of DOMString that can return null.
+  [implicit_jscontext]
+  attribute jsval numbers;
+
+  // A DOMString that can return and be set to "sent", "received" or null.
+  [Null(Empty)]
+  attribute DOMString delivery;
+};
--- a/dom/sms/interfaces/nsIDOMSmsManager.idl
+++ b/dom/sms/interfaces/nsIDOMSmsManager.idl
@@ -32,17 +32,33 @@
  * 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 "nsIDOMEventTarget.idl"
 
 interface nsIDOMEventListener;
+interface nsIDOMMozSmsRequest;
+interface nsIDOMMozSmsFilter;
 
-[scriptable, function, uuid(807d593c-09cb-4aa3-afa5-aa0a671bd0d3)]
+[scriptable, function, uuid(c9916dce-2947-41bb-95c2-818f792a020c)]
 interface nsIDOMMozSmsManager : nsIDOMEventTarget
 {
-  unsigned short getNumberOfMessagesForText(in DOMString text);
-            void send(in DOMString number, in DOMString message);
+  unsigned short      getNumberOfMessagesForText(in DOMString text);
+
+  // The first parameter can be either a DOMString (only one number) or an array
+  // of DOMStrings.
+  // The method returns a SmsRequest object if one number has been passed.
+  // An array of SmsRequest objects otherwise.
+  jsval send(in jsval number, in DOMString message);
+
+  [binaryname(GetMessageMoz)] nsIDOMMozSmsRequest getMessage(in long id);
+
+  // The parameter can be either a message id or a SmsMessage.
+  nsIDOMMozSmsRequest delete(in jsval param);
+
+  nsIDOMMozSmsRequest getMessages(in nsIDOMMozSmsFilter filter, in boolean reverse);
 
   attribute nsIDOMEventListener onreceived;
+  attribute nsIDOMEventListener onsent;
+  attribute nsIDOMEventListener ondelivered;
 };
new file mode 100644
--- /dev/null
+++ b/dom/sms/interfaces/nsIDOMSmsRequest.idl
@@ -0,0 +1,53 @@
+/* ***** 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 mozilla.org.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * 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 "nsIDOMEventTarget.idl"
+
+interface nsIDOMEventListener;
+
+[scriptable, function, uuid(1b24469d-cfb7-4667-aaf0-c1d17289ae7c)]
+interface nsIDOMMozSmsRequest : nsIDOMEventTarget
+{
+  // Returns whether "processing" or "done".
+  readonly attribute DOMString           readyState;
+  // Can be null.
+  readonly attribute DOMString           error;
+  // Can be bool, nsIDOMSmsMessage, nsIDOMSmsIterator or null.
+  readonly attribute jsval               result;
+
+           attribute nsIDOMEventListener onsuccess;
+           attribute nsIDOMEventListener onerror;
+};
new file mode 100644
--- /dev/null
+++ b/dom/sms/interfaces/nsISmsDatabaseService.idl
@@ -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 mozilla.org.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * 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"
+
+%{C++
+#define SMS_DATABASE_SERVICE_CID \
+{ 0x2454c2a1, 0xefdd, 0x4d96,    \
+{ 0x83, 0xbd, 0x51, 0xa2, 0x9a, 0x21, 0xf5, 0xab } }
+#define SMS_DATABASE_SERVICE_CONTRACTID "@mozilla.org/sms/smsdatabaseservice;1"
+%}
+
+interface nsIDOMMozSmsFilter;
+
+[scriptable, function, uuid(3ddf7dc3-626c-47ee-8b41-3f55d5af49c9)]
+interface nsISmsDatabaseService : nsISupports
+{
+  // Takes some information required to save the message and returns its id.
+  long saveSentMessage(in DOMString aReceiver, in DOMString aBody, in unsigned long long aDate);
+
+  [binaryname(GetMessageMoz)] void getMessage(in long messageId, in long requestId, [optional] in unsigned long long processId);
+  void deleteMessage(in long messageId, in long requestId, [optional] in unsigned long long processId);
+
+  void createMessageList(in nsIDOMMozSmsFilter filter, in boolean reverse, in long requestId, [optional] in unsigned long long processId);
+  void getNextMessageInList(in long listId, in long requestId, in unsigned long long processId);
+  void clearMessageList(in long listId);
+};
--- a/dom/sms/interfaces/nsISmsService.idl
+++ b/dom/sms/interfaces/nsISmsService.idl
@@ -35,26 +35,27 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface nsIDOMMozSmsMessage;
 
 %{C++
-#define NS_SMSSERVICE_CID { 0xbada3cb8, 0xa568, 0x4dff, { 0xb5, 0x43, 0x52, 0xbb, 0xb3, 0x14, 0x31, 0x21 } }
-#define SMSSERVICE_CONTRACTID "@mozilla.org/sms/smsservice;1"
+#define SMS_SERVICE_CID { 0xbada3cb8, 0xa568, 0x4dff, { 0xb5, 0x43, 0x52, 0xbb, 0xb3, 0x14, 0x31, 0x21 } }
+#define SMS_SERVICE_CONTRACTID "@mozilla.org/sms/smsservice;1"
 %}
 
 [scriptable, builtinclass, uuid(a0fbbe74-5d61-4b7e-b7ab-9b5224f9e5e9)]
 interface nsISmsService : nsISupports
 {
   boolean        hasSupport();
   unsigned short getNumberOfMessagesForText(in DOMString text);
-            void send(in DOMString number, in DOMString message);
+            void send(in DOMString number, in DOMString message,
+                      in long requestId, [optional] in unsigned long long processId);
 
   [implicit_jscontext]
   nsIDOMMozSmsMessage createSmsMessage(in long      id,
                                        in DOMString delivery,
                                        in DOMString sender,
                                        in DOMString receiver,
                                        in DOMString body,
                                        in jsval     timestamp);
--- a/dom/sms/src/Constants.cpp
+++ b/dom/sms/src/Constants.cpp
@@ -34,13 +34,15 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 namespace mozilla {
 namespace dom {
 namespace sms {
 
-const char* kSmsReceivedObserverTopic = "sms-received";
+const char* kSmsReceivedObserverTopic  = "sms-received";
+const char* kSmsSentObserverTopic      = "sms-sent";
+const char* kSmsDeliveredObserverTopic = "sms-delivered";
 
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
--- a/dom/sms/src/Constants.h
+++ b/dom/sms/src/Constants.h
@@ -37,17 +37,19 @@
 
 #ifndef mozilla_dom_sms_Constants_h
 #define mozilla_dom_sms_Constants_h
 
 namespace mozilla {
 namespace dom {
 namespace sms {
 
-extern const char* kSmsReceivedObserverTopic; // Defined in the .cpp.
+extern const char* kSmsReceivedObserverTopic;  // Defined in the .cpp.
+extern const char* kSmsSentObserverTopic;      // Defined in the .cpp.
+extern const char* kSmsDeliveredObserverTopic; // Defined in the .cpp.
 
 #define DELIVERY_RECEIVED NS_LITERAL_STRING("received")
 #define DELIVERY_SENT     NS_LITERAL_STRING("sent")
 
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/sms/src/Makefile.in
+++ b/dom/sms/src/Makefile.in
@@ -58,36 +58,44 @@ FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/dom/dom-config.mk
 
 EXPORTS_NAMESPACES = mozilla/dom/sms
 
 EXPORTS_mozilla/dom/sms = \
   SmsChild.h \
   SmsParent.h \
-  SmsServiceFactory.h \
+  SmsServicesFactory.h \
   Constants.h \
   Types.h \
   SmsMessage.h \
+  SmsRequestManager.h \
+  SmsRequest.h \
   $(NULL)
 
 CPPSRCS = \
   SmsManager.cpp \
   SmsService.cpp \
   SmsIPCService.cpp \
-  SmsServiceFactory.cpp \
+  SmsServicesFactory.cpp \
   SmsParent.cpp \
   SmsMessage.cpp \
   SmsEvent.cpp \
   Constants.cpp \
   SmsChild.cpp \
+  SmsDatabaseService.cpp \
+  SmsRequest.cpp \
+  SmsRequestManager.cpp \
+  SmsFilter.cpp \
+  SmsCursor.cpp \
   $(NULL)
 
 LOCAL_INCLUDES = \
   -I$(topsrcdir)/content/events/src \
+  -I$(topsrcdir)/dom/base \
   $(NULL)
 
 # Add VPATH to LOCAL_INCLUDES so we are going to include the correct backend
 # subdirectory (and the ipc one).
 LOCAL_INCLUDES += $(VPATH:%=-I%)
 
 ifdef MOZ_B2G_RIL
 LOCAL_INCLUDES += \
new file mode 100644
--- /dev/null
+++ b/dom/sms/src/SmsCursor.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 "SmsCursor.h"
+#include "nsIDOMClassInfo.h"
+#include "nsDOMError.h"
+#include "nsIDOMSmsMessage.h"
+#include "nsIDOMSmsRequest.h"
+#include "SmsRequest.h"
+#include "SmsRequestManager.h"
+#include "nsISmsDatabaseService.h"
+
+DOMCI_DATA(MozSmsCursor, mozilla::dom::sms::SmsCursor)
+
+namespace mozilla {
+namespace dom {
+namespace sms {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SmsCursor)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMozSmsCursor)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozSmsCursor)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_2(SmsCursor, mRequest, mMessage)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SmsCursor)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SmsCursor)
+
+SmsCursor::SmsCursor()
+  : mListId(-1)
+{
+}
+
+SmsCursor::SmsCursor(PRInt32 aListId, nsIDOMMozSmsRequest* aRequest)
+  : mListId(aListId)
+  , mRequest(aRequest)
+{
+}
+
+SmsCursor::~SmsCursor()
+{
+  NS_ASSERTION(!mMessage, "mMessage shouldn't be set!");
+
+  if (mListId != -1) {
+    nsCOMPtr<nsISmsDatabaseService> smsDBService =
+      do_GetService(SMS_DATABASE_SERVICE_CONTRACTID);
+
+    if (!smsDBService) {
+      NS_ERROR("Can't find SmsDBService!");
+    }
+
+    smsDBService->ClearMessageList(mListId);
+  }
+}
+
+void
+SmsCursor::Disconnect()
+{
+  NS_ASSERTION(!mMessage, "mMessage shouldn't be set!");
+
+  mRequest = nsnull;
+  mListId = -1;
+}
+
+NS_IMETHODIMP
+SmsCursor::GetMessage(nsIDOMMozSmsMessage** aMessage)
+{
+  NS_IF_ADDREF(*aMessage = mMessage);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsCursor::Continue()
+{
+  // No message means we are waiting for a message or we got the last one.
+  if (!mMessage) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  mMessage = nsnull;
+  static_cast<SmsRequest*>(mRequest.get())->Reset();
+
+  PRInt32 requestId = SmsRequestManager::GetInstance()->AddRequest(mRequest);
+
+  nsCOMPtr<nsISmsDatabaseService> smsDBService =
+    do_GetService(SMS_DATABASE_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(smsDBService, NS_ERROR_FAILURE);
+
+  smsDBService->GetNextMessageInList(mListId, requestId, 0);
+
+  return NS_OK;
+}
+
+} // namespace sms
+} // namespace dom
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/dom/sms/src/SmsCursor.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef mozilla_dom_sms_SmsCursor_h
+#define mozilla_dom_sms_SmsCursor_h
+
+#include "nsIDOMSmsCursor.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+
+class nsIDOMMozSmsMessage;
+class nsIDOMMozSmsRequest;
+
+namespace mozilla {
+namespace dom {
+namespace sms {
+
+class SmsCursor : public nsIDOMMozSmsCursor
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_NSIDOMMOZSMSCURSOR
+
+  NS_DECL_CYCLE_COLLECTION_CLASS(SmsCursor)
+
+  SmsCursor();
+  SmsCursor(PRInt32 aListId, nsIDOMMozSmsRequest* aRequest);
+
+  ~SmsCursor();
+
+  void SetMessage(nsIDOMMozSmsMessage* aMessage);
+
+  void Disconnect();
+
+private:
+  PRInt32                       mListId;
+  nsCOMPtr<nsIDOMMozSmsRequest> mRequest;
+  nsCOMPtr<nsIDOMMozSmsMessage> mMessage;
+};
+
+inline void
+SmsCursor::SetMessage(nsIDOMMozSmsMessage* aMessage)
+{
+  mMessage = aMessage;
+}
+
+} // namespace sms
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_sms_SmsCursor_h
--- a/dom/sms/src/SmsEvent.cpp
+++ b/dom/sms/src/SmsEvent.cpp
@@ -72,17 +72,17 @@ SmsEvent::Init(const nsAString& aEventTy
                                       aCancelableArg);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mMessage = aMessage;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-SmsEvent::GetMessage(nsIDOMMozSmsMessage** aMessage)
+SmsEvent::GetMessageMoz(nsIDOMMozSmsMessage** aMessage)
 {
   NS_IF_ADDREF(*aMessage = mMessage);
   return NS_OK;
 }
 
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/sms/src/SmsFilter.cpp
@@ -0,0 +1,264 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 "SmsFilter.h"
+#include "nsIDOMClassInfo.h"
+#include "Constants.h"
+#include "nsError.h"
+#include "Constants.h"
+#include "jsapi.h"
+#include "jsfriendapi.h" // For js_DateGetMsecSinceEpoch.
+#include "js/Utility.h"
+#include "nsJSUtils.h"
+#include "nsDOMString.h"
+
+DOMCI_DATA(MozSmsFilter, mozilla::dom::sms::SmsFilter)
+
+namespace mozilla {
+namespace dom {
+namespace sms {
+
+NS_INTERFACE_MAP_BEGIN(SmsFilter)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMozSmsFilter)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozSmsFilter)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(SmsFilter)
+NS_IMPL_RELEASE(SmsFilter)
+
+SmsFilter::SmsFilter()
+{
+  mData.startDate() = 0;
+  mData.endDate() = 0;
+  mData.delivery() = eDeliveryState_Unknown;
+}
+
+SmsFilter::SmsFilter(const SmsFilterData& aData)
+  : mData(aData)
+{
+}
+
+/* static */ nsresult
+SmsFilter::NewSmsFilter(nsISupports** aSmsFilter)
+{
+  NS_ADDREF(*aSmsFilter = new SmsFilter());
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsFilter::GetStartDate(JSContext* aCx, jsval* aStartDate)
+{
+  if (mData.startDate() == 0) {
+    *aStartDate = JSVAL_NULL;
+    return NS_OK;
+  }
+
+  aStartDate->setObjectOrNull(JS_NewDateObjectMsec(aCx, mData.startDate()));
+  NS_ENSURE_TRUE(aStartDate->isObject(), NS_ERROR_FAILURE);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsFilter::SetStartDate(JSContext* aCx, const jsval& aStartDate)
+{
+  if (aStartDate == JSVAL_NULL) {
+    mData.startDate() = 0;
+    return NS_OK;
+  }
+
+  if (!aStartDate.isObject()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  JSObject& obj = aStartDate.toObject();
+  if (!JS_ObjectIsDate(aCx, &obj)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  mData.startDate() = js_DateGetMsecSinceEpoch(aCx, &obj);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsFilter::GetEndDate(JSContext* aCx, jsval* aEndDate)
+{
+  if (mData.endDate() == 0) {
+    *aEndDate = JSVAL_NULL;
+    return NS_OK;
+  }
+
+  aEndDate->setObjectOrNull(JS_NewDateObjectMsec(aCx, mData.endDate()));
+  NS_ENSURE_TRUE(aEndDate->isObject(), NS_ERROR_FAILURE);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsFilter::SetEndDate(JSContext* aCx, const jsval& aEndDate)
+{
+  if (aEndDate == JSVAL_NULL) {
+    mData.endDate() = 0;
+    return NS_OK;
+  }
+
+  if (!aEndDate.isObject()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  JSObject& obj = aEndDate.toObject();
+  if (!JS_ObjectIsDate(aCx, &obj)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  mData.endDate() = js_DateGetMsecSinceEpoch(aCx, &obj);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsFilter::GetNumbers(JSContext* aCx, jsval* aNumbers)
+{
+  PRUint32 length = mData.numbers().Length();
+
+  if (length == 0) {
+    *aNumbers = JSVAL_NULL;
+    return NS_OK;
+  }
+
+  jsval* numbers = new jsval[length];
+
+  for (PRUint32 i=0; i<length; ++i) {
+    numbers[i].setString(JS_NewUCStringCopyN(aCx, mData.numbers()[i].get(),
+                                             mData.numbers()[i].Length()));
+  }
+
+  aNumbers->setObjectOrNull(JS_NewArrayObject(aCx, length, numbers));
+  NS_ENSURE_TRUE(aNumbers->isObject(), NS_ERROR_FAILURE);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsFilter::SetNumbers(JSContext* aCx, const jsval& aNumbers)
+{
+  if (aNumbers == JSVAL_NULL) {
+    mData.numbers().Clear();
+    return NS_OK;
+  }
+
+  if (!aNumbers.isObject()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  JSObject& obj = aNumbers.toObject();
+  if (!JS_IsArrayObject(aCx, &obj)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  jsuint size;
+  JS_ALWAYS_TRUE(JS_GetArrayLength(aCx, &obj, &size));
+
+  nsTArray<nsString> numbers;
+
+  for (jsuint i=0; i<size; ++i) {
+    jsval jsNumber;
+    if (!JS_GetElement(aCx, &obj, i, &jsNumber)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    if (!jsNumber.isString()) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    nsDependentJSString number;
+    number.init(aCx, jsNumber.toString());
+
+    numbers.AppendElement(number);
+  }
+
+  mData.numbers().Clear();
+  mData.numbers().AppendElements(numbers);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsFilter::GetDelivery(nsAString& aDelivery)
+{
+  switch (mData.delivery()) {
+    case eDeliveryState_Received:
+      aDelivery = DELIVERY_RECEIVED;
+      break;
+    case eDeliveryState_Sent:
+      aDelivery = DELIVERY_SENT;
+      break;
+    case eDeliveryState_Unknown:
+      SetDOMStringToNull(aDelivery);
+      break;
+    default:
+      NS_ASSERTION(false, "We shouldn't get another delivery state!");
+      return NS_ERROR_UNEXPECTED;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsFilter::SetDelivery(const nsAString& aDelivery)
+{
+  if (aDelivery.IsEmpty()) {
+    mData.delivery() = eDeliveryState_Unknown;
+    return NS_OK;
+  }
+
+  if (aDelivery.Equals(DELIVERY_RECEIVED)) {
+    mData.delivery() = eDeliveryState_Received;
+    return NS_OK;
+  }
+
+  if (aDelivery.Equals(DELIVERY_SENT)) {
+    mData.delivery() = eDeliveryState_Sent;
+    return NS_OK;
+  }
+
+  return NS_ERROR_INVALID_ARG;
+}
+
+} // namespace sms
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/sms/src/SmsFilter.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef mozilla_dom_sms_SmsFilter_h
+#define mozilla_dom_sms_SmsFilter_h
+
+#include "mozilla/dom/sms/PSms.h"
+#include "nsIDOMSmsFilter.h"
+#include "Types.h"
+
+namespace mozilla {
+namespace dom {
+namespace sms {
+
+class SmsFilter : public nsIDOMMozSmsFilter
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMMOZSMSFILTER
+
+  SmsFilter();
+  SmsFilter(const SmsFilterData& aData);
+
+  const SmsFilterData& GetData() const;
+
+  static nsresult NewSmsFilter(nsISupports** aSmsFilter);
+
+private:
+  SmsFilterData mData;
+};
+
+inline const SmsFilterData&
+SmsFilter::GetData() const {
+  return mData;
+}
+
+} // namespace sms
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_sms_SmsFilter_h
--- a/dom/sms/src/SmsManager.cpp
+++ b/dom/sms/src/SmsManager.cpp
@@ -30,47 +30,60 @@
  * 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 "SmsFilter.h"
 #include "SmsManager.h"
 #include "nsIDOMClassInfo.h"
 #include "nsISmsService.h"
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "Constants.h"
 #include "SmsEvent.h"
 #include "nsIDOMSmsMessage.h"
+#include "nsIDOMSmsRequest.h"
+#include "SmsRequestManager.h"
+#include "nsJSUtils.h"
+#include "nsContentUtils.h"
+#include "nsISmsDatabaseService.h"
+#include "nsIXPConnect.h"
 
 /**
  * We have to use macros here because our leak analysis tool things we are
  * leaking strings when we have |static const nsString|. Sad :(
  */
-#define RECEIVED_EVENT_NAME NS_LITERAL_STRING("received")
+#define RECEIVED_EVENT_NAME  NS_LITERAL_STRING("received")
+#define SENT_EVENT_NAME      NS_LITERAL_STRING("sent")
+#define DELIVERED_EVENT_NAME NS_LITERAL_STRING("delivered")
 
 DOMCI_DATA(MozSmsManager, mozilla::dom::sms::SmsManager)
 
 namespace mozilla {
 namespace dom {
 namespace sms {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(SmsManager)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SmsManager,
                                                   nsDOMEventTargetWrapperCache)
   NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(received)
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(sent)
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(delivered)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SmsManager,
                                                 nsDOMEventTargetWrapperCache)
   NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(received)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(sent)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(delivered)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SmsManager)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozSmsManager)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMMozSmsManager)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozSmsManager)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetWrapperCache)
 
@@ -86,53 +99,205 @@ SmsManager::Init(nsPIDOMWindow *aWindow,
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   // GetObserverService() can return null is some situations like shutdown.
   if (!obs) {
     return;
   }
 
   obs->AddObserver(this, kSmsReceivedObserverTopic, false);
+  obs->AddObserver(this, kSmsSentObserverTopic, false);
+  obs->AddObserver(this, kSmsDeliveredObserverTopic, false);
 }
 
 void
 SmsManager::Shutdown()
 {
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   // GetObserverService() can return null is some situations like shutdown.
   if (!obs) {
     return;
   }
 
   obs->RemoveObserver(this, kSmsReceivedObserverTopic);
+  obs->RemoveObserver(this, kSmsSentObserverTopic);
+  obs->RemoveObserver(this, kSmsDeliveredObserverTopic);
 }
 
 NS_IMETHODIMP
 SmsManager::GetNumberOfMessagesForText(const nsAString& aText, PRUint16* aResult)
 {
-  nsCOMPtr<nsISmsService> smsService = do_GetService(SMSSERVICE_CONTRACTID);
+  nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(smsService, NS_OK);
 
   smsService->GetNumberOfMessagesForText(aText, aResult);
 
   return NS_OK;
 }
 
+nsresult
+SmsManager::Send(JSContext* aCx, JSObject* aGlobal, JSString* aNumber,
+                 const nsAString& aMessage, jsval* aRequest)
+{
+  nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
+  if (!smsService) {
+    NS_ERROR("No SMS Service!");
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIDOMMozSmsRequest> request;
+
+  int requestId =
+    SmsRequestManager::GetInstance()->CreateRequest(mOwner, mScriptContext,
+                                                    getter_AddRefs(request));
+  NS_ASSERTION(request, "The request object must have been created!");
+
+  nsDependentJSString number;
+  number.init(aCx, aNumber);
+
+  smsService->Send(number, aMessage, requestId, 0);
+
+  nsresult rv = nsContentUtils::WrapNative(aCx, aGlobal, request, aRequest);
+  if (NS_FAILED(rv)) {
+    NS_ERROR("Failed to create the js value!");
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
-SmsManager::Send(const nsAString& aNumber, const nsAString& aMessage)
+SmsManager::Send(const jsval& aNumber, const nsAString& aMessage, jsval* aReturn)
 {
-  nsCOMPtr<nsISmsService> smsService = do_GetService(SMSSERVICE_CONTRACTID);
-  NS_ENSURE_TRUE(smsService, NS_OK);
+  JSContext* cx = mScriptContext->GetNativeContext();
+  NS_ASSERTION(cx, "Failed to get a context!");
+
+  if (!aNumber.isString() &&
+      !(aNumber.isObject() && JS_IsArrayObject(cx, &aNumber.toObject()))) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  JSObject* global = mScriptContext->GetNativeGlobal();
+  NS_ASSERTION(global, "Failed to get global object!");
+
+  JSAutoRequest ar(cx);
+  JSAutoEnterCompartment ac;
+  if (!ac.enter(cx, global)) {
+    NS_ERROR("Failed to enter the js compartment!");
+    return NS_ERROR_FAILURE;
+  }
+
+  if (aNumber.isString()) {
+    return Send(cx, global, aNumber.toString(), aMessage, aReturn);
+  }
+
+  // Must be an array then.
+  JSObject& numbers = aNumber.toObject();
+
+  jsuint size;
+  JS_ALWAYS_TRUE(JS_GetArrayLength(cx, &numbers, &size));
+
+  jsval* requests = new jsval[size];
+
+  for (jsuint i=0; i<size; ++i) {
+    jsval number;
+    if (!JS_GetElement(cx, &numbers, i, &number)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    nsresult rv = Send(cx, global, number.toString(), aMessage, &requests[i]);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  aReturn->setObjectOrNull(JS_NewArrayObject(cx, size, requests));
+  NS_ENSURE_TRUE(aReturn->isObject(), NS_ERROR_FAILURE);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsManager::GetMessageMoz(PRInt32 aId, nsIDOMMozSmsRequest** aRequest)
+{
+  int requestId =
+    SmsRequestManager::GetInstance()->CreateRequest(mOwner, mScriptContext, aRequest);
+  NS_ASSERTION(*aRequest, "The request object must have been created!");
+
+  nsCOMPtr<nsISmsDatabaseService> smsDBService =
+    do_GetService(SMS_DATABASE_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(smsDBService, NS_ERROR_FAILURE);
+
+  smsDBService->GetMessageMoz(aId, requestId, 0);
 
-  smsService->Send(aNumber, aMessage);
+  return NS_OK;
+}
+
+nsresult
+SmsManager::Delete(PRInt32 aId, nsIDOMMozSmsRequest** aRequest)
+{
+  int requestId =
+    SmsRequestManager::GetInstance()->CreateRequest(mOwner, mScriptContext, aRequest);
+  NS_ASSERTION(*aRequest, "The request object must have been created!");
+
+  nsCOMPtr<nsISmsDatabaseService> smsDBService =
+    do_GetService(SMS_DATABASE_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(smsDBService, NS_ERROR_FAILURE);
+
+  smsDBService->DeleteMessage(aId, requestId, 0);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsManager::Delete(const jsval& aParam, nsIDOMMozSmsRequest** aRequest)
+{
+  if (aParam.isInt32()) {
+    return Delete(aParam.toInt32(), aRequest);
+  }
+
+  if (!aParam.isObject()) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsCOMPtr<nsIDOMMozSmsMessage> message =
+    do_QueryInterface(nsContentUtils::XPConnect()->GetNativeOfWrapper(
+          mScriptContext->GetNativeContext(), &aParam.toObject()));
+  NS_ENSURE_TRUE(message, NS_ERROR_INVALID_ARG);
+
+  PRInt32 id;
+  message->GetId(&id);
+
+  return Delete(id, aRequest);
+}
+
+NS_IMETHODIMP
+SmsManager::GetMessages(nsIDOMMozSmsFilter* aFilter, bool aReverse,
+                        nsIDOMMozSmsRequest** aRequest)
+{
+  nsCOMPtr<nsIDOMMozSmsFilter> filter = aFilter;
+
+  if (!filter) {
+    filter = new SmsFilter();
+  }
+
+  int requestId =
+    SmsRequestManager::GetInstance()->CreateRequest(mOwner, mScriptContext, aRequest);
+  NS_ASSERTION(*aRequest, "The request object must have been created!");
+
+  nsCOMPtr<nsISmsDatabaseService> smsDBService =
+    do_GetService(SMS_DATABASE_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(smsDBService, NS_ERROR_FAILURE);
+
+  smsDBService->CreateMessageList(filter, aReverse, requestId, 0);
 
   return NS_OK;
 }
 
 NS_IMPL_EVENT_HANDLER(SmsManager, received)
+NS_IMPL_EVENT_HANDLER(SmsManager, sent)
+NS_IMPL_EVENT_HANDLER(SmsManager, delivered)
 
 nsresult
 SmsManager::DispatchTrustedSmsEventToSelf(const nsAString& aEventName, nsIDOMMozSmsMessage* aMessage)
 {
   nsRefPtr<nsDOMEvent> event = new SmsEvent(nsnull, nsnull);
   nsresult rv = static_cast<SmsEvent*>(event.get())->Init(aEventName, false,
                                                           false, aMessage);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -154,16 +319,39 @@ SmsManager::Observe(nsISupports* aSubjec
   if (!strcmp(aTopic, kSmsReceivedObserverTopic)) {
     nsCOMPtr<nsIDOMMozSmsMessage> message = do_QueryInterface(aSubject);
     if (!message) {
       NS_ERROR("Got a 'sms-received' topic without a valid message!");
       return NS_OK;
     }
 
     DispatchTrustedSmsEventToSelf(RECEIVED_EVENT_NAME, message);
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, kSmsSentObserverTopic)) {
+    nsCOMPtr<nsIDOMMozSmsMessage> message = do_QueryInterface(aSubject);
+    if (!message) {
+      NS_ERROR("Got a 'sms-sent' topic without a valid message!");
+      return NS_OK;
+    }
+
+    DispatchTrustedSmsEventToSelf(SENT_EVENT_NAME, message);
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, kSmsDeliveredObserverTopic)) {
+    nsCOMPtr<nsIDOMMozSmsMessage> message = do_QueryInterface(aSubject);
+    if (!message) {
+      NS_ERROR("Got a 'sms-delivered' topic without a valid message!");
+      return NS_OK;
+    }
+
+    DispatchTrustedSmsEventToSelf(DELIVERED_EVENT_NAME, message);
+    return NS_OK;
   }
 
   return NS_OK;
 }
 
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
--- a/dom/sms/src/SmsManager.h
+++ b/dom/sms/src/SmsManager.h
@@ -61,18 +61,31 @@ public:
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SmsManager,
                                            nsDOMEventTargetWrapperCache)
 
   void Init(nsPIDOMWindow *aWindow, nsIScriptContext* aScriptContext);
   void Shutdown();
 
 private:
+  /**
+   * Internal Send() method used to send one message.
+   */
+  nsresult Send(JSContext* aCx, JSObject* aGlobal, JSString* aNumber,
+                const nsAString& aMessage, jsval* aRequest);
+
+  /**
+   * Internal Delete() method used to delete a message.
+   */
+  nsresult Delete(PRInt32 aId, nsIDOMMozSmsRequest** aRequest);
+
   nsresult DispatchTrustedSmsEventToSelf(const nsAString& aEventName,
                                          nsIDOMMozSmsMessage* aMessage);
   NS_DECL_EVENT_HANDLER(received)
+  NS_DECL_EVENT_HANDLER(sent)
+  NS_DECL_EVENT_HANDLER(delivered)
 };
 
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_sms_SmsManager_h
--- a/dom/sms/src/SmsMessage.cpp
+++ b/dom/sms/src/SmsMessage.cpp
@@ -52,16 +52,23 @@ NS_INTERFACE_MAP_BEGIN(SmsMessage)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozSmsMessage)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozSmsMessage)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(SmsMessage)
 NS_IMPL_RELEASE(SmsMessage)
 
+SmsMessage::SmsMessage(PRInt32 aId, DeliveryState aDelivery,
+                       const nsString& aSender, const nsString& aReceiver,
+                       const nsString& aBody, PRUint64 aTimestamp)
+  : mData(aId, aDelivery, aSender, aReceiver, aBody, aTimestamp)
+{
+}
+
 SmsMessage::SmsMessage(const SmsMessageData& aData)
   : mData(aData)
 {
 }
 
 /* static */ nsresult
 SmsMessage::Create(PRInt32 aId,
                    const nsAString& aDelivery,
@@ -126,24 +133,25 @@ SmsMessage::GetId(PRInt32* aId)
   return NS_OK;
 }
 
 NS_IMETHODIMP
 SmsMessage::GetDelivery(nsAString& aDelivery)
 {
   switch (mData.delivery()) {
     case eDeliveryState_Received:
-      aDelivery.AssignLiteral("received");
+      aDelivery = DELIVERY_RECEIVED;
       break;
     case eDeliveryState_Sent:
-      aDelivery.AssignLiteral("sent");
+      aDelivery = DELIVERY_SENT;
       break;
     case eDeliveryState_Unknown:
+    case eDeliveryState_EndGuard:
     default:
-      NS_ASSERTION(true, "We shouldn't get an unknown delivery state!");
+      NS_ASSERTION(true, "We shouldn't get any other delivery state!");
       return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 SmsMessage::GetSender(nsAString& aSender)
--- a/dom/sms/src/SmsMessage.h
+++ b/dom/sms/src/SmsMessage.h
@@ -37,27 +37,31 @@
 
 #ifndef mozilla_dom_sms_SmsMessage_h
 #define mozilla_dom_sms_SmsMessage_h
 
 #include "mozilla/dom/sms/PSms.h"
 #include "nsIDOMSmsMessage.h"
 #include "nsString.h"
 #include "jspubtd.h"
+#include "Types.h"
 
 namespace mozilla {
 namespace dom {
 namespace sms {
 
 class SmsMessage : public nsIDOMMozSmsMessage
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMMOZSMSMESSAGE
 
+  SmsMessage(PRInt32 aId, DeliveryState aDelivery, const nsString& aSender,
+             const nsString& aReceiver, const nsString& aBody,
+             PRUint64 aTimestamp);
   SmsMessage(const SmsMessageData& aData);
 
   static nsresult Create(PRInt32 aId,
                          const nsAString& aDelivery,
                          const nsAString& aSender,
                          const nsAString& aReceiver,
                          const nsAString& aBody,
                          const JS::Value& aTimestamp,
new file mode 100644
--- /dev/null
+++ b/dom/sms/src/SmsRequest.cpp
@@ -0,0 +1,282 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 "SmsRequest.h"
+#include "nsIDOMClassInfo.h"
+#include "nsDOMString.h"
+#include "nsContentUtils.h"
+#include "nsIDOMSmsMessage.h"
+#include "nsIDOMSmsCursor.h"
+
+DOMCI_DATA(MozSmsRequest, mozilla::dom::sms::SmsRequest)
+
+namespace mozilla {
+namespace dom {
+namespace sms {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(SmsRequest)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SmsRequest,
+                                                  nsDOMEventTargetWrapperCache)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(success)
+  NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(error)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCursor)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(SmsRequest,
+                                                nsDOMEventTargetWrapperCache)
+  if (tmp->mResultRooted) {
+    tmp->mResult = JSVAL_VOID;
+    tmp->UnrootResult();
+  }
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(success)
+  NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(error)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCursor)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(SmsRequest,
+                                               nsDOMEventTargetWrapperCache)
+  if (JSVAL_IS_GCTHING(tmp->mResult)) {
+    void *gcThing = JSVAL_TO_GCTHING(tmp->mResult);
+    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_CALLBACK(gcThing, "mResult")
+  }
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SmsRequest)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMMozSmsRequest)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMMozSmsRequest)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozSmsRequest)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetWrapperCache)
+
+NS_IMPL_ADDREF_INHERITED(SmsRequest, nsDOMEventTargetWrapperCache)
+NS_IMPL_RELEASE_INHERITED(SmsRequest, nsDOMEventTargetWrapperCache)
+
+NS_IMPL_EVENT_HANDLER(SmsRequest, success)
+NS_IMPL_EVENT_HANDLER(SmsRequest, error)
+
+SmsRequest::SmsRequest(nsPIDOMWindow* aWindow, nsIScriptContext* aScriptContext)
+  : mResult(JSVAL_VOID)
+  , mResultRooted(false)
+  , mError(eNoError)
+  , mDone(false)
+{
+  // Those vars come from nsDOMEventTargetHelper.
+  mOwner = aWindow;
+  mScriptContext = aScriptContext;
+}
+
+SmsRequest::~SmsRequest()
+{
+  if (mResultRooted) {
+    UnrootResult();
+  }
+}
+
+void
+SmsRequest::Reset()
+{
+  NS_ASSERTION(mDone, "mDone should be true if we try to reset!");
+  NS_ASSERTION(mResult != JSVAL_VOID, "mResult should be set if we try to reset!");
+  NS_ASSERTION(mError == eNoError, "There should be no error if we try to reset!");
+
+  if (mResultRooted) {
+    UnrootResult();
+  }
+
+  mResult = JSVAL_VOID;
+  mDone = false;
+}
+
+void
+SmsRequest::RootResult()
+{
+  NS_ASSERTION(!mResultRooted, "Don't call RootResult() if already rooted!");
+  NS_HOLD_JS_OBJECTS(this, SmsRequest);
+  mResultRooted = true;
+}
+
+void
+SmsRequest::UnrootResult()
+{
+  NS_ASSERTION(mResultRooted, "Don't call UnrotResult() if not rooted!");
+  NS_DROP_JS_OBJECTS(this, SmsRequest);
+  mResultRooted = false;
+}
+
+void
+SmsRequest::SetSuccess(nsIDOMMozSmsMessage* aMessage)
+{
+  SetSuccessInternal(aMessage);
+}
+
+void
+SmsRequest::SetSuccess(bool aResult)
+{
+  NS_PRECONDITION(!mDone, "mDone shouldn't have been set to true already!");
+  NS_PRECONDITION(mError == eNoError, "mError shouldn't have been set!");
+  NS_PRECONDITION(mResult == JSVAL_NULL, "mResult shouldn't have been set!");
+
+  mResult.setBoolean(aResult);
+  mDone = true;
+}
+
+void
+SmsRequest::SetSuccess(nsIDOMMozSmsCursor* aCursor)
+{
+  if (!SetSuccessInternal(aCursor)) {
+    return;
+  }
+
+  NS_ASSERTION(!mCursor || mCursor == aCursor,
+               "SmsRequest can't change it's cursor!");
+
+  if (!mCursor) {
+    mCursor = aCursor;
+  }
+}
+
+bool
+SmsRequest::SetSuccessInternal(nsISupports* aObject)
+{
+  NS_PRECONDITION(!mDone, "mDone shouldn't have been set to true already!");
+  NS_PRECONDITION(mError == eNoError, "mError shouldn't have been set!");
+  NS_PRECONDITION(mResult == JSVAL_VOID, "mResult shouldn't have been set!");
+
+  JSContext* cx = mScriptContext->GetNativeContext();
+  NS_ASSERTION(cx, "Failed to get a context!");
+
+  JSObject* global = mScriptContext->GetNativeGlobal();
+  NS_ASSERTION(global, "Failed to get global object!");
+
+  JSAutoRequest ar(cx);
+  JSAutoEnterCompartment ac;
+  if (!ac.enter(cx, global)) {
+    SetError(eInternalError);
+    return false;
+  }
+
+  RootResult();
+
+  if (NS_FAILED(nsContentUtils::WrapNative(cx, global, aObject, &mResult))) {
+    UnrootResult();
+    mResult = JSVAL_VOID;
+    SetError(eInternalError);
+    return false;
+  }
+
+  mDone = true;
+  return true;
+}
+
+void
+SmsRequest::SetError(ErrorType aError)
+{
+  NS_PRECONDITION(!mDone, "mDone shouldn't have been set to true already!");
+  NS_PRECONDITION(mError == eNoError, "mError shouldn't have been set!");
+  NS_PRECONDITION(mResult == JSVAL_VOID, "mResult shouldn't have been set!");
+
+  mDone = true;
+  mError = aError;
+  mCursor = nsnull;
+}
+
+NS_IMETHODIMP
+SmsRequest::GetReadyState(nsAString& aReadyState)
+{
+  if (mDone) {
+    aReadyState.AssignLiteral("done");
+  } else {
+    aReadyState.AssignLiteral("processing");
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsRequest::GetError(nsAString& aError)
+{
+  if (!mDone) {
+    NS_ASSERTION(mError == eNoError,
+                 "There should be no error if the request is still processing!");
+
+    SetDOMStringToNull(aError);
+    return NS_OK;
+  }
+
+  NS_ASSERTION(mError == eNoError || mResult == JSVAL_VOID,
+               "mResult should be void when there is an error!");
+
+  switch (mError) {
+    case eNoError:
+      SetDOMStringToNull(aError);
+      break;
+    case eNoSignalError:
+      aError.AssignLiteral("NoSignalError");
+      break;
+    case eNotFoundError:
+      aError.AssignLiteral("NotFoundError");
+      break;
+    case eUnknownError:
+      aError.AssignLiteral("UnknownError");
+      break;
+    case eInternalError:
+      aError.AssignLiteral("InternalError");
+      break;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsRequest::GetResult(jsval* aResult)
+{
+  if (!mDone) {
+    NS_ASSERTION(mResult == JSVAL_VOID,
+                 "When not done, result should be null!");
+
+    *aResult = JSVAL_VOID;
+    return NS_OK;
+  }
+
+  *aResult = mResult;
+  return NS_OK;
+}
+
+} // namespace sms
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/sms/src/SmsRequest.h
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef mozilla_dom_sms_SmsRequest_h
+#define mozilla_dom_sms_SmsRequest_h
+
+#include "nsIDOMSmsRequest.h"
+#include "nsDOMEventTargetWrapperCache.h"
+
+class nsIDOMMozSmsMessage;
+class nsIDOMMozSmsCursor;
+
+namespace mozilla {
+namespace dom {
+namespace sms {
+
+class SmsRequest : public nsIDOMMozSmsRequest
+                 , public nsDOMEventTargetWrapperCache
+{
+public:
+  friend class SmsRequestManager;
+
+  /**
+   * All SMS related errors that could apply to SmsRequest objects.
+   * Make sure to keep this list in sync with the list in:
+   * embedding/android/GeckoSmsManager.java
+   */
+  enum ErrorType {
+    eNoError = 0,
+    eNoSignalError,
+    eNotFoundError,
+    eUnknownError,
+    eInternalError,
+  };
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMMOZSMSREQUEST
+
+  NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetWrapperCache::)
+
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(SmsRequest,
+                                                         nsDOMEventTargetWrapperCache)
+
+  void Reset();
+
+private:
+  SmsRequest() MOZ_DELETE;
+
+  SmsRequest(nsPIDOMWindow* aWindow, nsIScriptContext* aScriptContext);
+  ~SmsRequest();
+
+  /**
+   * Root mResult (jsval) to prevent garbage collection.
+   */
+  void RootResult();
+
+  /**
+   * Unroot mResult (jsval) to allow garbage collection.
+   */
+  void UnrootResult();
+
+  /**
+   * Set the object in a success state with the result being aMessage.
+   */
+  void SetSuccess(nsIDOMMozSmsMessage* aMessage);
+
+  /**
+   * Set the object in a success state with the result being a boolean.
+   */
+  void SetSuccess(bool aResult);
+
+  /**
+   * Set the object in a success state with the result being a SmsCursor.
+   */
+  void SetSuccess(nsIDOMMozSmsCursor* aCursor);
+
+  /**
+   * Set the object in an error state with the error type being aError.
+   */
+  void SetError(ErrorType aError);
+
+  /**
+   * Set the object in a success state with the result being the nsISupports
+   * object in parameter.
+   * @return whether setting the object was a success
+   */
+  bool SetSuccessInternal(nsISupports* aObject);
+
+  /**
+   * Return the internal cursor that is saved when
+   * SetSuccess(nsIDOMMozSmsCursor*) is used.
+   * Returns null if this request isn't associated to an cursor.
+   */
+  nsIDOMMozSmsCursor* GetCursor();
+
+  jsval     mResult;
+  bool      mResultRooted;
+  ErrorType mError;
+  bool      mDone;
+  nsCOMPtr<nsIDOMMozSmsCursor> mCursor;
+
+  NS_DECL_EVENT_HANDLER(success)
+  NS_DECL_EVENT_HANDLER(error)
+};
+
+inline nsIDOMMozSmsCursor*
+SmsRequest::GetCursor()
+{
+  return mCursor;
+}
+
+} // namespace sms
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_sms_SmsRequest_h
new file mode 100644
--- /dev/null
+++ b/dom/sms/src/SmsRequestManager.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 "SmsRequestManager.h"
+#include "nsIDOMSmsMessage.h"
+#include "nsDOMEvent.h"
+#include "SmsCursor.h"
+
+/**
+ * We have to use macros here because our leak analysis tool things we are
+ * leaking strings when we have |static const nsString|. Sad :(
+ */
+#define SUCCESS_EVENT_NAME NS_LITERAL_STRING("success")
+#define ERROR_EVENT_NAME   NS_LITERAL_STRING("error")
+
+namespace mozilla {
+namespace dom {
+namespace sms {
+
+SmsRequestManager* SmsRequestManager::sInstance = nsnull;
+
+void
+SmsRequestManager::Init()
+{
+  NS_PRECONDITION(!sInstance,
+                  "sInstance shouldn't be set. Did you call Init() twice?");
+  sInstance = new SmsRequestManager();
+}
+
+void
+SmsRequestManager::Shutdown()
+{
+  NS_PRECONDITION(sInstance, "sInstance should be set. Did you call Init()?");
+
+  delete sInstance;
+  sInstance = nsnull;
+}
+
+/* static */ SmsRequestManager*
+SmsRequestManager::GetInstance()
+{
+  return sInstance;
+}
+
+PRInt32
+SmsRequestManager::AddRequest(nsIDOMMozSmsRequest* aRequest)
+{
+  // TODO: merge with CreateRequest
+  PRInt32 size = mRequests.Count();
+
+  // Look for empty slots.
+  for (PRInt32 i=0; i<size; ++i) {
+    if (mRequests[i]) {
+      continue;
+    }
+
+    mRequests.ReplaceObjectAt(aRequest, i);
+    return i;
+  }
+
+  mRequests.AppendObject(aRequest);
+  return size;
+}
+
+PRInt32
+SmsRequestManager::CreateRequest(nsPIDOMWindow* aWindow,
+                                 nsIScriptContext* aScriptContext,
+                                 nsIDOMMozSmsRequest** aRequest)
+{
+  nsCOMPtr<nsIDOMMozSmsRequest> request =
+    new SmsRequest(aWindow, aScriptContext);
+
+  PRInt32 size = mRequests.Count();
+
+  // Look for empty slots.
+  for (PRInt32 i=0; i<size; ++i) {
+    if (mRequests[i]) {
+      continue;
+    }
+
+    mRequests.ReplaceObjectAt(request, i);
+    NS_ADDREF(*aRequest = request);
+    return i;
+  }
+
+  mRequests.AppendObject(request);
+  NS_ADDREF(*aRequest = request);
+  return size;
+}
+
+nsresult
+SmsRequestManager::DispatchTrustedEventToRequest(const nsAString& aEventName,
+                                                 nsIDOMMozSmsRequest* aRequest)
+{
+  nsRefPtr<nsDOMEvent> event = new nsDOMEvent(nsnull, nsnull);
+  nsresult rv = event->InitEvent(aEventName, false, false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = event->SetTrusted(PR_TRUE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool dummy;
+  return aRequest->DispatchEvent(event, &dummy);
+}
+
+SmsRequest*
+SmsRequestManager::GetRequest(PRInt32 aRequestId)
+{
+  NS_ASSERTION(mRequests.Count() > aRequestId && mRequests[aRequestId],
+               "Got an invalid request id or it has been already deleted!");
+
+  // It's safe to use the static_cast here given that we did call
+  // |new SmsRequest()|.
+  return static_cast<SmsRequest*>(mRequests[aRequestId]);
+}
+
+template <class T>
+void
+SmsRequestManager::NotifySuccess(PRInt32 aRequestId, T aParam)
+{
+  SmsRequest* request = GetRequest(aRequestId);
+  request->SetSuccess(aParam);
+
+  DispatchTrustedEventToRequest(SUCCESS_EVENT_NAME, request);
+
+  mRequests.ReplaceObjectAt(nsnull, aRequestId);
+}
+
+void
+SmsRequestManager::NotifyError(PRInt32 aRequestId, SmsRequest::ErrorType aError)
+{
+  SmsRequest* request = GetRequest(aRequestId);
+  request->SetError(aError);
+
+  DispatchTrustedEventToRequest(ERROR_EVENT_NAME, request);
+
+  mRequests.ReplaceObjectAt(nsnull, aRequestId);
+}
+
+void
+SmsRequestManager::NotifySmsSent(PRInt32 aRequestId, nsIDOMMozSmsMessage* aMessage)
+{
+  NotifySuccess<nsIDOMMozSmsMessage*>(aRequestId, aMessage);
+}
+
+void
+SmsRequestManager::NotifySmsSendFailed(PRInt32 aRequestId, SmsRequest::ErrorType aError)
+{
+  NotifyError(aRequestId, aError);
+}
+
+void
+SmsRequestManager::NotifyGotSms(PRInt32 aRequestId, nsIDOMMozSmsMessage* aMessage)
+{
+  NotifySuccess<nsIDOMMozSmsMessage*>(aRequestId, aMessage);
+}
+
+void
+SmsRequestManager::NotifyGetSmsFailed(PRInt32 aRequestId,
+                                      SmsRequest::ErrorType aError)
+{
+  NotifyError(aRequestId, aError);
+}
+
+void
+SmsRequestManager::NotifySmsDeleted(PRInt32 aRequestId, bool aDeleted)
+{
+  NotifySuccess<bool>(aRequestId, aDeleted);
+}
+
+void
+SmsRequestManager::NotifySmsDeleteFailed(PRInt32 aRequestId, SmsRequest::ErrorType aError)
+{
+  NotifyError(aRequestId, aError);
+}
+
+void
+SmsRequestManager::NotifyNoMessageInList(PRInt32 aRequestId)
+{
+  SmsRequest* request = GetRequest(aRequestId);
+
+  nsCOMPtr<nsIDOMMozSmsCursor> cursor = request->GetCursor();
+  if (!cursor) {
+    cursor = new SmsCursor();
+  } else {
+    static_cast<SmsCursor*>(cursor.get())->Disconnect();
+  }
+
+  NotifySuccess<nsIDOMMozSmsCursor*>(aRequestId, cursor);
+}
+
+void
+SmsRequestManager::NotifyCreateMessageList(PRInt32 aRequestId, PRInt32 aListId,
+                                           nsIDOMMozSmsMessage* aMessage)
+{
+  SmsRequest* request = GetRequest(aRequestId);
+
+  nsCOMPtr<SmsCursor> cursor = new SmsCursor(aListId, request);
+  cursor->SetMessage(aMessage);
+
+  NotifySuccess<nsIDOMMozSmsCursor*>(aRequestId, cursor);
+}
+
+void
+SmsRequestManager::NotifyGotNextMessage(PRInt32 aRequestId, nsIDOMMozSmsMessage* aMessage)
+{
+  SmsRequest* request = GetRequest(aRequestId);
+
+  nsCOMPtr<SmsCursor> cursor = static_cast<SmsCursor*>(request->GetCursor());
+  NS_ASSERTION(cursor, "Request should have an cursor in that case!");
+  cursor->SetMessage(aMessage);
+
+  NotifySuccess<nsIDOMMozSmsCursor*>(aRequestId, cursor);
+}
+
+void
+SmsRequestManager::NotifyReadMessageListFailed(PRInt32 aRequestId,
+                                               SmsRequest::ErrorType aError)
+{
+  SmsRequest* request = GetRequest(aRequestId);
+
+  nsCOMPtr<nsIDOMMozSmsCursor> cursor = request->GetCursor();
+  if (cursor) {
+    static_cast<SmsCursor*>(cursor.get())->Disconnect();
+  }
+
+  NotifyError(aRequestId, aError);
+}
+
+} // namespace sms
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/sms/src/SmsRequestManager.h
@@ -0,0 +1,96 @@
+
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef mozilla_dom_sms_SmsRequestManager_h
+#define mozilla_dom_sms_SmsRequestManager_h
+
+#include "nsCOMArray.h"
+#include "SmsRequest.h"
+
+class nsIDOMMozSmsRequest;
+class nsPIDOMWindow;
+class nsIScriptContext;
+class nsIDOMMozSmsMessage;
+
+namespace mozilla {
+namespace dom {
+namespace sms {
+
+class SmsRequestManager
+{
+public:
+  static void Init();
+  static void Shutdown();
+  static SmsRequestManager* GetInstance();
+
+  PRInt32 CreateRequest(nsPIDOMWindow* aWindow,
+                        nsIScriptContext* aScriptContext,
+                        nsIDOMMozSmsRequest** aRequest);
+
+  PRInt32 AddRequest(nsIDOMMozSmsRequest* aRequest);
+
+  void NotifySmsSent(PRInt32 aRequestId, nsIDOMMozSmsMessage* aMessage);
+  void NotifySmsSendFailed(PRInt32 aRequestId, SmsRequest::ErrorType aError);
+  void NotifyGotSms(PRInt32 aRequestId, nsIDOMMozSmsMessage* aMessage);
+  void NotifyGetSmsFailed(PRInt32 aRequestId, SmsRequest::ErrorType aError);
+  void NotifySmsDeleted(PRInt32 aRequestId, bool aDeleted);
+  void NotifySmsDeleteFailed(PRInt32 aRequestId, SmsRequest::ErrorType aError);
+  void NotifyNoMessageInList(PRInt32 aRequestId);
+  void NotifyCreateMessageList(PRInt32 aRequestId, PRInt32 aListId, nsIDOMMozSmsMessage* aMessage);
+  void NotifyGotNextMessage(PRInt32 aRequestId, nsIDOMMozSmsMessage* aMessage);
+  void NotifyReadMessageListFailed(PRInt32 aRequestId, SmsRequest::ErrorType aError);
+
+private:
+  static SmsRequestManager* sInstance;
+
+  nsresult DispatchTrustedEventToRequest(const nsAString& aEventName,
+                                         nsIDOMMozSmsRequest* aRequest);
+  SmsRequest* GetRequest(PRInt32 aRequestId);
+
+  template <class T>
+  void NotifySuccess(PRInt32 aRequestId, T aParam);
+  void NotifyError(PRInt32 aRequestId, SmsRequest::ErrorType aError);
+
+  nsCOMArray<nsIDOMMozSmsRequest> mRequests;
+};
+
+} // namespace sms
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_sms_SmsRequestManager_h
rename from dom/sms/src/SmsServiceFactory.cpp
rename to dom/sms/src/SmsServicesFactory.cpp
--- a/dom/sms/src/SmsServiceFactory.cpp
+++ b/dom/sms/src/SmsServicesFactory.cpp
@@ -30,34 +30,49 @@
  * 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 "SmsServiceFactory.h"
+#include "SmsServicesFactory.h"
 #include "nsXULAppAPI.h"
 #include "SmsService.h"
+#include "SmsDatabaseService.h"
 #include "SmsIPCService.h"
 
 namespace mozilla {
 namespace dom {
 namespace sms {
 
 /* static */ already_AddRefed<nsISmsService>
-SmsServiceFactory::Create()
+SmsServicesFactory::CreateSmsService()
 {
   nsCOMPtr<nsISmsService> smsService;
 
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     smsService = new SmsIPCService();
   } else {
     smsService = new SmsService();
   }
 
   return smsService.forget();
 }
 
+/* static */ already_AddRefed<nsISmsDatabaseService>
+SmsServicesFactory::CreateSmsDatabaseService()
+{
+  nsCOMPtr<nsISmsDatabaseService> smsDBService;
+
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    smsDBService = new SmsIPCService();
+  } else {
+    smsDBService = new SmsDatabaseService();
+  }
+
+  return smsDBService.forget();
+}
+
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
rename from dom/sms/src/SmsServiceFactory.h
rename to dom/sms/src/SmsServicesFactory.h
--- a/dom/sms/src/SmsServiceFactory.h
+++ b/dom/sms/src/SmsServicesFactory.h
@@ -30,30 +30,32 @@
  * 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 ***** */
 
-#ifndef mozilla_dom_sms_SmsServiceFactory_h
-#define mozilla_dom_sms_SmsServiceFactory_h
+#ifndef mozilla_dom_sms_SmsServicesFactory_h
+#define mozilla_dom_sms_SmsServicesFactory_h
 
 #include "nsCOMPtr.h"
 
 class nsISmsService;
+class nsISmsDatabaseService;
 
 namespace mozilla {
 namespace dom {
 namespace sms {
 
-class SmsServiceFactory
+class SmsServicesFactory
 {
 public:
-  static already_AddRefed<nsISmsService> Create();
+  static already_AddRefed<nsISmsService> CreateSmsService();
+  static already_AddRefed<nsISmsDatabaseService> CreateSmsDatabaseService();
 };
 
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
 
-#endif // mozilla_dom_sms_SmsServiceFactory_h
+#endif // mozilla_dom_sms_SmsServicesFactory_h
--- a/dom/sms/src/Types.h
+++ b/dom/sms/src/Types.h
@@ -33,39 +33,44 @@
  * 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 ***** */
 
 #ifndef mozilla_dom_sms_Types_h
 #define mozilla_dom_sms_Types_h
 
+#include "IPCMessageUtils.h"
+
 namespace mozilla {
 namespace dom {
 namespace sms {
 
 // For SmsMessageDate.delivery.
+// Please keep the following files in sync with enum below:
+// embedding/android/GeckoSmsManager.java
 enum DeliveryState {
-  eDeliveryState_Sent,
+  eDeliveryState_Sent = 0,
   eDeliveryState_Received,
+  eDeliveryState_Unknown,
   // This state should stay at the end.
-  eDeliveryState_Unknown
+  eDeliveryState_EndGuard
 };
 
 } // namespace sms
 } // namespace dom
 } // namespace mozilla
 
 namespace IPC {
 
 /**
  * Delivery state serializer.
  */
 template <>
 struct ParamTraits<mozilla::dom::sms::DeliveryState>
   : public EnumSerializer<mozilla::dom::sms::DeliveryState,
                           mozilla::dom::sms::eDeliveryState_Sent,
-                          mozilla::dom::sms::eDeliveryState_Unknown>
+                          mozilla::dom::sms::eDeliveryState_EndGuard>
 {};
 
 } // namespace IPC
 
 #endif // mozilla_dom_sms_Types_h
new file mode 100644
--- /dev/null
+++ b/dom/sms/src/android/SmsDatabaseService.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 "SmsFilter.h"
+#include "SmsDatabaseService.h"
+#include "AndroidBridge.h"
+
+namespace mozilla {
+namespace dom {
+namespace sms {
+
+NS_IMPL_ISUPPORTS1(SmsDatabaseService, nsISmsDatabaseService)
+
+NS_IMETHODIMP
+SmsDatabaseService::SaveSentMessage(const nsAString& aReceiver,
+                                    const nsAString& aBody,
+                                    PRUint64 aDate, PRInt32* aId)
+{
+  *aId = -1;
+
+  if (!AndroidBridge::Bridge()) {
+    return NS_OK;
+  }
+
+  *aId = AndroidBridge::Bridge()->SaveSentMessage(aReceiver, aBody, aDate);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsDatabaseService::GetMessageMoz(PRInt32 aMessageId, PRInt32 aRequestId,
+                                  PRUint64 aProcessId)
+{
+  if (!AndroidBridge::Bridge()) {
+    return NS_OK;
+  }
+
+  AndroidBridge::Bridge()->GetMessage(aMessageId, aRequestId, aProcessId);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsDatabaseService::DeleteMessage(PRInt32 aMessageId, PRInt32 aRequestId,
+                                  PRUint64 aProcessId)
+{
+  if (!AndroidBridge::Bridge()) {
+    return NS_OK;
+  }
+
+  AndroidBridge::Bridge()->DeleteMessage(aMessageId, aRequestId, aProcessId);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsDatabaseService::CreateMessageList(nsIDOMMozSmsFilter* aFilter,
+                                      bool aReverse, PRInt32 aRequestId,
+                                      PRUint64 aProcessId)
+{
+  if (!AndroidBridge::Bridge()) {
+    return NS_OK;
+  }
+
+  AndroidBridge::Bridge()->CreateMessageList(
+    static_cast<SmsFilter*>(aFilter)->GetData(), aReverse, aRequestId, aProcessId
+  );
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsDatabaseService::GetNextMessageInList(PRInt32 aListId, PRInt32 aRequestId,
+                                         PRUint64 aProcessId)
+{
+  if (!AndroidBridge::Bridge()) {
+    return NS_OK;
+  }
+
+  AndroidBridge::Bridge()->GetNextMessageInList(aListId, aRequestId, aProcessId);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsDatabaseService::ClearMessageList(PRInt32 aListId)
+{
+  if (!AndroidBridge::Bridge()) {
+    return NS_OK;
+  }
+
+  AndroidBridge::Bridge()->ClearMessageList(aListId);
+  return NS_OK;
+}
+
+} // namespace sms
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/sms/src/android/SmsDatabaseService.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef mozilla_dom_sms_SmsDatabaseService_h
+#define mozilla_dom_sms_SmsDatabaseService_h
+
+#include "nsISmsDatabaseService.h"
+
+namespace mozilla {
+namespace dom {
+namespace sms {
+
+class SmsDatabaseService : public nsISmsDatabaseService
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISMSDATABASESERVICE
+};
+
+} // namespace sms
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_sms_SmsDatabaseService_h
--- a/dom/sms/src/android/SmsService.cpp
+++ b/dom/sms/src/android/SmsService.cpp
@@ -62,23 +62,25 @@ SmsService::GetNumberOfMessagesForText(c
     return NS_OK;
   }
 
   *aResult = AndroidBridge::Bridge()->GetNumberOfMessagesForText(aText);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-SmsService::Send(const nsAString& aNumber, const nsAString& aMessage)
+SmsService::Send(const nsAString& aNumber, const nsAString& aMessage,
+                 PRInt32 aRequestId, PRUint64 aProcessId)
 {
   if (!AndroidBridge::Bridge()) {
     return NS_OK;
   }
 
-  AndroidBridge::Bridge()->SendMessage(aNumber, aMessage);
+  AndroidBridge::Bridge()->SendMessage(aNumber, aMessage, aRequestId,
+                                       aProcessId);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 SmsService::CreateSmsMessage(PRInt32 aId,
                              const nsAString& aDelivery,
                              const nsAString& aSender,
                              const nsAString& aReceiver,
new file mode 100644
--- /dev/null
+++ b/dom/sms/src/fallback/SmsDatabaseService.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 "SmsDatabaseService.h"
+
+namespace mozilla {
+namespace dom {
+namespace sms {
+
+NS_IMPL_ISUPPORTS1(SmsDatabaseService, nsISmsDatabaseService)
+
+NS_IMETHODIMP
+SmsDatabaseService::SaveSentMessage(const nsAString& aReceiver,
+                                    const nsAString& aBody,
+                                    PRUint64 aDate, PRInt32* aId)
+{
+  *aId = -1;
+  NS_ERROR("We should not be here!");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsDatabaseService::GetMessageMoz(PRInt32 aMessageId, PRInt32 aRequestId,
+                                  PRUint64 aProcessId)
+{
+  NS_ERROR("We should not be here!");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsDatabaseService::DeleteMessage(PRInt32 aMessageId, PRInt32 aRequestId,
+                                  PRUint64 aProcessId)
+{
+  NS_ERROR("We should not be here!");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsDatabaseService::CreateMessageList(nsIDOMMozSmsFilter* aFilter,
+                                      bool aReverse, PRInt32 aRequestId,
+                                      PRUint64 aProcessId)
+{
+  NS_ERROR("We should not be here!");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsDatabaseService::GetNextMessageInList(PRInt32 aListId, PRInt32 aRequestId,
+                                         PRUint64 aProcessId)
+{
+  NS_ERROR("We should not be here!");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+SmsDatabaseService::ClearMessageList(PRInt32 aListId)
+{
+  NS_ERROR("We should not be here!");
+  return NS_OK;
+}
+
+} // namespace sms
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/sms/src/fallback/SmsDatabaseService.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef mozilla_dom_sms_SmsDatabaseService_h
+#define mozilla_dom_sms_SmsDatabaseService_h
+
+#include "nsISmsDatabaseService.h"
+
+namespace mozilla {
+namespace dom {
+namespace sms {
+
+class SmsDatabaseService : public nsISmsDatabaseService
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISMSDATABASESERVICE
+};
+
+} // namespace sms
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_sms_SmsDatabaseService_h
--- a/dom/sms/src/fallback/SmsService.cpp
+++ b/dom/sms/src/fallback/SmsService.cpp
@@ -57,17 +57,18 @@ NS_IMETHODIMP
 SmsService::GetNumberOfMessagesForText(const nsAString& aText, PRUint16* aResult)
 {
   NS_ERROR("We should not be here!");
   *aResult = 0;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-SmsService::Send(const nsAString& aNumber, const nsAString& aMessage)
+SmsService::Send(const nsAString& aNumber, const nsAString& aMessage,
+                 PRInt32 aRequestId, PRUint64 aProcessId)
 {
   NS_ERROR("We should not be here!");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 SmsService::CreateSmsMessage(PRInt32 aId,
                              const nsAString& aDelivery,
--- a/dom/sms/src/ipc/PSms.ipdl
+++ b/dom/sms/src/ipc/PSms.ipdl
@@ -50,29 +50,81 @@ struct SmsMessageData {
   PRInt32       id;
   DeliveryState delivery;
   nsString      sender;
   nsString      receiver;
   nsString      body;
   PRUint64      timestamp; // ms since epoch.
 };
 
+struct SmsFilterData {
+  PRUint64      startDate;
+  PRU