Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 06 Dec 2013 16:13:37 -0500
changeset 174004 7229b03abd0f4a72c4e32fcc61018412c1f64d8f
parent 174003 17760eeea357da23abfae4b120c0e96c46a7e33d (current diff)
parent 173953 b3806ae5399d5b1a23ad5f14ecb28406c2ad59a2 (diff)
child 174005 54eac2d5c0392d2d8138e761a394ecf8e45407b4
push id3224
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:06:49 +0000
treeherdermozilla-beta@60c04d0987f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.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 m-c to inbound.
CLOBBER
mobile/android/base/RobocopAPI.java
--- a/CLOBBER
+++ b/CLOBBER
@@ -13,9 +13,9 @@
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
-Bug 947080 - bug 937317 required clobber on windows (relanding).
+Bug 933585 - clobber required on windows
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "d10e0357c63bb565db8bdd3c23f062bfb9e21315", 
+    "revision": "63d432c3395f95c0ba19578487b796c1707042bd", 
     "repo_path": "/integration/gaia-central"
 }
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -864,48 +864,51 @@ var PlacesMenuDNDHandler = {
  */
 let PlacesToolbarHelper = {
   _place: "place:folder=TOOLBAR",
 
   get _viewElt() {
     return document.getElementById("PlacesToolbar");
   },
 
-  init: function PTH_init() {
+  init: function PTH_init(forceToolbarOverflowCheck) {
     let viewElt = this._viewElt;
     if (!viewElt || viewElt._placesView)
       return;
 
     // If the bookmarks toolbar item is:
     // - not in a toolbar, or;
     // - the toolbar is collapsed, or;
     // - the toolbar is hidden some other way:
     // don't initialize.  Also, there is no need to initialize the toolbar if
     // customizing, because that will happen when the customization is done.
     let toolbar = this._getParentToolbar(viewElt);
     if (!toolbar || toolbar.collapsed || this._isCustomizing ||
         getComputedStyle(toolbar, "").display == "none")
       return;
 
     new PlacesToolbar(this._place);
+    if (forceToolbarOverflowCheck) {
+      viewElt._placesView.updateOverflowStatus();
+    }
   },
 
   customizeStart: function PTH_customizeStart() {
     try {
       let viewElt = this._viewElt;
       if (viewElt && viewElt._placesView)
         viewElt._placesView.uninit();
     } finally {
       this._isCustomizing = true;
     }
   },
 
   customizeDone: function PTH_customizeDone() {
     this._isCustomizing = false;
-    this.init();
+    this.init(true);
   },
 
   onPlaceholderCommand: function () {
     let widgetGroup = CustomizableUI.getWidget("personal-bookmarks");
     let widget = widgetGroup.forWindow(window);
     if (widget.overflowed ||
         widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
       PlacesCommandHook.showPlacesOrganizer("BookmarksToolbar");
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -1032,42 +1032,24 @@ PlacesToolbar.prototype = {
         break;
       case "resize":
         // This handler updates nodes visibility in both the toolbar
         // and the chevron popup when a window resize does not change
         // the overflow status of the toolbar.
         this.updateChevron();
         break;
       case "overflow":
-        if (aEvent.target != aEvent.currentTarget)
-          return;
-
-        // Ignore purely vertical overflows.
-        if (aEvent.detail == 0)
+        if (!this._isOverflowStateEventRelevant(aEvent))
           return;
-
-        // Attach the popup binding to the chevron popup if it has not yet
-        // been initialized.
-        if (!this._chevronPopup.hasAttribute("type")) {
-          this._chevronPopup.setAttribute("place", this.place);
-          this._chevronPopup.setAttribute("type", "places");
-        }
-        this._chevron.collapsed = false;
-        this.updateChevron();
+        this._onOverflow();
         break;
       case "underflow":
-        if (aEvent.target != aEvent.currentTarget)
+        if (!this._isOverflowStateEventRelevant(aEvent))
           return;
-
-        // Ignore purely vertical underflows.
-        if (aEvent.detail == 0)
-          return;
-
-        this.updateChevron();
-        this._chevron.collapsed = true;
+        this._onUnderflow();
         break;
       case "TabOpen":
       case "TabClose":
         this.updateChevron();
         break;
       case "dragstart":
         this._onDragStart(aEvent);
         break;
@@ -1098,16 +1080,45 @@ PlacesToolbar.prototype = {
       case "popuphidden":
         this._onPopupHidden(aEvent);
         break;
       default:
         throw "Trying to handle unexpected event.";
     }
   },
 
+  updateOverflowStatus: function() {
+    if (this._rootElt.scrollLeftMax > 0) {
+      this._onOverflow();
+    } else {
+      this._onUnderflow();
+    }
+  },
+
+  _isOverflowStateEventRelevant: function PT_isOverflowStateEventRelevant(aEvent) {
+    // Ignore events not aimed at ourselves, as well as purely vertical ones:
+    return aEvent.target == aEvent.currentTarget && aEvent.detail > 0;
+  },
+
+  _onOverflow: function PT_onOverflow() {
+    // Attach the popup binding to the chevron popup if it has not yet
+    // been initialized.
+    if (!this._chevronPopup.hasAttribute("type")) {
+      this._chevronPopup.setAttribute("place", this.place);
+      this._chevronPopup.setAttribute("type", "places");
+    }
+    this._chevron.collapsed = false;
+    this.updateChevron();
+  },
+
+  _onUnderflow: function PT_onUnderflow() {
+    this.updateChevron();
+    this._chevron.collapsed = true;
+  },
+
   updateChevron: function PT_updateChevron() {
     // If the chevron is collapsed there's nothing to update.
     if (this._chevron.collapsed)
       return;
 
     // Update the chevron on a timer.  This will avoid repeated work when
     // lot of changes happen in a small timeframe.
     if (this._updateChevronTimer)
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -1503,22 +1503,44 @@ let SessionStoreInternal = {
     if (DyingWindowCache.has(aWindow)) {
       let data = DyingWindowCache.get(aWindow);
       return this._toJSONString({ windows: [data] });
     }
 
     throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
   },
 
+  /**
+   * Restores the given state |aState| for a given window |aWindow|.
+   *
+   * @param aWindow (xul window)
+   *        The window that the given state will be restored to.
+   * @param aState (string)
+   *        The state that will be applied to the given window.
+   * @param aOverwrite (bool)
+   *        When true, existing tabs in the given window will be re-used or
+   *        removed. When false, only new tabs will be added, no existing ones
+   8        will be removed or overwritten.
+   */
   setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite) {
     if (!aWindow.__SSi) {
       throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
-    this.restoreWindow(aWindow, aState, {overwriteTabs: aOverwrite});
+    let winState = JSON.parse(aState);
+    if (!winState) {
+      throw Components.Exception("Invalid state string: not JSON", Cr.NS_ERROR_INVALID_ARG);
+    }
+
+    if (!winState.windows || !winState.windows[0]) {
+      throw Components.Exception("Invalid window state passed", Cr.NS_ERROR_INVALID_ARG);
+    }
+
+    let state = {windows: [winState.windows[0]]};
+    this.restoreWindow(aWindow, state, {overwriteTabs: aOverwrite});
   },
 
   getTabState: function ssi_getTabState(aTab) {
     if (!aTab.ownerDocument) {
       throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
     }
     if (!aTab.ownerDocument.defaultView.__SSi) {
       throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
@@ -2275,17 +2297,17 @@ let SessionStoreInternal = {
 
   /* ........ Restoring Functionality .............. */
 
   /**
    * restore features to a single window
    * @param aWindow
    *        Window reference
    * @param aState
-   *        JS object or its eval'able source
+   *        JS object
    * @param aOptions
    *        {overwriteTabs: true} to overwrite existing tabs w/ new ones
    *        {isFollowUp: true} if this is not the restoration of the 1st window
    *        {firstWindow: true} if this is the first non-private window we're
    *                            restoring in this session, that might open an
    *                            external link as well
    */
   restoreWindow: function ssi_restoreWindow(aWindow, aState, aOptions = {}) {
@@ -2295,27 +2317,20 @@ let SessionStoreInternal = {
 
     if (isFollowUp) {
       this.windowToFocus = aWindow;
     }
     // initialize window if necessary
     if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
       this.onLoad(aWindow);
 
-    try {
-      var root = typeof aState == "string" ? JSON.parse(aState) : aState;
-      if (!root.windows[0]) {
-        this._sendRestoreCompletedNotifications();
-        return; // nothing to restore
-      }
-    }
-    catch (ex) { // invalid state object - don't restore anything
-      debug(ex);
+    var root = aState;
+    if (!root.windows[0]) {
       this._sendRestoreCompletedNotifications();
-      return;
+      return; // nothing to restore
     }
 
     TelemetryStopwatch.start("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
 
     // We're not returning from this before we end up calling restoreTabs
     // for this window, so make sure we send the SSWindowStateBusy event.
     this._setWindowStateBusy(aWindow);
 
--- a/browser/components/sessionstore/test/browser_491577.js
+++ b/browser/components/sessionstore/test/browser_491577.js
@@ -73,49 +73,43 @@ function test() {
       aFunction();
       return false;
     }
     catch (ex) {
       return ex.name == "NS_ERROR_ILLEGAL_VALUE";
     }
   }
 
-  // open a window and add the above closed window list
-  let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
-  newWin.addEventListener("load", function(aEvent) {
-    this.removeEventListener("load", arguments.callee, false);
-    gPrefService.setIntPref("browser.sessionstore.max_windows_undo",
-                            test_state._closedWindows.length);
-    ss.setWindowState(newWin, JSON.stringify(test_state), true);
+  gPrefService.setIntPref("browser.sessionstore.max_windows_undo",
+                          test_state._closedWindows.length);
+  ss.setBrowserState(JSON.stringify(test_state), true);
 
-    let closedWindows = JSON.parse(ss.getClosedWindowData());
-    is(closedWindows.length, test_state._closedWindows.length,
-       "Closed window list has the expected length");
-    is(countByTitle(closedWindows, FORGET),
-       test_state._closedWindows.length - remember_count,
-       "The correct amount of windows are to be forgotten");
-    is(countByTitle(closedWindows, REMEMBER), remember_count,
-       "Everything is set up.");
+  let closedWindows = JSON.parse(ss.getClosedWindowData());
+  is(closedWindows.length, test_state._closedWindows.length,
+     "Closed window list has the expected length");
+  is(countByTitle(closedWindows, FORGET),
+     test_state._closedWindows.length - remember_count,
+     "The correct amount of windows are to be forgotten");
+  is(countByTitle(closedWindows, REMEMBER), remember_count,
+     "Everything is set up.");
 
-    // all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
-    ok(testForError(function() ss.forgetClosedWindow(-1)),
-       "Invalid window for forgetClosedWindow throws");
-    ok(testForError(function() ss.forgetClosedWindow(test_state._closedWindows.length + 1)),
-       "Invalid window for forgetClosedWindow throws");
+  // all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
+  ok(testForError(function() ss.forgetClosedWindow(-1)),
+     "Invalid window for forgetClosedWindow throws");
+  ok(testForError(function() ss.forgetClosedWindow(test_state._closedWindows.length + 1)),
+     "Invalid window for forgetClosedWindow throws");
 
-    // Remove third window, then first window
-    ss.forgetClosedWindow(2);
-    ss.forgetClosedWindow(null);
+  // Remove third window, then first window
+  ss.forgetClosedWindow(2);
+  ss.forgetClosedWindow(null);
 
-    closedWindows = JSON.parse(ss.getClosedWindowData());
-    is(closedWindows.length, remember_count,
-       "The correct amount of windows were removed");
-    is(countByTitle(closedWindows, FORGET), 0,
-       "All windows specifically forgotten were indeed removed");
-    is(countByTitle(closedWindows, REMEMBER), remember_count,
-       "... and windows not specifically forgetten weren't.");
+  closedWindows = JSON.parse(ss.getClosedWindowData());
+  is(closedWindows.length, remember_count,
+     "The correct amount of windows were removed");
+  is(countByTitle(closedWindows, FORGET), 0,
+     "All windows specifically forgotten were indeed removed");
+  is(countByTitle(closedWindows, REMEMBER), remember_count,
+     "... and windows not specifically forgetten weren't.");
 
-    // clean up
-    newWin.close();
-    gPrefService.clearUserPref("browser.sessionstore.max_windows_undo");
-    finish();
-  }, false);
+  // clean up
+  gPrefService.clearUserPref("browser.sessionstore.max_windows_undo");
+  finish();
 }
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -681,19 +681,19 @@ SourcesView.prototype = Heritage.extend(
    */
   _onSourceSelect: function({ detail: sourceItem }) {
     if (!sourceItem) {
       return;
     }
     // The container is not empty and an actual item was selected.
     DebuggerView.setEditorLocation(sourceItem.value);
 
-    // Set window title.
-    let script = sourceItem.value.split(" -> ").pop();
-    document.title = L10N.getFormatStr("DebuggerWindowScriptTitle", script);
+    // Set window title. No need to split the url by " -> " here, because it was
+    // already sanitized when the source was added.
+    document.title = L10N.getFormatStr("DebuggerWindowScriptTitle", sourceItem.value);
 
     DebuggerView.maybeShowBlackBoxMessage();
     this.updateToolbarButtonsState();
   },
 
   /**
    * Update the checked/unchecked and enabled/disabled states of the buttons in
    * the sources toolbar based on the currently selected source's state.
@@ -1080,48 +1080,24 @@ let SourceUtils = {
   getSourceGroup: function(aUrl) {
     let cachedGroup = this._groupsCache.get(aUrl);
     if (cachedGroup) {
       return cachedGroup;
     }
 
     try {
       // Use an nsIURL to parse all the url path parts.
-      var uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
+      let url = aUrl.split(" -> ").pop();
+      var uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
     } catch (e) {
       // This doesn't look like a url, or nsIURL can't handle it.
       return "";
     }
 
-    let { scheme, directory, fileName } = uri;
-    let hostPort;
-    // Add-on SDK jar: URLs will cause accessing hostPort to throw.
-    if (scheme != "jar") {
-      hostPort = uri.hostPort;
-    }
-    let lastDir = directory.split("/").reverse()[1];
-    let group = [];
-
-    // Only show interesting schemes, http is implicit.
-    if (scheme != "http") {
-      group.push(scheme);
-    }
-    // Hostnames don't always exist for files or some resource urls.
-    // e.g. file://foo/bar.js or resource:///foo/bar.js don't have a host.
-    if (hostPort) {
-      // If the hostname is a dot-separated identifier, show the first 2 parts.
-      group.push(hostPort.split(".").slice(0, 2).join("."));
-    }
-    // Append the last directory if the path leads to an actual file.
-    // e.g. http://foo.org/bar/ should only show "foo.org", not "foo.org bar"
-    if (fileName) {
-      group.push(lastDir);
-    }
-
-    let groupLabel = group.join(" ");
+    let groupLabel = uri.prePath;
     let unicodeLabel = NetworkHelper.convertToUnicode(unescape(groupLabel));
     this._groupsCache.set(aUrl, unicodeLabel)
     return unicodeLabel;
   },
 
   /**
    * Trims the url by shortening it if it exceeds a certain length, adding an
    * ellipsis at the end.
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -394,31 +394,26 @@ function StackFramesView() {
 
 StackFramesView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the StackFramesView");
 
-    let commandset = this._commandset = document.createElement("commandset");
-    let menupopup = this._menupopup = document.createElement("menupopup");
-    commandset.id = "stackframesCommandset";
-    menupopup.id = "stackframesMenupopup";
-
-    document.getElementById("debuggerPopupset").appendChild(menupopup);
-    document.getElementById("debuggerCommands").appendChild(commandset);
-
     this.widget = new BreadcrumbsWidget(document.getElementById("stackframes"));
     this.widget.addEventListener("select", this._onSelect, false);
     this.widget.addEventListener("scroll", this._onScroll, true);
     window.addEventListener("resize", this._onScroll, true);
 
     this.autoFocusOnFirstItem = false;
     this.autoFocusOnSelection = false;
+
+    // This view's contents are also mirrored in a different container.
+    this._mirror = DebuggerView.StackFramesClassicList;
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the StackFramesView");
 
@@ -448,34 +443,32 @@ StackFramesView.prototype = Heritage.ext
       if (this._prevBlackBoxedUrl == aUrl) {
         return;
       }
       this._prevBlackBoxedUrl = aUrl;
     } else {
       this._prevBlackBoxedUrl = null;
     }
 
-    // Create the element node and menu entry for the stack frame item.
+    // Create the element node for the stack frame item.
     let frameView = this._createFrameView.apply(this, arguments);
-    let menuEntry = this._createMenuEntry.apply(this, arguments);
 
     // Append a stack frame item to this container.
     this.push([frameView, aTitle, aUrl], {
       index: 0, /* specifies on which position should the item be appended */
       attachment: {
-        popup: menuEntry,
         depth: aDepth
       },
-      attributes: [
-        ["contextmenu", "stackframesMenupopup"]
-      ],
       // Make sure that when the stack frame item is removed, the corresponding
-      // menuitem and command are also destroyed.
+      // mirrored item in the classic list is also removed.
       finalize: this._onStackframeRemoved
     });
+
+    // Mirror this newly inserted item inside the "Call Stack" tab.
+    this._mirror.addFrame(aTitle, aUrl, aLine, aDepth);
   },
 
   /**
    * Selects the frame at the specified depth in this container.
    * @param number aDepth
    */
   set selectedDepth(aDepth) {
     this.selectedItem = aItem => aItem.attachment.depth == aDepth;
@@ -538,110 +531,46 @@ StackFramesView.prototype = Heritage.ext
     frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id";
     frameDetailsNode.setAttribute("value", frameDetails);
     container.appendChild(frameDetailsNode);
 
     return container;
   },
 
   /**
-   * Customization function for populating an item's context menu.
-   *
-   * @param string aTitle
-   *        The frame title to be displayed in the list.
-   * @param string aUrl
-   *        The frame source url.
-   * @param string aLine
-   *        The frame line number.
-   * @param number aDepth
-   *        The frame depth in the stack.
-   * @param boolean aIsBlackBoxed
-   *        Whether or not the frame is black boxed.
-   * @return object
-   *         An object containing the stack frame command and menu item.
-   */
-  _createMenuEntry: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) {
-    let frameDescription = SourceUtils.trimUrlLength(
-      SourceUtils.getSourceLabel(aUrl),
-      STACK_FRAMES_POPUP_SOURCE_URL_MAX_LENGTH,
-      STACK_FRAMES_POPUP_SOURCE_URL_TRIM_SECTION) +
-      SEARCH_LINE_FLAG + aLine;
-
-    let prefix = "sf-cMenu-"; // "stackframes context menu"
-    let commandId = prefix + aDepth + "-" + "-command";
-    let menuitemId = prefix + aDepth + "-" + "-menuitem";
-
-    let command = document.createElement("command");
-    command.id = commandId;
-    command.addEventListener("command", () => this.selectedDepth = aDepth, false);
-
-    let menuitem = document.createElement("menuitem");
-    menuitem.id = menuitemId;
-    menuitem.className = "dbg-stackframe-menuitem";
-    menuitem.setAttribute("type", "checkbox");
-    menuitem.setAttribute("command", commandId);
-    menuitem.setAttribute("tooltiptext", aUrl);
-
-    let labelNode = document.createElement("label");
-    labelNode.className = "plain dbg-stackframe-menuitem-title";
-    labelNode.setAttribute("value", aTitle);
-    labelNode.setAttribute("flex", "1");
-
-    let descriptionNode = document.createElement("label");
-    descriptionNode.className = "plain dbg-stackframe-menuitem-details";
-    descriptionNode.setAttribute("value", frameDescription);
-
-    menuitem.appendChild(labelNode);
-    menuitem.appendChild(descriptionNode);
-
-    this._commandset.appendChild(command);
-    this._menupopup.appendChild(menuitem);
-
-    return {
-      command: command,
-      menuitem: menuitem
-    };
-  },
-
-  /**
    * Function called each time a stack frame item is removed.
    *
    * @param object aItem
    *        The corresponding item.
    */
   _onStackframeRemoved: function(aItem) {
     dumpn("Finalizing stackframe item: " + aItem);
 
-    // Destroy the context menu item for the stack frame.
-    let contextItem = aItem.attachment.popup;
-    contextItem.command.remove();
-    contextItem.menuitem.remove();
+    // Remove the mirrored item in the classic list.
+    let depth = aItem.attachment.depth;
+    this._mirror.remove(this._mirror.getItemForAttachment(e => e.depth == depth));
 
     // Forget the previously blackboxed stack frame url.
     this._prevBlackBoxedUrl = null;
   },
 
   /**
    * The select listener for the stackframes container.
    */
   _onSelect: function(e) {
     let stackframeItem = this.selectedItem;
     if (stackframeItem) {
       // The container is not empty and an actual item was selected.
-      DebuggerController.StackFrames.selectFrame(stackframeItem.attachment.depth);
+      let depth = stackframeItem.attachment.depth;
+      DebuggerController.StackFrames.selectFrame(depth);
 
-      // Update the context menu to show the currently selected stackframe item
-      // as a checked entry.
-      for (let otherItem of this) {
-        if (otherItem != stackframeItem) {
-          otherItem.attachment.popup.menuitem.removeAttribute("checked");
-        } else {
-          otherItem.attachment.popup.menuitem.setAttribute("checked", "");
-        }
-      }
+      // Mirror the selected item in the classic list.
+      this.suppressSelectionEvents = true;
+      this._mirror.selectedItem = e => e.attachment.depth == depth;
+      this.suppressSelectionEvents = false;
     }
   },
 
   /**
    * The scroll listener for the stackframes container.
    */
   _onScroll: function() {
     // Update the stackframes container only if we have to.
@@ -668,21 +597,152 @@ StackFramesView.prototype = Heritage.ext
       list.ensureElementIsVisible(this.getItemAtIndex(CALL_STACK_PAGE_SIZE - 1).target);
       this.dirty = false;
 
       // Loads more stack frames from the debugger server cache.
       DebuggerController.StackFrames.addMoreFrames();
     }
   },
 
-  _commandset: null,
-  _menupopup: null,
+  _mirror: null,
   _prevBlackBoxedUrl: null
 });
 
+/*
+ * Functions handling the stackframes classic list UI.
+ * Controlled by the DebuggerView.StackFrames isntance.
+ */
+function StackFramesClassicListView() {
+  dumpn("StackFramesClassicListView was instantiated");
+
+  this._onSelect = this._onSelect.bind(this);
+}
+
+StackFramesClassicListView.prototype = Heritage.extend(WidgetMethods, {
+  /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function() {
+    dumpn("Initializing the StackFramesClassicListView");
+
+    this.widget = new SideMenuWidget(document.getElementById("callstack-list"), {
+      theme: "light"
+    });
+    this.widget.addEventListener("select", this._onSelect, false);
+
+    this.emptyText = L10N.getStr("noStackFramesText");
+    this.autoFocusOnFirstItem = false;
+    this.autoFocusOnSelection = false;
+
+    // This view's contents are also mirrored in a different container.
+    this._mirror = DebuggerView.StackFrames;
+
+    // Show an empty label by default.
+    this.empty();
+  },
+
+  /**
+   * Destruction function, called when the debugger is closed.
+   */
+  destroy: function() {
+    dumpn("Destroying the StackFramesClassicListView");
+
+    this.widget.removeEventListener("select", this._onSelect, false);
+  },
+
+  /**
+   * Adds a frame in this stackframes container.
+   *
+   * @param string aTitle
+   *        The frame title (function name).
+   * @param string aUrl
+   *        The frame source url.
+   * @param string aLine
+   *        The frame line number.
+   * @param number aDepth
+   *        The frame depth in the stack.
+   */
+  addFrame: function(aTitle, aUrl, aLine, aDepth) {
+    // Create the element node for the stack frame item.
+    let frameView = this._createFrameView.apply(this, arguments);
+
+    // Append a stack frame item to this container.
+    this.push([frameView, aUrl], {
+      attachment: {
+        depth: aDepth
+      }
+    });
+  },
+
+  /**
+   * Customization function for creating an item's UI.
+   *
+   * @param string aTitle
+   *        The frame title to be displayed in the list.
+   * @param string aUrl
+   *        The frame source url.
+   * @param string aLine
+   *        The frame line number.
+   * @param number aDepth
+   *        The frame depth in the stack.
+   * @return nsIDOMNode
+   *         The stack frame view.
+   */
+  _createFrameView: function(aTitle, aUrl, aLine, aDepth) {
+    let container = document.createElement("hbox");
+    container.id = "classic-stackframe-" + aDepth;
+    container.className = "dbg-classic-stackframe";
+    container.setAttribute("flex", "1");
+
+    let frameTitleNode = document.createElement("label");
+    frameTitleNode.className = "plain dbg-classic-stackframe-title";
+    frameTitleNode.setAttribute("value", aTitle);
+    frameTitleNode.setAttribute("crop", "center");
+
+    let frameDetailsNode = document.createElement("hbox");
+    frameDetailsNode.className = "plain dbg-classic-stackframe-details";
+
+    let frameUrlNode = document.createElement("label");
+    frameUrlNode.className = "plain dbg-classic-stackframe-details-url";
+    frameUrlNode.setAttribute("value", SourceUtils.getSourceLabel(aUrl));
+    frameUrlNode.setAttribute("crop", "center");
+    frameDetailsNode.appendChild(frameUrlNode);
+
+    let frameDetailsSeparator = document.createElement("label");
+    frameDetailsSeparator.className = "plain dbg-classic-stackframe-details-sep";
+    frameDetailsSeparator.setAttribute("value", SEARCH_LINE_FLAG);
+    frameDetailsNode.appendChild(frameDetailsSeparator);
+
+    let frameLineNode = document.createElement("label");
+    frameLineNode.className = "plain dbg-classic-stackframe-details-line";
+    frameLineNode.setAttribute("value", aLine);
+    frameDetailsNode.appendChild(frameLineNode);
+
+    container.appendChild(frameTitleNode);
+    container.appendChild(frameDetailsNode);
+
+    return container;
+  },
+
+  /**
+   * The select listener for the stackframes container.
+   */
+  _onSelect: function(e) {
+    let stackframeItem = this.selectedItem;
+    if (stackframeItem) {
+      // The container is not empty and an actual item was selected.
+      // Mirror the selected item in the breadcrumbs list.
+      let depth = stackframeItem.attachment.depth;
+      this._mirror.selectedItem = e => e.attachment.depth == depth;
+    }
+  },
+
+  _mirror: null
+});
+
 /**
  * Functions handling the filtering UI.
  */
 function FilterView() {
   dumpn("FilterView was instantiated");
 
   this._onClick = this._onClick.bind(this);
   this._onInput = this._onInput.bind(this);
@@ -1528,8 +1588,9 @@ FilteredFunctionsView.prototype = Herita
  */
 DebuggerView.Toolbar = new ToolbarView();
 DebuggerView.Options = new OptionsView();
 DebuggerView.Filtering = new FilterView();
 DebuggerView.FilteredSources = new FilteredSourcesView();
 DebuggerView.FilteredFunctions = new FilteredFunctionsView();
 DebuggerView.ChromeGlobals = new ChromeGlobalsView();
 DebuggerView.StackFrames = new StackFramesView();
+DebuggerView.StackFramesClassicList = new StackFramesClassicListView();
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -4,18 +4,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
 const SOURCE_URL_DEFAULT_MAX_LENGTH = 64; // chars
 const STACK_FRAMES_SOURCE_URL_MAX_LENGTH = 15; // chars
 const STACK_FRAMES_SOURCE_URL_TRIM_SECTION = "center";
-const STACK_FRAMES_POPUP_SOURCE_URL_MAX_LENGTH = 32; // chars
-const STACK_FRAMES_POPUP_SOURCE_URL_TRIM_SECTION = "center";
 const STACK_FRAMES_SCROLL_DELAY = 100; // ms
 const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
 const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start";
 const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X = 7; // px
 const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y = -3; // px
 const RESULTS_PANEL_POPUP_POSITION = "before_end";
 const RESULTS_PANEL_MAX_RESULTS = 10;
 const FILE_SEARCH_ACTION_MAX_DELAY = 300; // ms
@@ -54,16 +52,17 @@ let DebuggerView = {
     this._initializePanes();
     this.Toolbar.initialize();
     this.Options.initialize();
     this.Filtering.initialize();
     this.FilteredSources.initialize();
     this.FilteredFunctions.initialize();
     this.ChromeGlobals.initialize();
     this.StackFrames.initialize();
+    this.StackFramesClassicList.initialize();
     this.Sources.initialize();
     this.VariableBubble.initialize();
     this.WatchExpressions.initialize();
     this.EventListeners.initialize();
     this.GlobalSearch.initialize();
     this._initializeVariablesView();
     this._initializeEditor(deferred.resolve);
 
@@ -88,16 +87,17 @@ let DebuggerView = {
 
     this.Toolbar.destroy();
     this.Options.destroy();
     this.Filtering.destroy();
     this.FilteredSources.destroy();
     this.FilteredFunctions.destroy();
     this.ChromeGlobals.destroy();
     this.StackFrames.destroy();
+    this.StackFramesClassicList.destroy();
     this.Sources.destroy();
     this.VariableBubble.destroy();
     this.WatchExpressions.destroy();
     this.EventListeners.destroy();
     this.GlobalSearch.destroy();
     this._destroyPanes();
     this._destroyEditor(deferred.resolve);
 
@@ -526,17 +526,17 @@ let DebuggerView = {
    *        A function to invoke when the toggle finishes.
    */
   showInstrumentsPane: function(aCallback) {
     DebuggerView.toggleInstrumentsPane({
       visible: true,
       animated: true,
       delayed: true,
       callback: aCallback
-    }, 0);
+    });
   },
 
   /**
    * Handles a tab selection event on the instruments pane.
    */
   _onInstrumentsPaneTabSelect: function() {
     if (this._instrumentsPane.selectedTab.id == "events-tab") {
       DebuggerController.Breakpoints.DOM.scheduleEventListenersFetch();
--- a/browser/devtools/debugger/debugger.css
+++ b/browser/devtools/debugger/debugger.css
@@ -1,31 +1,30 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* Sources search view */
+/* Side pane views */
 
-#globalsearch {
-  overflow: auto;
-}
-
-/* Instruments pane view (watch expressions, variables, events...) */
-
+#sources-pane > tabpanels > tabpanel,
 #instruments-pane > tabpanels > tabpanel {
   -moz-box-orient: vertical;
 }
 
 #expressions {
   overflow-x: hidden;
   overflow-y: auto;
 }
 
+#globalsearch {
+  overflow: auto;
+}
+
 /* Toolbar controls */
 
 .devtools-toolbarbutton:not([label]) > .toolbarbutton-text {
   display: none;
 }
 
 /* Horizontal vs. vertical layout */
 
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -316,33 +316,45 @@
                      class="devtools-option-toolbarbutton"
                      tooltiptext="&debuggerUI.optsButton.tooltip;"
                      popup="debuggerPrefsContextMenu"
                      tabindex="0"/>
     </toolbar>
     <scrollbox id="globalsearch" orient="vertical" hidden="true"/>
     <splitter class="devtools-horizontal-splitter" hidden="true"/>
     <hbox id="debugger-widgets" flex="1">
-      <vbox id="sources-pane">
-        <vbox id="sources" flex="1"/>
-        <toolbar id="sources-toolbar" class="devtools-toolbar">
-          <hbox id="sources-controls">
-            <toolbarbutton id="black-box"
-                           tooltiptext="&debuggerUI.sources.blackBoxTooltip;"
-                           command="blackBoxCommand"
-                           class="devtools-toolbarbutton"/>
-            <toolbarbutton id="pretty-print"
-                           label="{}"
-                           tooltiptext="&debuggerUI.sources.prettyPrint;"
-                           class="devtools-toolbarbutton devtools-monospace"
-                           command="prettyPrintCommand"
-                           hidden="true"/>
-          </hbox>
-        </toolbar>
-      </vbox>
+      <tabbox id="sources-pane"
+              class="devtools-sidebar-tabs">
+        <tabs>
+          <tab id="sources-tab" label="&debuggerUI.tabs.sources;"/>
+          <tab id="callstack-tab" label="&debuggerUI.tabs.callstack;"/>
+        </tabs>
+        <tabpanels flex="1">
+          <tabpanel id="sources-tabpanel">
+            <vbox id="sources" flex="1"/>
+            <toolbar id="sources-toolbar" class="devtools-toolbar">
+              <hbox id="sources-controls">
+                <toolbarbutton id="black-box"
+                               tooltiptext="&debuggerUI.sources.blackBoxTooltip;"
+                               command="blackBoxCommand"
+                               class="devtools-toolbarbutton"/>
+                <toolbarbutton id="pretty-print"
+                               label="{}"
+                               tooltiptext="&debuggerUI.sources.prettyPrint;"
+                               class="devtools-toolbarbutton devtools-monospace"
+                               command="prettyPrintCommand"
+                               hidden="true"/>
+              </hbox>
+            </toolbar>
+          </tabpanel>
+          <tabpanel id="callstack-tabpanel">
+            <vbox id="callstack-list" flex="1"/>
+          </tabpanel>
+        </tabpanels>
+      </tabbox>
       <splitter id="sources-and-editor-splitter"
                 class="devtools-side-splitter"/>
       <deck id="editor-deck" flex="4">
         <vbox id="editor"/>
         <vbox id="black-boxed-message" align="center" pack="center">
           <label id="black-boxed-message-label">
             &debuggerUI.blackBoxMessage.label;
           </label>
@@ -362,22 +374,22 @@
       <tabbox id="instruments-pane"
               class="devtools-sidebar-tabs"
               hidden="true">
         <tabs>
           <tab id="variables-tab" label="&debuggerUI.tabs.variables;"/>
           <tab id="events-tab" label="&debuggerUI.tabs.events;"/>
         </tabs>
         <tabpanels flex="1">
-          <tabpanel id="variables-tabpanel">
+          <tabpanel id="variables-tabpanel" class="theme-body">
             <vbox id="expressions"/>
             <splitter class="devtools-horizontal-splitter"/>
             <vbox id="variables" flex="1"/>
           </tabpanel>
-          <tabpanel id="events-tabpanel">
+          <tabpanel id="events-tabpanel" class="theme-body">
             <vbox id="event-listeners" flex="1"/>
           </tabpanel>
         </tabpanels>
       </tabbox>
       <splitter id="vertical-layout-splitter"
                 class="devtools-horizontal-splitter"/>
       <hbox id="vertical-layout-panes-container">
         <splitter id="sources-and-instruments-splitter"
--- a/browser/devtools/debugger/test/browser_dbg_breadcrumbs-access.js
+++ b/browser/devtools/debugger/test/browser_dbg_breadcrumbs-access.js
@@ -43,44 +43,50 @@ function test() {
 
   function focusCurrentStackFrame() {
     EventUtils.sendMouseEvent({ type: "mousedown" },
       gFrames.selectedItem.target,
       gDebugger);
   }
 
   function checkNavigationWhileFocused() {
-    let deferred = promise.defer();
+    return Task.spawn(function() {
+      yield promise.all([
+        waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+        EventUtils.sendKey("UP", gDebugger)
+      ]);
+      checkState({ frame: 2, source: 1, line: 6 });
 
-    EventUtils.sendKey("UP", gDebugger);
-    checkState({ frame: 2, source: 1, line: 6 });
-
-    waitForSourceAndCaret(gPanel, "-01.js", 5).then(() => {
+      yield promise.all([
+        waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+        waitForSourceAndCaret(gPanel, "-01.js", 5),
+        EventUtils.sendKey("UP", gDebugger)
+      ]);
       checkState({ frame: 1, source: 0, line: 5 });
 
-      EventUtils.sendKey("UP", gDebugger);
+      yield promise.all([
+        waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+        EventUtils.sendKey("UP", gDebugger)
+      ]);
       checkState({ frame: 0, source: 0, line: 5 });
 
-      waitForSourceAndCaret(gPanel, "-02.js", 6).then(() => {
-        checkState({ frame: 3, source: 1, line: 6 });
+      yield promise.all([
+        waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+        waitForSourceAndCaret(gPanel, "-02.js", 6),
+        EventUtils.sendKey("END", gDebugger)
+      ]);
+      checkState({ frame: 3, source: 1, line: 6 });
 
-        waitForSourceAndCaret(gPanel, "-01.js", 5).then(() => {
-          checkState({ frame: 0, source: 0, line: 5 });
-          deferred.resolve();
-        });
-
+      yield promise.all([
+        waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+        waitForSourceAndCaret(gPanel, "-01.js", 5),
         EventUtils.sendKey("HOME", gDebugger)
-      });
-
-      EventUtils.sendKey("END", gDebugger)
+      ]);
+      checkState({ frame: 0, source: 0, line: 5 });
     });
-
-    EventUtils.sendKey("UP", gDebugger)
-
-    return deferred.promise;
   }
 
   function checkState({ frame, source, line }) {
     is(gFrames.selectedIndex, frame,
       "The currently selected stackframe is incorrect.");
     is(gSources.selectedIndex, source,
       "The currently selected source is incorrect.");
     ok(isCaretPos(gPanel, line),
--- a/browser/devtools/debugger/test/browser_dbg_location-changes-01-simple.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes-01-simple.js
@@ -41,17 +41,17 @@ function performTest() {
     "There should be a selected source value.");
   isnot(gEditor.getText().length, 0,
     "The source editor should have some text displayed.");
   isnot(gEditor.getText(), gDebugger.L10N.getStr("loadingText"),
     "The source editor text should not be 'Loading...'");
 
   is(gSources.widget.getAttribute("label"), "doc_recursion-stack.html",
     "The sources widget should have a correct label attribute.");
-  is(gSources.widget.getAttribute("tooltiptext"), "example.com test",
+  is(gSources.widget.getAttribute("tooltiptext"), "http://example.com",
     "The sources widget should have a correct tooltip text attribute.");
 
   is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-notice-container").length, 0,
     "The sources widget should not display any notice at this point (1).");
   is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-notice").length, 0,
     "The sources widget should not display any notice at this point (2).");
   is(gDebugger.document.querySelector("#sources .side-menu-widget-empty-notice > label"), null,
     "The sources widget should not display a notice at this point (3).");
--- a/browser/devtools/debugger/test/browser_dbg_location-changes-03-new.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes-03-new.js
@@ -43,17 +43,17 @@ function testLocationChange() {
       "There should be a selected source value.");
     isnot(gEditor.getText().length, 0,
       "The source editor should have some text displayed.");
     is(gEditor.getText(), gDebugger.L10N.getStr("loadingText"),
       "The source editor text should not be 'Loading...'");
 
     is(gSources.widget.getAttribute("label"), "doc_inline-debugger-statement.html",
       "The sources widget should have a correct label attribute.");
-    is(gSources.widget.getAttribute("tooltiptext"), "example.com test",
+    is(gSources.widget.getAttribute("tooltiptext"), "http://example.com",
       "The sources widget should have a correct tooltip text attribute.");
 
     is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-notice-container").length, 0,
       "The sources widget should not display any notice at this point (1).");
     is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-notice").length, 0,
       "The sources widget should not display any notice at this point (2).");
     is(gDebugger.document.querySelector("#sources .side-menu-widget-empty-notice > label"), null,
       "The sources widget should not display a notice at this point (3).");
--- a/browser/devtools/debugger/test/browser_dbg_stack-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-01.js
@@ -3,39 +3,43 @@
 
 /**
  * Test that stackframes are added when debugger is paused.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
 
 let gTab, gDebuggee, gPanel, gDebugger;
-let gFrames;
+let gFrames, gClassicFrames;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
+    gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
 
     waitForSourceAndCaretAndScopes(gPanel, ".html", 14).then(performTest);
     gDebuggee.simpleCall();
   });
 }
 
 function performTest() {
   is(gDebugger.gThreadClient.state, "paused",
     "Should only be getting stack frames while paused.");
   is(gFrames.itemCount, 1,
     "Should have only one frame.");
+  is(gClassicFrames.itemCount, 1,
+    "Should also have only one frame in the mirrored view.");
 
   resumeDebuggerThenCloseAndFinish(gPanel);
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gFrames = null;
+  gClassicFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_stack-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-02.js
@@ -3,81 +3,109 @@
 
 /**
  * Test that stackframes are added when debugger is paused in eval calls.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
 
 let gTab, gDebuggee, gPanel, gDebugger;
-let gFrames;
+let gFrames, gClassicFrames;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
+    gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
 
     waitForSourceAndCaretAndScopes(gPanel, ".html", 18).then(performTest);
     gDebuggee.evalCall();
   });
 }
 
 function performTest() {
   is(gDebugger.gThreadClient.state, "paused",
     "Should only be getting stack frames while paused.");
   is(gFrames.itemCount, 2,
     "Should have two frames.");
+  is(gClassicFrames.itemCount, 2,
+    "Should also have only two in the mirrored view.");
 
   is(gFrames.getItemAtIndex(0).value,
     "evalCall", "Oldest frame name should be correct.");
   is(gFrames.getItemAtIndex(0).description,
     TAB_URL, "Oldest frame url should be correct.");
+  is(gClassicFrames.getItemAtIndex(0).value,
+    TAB_URL, "Oldest frame name is mirrored correctly.");
 
   is(gFrames.getItemAtIndex(1).value,
     "(eval)", "Newest frame name should be correct.");
   is(gFrames.getItemAtIndex(1).description,
     TAB_URL, "Newest frame url should be correct.");
+  is(gClassicFrames.getItemAtIndex(1).value,
+    TAB_URL, "Newest frame name is mirrored correctly.");
 
   is(gFrames.selectedIndex, 1,
     "Newest frame should be selected by default.");
+  is(gClassicFrames.selectedIndex, 0,
+    "Newest frame should be selected by default in the mirrored view.");
+
   isnot(gFrames.selectedIndex, 0,
     "Oldest frame should not be selected.");
+  isnot(gClassicFrames.selectedIndex, 1,
+    "Oldest frame should not be selected in the mirrored view.");
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     gFrames.getItemAtIndex(0).target,
     gDebugger);
 
   isnot(gFrames.selectedIndex, 1,
     "Newest frame should not be selected after click.");
+  isnot(gClassicFrames.selectedIndex, 0,
+    "Newest frame in the mirrored view should not be selected.");
+
   is(gFrames.selectedIndex, 0,
     "Oldest frame should be selected after click.");
+  is(gClassicFrames.selectedIndex, 1,
+    "Oldest frame in the mirrored view should be selected.");
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     gFrames.getItemAtIndex(1).target.querySelector(".dbg-stackframe-title"),
     gDebugger);
 
   is(gFrames.selectedIndex, 1,
     "Newest frame should be selected after click inside the newest frame.");
+  is(gClassicFrames.selectedIndex, 0,
+    "Newest frame in the mirrored view should be selected.");
+
   isnot(gFrames.selectedIndex, 0,
     "Oldest frame should not be selected after click inside the newest frame.");
+  isnot(gClassicFrames.selectedIndex, 1,
+    "Oldest frame in the mirrored view should not be selected.");
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     gFrames.getItemAtIndex(0).target.querySelector(".dbg-stackframe-details"),
     gDebugger);
 
   isnot(gFrames.selectedIndex, 1,
     "Newest frame should not be selected after click inside the oldest frame.");
+  isnot(gClassicFrames.selectedIndex, 0,
+    "Newest frame in the mirrored view should not be selected.");
+
   is(gFrames.selectedIndex, 0,
     "Oldest frame should be selected after click inside the oldest frame.");
+  is(gClassicFrames.selectedIndex, 1,
+    "Oldest frame in the mirrored view should be selected.");
 
   resumeDebuggerThenCloseAndFinish(gPanel);
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gFrames = null;
+  gClassicFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_stack-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-03.js
@@ -3,46 +3,53 @@
 
 /**
  * Test that stackframes are scrollable.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
 
 let gTab, gDebuggee, gPanel, gDebugger;
-let gFrames, gFramesScrollingInterval;
+let gFrames, gClassicFrames, gFramesScrollingInterval;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
+    gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
 
     waitForSourceAndCaretAndScopes(gPanel, ".html", 26).then(performTest);
 
     gDebuggee.gRecurseLimit = (gDebugger.gCallStackPageSize * 2) + 1;
     gDebuggee.recurse();
   });
 }
 
 function performTest() {
   is(gDebugger.gThreadClient.state, "paused",
     "Should only be getting stack frames while paused.");
   is(gFrames.itemCount, gDebugger.gCallStackPageSize,
     "Should have only the max limit of frames.");
+  is(gClassicFrames.itemCount, gDebugger.gCallStackPageSize,
+    "Should have only the max limit of frames in the mirrored view as well.")
 
   gDebugger.gThreadClient.addOneTimeListener("framesadded", () => {
     is(gFrames.itemCount, gDebugger.gCallStackPageSize * 2,
       "Should now have twice the max limit of frames.");
+    is(gClassicFrames.itemCount, gDebugger.gCallStackPageSize * 2,
+      "Should now have twice the max limit of frames in the mirrored view as well.");
 
     gDebugger.gThreadClient.addOneTimeListener("framesadded", () => {
       is(gFrames.itemCount, gDebuggee.gRecurseLimit,
         "Should have reached the recurse limit.");
+      is(gClassicFrames.itemCount, gDebuggee.gRecurseLimit,
+        "Should have reached the recurse limit in the mirrored view as well.");
 
       gDebugger.gThreadClient.resume(() => {
         window.clearInterval(gFramesScrollingInterval);
         closeDebuggerAndFinish(gPanel);
       });
     });
   });
 
@@ -55,9 +62,10 @@ registerCleanupFunction(function() {
   window.clearInterval(gFramesScrollingInterval);
   gFramesScrollingInterval = null;
 
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gFrames = null;
+  gClassicFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_stack-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-04.js
@@ -3,46 +3,52 @@
 
 /**
  * Test that stackframes are cleared after resume.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
 
 let gTab, gDebuggee, gPanel, gDebugger;
-let gFrames;
+let gFrames, gClassicFrames;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
+    gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
 
     waitForSourceAndCaretAndScopes(gPanel, ".html", 18).then(performTest);
     gDebuggee.evalCall();
   });
 }
 
 function performTest() {
   is(gDebugger.gThreadClient.state, "paused",
     "Should only be getting stack frames while paused.");
   is(gFrames.itemCount, 2,
     "Should have two frames.");
+  is(gClassicFrames.itemCount, 2,
+    "Should also have two frames in the mirrored view.");
 
   gDebugger.once(gDebugger.EVENTS.AFTER_FRAMES_CLEARED, () => {
     is(gFrames.itemCount, 0,
       "Should have no frames after resume.");
+    is(gClassicFrames.itemCount, 0,
+      "Should also have no frames in the mirrored view after resume.");
 
     closeDebuggerAndFinish(gPanel);
   }, true);
 
   gDebugger.gThreadClient.resume();
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gFrames = null;
+  gClassicFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_stack-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-05.js
@@ -4,27 +4,28 @@
 /**
  * Test that switching between stack frames properly sets the current debugger
  * location in the source editor.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 let gTab, gDebuggee, gPanel, gDebugger;
-let gEditor, gSources, gFrames;
+let gEditor, gSources, gFrames, gClassicFrames;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gFrames = gDebugger.DebuggerView.StackFrames;
+    gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6)
       .then(initialChecks)
       .then(testNewestTwoFrames)
       .then(testOldestTwoFrames)
       .then(testAfterResume)
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
@@ -35,39 +36,45 @@ function test() {
   });
 }
 
 function initialChecks() {
   is(gDebugger.gThreadClient.state, "paused",
     "Should only be getting stack frames while paused.");
   is(gFrames.itemCount, 4,
     "Should have four frames.");
+  is(gClassicFrames.itemCount, 4,
+    "Should also have four frames in the mirrored view.");
 }
 
 function testNewestTwoFrames() {
   let deferred = promise.defer();
 
   is(gFrames.selectedIndex, 3,
     "Newest frame should be selected by default.");
+  is(gClassicFrames.selectedIndex, 0,
+    "Newest frame should be selected in the mirrored view as well.");
   is(gSources.selectedIndex, 1,
     "The second source is selected in the widget.");
   ok(isCaretPos(gPanel, 6),
     "Editor caret location is correct.");
 
   // The editor's debug location takes a tick to update.
   executeSoon(() => {
     is(gEditor.getDebugLocation(), 5,
       "Editor debug location is correct.");
 
     EventUtils.sendMouseEvent({ type: "mousedown" },
       gFrames.getItemAtIndex(2).target,
       gDebugger);
 
     is(gFrames.selectedIndex, 2,
       "Third frame should be selected after click.");
+    is(gClassicFrames.selectedIndex, 1,
+      "Third frame should be selected in the mirrored view as well.");
     is(gSources.selectedIndex, 1,
       "The second source is still selected in the widget.");
     ok(isCaretPos(gPanel, 6),
       "Editor caret location is correct.");
 
     // The editor's debug location takes a tick to update.
     executeSoon(() => {
       is(gEditor.getDebugLocation(), 5,
@@ -78,63 +85,69 @@ function testNewestTwoFrames() {
   });
 
   return deferred.promise;
 }
 
 function testOldestTwoFrames() {
   let deferred = promise.defer();
 
-  waitForSourceAndCaret(gPanel, "-01.js", 5).then(() => {
+  waitForSourceAndCaret(gPanel, "-01.js", 5).then(waitForTick).then(() => {
     is(gFrames.selectedIndex, 1,
       "Second frame should be selected after click.");
+    is(gClassicFrames.selectedIndex, 2,
+      "Second frame should be selected in the mirrored view as well.");
     is(gSources.selectedIndex, 0,
       "The first source is now selected in the widget.");
     ok(isCaretPos(gPanel, 5),
       "Editor caret location is correct.");
 
     // The editor's debug location takes a tick to update.
     executeSoon(() => {
       is(gEditor.getDebugLocation(), 4,
         "Editor debug location is correct.");
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         gFrames.getItemAtIndex(0).target,
         gDebugger);
 
       is(gFrames.selectedIndex, 0,
         "Oldest frame should be selected after click.");
+      is(gClassicFrames.selectedIndex, 3,
+        "Oldest frame should be selected in the mirrored view as well.");
       is(gSources.selectedIndex, 0,
         "The first source is still selected in the widget.");
       ok(isCaretPos(gPanel, 5),
         "Editor caret location is correct.");
 
       // The editor's debug location takes a tick to update.
       executeSoon(() => {
         is(gEditor.getDebugLocation(), 4,
           "Editor debug location is correct.");
 
         deferred.resolve();
       });
     });
   });
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    gFrames.getItemAtIndex(1).target,
+    gDebugger.document.querySelector("#stackframe-2"),
     gDebugger);
 
   return deferred.promise;
 }
 
 function testAfterResume() {
   let deferred = promise.defer();
 
   gDebugger.once(gDebugger.EVENTS.AFTER_FRAMES_CLEARED, () => {
     is(gFrames.itemCount, 0,
       "Should have no frames after resume.");
+    is(gClassicFrames.itemCount, 0,
+      "Should have no frames in the mirrored view as well.");
     ok(isCaretPos(gPanel, 5),
       "Editor caret location is correct after resume.");
     is(gEditor.getDebugLocation(), null,
       "Editor debug location is correct after resume.");
 
     deferred.resolve();
   }, true);
 
@@ -145,10 +158,11 @@ function testAfterResume() {
 
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gFrames = null;
+  gClassicFrames = null;
 });
 
--- a/browser/devtools/debugger/test/browser_dbg_stack-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-06.js
@@ -4,62 +4,85 @@
 /**
  * Make sure that selecting a stack frame loads the right source in the editor
  * pane and highlights the proper line.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 let gTab, gDebuggee, gPanel, gDebugger;
-let gEditor, gSources, gFrames;
+let gEditor, gSources, gFrames, gClassicFrames;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gFrames = gDebugger.DebuggerView.StackFrames;
+    gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6).then(performTest);
     gDebuggee.firstCall();
   });
 }
 
 function performTest() {
   is(gFrames.selectedIndex, 3,
     "Newest frame should be selected by default.");
+  is(gClassicFrames.selectedIndex, 0,
+    "Newest frame should also be selected in the mirrored view.");
   is(gSources.selectedIndex, 1,
     "The second source is selected in the widget.");
   is(gEditor.getText().search(/firstCall/), -1,
     "The first source is not displayed.");
   is(gEditor.getText().search(/debugger/), 172,
-    "The second source is displayed.")
+    "The second source is displayed.");
 
-  waitForSourceAndCaret(gPanel, "-01.js", 6).then(() => {
+  waitForSourceAndCaret(gPanel, "-01.js", 6).then(waitForTick).then(() => {
     is(gFrames.selectedIndex, 0,
       "Oldest frame should be selected after click.");
+    is(gClassicFrames.selectedIndex, 3,
+      "Oldest frame should also be selected in the mirrored view.");
     is(gSources.selectedIndex, 0,
       "The first source is now selected in the widget.");
     is(gEditor.getText().search(/firstCall/), 118,
       "The first source is displayed.");
     is(gEditor.getText().search(/debugger/), -1,
       "The second source is not displayed.");
 
-    resumeDebuggerThenCloseAndFinish(gPanel);
+    waitForSourceAndCaret(gPanel, "-02.js", 6).then(waitForTick).then(() => {
+      is(gFrames.selectedIndex, 3,
+        "Newest frame should be selected again after click.");
+      is(gClassicFrames.selectedIndex, 0,
+        "Newest frame should also be selected again in the mirrored view.");
+      is(gSources.selectedIndex, 1,
+        "The second source is selected in the widget.");
+      is(gEditor.getText().search(/firstCall/), -1,
+        "The first source is not displayed.");
+      is(gEditor.getText().search(/debugger/), 172,
+        "The second source is displayed.");
+
+      resumeDebuggerThenCloseAndFinish(gPanel);
+    });
+
+    EventUtils.sendMouseEvent({ type: "mousedown" },
+      gDebugger.document.querySelector("#classic-stackframe-0"),
+      gDebugger);
   });
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     gDebugger.document.querySelector("#stackframe-3"),
     gDebugger);
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gFrames = null;
+  gClassicFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_stack-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-07.js
@@ -5,27 +5,28 @@
  * Make sure that after selecting a different stack frame, resuming reselects
  * the topmost stackframe, loads the right source in the editor pane and
  * highlights the proper line.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
 let gTab, gDebuggee, gPanel, gDebugger;
-let gEditor, gSources, gFrames, gToolbar;
+let gEditor, gSources, gFrames, gClassicFrames, gToolbar;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gFrames = gDebugger.DebuggerView.StackFrames;
+    gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
     gToolbar = gDebugger.DebuggerView.Toolbar;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6).then(performTest);
     gDebuggee.firstCall();
   });
 }
 
 function performTest() {
@@ -48,23 +49,25 @@ function performTest() {
     yield performStep("StepOut");
     testTopFrame(2);
 
     yield resumeDebuggerThenCloseAndFinish(gPanel);
   });
 
   function selectBottomFrame() {
     let updated = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
-    gFrames.selectedIndex = 0;
+    gClassicFrames.selectedIndex = gClassicFrames.itemCount - 1;
     return updated.then(waitForTick);
   }
 
   function testBottomFrame(debugLocation) {
     is(gFrames.selectedIndex, 0,
       "Oldest frame should be selected after click.");
+    is(gClassicFrames.selectedIndex, gFrames.itemCount - 1,
+      "Oldest frame should also be selected in the mirrored view.");
     is(gSources.selectedIndex, 0,
       "The first source is now selected in the widget.");
     is(gEditor.getText().search(/firstCall/), 118,
       "The first source is displayed.");
     is(gEditor.getText().search(/debugger/), -1,
       "The second source is not displayed.");
 
     is(gEditor.getDebugLocation(), debugLocation,
@@ -77,16 +80,18 @@ function performTest() {
     let updated = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
     gToolbar["_on" + type + "Pressed"]();
     return updated.then(waitForTick);
   }
 
   function testTopFrame(frameIndex) {
     is(gFrames.selectedIndex, frameIndex,
       "Topmost frame should be selected after click.");
+    is(gClassicFrames.selectedIndex, gFrames.itemCount - frameIndex - 1,
+      "Topmost frame should also be selected in the mirrored view.");
     is(gSources.selectedIndex, 1,
       "The second source is now selected in the widget.");
     is(gEditor.getText().search(/firstCall/), -1,
       "The second source is displayed.");
     is(gEditor.getText().search(/debugger/), 172,
       "The first source is not displayed.");
   }
 }
@@ -94,10 +99,11 @@ function performTest() {
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gFrames = null;
+  gClassicFrames = null;
   gToolbar = null;
 });
--- a/browser/devtools/shadereditor/panel.js
+++ b/browser/devtools/shadereditor/panel.js
@@ -1,17 +1,17 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { Cc, Ci, Cu, Cr } = require("chrome");
-const promise = require("sdk/core/promise");
+const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 const EventEmitter = require("devtools/shared/event-emitter");
 const { WebGLFront } = require("devtools/server/actors/webgl");
 
 function ShaderEditorPanel(iframeWindow, toolbox) {
   this.panelWin = iframeWindow;
   this._toolbox = toolbox;
   this._destroyer = null;
 
--- a/browser/devtools/shadereditor/shadereditor.js
+++ b/browser/devtools/shadereditor/shadereditor.js
@@ -8,32 +8,38 @@ const { classes: Cc, interfaces: Ci, uti
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/devtools/Loader.jsm");
 Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
-const promise = require("sdk/core/promise");
+const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 const EventEmitter = require("devtools/shared/event-emitter");
 const {Tooltip} = require("devtools/shared/widgets/Tooltip");
 const Editor = require("devtools/sourceeditor/editor");
 
 // The panel's window global is an EventEmitter firing the following events:
 const EVENTS = {
   // When new programs are received from the server.
   NEW_PROGRAM: "ShaderEditor:NewProgram",
   PROGRAMS_ADDED: "ShaderEditor:ProgramsAdded",
 
   // When the vertex and fragment sources were shown in the editor.
   SOURCES_SHOWN: "ShaderEditor:SourcesShown",
 
   // When a shader's source was edited and compiled via the editor.
-  SHADER_COMPILED: "ShaderEditor:ShaderCompiled"
+  SHADER_COMPILED: "ShaderEditor:ShaderCompiled",
+
+  // When the UI is reset from tab navigation
+  UI_RESET: "ShaderEditor:UIReset",
+
+  // When the editor's error markers are all removed
+  EDITOR_ERROR_MARKERS_REMOVED: "ShaderEditor:EditorCleaned"
 };
 
 const STRINGS_URI = "chrome://browser/locale/devtools/shadereditor.properties"
 const HIGHLIGHT_TINT = [1, 0, 0.25, 1]; // rgba
 const TYPING_MAX_DELAY = 500; // ms
 const SHADERS_AUTOGROW_ITEMS = 4;
 const GUTTER_ERROR_PANEL_OFFSET_X = 7; // px
 const GUTTER_ERROR_PANEL_DELAY = 100; // ms
@@ -109,25 +115,27 @@ let EventsHandler = {
   },
 
   /**
    * Called for each location change in the debugged tab.
    */
   _onTabNavigated: function(event) {
     switch (event) {
       case "will-navigate": {
-        // Make sure the backend is prepared to handle WebGL contexts.
-        gFront.setup({ reload: false });
+        Task.spawn(function() {
+          // Make sure the backend is prepared to handle WebGL contexts.
+          gFront.setup({ reload: false });
 
-        // Reset UI.
-        ShadersListView.empty();
-        ShadersEditorsView.setText({ vs: "", fs: "" });
-        $("#reload-notice").hidden = true;
-        $("#waiting-notice").hidden = false;
-        $("#content").hidden = true;
+          // Reset UI.
+          ShadersListView.empty();
+          $("#reload-notice").hidden = true;
+          $("#waiting-notice").hidden = false;
+          yield ShadersEditorsView.setText({ vs: "", fs: "" });
+          $("#content").hidden = true;
+        }).then(() => window.emit(EVENTS.UI_RESET));
         break;
       }
       case "navigate": {
         // Manually retrieve the list of program actors known to the server,
         // because the backend won't emit "program-linked" notifications
         // in the case of a bfcache navigation (since no new programs are
         // actually linked).
         gFront.getPrograms().then(this._onProgramsAdded);
@@ -267,23 +275,26 @@ let ShadersListView = Heritage.extend(Wi
     }
     function getSources([vertexShaderActor, fragmentShaderActor]) {
       return promise.all([
         vertexShaderActor.getText(),
         fragmentShaderActor.getText()
       ]);
     }
     function showSources([vertexShaderText, fragmentShaderText]) {
-      ShadersEditorsView.setText({
+      return ShadersEditorsView.setText({
         vs: vertexShaderText,
         fs: fragmentShaderText
       });
     }
 
-    getShaders().then(getSources).then(showSources).then(null, Cu.reportError);
+    getShaders()
+      .then(getSources)
+      .then(showSources)
+      .then(null, Cu.reportError);
   },
 
   /**
    * The check listener for the programs container.
    */
   _onProgramCheck: function({ detail: { checked }, target }) {
     let sourceItem = this.getItemForElement(target);
     let attachment = sourceItem.attachment;
@@ -346,41 +357,48 @@ let ShadersEditorsView = {
 
   /**
    * Sets the text displayed in the vertex and fragment shader editors.
    *
    * @param object sources
    *        An object containing the following properties
    *          - vs: the vertex shader source code
    *          - fs: the fragment shader source code
+   * @return object
+   *        A promise resolving upon completion of text setting.
    */
   setText: function(sources) {
+    let view = this;
     function setTextAndClearHistory(editor, text) {
       editor.setText(text);
       editor.clearHistory();
     }
 
-    this._toggleListeners("off");
-    this._getEditor("vs").then(e => setTextAndClearHistory(e, sources.vs));
-    this._getEditor("fs").then(e => setTextAndClearHistory(e, sources.fs));
-    this._toggleListeners("on");
-
-    window.emit(EVENTS.SOURCES_SHOWN, sources);
+    return Task.spawn(function() {
+      yield view._toggleListeners("off");
+      yield promise.all([
+        view._getEditor("vs").then(e => setTextAndClearHistory(e, sources.vs)),
+        view._getEditor("fs").then(e => setTextAndClearHistory(e, sources.fs))
+      ]);
+      yield view._toggleListeners("on");
+    }).then(() => window.emit(EVENTS.SOURCES_SHOWN, sources));
   },
 
   /**
    * Lazily initializes and returns a promise for an Editor instance.
    *
    * @param string type
    *        Specifies for which shader type should an editor be retrieved,
    *        either are "vs" for a vertex, or "fs" for a fragment shader.
+   * @return object
+   *        Returns a promise that resolves to an editor instance
    */
   _getEditor: function(type) {
     if ($("#content").hidden) {
-      return promise.reject(null);
+      return promise.reject(new Error("Shader Editor is still waiting for a WebGL context to be created."));
     }
     if (this._editorPromises.has(type)) {
       return this._editorPromises.get(type);
     }
 
     let deferred = promise.defer();
     this._editorPromises.set(type, deferred.promise);
 
@@ -394,24 +412,26 @@ let ShadersEditorsView = {
     return deferred.promise;
   },
 
   /**
    * Toggles all the event listeners for the editors either on or off.
    *
    * @param string flag
    *        Either "on" to enable the event listeners, "off" to disable them.
+   * @return object
+   *        A promise resolving upon completion of toggling the listeners.
    */
   _toggleListeners: function(flag) {
-    ["vs", "fs"].forEach(type => {
-      this._getEditor(type).then(editor => {
+    return promise.all(["vs", "fs"].map(type => {
+      return this._getEditor(type).then(editor => {
         editor[flag]("focus", this["_" + type + "Focused"]);
         editor[flag]("change", this["_" + type + "Changed"]);
       });
-    });
+    }));
   },
 
   /**
    * The focus listener for a source editor.
    *
    * @param string focused
    *        The corresponding shader type for the focused editor (e.g. "vs").
    * @param string focused
@@ -481,17 +501,17 @@ let ShadersEditorsView = {
       };
     }
     function discardInvalidMatches(e) {
       // Discard empty line and text matches.
       return e.lineMatch && e.textMatch;
     }
     function sanitizeValidMatches(e) {
       return {
-        // Drivers might yield retarded line numbers under some obscure
+        // Drivers might yield confusing line numbers under some obscure
         // circumstances. Don't throw the errors away in those cases,
         // just display them on the currently edited line.
         line: e.lineMatch[0] > lineCount ? currentLine : e.lineMatch[0] - 1,
         // Trim whitespace from the beginning and the end of the message,
         // and replace all other occurences of double spaces to a single space.
         text: e.textMatch[0].trim().replace(/\s{2,}/g, " ")
       };
     }
@@ -549,16 +569,17 @@ let ShadersEditorsView = {
   /**
    * Removes all the gutter markers and line classes from the editor.
    */
   _cleanEditor: function(type) {
     this._getEditor(type).then(editor => {
       editor.removeAllMarkers("errors");
       this._errors[type].forEach(e => editor.removeLineClass(e.line));
       this._errors[type].length = 0;
+      window.emit(EVENTS.EDITOR_ERROR_MARKERS_REMOVED);
     });
   },
 
   _errors: {
     vs: [],
     fs: []
   }
 };
--- a/browser/devtools/shadereditor/test/browser_se_bfcache.js
+++ b/browser/devtools/shadereditor/test/browser_se_bfcache.js
@@ -9,18 +9,17 @@ function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
   let { gFront, $, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
 
   let reloaded = reload(target);
   let firstProgram = yield once(gFront, "program-linked");
   yield reloaded;
 
   let navigated = navigate(target, MULTIPLE_CONTEXTS_URL);
-  let secondProgram = yield once(gFront, "program-linked");
-  let thirdProgram = yield once(gFront, "program-linked");
+  let [secondProgram, thirdProgram] = yield getPrograms(gFront, 2);
   yield navigated;
 
   let vsEditor = yield ShadersEditorsView._getEditor("vs");
   let fsEditor = yield ShadersEditorsView._getEditor("fs");
 
   yield navigateInHistory(target, "back", "will-navigate");
   yield once(panel.panelWin, EVENTS.PROGRAMS_ADDED);
   yield once(panel.panelWin, EVENTS.SOURCES_SHOWN);
@@ -51,14 +50,8 @@ function ifWebGLSupported() {
   is(vsEditor.getText().indexOf("gl_Position"), 100,
     "The vertex shader editor contains the correct text.");
   is(fsEditor.getText().indexOf("gl_FragColor"), 89,
     "The fragment shader editor contains the correct text.");
 
   yield teardown(panel);
   finish();
 }
-
-function once(aTarget, aEvent) {
-  let deferred = promise.defer();
-  aTarget.once(aEvent, deferred.resolve);
-  return deferred.promise;
-}
--- a/browser/devtools/shadereditor/test/browser_se_editors-contents.js
+++ b/browser/devtools/shadereditor/test/browser_se_editors-contents.js
@@ -3,24 +3,28 @@
 
 /**
  * Tests if the editors contain the correct text when a program
  * becomes available.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
-  let { gFront, ShadersEditorsView } = panel.panelWin;
+  let { gFront, ShadersEditorsView, EVENTS } = panel.panelWin;
 
   reload(target);
-  yield once(gFront, "program-linked");
+  yield promise.all([
+    once(gFront, "program-linked"),
+    once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+  ]);
 
   let vsEditor = yield ShadersEditorsView._getEditor("vs");
   let fsEditor = yield ShadersEditorsView._getEditor("fs");
 
+
   is(vsEditor.getText().indexOf("gl_Position"), 170,
     "The vertex shader editor contains the correct text.");
   is(fsEditor.getText().indexOf("gl_FragColor"), 97,
     "The fragment shader editor contains the correct text.");
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/shadereditor/test/browser_se_editors-error-gutter.js
+++ b/browser/devtools/shadereditor/test/browser_se_editors-error-gutter.js
@@ -6,65 +6,71 @@
  * when there's a shader compilation error.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
   let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
 
   reload(target);
-  yield once(gFront, "program-linked");
+  yield promise.all([
+    once(gFront, "program-linked"),
+    once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+  ]);
 
   let vsEditor = yield ShadersEditorsView._getEditor("vs");
   let fsEditor = yield ShadersEditorsView._getEditor("fs");
 
   vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
-  let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  let [, vertError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
   checkHasVertFirstError(true, vertError);
   checkHasVertSecondError(false, vertError);
   info("Error marks added in the vertex shader editor.");
 
   vsEditor.insertText(" ", { line: 1, ch: 0 });
+  yield once(panel.panelWin, EVENTS.EDITOR_ERROR_MARKERS_REMOVED);
   is(vsEditor.getText(1), "       precision lowp float;", "Typed space.");
   checkHasVertFirstError(false, vertError);
   checkHasVertSecondError(false, vertError);
   info("Error marks removed while typing in the vertex shader editor.");
 
-  let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  [, vertError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
   checkHasVertFirstError(true, vertError);
   checkHasVertSecondError(false, vertError);
   info("Error marks were re-added after recompiling the vertex shader.");
 
   fsEditor.replaceText("vec4", { line: 2, ch: 14 }, { line: 2, ch: 18 });
-  let fragError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  let [, fragError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
   checkHasVertFirstError(true, vertError);
   checkHasVertSecondError(false, vertError);
   checkHasFragError(true, fragError);
   info("Error marks added in the fragment shader editor.");
 
   fsEditor.insertText(" ", { line: 1, ch: 0 });
+  yield once(panel.panelWin, EVENTS.EDITOR_ERROR_MARKERS_REMOVED);
   is(fsEditor.getText(1), "       precision lowp float;", "Typed space.");
   checkHasVertFirstError(true, vertError);
   checkHasVertSecondError(false, vertError);
   checkHasFragError(false, fragError);
   info("Error marks removed while typing in the fragment shader editor.");
 
-  let fragError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  [, fragError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
   checkHasVertFirstError(true, vertError);
   checkHasVertSecondError(false, vertError);
   checkHasFragError(true, fragError);
   info("Error marks were re-added after recompiling the fragment shader.");
 
   vsEditor.replaceText("2", { line: 3, ch: 19 }, { line: 3, ch: 20 });
+  yield once(panel.panelWin, EVENTS.EDITOR_ERROR_MARKERS_REMOVED);
   checkHasVertFirstError(false, vertError);
   checkHasVertSecondError(false, vertError);
   checkHasFragError(true, fragError);
   info("Error marks removed while typing in the vertex shader editor again.");
 
-  let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  [, vertError] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
   checkHasVertFirstError(true, vertError);
   checkHasVertSecondError(true, vertError);
   checkHasFragError(true, fragError);
   info("Error marks were re-added after recompiling the fragment shader again.");
 
   yield teardown(panel);
   finish();
 
@@ -143,14 +149,8 @@ function ifWebGLSupported() {
         "The correct line was parsed.");
       is(parsed[0].messages.length, 1,
         "There is 1 parsed message.");
       ok(parsed[0].messages[0].contains("'constructor' : too many arguments"),
         "The correct message was parsed.");
     }
   }
 }
-
-function once(aTarget, aEvent) {
-  let deferred = promise.defer();
-  aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
-  return deferred.promise;
-}
--- a/browser/devtools/shadereditor/test/browser_se_editors-error-tooltip.js
+++ b/browser/devtools/shadereditor/test/browser_se_editors-error-tooltip.js
@@ -6,17 +6,20 @@
  * a shader compilation error.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
   let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
 
   reload(target);
-  yield once(gFront, "program-linked");
+  yield promise.all([
+    once(gFront, "program-linked"),
+    once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+  ]);
 
   let vsEditor = yield ShadersEditorsView._getEditor("vs");
   let fsEditor = yield ShadersEditorsView._getEditor("fs");
 
   vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
   yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
 
   // Synthesizing 'mouseenter' events doesn't work, hack around this by
@@ -46,14 +49,8 @@ function ifWebGLSupported() {
   ok(messages[0].textContent.contains("'constructor' : too many arguments"),
     "The first message contains the correct text.");
   ok(messages[1].textContent.contains("'assign' : cannot convert"),
     "The second message contains the correct text.");
 
   yield teardown(panel);
   finish();
 }
-
-function once(aTarget, aEvent) {
-  let deferred = promise.defer();
-  aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
-  return deferred.promise;
-}
--- a/browser/devtools/shadereditor/test/browser_se_navigation.js
+++ b/browser/devtools/shadereditor/test/browser_se_navigation.js
@@ -2,20 +2,23 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests target navigations are handled correctly in the UI.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
-  let { gFront, $, ShadersListView, ShadersEditorsView } = panel.panelWin;
+  let { gFront, $, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
 
   reload(target);
-  yield once(gFront, "program-linked");
+  yield promise.all([
+    once(gFront, "program-linked"),
+    once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+  ]);
 
   is($("#reload-notice").hidden, true,
     "The 'reload this page' notice should be hidden after linking.");
   is($("#waiting-notice").hidden, true,
     "The 'waiting for a WebGL context' notice should be visible after linking.");
   is($("#content").hidden, false,
     "The tool's content should not be hidden anymore.");
 
@@ -34,44 +37,43 @@ function ifWebGLSupported() {
   is(fsEditor.getText().indexOf("gl_FragColor"), 97,
     "The fragment shader editor contains the correct text.");
 
   let navigating = once(target, "will-navigate");
   let navigated = once(target, "will-navigate");
   navigate(target, "about:blank");
 
   yield navigating;
+  yield once(panel.panelWin, EVENTS.UI_RESET);
 
   is($("#reload-notice").hidden, true,
     "The 'reload this page' notice should be hidden while navigating.");
   is($("#waiting-notice").hidden, false,
     "The 'waiting for a WebGL context' notice should be visible while navigating.");
   is($("#content").hidden, true,
     "The tool's content should be hidden now that there's no WebGL content.");
 
   is(ShadersListView.itemCount, 0,
     "The shaders list should be empty.");
   is(ShadersListView.selectedItem, null,
     "The shaders list has no correct item.");
   is(ShadersListView.selectedIndex, -1,
     "The shaders list has a negative index.");
 
-  try {
-    yield ShadersEditorsView._getEditor("vs");
+  yield ShadersEditorsView._getEditor("vs").then(() => {
     ok(false, "The promise for a vertex shader editor should be rejected.");
-  } catch (e) {
+  }, () => {
     ok(true, "The vertex shader editors wasn't initialized.");
-  }
+  });
 
-  try {
-    yield ShadersEditorsView._getEditor("fs");
+  yield ShadersEditorsView._getEditor("fs").then(() => {
     ok(false, "The promise for a fragment shader editor should be rejected.");
-  } catch (e) {
+  }, () => {
     ok(true, "The fragment shader editors wasn't initialized.");
-  }
+  });
 
   yield navigated;
 
   is($("#reload-notice").hidden, true,
     "The 'reload this page' notice should still be hidden after navigating.");
   is($("#waiting-notice").hidden, false,
     "The 'waiting for a WebGL context' notice should still be visible after navigating.");
   is($("#content").hidden, true,
--- a/browser/devtools/shadereditor/test/browser_se_programs-blackbox-01.js
+++ b/browser/devtools/shadereditor/test/browser_se_programs-blackbox-01.js
@@ -9,18 +9,20 @@ function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(MULTIPLE_CONTEXTS_URL);
   let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
 
   once(panel.panelWin, EVENTS.SHADER_COMPILED).then(() => {
     ok(false, "No shaders should be publicly compiled during this test.");
   });
 
   reload(target);
-  let firstProgramActor = yield once(gFront, "program-linked");
-  let secondProgramActor = yield once(gFront, "program-linked");
+  let [firstProgramActor, secondProgramActor] = yield promise.all([
+    getPrograms(gFront, 2),
+    once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+  ]).then(([programs]) => programs);
 
   let vsEditor = yield ShadersEditorsView._getEditor("vs");
   let fsEditor = yield ShadersEditorsView._getEditor("fs");
 
   vsEditor.once("change", () => {
     ok(false, "The vertex shader source was unexpectedly changed.");
   });
   fsEditor.once("change", () => {
--- a/browser/devtools/shadereditor/test/browser_se_programs-blackbox-02.js
+++ b/browser/devtools/shadereditor/test/browser_se_programs-blackbox-02.js
@@ -6,18 +6,20 @@
  * overlapping geometry.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(BLENDED_GEOMETRY_CANVAS_URL);
   let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
 
   reload(target);
-  let firstProgramActor = yield once(gFront, "program-linked");
-  let secondProgramActor = yield once(gFront, "program-linked");
+  let [firstProgramActor, secondProgramActor] = yield promise.all([
+    getPrograms(gFront, 2),
+    once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+  ]).then(([programs]) => programs);
 
   yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 127, g: 127, b: 127, a: 255 }, true);
   yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 127, b: 127, a: 127 }, true);
   ok(true, "The canvas was correctly drawn.");
 
   getBlackBoxCheckbox(panel, 0).click();
 
   yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
@@ -54,14 +56,8 @@ function ifWebGLSupported() {
   yield teardown(panel);
   finish();
 }
 
 function getBlackBoxCheckbox(aPanel, aIndex) {
   return aPanel.panelWin.document.querySelectorAll(
     ".side-menu-widget-item-checkbox")[aIndex];
 }
-
-function once(aTarget, aEvent) {
-  let deferred = promise.defer();
-  aTarget.once(aEvent, deferred.resolve);
-  return deferred.promise;
-}
--- a/browser/devtools/shadereditor/test/browser_se_programs-cache.js
+++ b/browser/devtools/shadereditor/test/browser_se_programs-cache.js
@@ -2,20 +2,24 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that program and shader actors are cached in the frontend.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(MULTIPLE_CONTEXTS_URL);
-  let { gFront, ShadersListView, ShadersEditorsView } = panel.panelWin;
+  let { EVENTS, gFront, ShadersListView, ShadersEditorsView } = panel.panelWin;
 
   reload(target);
-  let programActor = yield once(gFront, "program-linked");
+  let [programActor] = yield promise.all([
+    getPrograms(gFront, 1),
+    once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+  ]).then(([programs]) => programs);
+
   let programItem = ShadersListView.selectedItem;
 
   is(programItem.attachment.programActor, programActor,
     "The correct program actor is cached for the selected item.");
 
   is((yield programActor.getVertexShader()),
      (yield programItem.attachment.vs),
     "The cached vertex shader promise returns the correct actor.");
--- a/browser/devtools/shadereditor/test/browser_se_programs-highlight-01.js
+++ b/browser/devtools/shadereditor/test/browser_se_programs-highlight-01.js
@@ -9,18 +9,20 @@ function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(MULTIPLE_CONTEXTS_URL);
   let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
 
   once(panel.panelWin, EVENTS.SHADER_COMPILED).then(() => {
     ok(false, "No shaders should be publicly compiled during this test.");
   });
 
   reload(target);
-  let firstProgramActor = yield once(gFront, "program-linked");
-  let secondProgramActor = yield once(gFront, "program-linked");
+  let [firstProgramActor, secondProgramActor] = yield promise.all([
+    getPrograms(gFront, 2),
+    once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+  ]).then(([programs]) => programs);
 
   let vsEditor = yield ShadersEditorsView._getEditor("vs");
   let fsEditor = yield ShadersEditorsView._getEditor("fs");
 
   vsEditor.once("change", () => {
     ok(false, "The vertex shader source was unexpectedly changed.");
   });
   fsEditor.once("change", () => {
@@ -84,14 +86,8 @@ function getItemLabel(aPanel, aIndex) {
   return aPanel.panelWin.document.querySelectorAll(
     ".side-menu-widget-item-label")[aIndex];
 }
 
 function getBlackBoxCheckbox(aPanel, aIndex) {
   return aPanel.panelWin.document.querySelectorAll(
     ".side-menu-widget-item-checkbox")[aIndex];
 }
-
-function once(aTarget, aEvent) {
-  let deferred = promise.defer();
-  aTarget.once(aEvent, deferred.resolve);
-  return deferred.promise;
-}
--- a/browser/devtools/shadereditor/test/browser_se_programs-highlight-02.js
+++ b/browser/devtools/shadereditor/test/browser_se_programs-highlight-02.js
@@ -6,18 +6,20 @@
  * overlapping geometry.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(BLENDED_GEOMETRY_CANVAS_URL);
   let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
 
   reload(target);
-  let firstProgramActor = yield once(gFront, "program-linked");
-  let secondProgramActor = yield once(gFront, "program-linked");
+  let [firstProgramActor, secondProgramActor] = yield promise.all([
+    getPrograms(gFront, 2),
+    once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+  ]).then(([programs]) => programs);
 
   yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 127, g: 127, b: 127, a: 255 }, true);
   yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 127, b: 127, a: 127 }, true);
   ok(true, "The canvas was correctly drawn.");
 
   ShadersListView._onProgramMouseEnter({ target: getItemLabel(panel, 0) });
 
   yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 127, g: 0, b: 32, a: 255 }, true);
@@ -40,14 +42,8 @@ function ifWebGLSupported() {
   yield teardown(panel);
   finish();
 }
 
 function getItemLabel(aPanel, aIndex) {
   return aPanel.panelWin.document.querySelectorAll(
     ".side-menu-widget-item-label")[aIndex];
 }
-
-function once(aTarget, aEvent) {
-  let deferred = promise.defer();
-  aTarget.once(aEvent, deferred.resolve);
-  return deferred.promise;
-}
--- a/browser/devtools/shadereditor/test/browser_se_programs-list.js
+++ b/browser/devtools/shadereditor/test/browser_se_programs-list.js
@@ -14,33 +14,27 @@ function ifWebGLSupported() {
     "The shaders list should initially be empty.");
   is(ShadersListView.selectedItem, null,
     "The shaders list has no selected item.");
   is(ShadersListView.selectedIndex, -1,
     "The shaders list has a negative index.");
 
   reload(target);
 
-  let firstProgramActor = yield once(gFront, "program-linked");
-
-  is(ShadersListView.itemCount, 1,
-    "The shaders list contains one entry.");
-  is(ShadersListView.selectedItem, ShadersListView.items[0],
-    "The shaders list has a correct item selected.");
-  is(ShadersListView.selectedIndex, 0,
-    "The shaders list has a correct index selected.");
-
-  let secondProgramActor = yield once(gFront, "program-linked");
-
-  is(ShadersListView.itemCount, 2,
-    "The shaders list contains two entries.");
-  is(ShadersListView.selectedItem, ShadersListView.items[0],
-    "The shaders list has a correct item selected.");
-  is(ShadersListView.selectedIndex, 0,
-    "The shaders list has a correct index selected.");
+  let [firstProgramActor, secondProgramActor] = yield promise.all([
+    getPrograms(gFront, 2, (actors) => {
+      // Fired upon each actor addition, we want to check only
+      // after the first actor has been added so we can test state
+      if (actors.length === 1)
+        checkFirstProgram();
+      if (actors.length === 2)
+        checkSecondProgram();
+    }),
+    once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+  ]).then(([programs, ]) => programs);
 
   is(ShadersListView.labels[0], L10N.getFormatStr("shadersList.programLabel", 0),
     "The correct first label is shown in the shaders list.");
   is(ShadersListView.labels[1], L10N.getFormatStr("shadersList.programLabel", 1),
     "The correct second label is shown in the shaders list.");
 
   let vertexShader = yield firstProgramActor.getVertexShader();
   let fragmentShader = yield firstProgramActor.getFragmentShader();
@@ -68,15 +62,26 @@ function ifWebGLSupported() {
 
   is(ShadersListView.selectedItem, ShadersListView.items[1],
     "The shaders list has a correct item selected.");
   is(ShadersListView.selectedIndex, 1,
     "The shaders list has a correct index selected.");
 
   yield teardown(panel);
   finish();
+
+  function checkFirstProgram () {
+    is(ShadersListView.itemCount, 1,
+      "The shaders list contains one entry.");
+    is(ShadersListView.selectedItem, ShadersListView.items[0],
+      "The shaders list has a correct item selected.");
+    is(ShadersListView.selectedIndex, 0,
+      "The shaders list has a correct index selected.");
+  }
+  function checkSecondProgram () {
+    is(ShadersListView.itemCount, 2,
+      "The shaders list contains two entries.");
+    is(ShadersListView.selectedItem, ShadersListView.items[0],
+      "The shaders list has a correct item selected.");
+    is(ShadersListView.selectedIndex, 0,
+      "The shaders list has a correct index selected.");
+  }
 }
-
-function once(aTarget, aEvent) {
-  let deferred = promise.defer();
-  aTarget.once(aEvent, deferred.resolve);
-  return deferred.promise;
-}
--- a/browser/devtools/shadereditor/test/browser_se_shaders-edit-01.js
+++ b/browser/devtools/shadereditor/test/browser_se_shaders-edit-01.js
@@ -5,17 +5,20 @@
  * Tests if editing a vertex and a fragment shader works properly.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
   let { gFront, $, EVENTS, ShadersEditorsView } = panel.panelWin;
 
   reload(target);
-  yield once(gFront, "program-linked");
+  yield promise.all([
+    once(gFront, "program-linked"),
+    once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+  ]);
 
   let vsEditor = yield ShadersEditorsView._getEditor("vs");
   let fsEditor = yield ShadersEditorsView._getEditor("fs");
 
   is(vsEditor.getText().indexOf("gl_Position"), 170,
     "The vertex shader editor contains the correct text.");
   is(fsEditor.getText().indexOf("gl_FragColor"), 97,
     "The fragment shader editor contains the correct text.");
@@ -63,14 +66,8 @@ function ifWebGLSupported() {
   yield ensurePixelIs(debuggee, { x: 128, y: 128 }, { r: 255, g: 0, b: 0, a: 127 }, true);
   yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 0, a: 255 }, true);
 
   ok(true, "The fragment shader was recompiled successfully.");
 
   yield teardown(panel);
   finish();
 }
-
-function once(aTarget, aEvent) {
-  let deferred = promise.defer();
-  aTarget.once(aEvent, deferred.resolve);
-  return deferred.promise;
-}
--- a/browser/devtools/shadereditor/test/browser_se_shaders-edit-02.js
+++ b/browser/devtools/shadereditor/test/browser_se_shaders-edit-02.js
@@ -6,66 +6,63 @@
  * gets malformed after being edited.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
   let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
 
   reload(target);
-  yield once(gFront, "program-linked");
+  yield promise.all([
+    once(gFront, "program-linked"),
+    once(panel.panelWin, EVENTS.SOURCES_SHOWN)
+  ]);
 
   let vsEditor = yield ShadersEditorsView._getEditor("vs");
   let fsEditor = yield ShadersEditorsView._getEditor("fs");
 
   vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
-  let error = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  let [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
 
   ok(error,
     "The new vertex shader source was compiled with errors.");
   is(error.compile, "",
     "The compilation status should be empty.");
   isnot(error.link, "",
     "The linkage status should not be empty.");
   is(error.link.split("ERROR").length - 1, 2,
     "The linkage status contains two errors.");
   ok(error.link.contains("ERROR: 0:8: 'constructor'"),
     "A constructor error is contained in the linkage status.");
   ok(error.link.contains("ERROR: 0:8: 'assign'"),
     "An assignment error is contained in the linkage status.");
 
   fsEditor.replaceText("vec4", { line: 2, ch: 14 }, { line: 2, ch: 18 });
-  let error = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  let [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
 
   ok(error,
     "The new fragment shader source was compiled with errors.");
   is(error.compile, "",
     "The compilation status should be empty.");
   isnot(error.link, "",
     "The linkage status should not be empty.");
   is(error.link.split("ERROR").length - 1, 1,
     "The linkage status contains one error.");
   ok(error.link.contains("ERROR: 0:6: 'constructor'"),
     "A constructor error is contained in the linkage status.");
 
   yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
   yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
 
   vsEditor.replaceText("vec4", { line: 7, ch: 22 }, { line: 7, ch: 26 });
-  let error = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  let [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
   ok(!error, "The new vertex shader source was compiled successfully.");
 
   fsEditor.replaceText("vec3", { line: 2, ch: 14 }, { line: 2, ch: 18 });
-  let error = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  let [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
   ok(!error, "The new fragment shader source was compiled successfully.");
 
   yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
   yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
 
   yield teardown(panel);
   finish();
 }
-
-function once(aTarget, aEvent) {
-  let deferred = promise.defer();
-  aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
-  return deferred.promise;
-}
--- a/browser/devtools/shadereditor/test/browser_se_shaders-edit-03.js
+++ b/browser/devtools/shadereditor/test/browser_se_shaders-edit-03.js
@@ -6,18 +6,23 @@
  * their new source on the backend and reshow it in the frontend when required.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(MULTIPLE_CONTEXTS_URL);
   let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
 
   reload(target);
-  let firstProgramActor = yield once(gFront, "program-linked");
-  let secondProgramActor = yield once(gFront, "program-linked");
+
+  yield promise.all([
+    once(gFront, "program-linked"),
+    once(gFront, "program-linked")
+  ]);
+
+  yield once(panel.panelWin, EVENTS.SOURCES_SHOWN)
 
   let vsEditor = yield ShadersEditorsView._getEditor("vs");
   let fsEditor = yield ShadersEditorsView._getEditor("fs");
 
   is(ShadersListView.selectedIndex, 0,
     "The first program is currently selected.");
   is(vsEditor.getText().indexOf("1);"), 136,
     "The vertex shader editor contains the correct initial text (1).");
@@ -73,14 +78,8 @@ function ifWebGLSupported() {
   is(vsEditor.getText().indexOf("2.);"), 136,
     "The vertex shader editor contains the correct text (4).");
   is(fsEditor.getText().indexOf(".0);"), 116,
     "The fragment shader editor contains the correct text (4).");
 
   yield teardown(panel);
   finish();
 }
-
-function once(aTarget, aEvent) {
-  let deferred = promise.defer();
-  aTarget.once(aEvent, deferred.resolve);
-  return deferred.promise;
-}
--- a/browser/devtools/shadereditor/test/browser_webgl-actor-test-13.js
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-13.js
@@ -4,18 +4,17 @@
 /**
  * Tests if multiple WebGL contexts are correctly handled.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, front] = yield initBackend(MULTIPLE_CONTEXTS_URL);
   front.setup({ reload: true });
 
-  let firstProgramActor = yield once(front, "program-linked");
-  let secondProgramActor = yield once(front, "program-linked");
+  let [firstProgramActor, secondProgramActor] = yield getPrograms(front, 2);
 
   isnot(firstProgramActor, secondProgramActor,
     "Two distinct program actors were recevide from two separate contexts.");
 
   let firstVertexShader = yield firstProgramActor.getVertexShader();
   let firstFragmentShader = yield firstProgramActor.getFragmentShader();
   let secondVertexShader = yield secondProgramActor.getVertexShader();
   let secondFragmentShader = yield secondProgramActor.getFragmentShader();
--- a/browser/devtools/shadereditor/test/browser_webgl-actor-test-14.js
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-14.js
@@ -5,18 +5,18 @@
  * Tests that the rendering is updated when a uniform variable is
  * changed in one shader of a page with multiple WebGL contexts.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, front] = yield initBackend(MULTIPLE_CONTEXTS_URL);
   front.setup({ reload: true });
 
-  let firstProgramActor = yield once(front, "program-linked");
-  let secondProgramActor = yield once(front, "program-linked");
+  let [firstProgramActor, secondProgramActor] = yield getPrograms(front, 2);
+
   let firstFragmentShader = yield firstProgramActor.getFragmentShader();
   let secondFragmentShader = yield secondProgramActor.getFragmentShader();
 
   let oldFragSource = yield firstFragmentShader.getText();
   let newFragSource = oldFragSource.replace("vec4(uColor", "vec4(0.25, 0.25, 0.25");
   let status = yield firstFragmentShader.compile(newFragSource);
   ok(!status,
     "The first new fragment shader source was compiled without errors.");
--- a/browser/devtools/shadereditor/test/browser_webgl-actor-test-15.js
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-15.js
@@ -11,18 +11,17 @@ function ifWebGLSupported() {
 
   reload(target);
   let firstProgram = yield once(front, "program-linked");
   yield checkFirstCachedPrograms(firstProgram);
   yield checkHighlightingInTheFirstPage(firstProgram);
   ok(true, "The cached programs behave correctly before the navigation.");
 
   navigate(target, MULTIPLE_CONTEXTS_URL);
-  let secondProgram = yield once(front, "program-linked");
-  let thirdProgram = yield once(front, "program-linked");
+  let [secondProgram, thirdProgram] = yield getPrograms(front, 2);
   yield checkSecondCachedPrograms(firstProgram, [secondProgram, thirdProgram]);
   yield checkHighlightingInTheSecondPage(secondProgram, thirdProgram);
   ok(true, "The cached programs behave correctly after the navigation.");
 
   once(front, "program-linked").then(() => {
     ok(false, "Shouldn't have received any more program-linked notifications.");
   });
 
--- a/browser/devtools/shadereditor/test/browser_webgl-actor-test-16.js
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-16.js
@@ -18,18 +18,17 @@ function ifWebGLSupported() {
   is(programs.length, 1,
     "The first program should be returned by a call to getPrograms().");
   is(programs[0], firstProgram,
     "The first programs was correctly retrieved from the cache.");
 
   // 1. Perform a simple navigation.
 
   navigate(target, MULTIPLE_CONTEXTS_URL);
-  let secondProgram = yield once(front, "program-linked");
-  let thirdProgram = yield once(front, "program-linked");
+  let [secondProgram, thirdProgram] = yield getPrograms(front, 2);
   let programs = yield front.getPrograms();
   is(programs.length, 2,
     "The second and third programs should be returned by a call to getPrograms().");
   is(programs[0], secondProgram,
     "The second programs was correctly retrieved from the cache.");
   is(programs[1], thirdProgram,
     "The third programs was correctly retrieved from the cache.");
 
@@ -60,18 +59,17 @@ function ifWebGLSupported() {
   let globalCreated = observe("content-document-global-created");
   reload(target);
 
   yield globalDestroyed;
   let programs = yield front.getPrograms();
   is(programs.length, 0,
     "There should be no cached program actors yet.");
 
-  yield once(front, "program-linked");
-  yield once(front, "program-linked");
+  yield getPrograms(front, 2);
   yield globalCreated;
   let programs = yield front.getPrograms();
   is(programs.length, 2,
     "There should be 2 cached program actors now.");
 
   yield checkHighlightingInTheSecondPage(programs[0], programs[1]);
   ok(true, "The cached programs behave correctly after navigating forward and reloading.");
 
--- a/browser/devtools/shadereditor/test/browser_webgl-actor-test-17.js
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-17.js
@@ -5,18 +5,17 @@
  * Tests that the blackbox/unblackbox operations work as expected with
  * overlapping geometry.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, front] = yield initBackend(OVERLAPPING_GEOMETRY_CANVAS_URL);
   front.setup({ reload: true });
 
-  let firstProgramActor = yield once(front, "program-linked");
-  let secondProgramActor = yield once(front, "program-linked");
+  let [firstProgramActor, secondProgramActor] = yield getPrograms(front, 2);
 
   yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true);
   yield ensurePixelIs(debuggee, { x: 64, y: 64 }, { r: 0, g: 255, b: 255, a: 255 }, true);
   yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true);
   ok(true, "The corner vs. center pixel colors are correct before blackboxing.");
 
   yield firstProgramActor.blackbox();
   yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
--- a/browser/devtools/shadereditor/test/doc_blended-geometry.html
+++ b/browser/devtools/shadereditor/test/doc_blended-geometry.html
@@ -43,17 +43,17 @@
       let canvas, gl;
       let program = [];
       let squareVerticesPositionBuffer;
       let vertexPositionAttribute = [];
       let depthUniform = [];
 
       window.onload = function() {
         canvas = document.querySelector("canvas");
-        gl = canvas.getContext("webgl");
+        gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
         gl.clearColor(0.0, 0.0, 0.0, 1.0);
 
         initProgram(0);
         initProgram(1);
         initBuffers();
         drawScene();
       }
 
--- a/browser/devtools/shadereditor/test/doc_multiple-contexts.html
+++ b/browser/devtools/shadereditor/test/doc_multiple-contexts.html
@@ -37,17 +37,17 @@
       let program = [];
       let squareVerticesPositionBuffer = [];
       let vertexPositionAttribute = [];
       let colorUniform = [];
 
       window.onload = function() {
         for (let i = 0; i < 2; i++) {
           canvas[i] = document.querySelector("#canvas" + (i + 1));
-          gl[i] = canvas[i].getContext("webgl");
+          gl[i] = canvas[i].getContext("webgl", { preserveDrawingBuffer: true });
           gl[i].clearColor(0.0, 0.0, 0.0, 1.0);
 
           initProgram(i);
           initBuffers(i);
           drawScene(i);
         }
       }
 
--- a/browser/devtools/shadereditor/test/doc_overlapping-geometry.html
+++ b/browser/devtools/shadereditor/test/doc_overlapping-geometry.html
@@ -43,17 +43,17 @@
       let canvas, gl;
       let program = [];
       let squareVerticesPositionBuffer;
       let vertexPositionAttribute = [];
       let depthUniform = [];
 
       window.onload = function() {
         canvas = document.querySelector("canvas");
-        gl = canvas.getContext("webgl");
+        gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
         gl.clearColor(0.0, 0.0, 0.0, 1.0);
 
         initProgram(0);
         initProgram(1);
         initBuffers();
         drawScene();
       }
 
--- a/browser/devtools/shadereditor/test/doc_shader-order.html
+++ b/browser/devtools/shadereditor/test/doc_shader-order.html
@@ -30,17 +30,17 @@
 
     <script type="text/javascript;version=1.8">
       "use strict";
 
       let canvas, gl;
 
       window.onload = function() {
         canvas = document.querySelector("canvas");
-        gl = canvas.getContext("webgl");
+        gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
 
         let shaderProgram = gl.createProgram();
         let vertexShader, fragmentShader;
 
         // Compile and attach the shaders in a random order. The test will
         // ensure that the correct vertex and fragment source is retrieved
         // regardless of this crazyness.
         if (Math.random() > 0.5) {
--- a/browser/devtools/shadereditor/test/doc_simple-canvas.html
+++ b/browser/devtools/shadereditor/test/doc_simple-canvas.html
@@ -39,17 +39,17 @@
       let program;
       let squareVerticesPositionBuffer;
       let squareVerticesColorBuffer;
       let vertexPositionAttribute;
       let vertexColorAttribute;
 
       window.onload = function() {
         canvas = document.querySelector("canvas");
-        gl = canvas.getContext("webgl");
+        gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
         gl.clearColor(0.0, 0.0, 0.0, 1.0);
 
         initProgram();
         initBuffers();
         drawScene();
       }
 
       function initProgram() {
--- a/browser/devtools/shadereditor/test/head.js
+++ b/browser/devtools/shadereditor/test/head.js
@@ -7,17 +7,17 @@ const { classes: Cc, interfaces: Ci, uti
 let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 
 // Enable logging for all the tests. Both the debugger server and frontend will
 // be affected by this pref.
 let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", true);
 
 let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
-let { Promise: promise } = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
 let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
 
 let { WebGLFront } = devtools.require("devtools/server/actors/webgl");
 let TiltGL = devtools.require("devtools/tilt/tilt-gl");
 let TargetFactory = devtools.TargetFactory;
@@ -119,32 +119,42 @@ function isWebGLSupported() {
 }
 
 function once(aTarget, aEventName, aUseCapture = false) {
   info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
 
   let deferred = promise.defer();
 
   for (let [add, remove] of [
+    ["on", "off"], // Use event emitter before DOM events for consistency
     ["addEventListener", "removeEventListener"],
-    ["addListener", "removeListener"],
-    ["on", "off"]
+    ["addListener", "removeListener"]
   ]) {
     if ((add in aTarget) && (remove in aTarget)) {
       aTarget[add](aEventName, function onEvent(...aArgs) {
         aTarget[remove](aEventName, onEvent, aUseCapture);
-        deferred.resolve.apply(deferred, aArgs);
+        deferred.resolve(...aArgs);
       }, aUseCapture);
       break;
     }
   }
 
   return deferred.promise;
 }
 
+// Hack around `once`, as that only resolves to a single (first) argument
+// and discards the rest. `onceSpread` is similar, except resolves to an
+// array of all of the arguments in the handler. These should be consolidated
+// into the same function, but many tests will need to be changed.
+function onceSpread(aTarget, aEvent) {
+  let deferred = promise.defer();
+  aTarget.once(aEvent, (...args) => deferred.resolve(args));
+  return deferred.promise;
+}
+
 function observe(aNotificationName, aOwnsWeak = false) {
   info("Waiting for observer notification: '" + aNotificationName + ".");
 
   let deferred = promise.defer();
 
   Services.obs.addObserver(function onNotification(...aArgs) {
     Services.obs.removeObserver(onNotification, aNotificationName);
     deferred.resolve.apply(deferred, aArgs);
@@ -268,8 +278,32 @@ function initShaderEditor(aUrl) {
 function teardown(aPanel) {
   info("Destroying the specified shader editor.");
 
   return promise.all([
     once(aPanel, "destroyed"),
     removeTab(aPanel.target.tab)
   ]);
 }
+
+// Due to `program-linked` events firing synchronously, we cannot
+// just yield/chain them together, as then we miss all actors after the
+// first event since they're fired consecutively. This allows us to capture
+// all actors and returns an array containing them.
+//
+// Takes a `front` object that is an event emitter, the number of
+// programs that should be listened to and waited on, and an optional
+// `onAdd` function that calls with the entire actors array on program link
+function getPrograms(front, count, onAdd) {
+  let actors = [];
+  let deferred = promise.defer();
+  front.on("program-linked", function onLink (actor) {
+    if (actors.length !== count) {
+      actors.push(actor);
+      if (typeof onAdd === 'function') onAdd(actors)
+    }
+    if (actors.length === count) {
+      front.off("program-linked", onLink);
+      deferred.resolve(actors);
+    }
+  });
+  return deferred.promise;
+}
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -72,16 +72,17 @@ const STR = Services.strings.createBundl
 this.VariablesView = function VariablesView(aParentNode, aFlags = {}) {
   this._store = []; // Can't use a Map because Scope names needn't be unique.
   this._itemsByElement = new WeakMap();
   this._prevHierarchy = new Map();
   this._currHierarchy = new Map();
 
   this._parent = aParentNode;
   this._parent.classList.add("variables-view-container");
+  this._parent.classList.add("theme-body");
   this._appendEmptyNotice();
 
   this._onSearchboxInput = this._onSearchboxInput.bind(this);
   this._onSearchboxKeyPress = this._onSearchboxKeyPress.bind(this);
   this._onViewKeyPress = this._onViewKeyPress.bind(this);
   this._onViewKeyDown = this._onViewKeyDown.bind(this);
 
   // Create an internal scrollbox container.
--- a/browser/devtools/shared/widgets/ViewHelpers.jsm
+++ b/browser/devtools/shared/widgets/ViewHelpers.jsm
@@ -649,17 +649,17 @@ this.WidgetMethods = {
    *          - description: an optional description of the item
    * @param object aOptions [optional]
    *        Additional options or flags supported by this operation:
    *          - staged: true to stage the item to be appended later
    *          - index: specifies on which position should the item be appended
    *          - relaxed: true if this container should allow dupes & degenerates
    *          - attachment: some attached primitive/object for the item
    *          - attributes: a batch of attributes set to the displayed element
-   *          - finalize: function invokde when the item is removed
+   *          - finalize: function invoked when the item is removed
    * @return Item
    *         The item associated with the displayed element if an unstaged push,
    *         undefined if the item was staged for a later commit.
    */
   push: function(aContents, aOptions = {}) {
     let item = new Item(this, aOptions.attachment, aContents);
 
     // Batch the item to be added later.
@@ -728,16 +728,17 @@ this.WidgetMethods = {
    *        The item associated with the element to remove.
    */
   remove: function(aItem) {
     if (!aItem) {
       return;
     }
     this._widget.removeChild(aItem._target);
     this._untangleItem(aItem);
+    if (!this.itemCount) this.empty();
   },
 
   /**
    * Removes the item at the specified index from this container.
    *
    * @param number aIndex
    *        The index of the item to remove.
    */
@@ -1004,17 +1005,19 @@ this.WidgetMethods = {
     if (this.autoFocusOnSelection && targetElement) {
       targetElement.focus();
     }
 
     // Prevent selecting the same item again and avoid dispatching
     // a redundant selection event, so return early.
     if (targetElement != prevElement) {
       this._widget.selectedItem = targetElement;
-      ViewHelpers.dispatchEvent(targetElement || prevElement, "select", aItem);
+      let dispTarget = targetElement || prevElement;
+      let dispName = this.suppressSelectionEvents ? "suppressed-select" : "select";
+      ViewHelpers.dispatchEvent(dispTarget, dispName, aItem);
     }
 
     // Updates this container to reflect the information provided by the
     // currently selected item.
     this.refresh();
   },
 
   /**
@@ -1040,16 +1043,25 @@ this.WidgetMethods = {
   /**
    * Selects the element with the specified value in this container.
    * @param string aValue
    */
   set selectedValue(aValue)
     this.selectedItem = this._itemsByValue.get(aValue),
 
   /**
+   * Specifies if "select" events dispatched from the elements in this container
+   * when their respective items are selected should be suppressed or not.
+   *
+   * If this flag is set to true, then consumers of this container won't
+   * be normally notified when items are selected.
+   */
+  suppressSelectionEvents: false,
+
+  /**
    * Focus this container the first time an element is inserted?
    *
    * If this flag is set to true, then when the first item is inserted in
    * this container (and thus it's the only item available), its corresponding
    * target element is focused as well.
    */
   autoFocusOnFirstItem: true,
 
@@ -1301,16 +1313,24 @@ this.WidgetMethods = {
       if (aPredicate(item)) {
         return item;
       }
     }
     return null;
   },
 
   /**
+   * Shortcut function for getItemForPredicate which works on item attachments.
+   * @see getItemForPredicate
+   */
+  getItemForAttachment: function(aPredicate, aOwner = this) {
+    return this.getItemForPredicate(e => aPredicate(e.attachment));
+  },
+
+  /**
    * Finds the index of an item in the container.
    *
    * @param Item aItem
    *        The item get the index for.
    * @return number
    *         The index of the matched item, or -1 if nothing is found.
    */
   indexOfItem: function(aItem) {
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -231,17 +231,23 @@ Editor.prototype = {
       cm = win.CodeMirror(win.document.body, this.config);
       cm.getWrapperElement().addEventListener("contextmenu", (ev) => {
         ev.preventDefault();
         this.showContextMenu(el.ownerDocument, ev.screenX, ev.screenY);
       }, false);
 
       cm.on("focus", () => this.emit("focus"));
       cm.on("scroll", () => this.emit("scroll"));
-      cm.on("change", () => this.emit("change"));
+      cm.on("change", () => {
+        this.emit("change");
+        if (!this._lastDirty) {
+          this._lastDirty = true;
+          this.emit("dirty-change");
+        }
+      });
       cm.on("cursorActivity", (cm) => this.emit("cursorActivity"));
 
       cm.on("gutterClick", (cm, line, gutter, ev) => {
         let head = { line: line, ch: 0 };
         let tail = { line: line, ch: this.getText(line).length };
 
         // Shift-click on a gutter selects the whole line.
         if (ev.shiftKey)
@@ -609,16 +615,18 @@ Editor.prototype = {
 
   /**
    * Marks the contents as clean and returns the current
    * version number.
    */
   setClean: function () {
     let cm = editors.get(this);
     this.version = cm.changeGeneration();
+    this._lastDirty = false;
+    this.emit("dirty-change");
     return this.version;
   },
 
   /**
    * Returns true if contents of the text area are
    * clean i.e. no changes were made since the last version.
    */
   isClean: function () {
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -229,17 +229,17 @@ StyleSheetEditor.prototype = {
 
       sourceEditor.setFirstVisibleLine(this._state.topIndex);
       sourceEditor.setSelection(this._state.selection.start,
                                 this._state.selection.end);
 
       this.emit("source-editor-load");
     });
 
-    sourceEditor.on("change", this._onPropertyChange);
+    sourceEditor.on("dirty-change", this._onPropertyChange);
   },
 
   /**
    * Get the source editor for this editor.
    *
    * @return {Promise}
    *         Promise that will resolve with the editor.
    */
--- a/browser/devtools/styleeditor/test/browser_styleeditor_filesave.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_filesave.js
@@ -9,17 +9,16 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 let tempScope = {};
 Components.utils.import("resource://gre/modules/FileUtils.jsm", tempScope);
 Components.utils.import("resource://gre/modules/NetUtil.jsm", tempScope);
 let FileUtils = tempScope.FileUtils;
 let NetUtil = tempScope.NetUtil;
 
-
 function test()
 {
   waitForExplicitFinish();
 
   copy(TESTCASE_URI_HTML, "simple.html", function(htmlFile) {
     copy(TESTCASE_URI_CSS, "simple.css", function(cssFile) {
       addTabAndOpenStyleEditor(function(panel) {
         let UI = panel.UI;
@@ -36,19 +35,32 @@ function test()
       let filePath = uri.resolve("");
       content.location = filePath;
     });
   });
 }
 
 function runTests(editor)
 {
+  editor.sourceEditor.once("dirty-change", () => {
+    is(editor.sourceEditor.isClean(), false, "Editor is dirty.");
+    ok(editor.summary.classList.contains("unsaved"),
+       "Star icon is present in the corresponding summary.");
+  });
+  let beginCursor = {line: 0, ch: 0};
+  editor.sourceEditor.replaceText("DIRTY TEXT", beginCursor, beginCursor);
+
+  editor.sourceEditor.once("dirty-change", () => {
+    is(editor.sourceEditor.isClean(), true, "Editor is clean.");
+    ok(!editor.summary.classList.contains("unsaved"),
+       "Star icon is not present in the corresponding summary.");
+    finish();
+  });
   editor.saveToFile(null, function (file) {
     ok(file, "file should get saved directly when using a file:// URI");
-    finish();
   });
 }
 
 function copy(aSrcChromeURL, aDestFileName, aCallback)
 {
   let destFile = FileUtils.getFile("ProfD", [aDestFileName]);
   write(read(aSrcChromeURL), destFile, aCallback);
 }
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -3317,20 +3317,22 @@ JSTerm.prototype = {
     if (aOptions.targetElement) {
       let deferred = promise.defer();
       openPromise = deferred.promise;
       let document = aOptions.targetElement.ownerDocument;
       let iframe = document.createElementNS(XHTML_NS, "iframe");
 
       iframe.addEventListener("load", function onIframeLoad(aEvent) {
         iframe.removeEventListener("load", onIframeLoad, true);
+        iframe.style.visibility = "visible";
         deferred.resolve(iframe.contentWindow);
       }, true);
 
       iframe.flex = 1;
+      iframe.style.visibility = "hidden";
       iframe.setAttribute("src", VARIABLES_VIEW_URL);
       aOptions.targetElement.appendChild(iframe);
     }
     else {
       if (!this.sidebar) {
         this._createSidebar();
       }
       openPromise = this._addVariablesViewSidebarTab();
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
@@ -124,18 +124,20 @@
 <!ENTITY debuggerUI.seMenuBreak.key     "B">
 
 <!-- LOCALIZATION NOTE (debuggerUI.seMenuCondBreak): This is the text that
   -  appears in the source editor context menu for adding a conditional
   -  breakpoint. -->
 <!ENTITY debuggerUI.seMenuCondBreak     "Add conditional breakpoint">
 <!ENTITY debuggerUI.seMenuCondBreak.key "B">
 
-<!-- LOCALIZATION NOTE (debuggerUI.instruments.*): This is the text that
-  -  appears in the debugger's instruments pane tabs. -->
+<!-- LOCALIZATION NOTE (debuggerUI.tabs.*): This is the text that
+  -  appears in the debugger's side pane tabs. -->
+<!ENTITY debuggerUI.tabs.sources        "Sources">
+<!ENTITY debuggerUI.tabs.callstack      "Call Stack">
 <!ENTITY debuggerUI.tabs.variables      "Variables">
 <!ENTITY debuggerUI.tabs.events         "Events">
 
 <!-- LOCALIZATION NOTE (debuggerUI.seMenuAddWatch): This is the text that
   -  appears in the source editor context menu for adding an expression. -->
 <!ENTITY debuggerUI.seMenuAddWatch      "Selection to watch expression">
 <!ENTITY debuggerUI.seMenuAddWatch.key  "E">
 
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties
@@ -64,20 +64,24 @@ stepOutTooltip=Step Out (%S)
 # LOCALIZATION NOTE (emptyGlobalsText): The text to display in the menulist
 # when there are no chrome globals available.
 noGlobalsText=No globals
 
 # LOCALIZATION NOTE (noSourcesText): The text to display in the sources menu
 # when there are no scripts.
 noSourcesText=This page has no sources.
 
-# LOCALIZATION NOTE (noEventsTExt): The text to display in the events tab
+# LOCALIZATION NOTE (noEventListenersText): The text to display in the events tab
 # when there are no events.
 noEventListenersText=No event listeners to display
 
+# LOCALIZATION NOTE (noStackFramesText): The text to display in the call stack tab
+# when there are no stack frames.
+noStackFramesText=No stack frames to display
+
 # LOCALIZATION NOTE (eventCheckboxTooltip): The tooltip text to display when
 # the user hovers over the checkbox used to toggle an event breakpoint.
 eventCheckboxTooltip=Toggle breaking on this event
 
 # LOCALIZATION NOTE (eventOnSelector): The text to display in the events tab
 # for every event item, between the event type and event selector.
 eventOnSelector=on
 
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -242,17 +242,17 @@ Desktop browser's sync prefs.
             </box>
 
             <textbox id="urlbar-edit" type="url" flex="1"
                      autocompletesearch="history"
                      autocompletepopup="urlbar-autocomplete"
                      completeselectedindex="true"
                      placeholder="&urlbar.emptytext;"
                      tabscrolling="true"
-                     onclick="SelectionHelperUI.urlbarClick();"/>
+                     onclick="SelectionHelperUI.urlbarTextboxClick(this);"/>
 
             <toolbarbutton id="go-button" class="urlbar-button"
                            command="cmd_go"/>
             <toolbarbutton id="reload-button" class="urlbar-button"
                            oncommand="CommandUpdater.doCommand(
                                         event.shiftKey ? 'cmd_forceReload'
                                                        : 'cmd_reload');"/>
             <toolbarbutton id="stop-button" class="urlbar-button"
--- a/browser/metro/base/content/helperui/ChromeSelectionHandler.js
+++ b/browser/metro/base/content/helperui/ChromeSelectionHandler.js
@@ -29,20 +29,19 @@ var ChromeSelectionHandler = {
 
   /*
    * General selection start method for both caret and selection mode.
    */
   _onSelectionAttach: function _onSelectionAttach(aJson) {
     this._domWinUtils = Util.getWindowUtils(window);
     this._contentWindow = window;
     this._targetElement = this._domWinUtils.elementFromPoint(aJson.xPos, aJson.yPos, true, false);
-
     this._targetIsEditable = this._targetElement instanceof Components.interfaces.nsIDOMXULTextBoxElement;
     if (!this._targetIsEditable) {
-      this._onFail("not an editable?");
+      this._onFail("not an editable?", this._targetElement);
       return;
     }
 
     let selection = this._getSelection();
     if (!selection) {
       this._onFail("no selection.");
       return;
     }
--- a/browser/metro/base/content/helperui/SelectionHelperUI.js
+++ b/browser/metro/base/content/helperui/SelectionHelperUI.js
@@ -339,19 +339,19 @@ var SelectionHelperUI = {
 
   /*
    * Observers
    */
 
   observe: function (aSubject, aTopic, aData) {
     switch (aTopic) {
       case "attach_edit_session_to_content":
-        let event = aSubject;
-        this.attachEditSession(Browser.selectedTab.browser,
-                               event.clientX, event.clientY);
+        // We receive this from text input bindings when this module
+        // isn't accessible.
+        this.chromeTextboxClick(aSubject);
         break;
 
       case "apzc-transform-begin":
         if (this.isActive && this.layerMode == kContentLayer) {
           this._hideMonocles();
         }
         break;
 
@@ -510,16 +510,50 @@ var SelectionHelperUI = {
     // which we will call _shutdown().
     let clearSelection = aClearSelection || false;
     this._sendAsyncMessage("Browser:SelectionClose", {
       clearSelection: clearSelection
     });
   },
 
   /*
+   * Event handler on the navbar text input. Called from navbar bindings
+   * when focus is applied to the edit.
+   */
+  urlbarTextboxClick: function(aEdit) {
+    // workaround for bug 925457: taping browser chrome resets last tap
+    // co-ordinates to 'undefined' so that we know not to shift the browser
+    // when the keyboard is up in SelectionHandler's _calcNewContentPosition().
+    Browser.selectedTab.browser.messageManager.sendAsyncMessage("Browser:ResetLastPos", {
+      xPos: null,
+      yPos: null
+    });
+
+    if (InputSourceHelper.isPrecise || !aEdit.textLength) {
+      return;
+    }
+
+    // Enable selection when there's text in the control
+    let innerRect = aEdit.inputField.getBoundingClientRect();
+    this.attachEditSession(ChromeSelectionHandler,
+                           innerRect.left,
+                           innerRect.top);
+  },
+
+  /*
+   * Click handler for chrome pages loaded into the browser (about:config).
+   * Called from the text input bindings via the attach_edit_session_to_content
+   * observer.
+   */
+  chromeTextboxClick: function (aEvent) {
+    this.attachEditSession(Browser.selectedTab.browser,
+                           aEvent.clientX, aEvent.clientY);
+  },
+
+  /*
    * Handy debug routines that work independent of selection. They
    * make use of the selection overlay for drawing points.
    */
 
   debugDisplayDebugPoint: function (aLeft, aTop, aSize, aCssColorStr, aFill) {
     this.overlay.enabled = true;
     this.overlay.displayDebugLayer = true;
     this.overlay.addDebugRect(aLeft, aTop, aLeft + aSize, aTop + aSize,
@@ -830,30 +864,20 @@ var SelectionHelperUI = {
     }
     return true;
   },
 
   /*
    * Event handlers for document events
    */
 
-   urlbarClick: function() {
-    // Workaround for bug 925457: taping browser chrome resets last tap
-    // co-ordinates to 'undefined' so that we know not to shift the browser
-    // when the keyboard is up (in SelectionHandler._calcNewContentPosition())
-    Browser.selectedTab.browser.messageManager.sendAsyncMessage("Browser:ResetLastPos", {
-      xPos: null,
-      yPos: null
-    });
-   },
-
   /*
    * Handles taps that move the current caret around in text edits,
    * clear active selection and focus when neccessary, or change
-   * modes.
+   * modes. Only active afer SelectionHandlerUI is initialized.
    */
   _onClick: function(aEvent) {
     if (this.layerMode == kChromeLayer && this._targetIsEditable) {
       this.attachToCaret(this._msgTarget, aEvent.clientX, aEvent.clientY);
     }
   },
 
   _onKeypress: function _onKeypress() {
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -39,17 +39,16 @@ pref("prompts.tab_modal.enabled", true);
 pref("layers.offmainthreadcomposition.enabled", true);
 pref("layers.async-pan-zoom.enabled", true);
 pref("layers.componentalpha.enabled", false);
 
 // Prefs to control the async pan/zoom behaviour
 pref("apz.touch_start_tolerance", "0.1"); // dpi * tolerance = pixel threshold
 pref("apz.pan_repaint_interval", 50);   // prefer 20 fps
 pref("apz.fling_repaint_interval", 50); // prefer 20 fps
-pref("apz.fling_friction", "0.002");
 pref("apz.fling_stopped_threshold", "0.2");
 
 // 0 = free, 1 = standard, 2 = sticky
 pref("apz.axis_lock_mode", 2);
 pref("apz.cross_slide.enabled", true);
 
 // Enable Microsoft TSF support by default for imes.
 pref("intl.enable_tsf_support", true);
--- a/browser/themes/linux/devtools/debugger.css
+++ b/browser/themes/linux/devtools/debugger.css
@@ -4,28 +4,32 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Sources and breakpoints pane */
 
 #sources-pane {
   min-width: 50px;
 }
 
+#sources-pane > tabs {
+  -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
+}
+
 #sources-and-editor-splitter {
   -moz-border-start-color: transparent;
 }
 
 /* Sources toolbar */
 
 #sources-toolbar {
   border: none; /* Remove the devtools-toolbar's black bottom border. */
   -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
 }
 
-#sources-toolbar .devtools-toolbarbutton {
+#sources-toolbar > #sources-controls > .devtools-toolbarbutton {
   min-width: 32px;
 }
 
 #pretty-print {
   font-weight: bold;
 }
 
 #black-box {
@@ -82,50 +86,89 @@
 }
 
 /* ListWidget items */
 
 .list-widget-item {
   padding: 2px;
 }
 
-.list-widget-item:not(.selected):not(.empty):hover {
+.theme-light .list-widget-item:not(.selected):not(.empty):hover {
   background: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.85)), Highlight;
 }
 
-.list-widget-item.selected.light {
+.theme-light .list-widget-item.selected.light {
   background: linear-gradient(rgba(255,255,255,0.85), rgba(255,255,255,0.8)), Highlight;
   color: #000;
 }
 
+.theme-dark .list-widget-item:not(.selected):not(.empty):hover {
+  background: linear-gradient(rgba(255,255,255,0.1), rgba(255,255,255,0.05));
+}
+
+.theme-dark .list-widget-item.selected.light {
+  background: linear-gradient(rgba(255,255,255,0.05), rgba(255,255,255,0.025));
+}
+
 .list-widget-item.selected {
   background: Highlight;
   color: HighlightText;
 }
 
 .list-widget-item.empty {
   color: GrayText;
   padding: 2px;
 }
 
-/* Stack frames view */
+/* Breadcrumbs stack frames view */
+
+.breadcrumbs-widget-item {
+  max-width: none;
+}
 
 .dbg-stackframe-details {
   -moz-padding-start: 4px;
 }
 
-.dbg-stackframe-menuitem[checked] {
-  margin-top: 3px;
-  margin-bottom: 3px;
-  outline: 1px solid #eee;
+/* Classic stack frames view */
+
+.dbg-classic-stackframe {
+  display: block;
+  padding: 4px;
+}
+
+.dbg-classic-stackframe-title {
   font-weight: 600;
+  color: #046;
+}
+
+.dbg-classic-stackframe-details:-moz-locale-dir(ltr) {
+  float: right;
 }
 
-.dbg-stackframe-menuitem-details {
-  -moz-padding-start: 16px;
+.dbg-classic-stackframe-details:-moz-locale-dir(rtl) {
+  float: left;
+}
+
+.dbg-classic-stackframe-details-url {
+  max-width: 90%;
+  text-align: end;
+  color: #666;
+}
+
+.dbg-classic-stackframe-details-sep {
+  color: #aaa;
+}
+
+.dbg-classic-stackframe-details-line {
+  color: #58b;
+}
+
+#callstack-list .side-menu-widget-item.selected label {
+  color: #fff;
 }
 
 /* Sources and breakpoints view */
 
 .dbg-breakpoint {
   -moz-margin-start: 4px;
 }
 
@@ -175,18 +218,19 @@
 
 /* Instruments pane (watch expressions, variables, event listeners...) */
 
 #instruments-pane > tabs > tab {
   min-height: 25px !important;
   padding: 0 !important;
 }
 
-#instruments-pane > tabpanels > tabpanel {
-  background: #fff;
+#instruments-pane .side-menu-widget-container,
+#instruments-pane .side-menu-widget-empty-notice-container {
+  box-shadow: none !important;
 }
 
 /* Watch expressions view */
 
 #expressions {
   min-height: 10px;
   max-height: 125px;
 }
@@ -198,16 +242,17 @@
 .dbg-expression-arrow {
   width: 16px;
   height: auto;
   background: -moz-image-rect(url(commandline-icon.png), 0, 32, 16, 16);
 }
 
 .dbg-expression-input {
   -moz-padding-start: 2px !important;
+  color: inherit;
 }
 
 /* Event listeners view */
 
 .dbg-event-listener {
   padding: 4px 8px;
 }
 
@@ -222,16 +267,20 @@
 .dbg-event-listener-targets {
   color: #046;
 }
 
 .dbg-event-listener-location {
   color: #666;
 }
 
+#event-listeners .side-menu-widget-item.selected {
+  background: none !important;
+}
+
 /* Searchbox and the search operations help panel */
 
 #searchbox {
   min-width: 220px;
   -moz-margin-start: 1px;
 }
 
 #filter-label {
@@ -375,16 +424,21 @@
 
 .dbg-results-line-contents-string[match=true][focused] {
   transition-duration: 0.1s;
   transform: scale(1.75, 1.75);
 }
 
 /* Toolbar controls */
 
+.devtools-sidebar-tabs > tabs > tab {
+  min-height: 25px !important;
+  padding: 0 !important;
+}
+
 #resumption-panel-desc {
   width: 200px;
 }
 
 #resumption-order-panel {
   -moz-margin-start: -8px;
 }
 
@@ -463,22 +517,27 @@
 
 /* Horizontal vs. vertical layout */
 
 #vertical-layout-panes-container {
   min-height: 35vh;
   max-height: 80vh;
 }
 
+#body[layout=vertical] #sources-pane > tabs {
+  -moz-border-end: none;
+}
+
 #body[layout=vertical] #instruments-pane {
   margin: 0 !important;
   /* To prevent all the margin hacks to hide the sidebar. */
 }
 
-#body[layout=vertical] .side-menu-widget-container {
+#body[layout=vertical] .side-menu-widget-container,
+#body[layout=vertical] .side-menu-widget-empty-notice-container {
   box-shadow: none !important;
 }
 
 #body[layout=vertical] .side-menu-widget-item-arrow {
   background-image: none !important;
 }
 
 #body[layout=vertical] .side-menu-widget-group,
--- a/browser/themes/linux/devtools/netmonitor.css
+++ b/browser/themes/linux/devtools/netmonitor.css
@@ -307,16 +307,20 @@ box.requests-menu-status[code^="5"] {
 .requests-menu-timings-cap.receive {
   background-color: rgba(255,255,255,1.0);
   box-shadow: 0 0 8px 0 rgba(128,255,255,1.0),
               0 0 4px 0 rgba(255,255,255,1.0) inset;
 }
 
 /* SideMenuWidget */
 
+.side-menu-widget-container {
+  box-shadow: none !important;
+}
+
 .side-menu-widget-item[odd] {
   background: rgba(255,255,255,0.05);
 }
 
 /* Network request details */
 
 #details-pane {
   background: hsl(208,11%,27%);
@@ -358,20 +362,16 @@ box.requests-menu-status[code^="5"] {
   text-shadow: 0 1px 0 #000;
   color: hsl(210,30%,85%);
 }
 
 .tabpanel-summary-value {
   -moz-padding-start: 3px;
 }
 
-.variable-or-property:not(:focus) > .title > .token-string {
-  color: #10c !important;
-}
-
 /* Headers tabpanel */
 
 #headers-summary-status,
 #headers-summary-version {
   padding-bottom: 2px;
 }
 
 #headers-summary-size {
--- a/browser/themes/linux/devtools/widgets.css
+++ b/browser/themes/linux/devtools/widgets.css
@@ -276,25 +276,27 @@
 
 .side-menu-widget-container[theme="light"] {
   background: #fff;
   color: #000;
 }
 
 /* SideMenuWidget container */
 
-.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(ltr) {
+.side-menu-widget-container:-moz-locale-dir(ltr),
+.side-menu-widget-empty-notice-container:-moz-locale-dir(ltr) {
   box-shadow: inset -1px 0 0 #222426;
 }
 
-.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(rtl) {
+.side-menu-widget-container:-moz-locale-dir(rtl),
+.side-menu-widget-empty-notice-container:-moz-locale-dir(rtl) {
   box-shadow: inset 1px 0 0 #222426;
 }
 
-.side-menu-widget-container[with-arrows=true] .side-menu-widget-group {
+.side-menu-widget-group {
   /* To allow visibility of the dark margin shadow. */
   -moz-margin-end: 1px;
 }
 
 .side-menu-widget-container[with-arrows=true] .side-menu-widget-item {
   /* To compensate for the arrow image's dark margin. */
   -moz-margin-end: -1px;
 }
@@ -323,36 +325,34 @@
   border-top: 1px solid hsla(210,8%,5%,.25);
   border-bottom: 1px solid hsla(210,16%,76%,.1);
   margin-top: -1px;
   margin-bottom: -1px;
 }
 
 .side-menu-widget-item[theme="light"] {
   border-top: 1px solid hsla(210,8%,75%,.25);
+  border-bottom: 1px solid hsla(210,16%,76%,.1);
   margin-top: -1px;
+  margin-bottom: -1px;
 }
 
 .side-menu-widget-item[theme="dark"]:last-of-type {
   box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25);
 }
 
 .side-menu-widget-item[theme="light"]:last-of-type {
   box-shadow: inset 0 -1px 0 hsla(210,8%,75%,.25);
 }
 
-.side-menu-widget-item[theme="dark"].selected {
+.side-menu-widget-item.selected {
   background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left !important;
   box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15);
 }
 
-.side-menu-widget-item[theme="light"].selected {
-  /* Nothing here yet */
-}
-
 .side-menu-widget-item.selected > .side-menu-widget-item-arrow {
   background-size: auto, 1px 100%;
   background-repeat: no-repeat;
 }
 
 .side-menu-widget-item.selected > .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
   background-image: url(itemArrow-ltr.png), linear-gradient(to right, #222426, #222426);
   background-position: center right, top right;
@@ -427,135 +427,93 @@
 .side-menu-widget-empty-notice-container[theme="light"] {
   background: #fff;
   padding: 4px 8px;
   color: GrayText;
 }
 
 /* VariablesView */
 
-.variables-view-container {
-  background: #fff;
-}
-
 .variables-view-empty-notice {
   color: GrayText;
   padding: 2px;
 }
 
 .variables-view-scope > .title {
   color: #fff;
 }
 
-.variables-view-scope:focus > .title {
-  background: Highlight;
-  color: HighlightText;
-}
-
 .variables-view-scope > .variables-view-element-details:not(:empty) {
   -moz-margin-start: 2px;
   -moz-margin-end: 1px;
 }
 
 /* Generic traits applied to both variables and properties */
 
 .variable-or-property {
-  transition: background 1s ease-in-out;
-  color: #000;
+  transition: background 1s ease-in-out, color 1s ease-in-out;
 }
 
 .variable-or-property[changed] {
-  background: rgba(255,255,0,0.65);
-  transition-duration: 0.4s;
+  color: black;
+  transition-duration: .4s;
 }
 
 .variable-or-property > .title > .value {
   -moz-box-flex: 1;
   -moz-padding-start: 6px;
   -moz-padding-end: 4px;
 }
 
-.variable-or-property:focus > .title {
-  background: Highlight;
-  color: HighlightText;
-  border-radius: 4px;
-}
-
 .variable-or-property[editable] > .title > .value {
   cursor: text;
 }
 
 .variable-or-property:not([non-header]) > .variables-view-element-details {
   -moz-margin-start: 10px;
 }
 
 /* Custom variables and properties traits */
 
 .variables-view-variable {
   -moz-margin-start: 1px;
   -moz-margin-end: 1px;
 }
 
 .variables-view-variable:not(:last-child) {
-  border-bottom: 1px solid #eee;
+  border-bottom: 1px solid rgba(128, 128, 128, .15);
 }
 
 .variables-view-variable > .title > .name {
   font-weight: 600;
 }
 
-.variables-view-variable:not(:focus) > .title > .name {
-  color: #048;
-}
-
-.variables-view-property:not(:focus) > .title > .name {
-  color: #881090;
-}
-
-/* Token value colors */
-
-.variable-or-property:not(:focus) > .title > .token-undefined {
-  color: #bbb;
-}
-
-.variable-or-property:not(:focus) > .title > .token-null {
-  color: #999;
-}
-
-.variable-or-property:not(:focus) > .title > .token-boolean {
-  color: #10c;
-}
-
-.variable-or-property:not(:focus) > .title > .token-number {
-  color: #c00;
-}
-
-.variable-or-property:not(:focus) > .title > .token-string {
-  color: #282;
-}
-
-.variable-or-property:not(:focus) > .title > .token-other {
-  color: #333;
+.variable-or-property:focus > .title > label {
+  color: inherit !important;
 }
 
 /* Custom configurable/enumerable/writable or frozen/sealed/extensible
  * variables and properties */
 
 .variable-or-property[non-enumerable]:not([self]):not([exception]):not([return]):not([scope]) > .title > .name {
-  opacity: 0.5;
+  opacity: 0.6;
 }
 
 .variable-or-property[non-configurable] > .title > .name {
   border-bottom: 1px dashed #99f;
 }
 
 .variable-or-property[non-writable] > .title > .name {
   border-bottom: 1px dashed #f99;
 }
 
+.variable-or-property[safe-getter] > .title > .name {
+  border-bottom: 1px dashed #8b0;
+}
+
 .variable-or-property-non-writable-icon {
   background: url("chrome://browser/skin/identity-icons-https.png") no-repeat;
   width: 16px;
   height: 16px;
   opacity: 0.5;
 }
 
 @media (min-resolution: 2dppx) {
@@ -572,37 +530,16 @@
 }
 
 .variable-or-property:not(:focus) > .title > .variable-or-property-frozen-label,
 .variable-or-property:not(:focus) > .title > .variable-or-property-sealed-label,
 .variable-or-property:not(:focus) > .title > .variable-or-property-non-extensible-label {
   color: #666;
 }
 
-/* Special variables and properties */
-
-.variable-or-property[safe-getter] > .title > .name {
-  border-bottom: 1px dashed #8b0;
-}
-
-.variable-or-property[exception]:not(:focus) > .title > .name {
-  color: #a00;
-  text-shadow: 0 0 8px #fcc;
-}
-
-.variable-or-property[return]:not(:focus) > .title > .name {
-  color: #0a0;
-  text-shadow: 0 0 8px #cfc;
-}
-
-.variable-or-property[scope]:not(:focus) > .title > .name {
-  color: #00a;
-  text-shadow: 0 0 8px #ccf;
-}
-
 /* Aligned values */
 
 .variables-view-container[aligned-values] .title > .separator {
   -moz-box-flex: 1;
 }
 
 .variables-view-container[aligned-values] .title > .value {
   -moz-box-flex: 0;
@@ -629,17 +566,17 @@
 .variable-or-property > tooltip > label {
   margin: 0 2px 0 2px;
 }
 
 .variable-or-property[non-enumerable] > tooltip > label[value=enumerable],
 .variable-or-property[non-configurable] > tooltip > label[value=configurable],
 .variable-or-property[non-writable] > tooltip > label[value=writable],
 .variable-or-property[non-extensible] > tooltip > label[value=extensible] {
-  color: #f44;
+  color: #800;
   text-decoration: line-through;
 }
 
 .variable-or-property[safe-getter] > tooltip > label[value=WebIDL] {
   -moz-padding-start: 4px;
   -moz-border-start: 1px dotted #000;
   color: #080;
 }
@@ -675,24 +612,23 @@
 .element-value-input {
   -moz-margin-start: 4px !important;
   -moz-margin-end: 2px !important;
 }
 
 .element-name-input {
   -moz-margin-start: -2px !important;
   -moz-margin-end: 2px !important;
-  color: #048;
   font-weight: 600;
 }
 
 .element-value-input,
 .element-name-input {
-  border: 1px solid #999 !important;
-  box-shadow: 1px 2px 4px #aaa;
+  border: 1px solid rgba(128, 128, 128, .5) !important;
+  color: inherit;
 }
 
 /* Variables and properties searching */
 
 .variables-view-searchinput {
   min-height: 24px;
 }
 
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3589,21 +3589,26 @@ toolbarbutton.chevron > .toolbarbutton-m
   margin-left: 1em;
 }
 
 /* Lion Fullscreen window styling */
 @media (-moz-mac-lion-theme) {
   #navigator-toolbox[inFullscreen]:not(:-moz-lwtheme)::before {
     height: calc(@tabHeight@ + 11px) !important;
   }
+  #main-window[inFullscreen][privatebrowsingmode=temporary],
   #main-window[inFullscreen]:-moz-lwtheme {
     /* This additional padding matches the change in height in the pseudo-element
-     * above. The rules combined force the top 22px of the background image to
-     * be hidden, so there image doesn't jump around with the loss of the titlebar */
+     * above. */
     padding-top: 11px;
+  }
+  #main-window[inFullscreen]:not([privatebrowsingmode=temporary]):-moz-lwtheme {
+    /* In combination with the previous rule, forces the top 22px of the
+     * background image to be hidden, so the image doesn't jump around with
+     * the loss of the titlebar. */
     background-position: right -11px;
   }
 }
 
 #full-screen-warning-message {
   background-image: url("chrome://browser/skin/fullscreen-darknoise.png");
   color: white;
   border-radius: 4px;
--- a/browser/themes/osx/devtools/debugger.css
+++ b/browser/themes/osx/devtools/debugger.css
@@ -6,28 +6,32 @@
 %include ../shared.inc
 
 /* Sources and breakpoints pane */
 
 #sources-pane {
   min-width: 50px;
 }
 
+#sources-pane > tabs {
+  -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
+}
+
 #sources-and-editor-splitter {
   -moz-border-start-color: transparent;
 }
 
 /* Sources toolbar */
 
 #sources-toolbar {
   border: none; /* Remove the devtools-toolbar's black bottom border. */
   -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
 }
 
-#sources-toolbar .devtools-toolbarbutton {
+#sources-toolbar > #sources-controls > .devtools-toolbarbutton {
   min-width: 32px;
 }
 
 #pretty-print {
   font-weight: bold;
 }
 
 #black-box {
@@ -84,50 +88,89 @@
 }
 
 /* ListWidget items */
 
 .list-widget-item {
   padding: 2px;
 }
 
-.list-widget-item:not(.selected):not(.empty):hover {
+.theme-light .list-widget-item:not(.selected):not(.empty):hover {
   background: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.85)), Highlight;
 }
 
-.list-widget-item.selected.light {
+.theme-light .list-widget-item.selected.light {
   background: linear-gradient(rgba(255,255,255,0.85), rgba(255,255,255,0.8)), Highlight;
   color: #000;
 }
 
+.theme-dark .list-widget-item:not(.selected):not(.empty):hover {
+  background: linear-gradient(rgba(255,255,255,0.1), rgba(255,255,255,0.05));
+}
+
+.theme-dark .list-widget-item.selected.light {
+  background: linear-gradient(rgba(255,255,255,0.05), rgba(255,255,255,0.025));
+}
+
 .list-widget-item.selected {
   background: Highlight;
   color: HighlightText;
 }
 
 .list-widget-item.empty {
   color: GrayText;
   padding: 2px;
 }
 
-/* Stack frames view */
+/* Breadcrumbs stack frames view */
+
+.breadcrumbs-widget-item {
+  max-width: none;
+}
 
 .dbg-stackframe-details {
   -moz-padding-start: 4px;
 }
 
-.dbg-stackframe-menuitem[checked] {
-  margin-top: 3px;
-  margin-bottom: 3px;
-  outline: 1px solid #eee;
+/* Classic stack frames view */
+
+.dbg-classic-stackframe {
+  display: block;
+  padding: 4px;
+}
+
+.dbg-classic-stackframe-title {
   font-weight: 600;
+  color: #046;
+}
+
+.dbg-classic-stackframe-details:-moz-locale-dir(ltr) {
+  float: right;
 }
 
-.dbg-stackframe-menuitem-details {
-  -moz-padding-start: 16px;
+.dbg-classic-stackframe-details:-moz-locale-dir(rtl) {
+  float: left;
+}
+
+.dbg-classic-stackframe-details-url {
+  max-width: 90%;
+  text-align: end;
+  color: #666;
+}
+
+.dbg-classic-stackframe-details-sep {
+  color: #aaa;
+}
+
+.dbg-classic-stackframe-details-line {
+  color: #58b;
+}
+
+#callstack-list .side-menu-widget-item.selected label {
+  color: #fff;
 }
 
 /* Sources and breakpoints view */
 
 .dbg-breakpoint {
   -moz-margin-start: 4px;
 }
 
@@ -177,18 +220,19 @@
 
 /* Instruments pane (watch expressions, variables, event listeners...) */
 
 #instruments-pane > tabs > tab {
   min-height: 1em !important;
   padding: 0 !important;
 }
 
-#instruments-pane > tabpanels > tabpanel {
-  background: #fff;
+#instruments-pane .side-menu-widget-container,
+#instruments-pane .side-menu-widget-empty-notice-container {
+  box-shadow: none !important;
 }
 
 /* Watch expressions view */
 
 #expressions {
   min-height: 10px;
   max-height: 125px;
 }
@@ -200,16 +244,17 @@
 .dbg-expression-arrow {
   width: 16px;
   height: auto;
   background: -moz-image-rect(url(commandline-icon.png), 0, 32, 16, 16);
 }
 
 .dbg-expression-input {
   -moz-padding-start: 2px !important;
+  color: inherit;
 }
 
 /* Event listeners view */
 
 .dbg-event-listener {
   padding: 4px 8px;
 }
 
@@ -224,16 +269,20 @@
 .dbg-event-listener-targets {
   color: #046;
 }
 
 .dbg-event-listener-location {
   color: #666;
 }
 
+#event-listeners .side-menu-widget-item.selected {
+  background: none !important;
+}
+
 /* Searchbox and the search operations help panel */
 
 #searchbox {
   min-width: 220px;
   -moz-margin-start: 1px;
 }
 
 #filter-label {
@@ -377,16 +426,21 @@
 
 .dbg-results-line-contents-string[match=true][focused] {
   transition-duration: 0.1s;
   transform: scale(1.75, 1.75);
 }
 
 /* Toolbar controls */
 
+.devtools-sidebar-tabs > tabs > tab {
+  min-height: 1em !important;
+  padding: 0 !important;
+}
+
 #resumption-panel-desc {
   width: 200px;
 }
 
 #resumption-order-panel {
   -moz-margin-start: -8px;
 }
 
@@ -465,22 +519,27 @@
 
 /* Horizontal vs. vertical layout */
 
 #vertical-layout-panes-container {
   min-height: 35vh;
   max-height: 80vh;
 }
 
+#body[layout=vertical] #sources-pane > tabs {
+  -moz-border-end: none;
+}
+
 #body[layout=vertical] #instruments-pane {
   margin: 0 !important;
   /* To prevent all the margin hacks to hide the sidebar. */
 }
 
-#body[layout=vertical] .side-menu-widget-container {
+#body[layout=vertical] .side-menu-widget-container,
+#body[layout=vertical] .side-menu-widget-empty-notice-container {
   box-shadow: none !important;
 }
 
 #body[layout=vertical] .side-menu-widget-item-arrow {
   background-image: none !important;
 }
 
 #body[layout=vertical] .side-menu-widget-group,
--- a/browser/themes/osx/devtools/netmonitor.css
+++ b/browser/themes/osx/devtools/netmonitor.css
@@ -307,16 +307,20 @@ box.requests-menu-status[code^="5"] {
 .requests-menu-timings-cap.receive {
   background-color: rgba(255,255,255,1.0);
   box-shadow: 0 0 8px 0 rgba(128,255,255,1.0),
               0 0 4px 0 rgba(255,255,255,1.0) inset;
 }
 
 /* SideMenuWidget */
 
+.side-menu-widget-container {
+  box-shadow: none !important;
+}
+
 .side-menu-widget-item[odd] {
   background: rgba(255,255,255,0.05);
 }
 
 /* Network request details */
 
 #details-pane {
   background: hsl(208,11%,27%);
@@ -358,20 +362,16 @@ box.requests-menu-status[code^="5"] {
   text-shadow: 0 1px 0 #000;
   color: hsl(210,30%,85%);
 }
 
 .tabpanel-summary-value {
   -moz-padding-start: 3px;
 }
 
-.variable-or-property:not(:focus) > .title > .token-string {
-  color: #10c !important;
-}
-
 /* Headers tabpanel */
 
 #headers-summary-status,
 #headers-summary-version {
   padding-bottom: 2px;
 }
 
 #headers-summary-size {
--- a/browser/themes/osx/devtools/widgets.css
+++ b/browser/themes/osx/devtools/widgets.css
@@ -276,25 +276,27 @@
 
 .side-menu-widget-container[theme="light"] {
   background: #fff;
   color: #000;
 }
 
 /* SideMenuWidget container */
 
-.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(ltr) {
+.side-menu-widget-container:-moz-locale-dir(ltr),
+.side-menu-widget-empty-notice-container:-moz-locale-dir(ltr) {
   box-shadow: inset -1px 0 0 #222426;
 }
 
-.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(rtl) {
+.side-menu-widget-container:-moz-locale-dir(rtl),
+.side-menu-widget-empty-notice-container:-moz-locale-dir(rtl) {
   box-shadow: inset 1px 0 0 #222426;
 }
 
-.side-menu-widget-container[with-arrows=true] .side-menu-widget-group {
+.side-menu-widget-group {
   /* To allow visibility of the dark margin shadow. */
   -moz-margin-end: 1px;
 }
 
 .side-menu-widget-container[with-arrows=true] .side-menu-widget-item {
   /* To compensate for the arrow image's dark margin. */
   -moz-margin-end: -1px;
 }
@@ -323,36 +325,34 @@
   border-top: 1px solid hsla(210,8%,5%,.25);
   border-bottom: 1px solid hsla(210,16%,76%,.1);
   margin-top: -1px;
   margin-bottom: -1px;
 }
 
 .side-menu-widget-item[theme="light"] {
   border-top: 1px solid hsla(210,8%,75%,.25);
+  border-bottom: 1px solid hsla(210,16%,76%,.1);
   margin-top: -1px;
+  margin-bottom: -1px;
 }
 
 .side-menu-widget-item[theme="dark"]:last-of-type {
   box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25);
 }
 
 .side-menu-widget-item[theme="light"]:last-of-type {
   box-shadow: inset 0 -1px 0 hsla(210,8%,75%,.25);
 }
 
-.side-menu-widget-item[theme="dark"].selected {
+.side-menu-widget-item.selected {
   background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left !important;
   box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15);
 }
 
-.side-menu-widget-item[theme="light"].selected {
-  /* Nothing here yet */
-}
-
 .side-menu-widget-item.selected > .side-menu-widget-item-arrow {
   background-size: auto, 1px 100%;
   background-repeat: no-repeat;
 }
 
 .side-menu-widget-item.selected > .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
   background-image: url(itemArrow-ltr.png), linear-gradient(to right, #222426, #222426);
   background-position: center right, top right;
@@ -421,135 +421,93 @@
 .side-menu-widget-empty-notice-container[theme="light"] {
   background: #fff;
   padding: 4px 8px;
   color: GrayText;
 }
 
 /* VariablesView */
 
-.variables-view-container {
-  background: #fff;
-}
-
 .variables-view-empty-notice {
   color: GrayText;
   padding: 2px;
 }
 
 .variables-view-scope > .title {
   color: #fff;
 }
 
-.variables-view-scope:focus > .title {
-  background: Highlight;
-  color: HighlightText;
-}
-
 .variables-view-scope > .variables-view-element-details:not(:empty) {
   -moz-margin-start: 2px;
   -moz-margin-end: 1px;
 }
 
 /* Generic traits applied to both variables and properties */
 
 .variable-or-property {
-  transition: background 1s ease-in-out;
-  color: #000;
+  transition: background 1s ease-in-out, color 1s ease-in-out;
 }
 
 .variable-or-property[changed] {
-  background: rgba(255,255,0,0.65);
-  transition-duration: 0.4s;
+  color: black;
+  transition-duration: .4s;
 }
 
 .variable-or-property > .title > .value {
   -moz-box-flex: 1;
   -moz-padding-start: 6px;
   -moz-padding-end: 4px;
 }
 
-.variable-or-property:focus > .title {
-  background: Highlight;
-  color: HighlightText;
-  border-radius: 4px;
-}
-
 .variable-or-property[editable] > .title > .value {
   cursor: text;
 }
 
 .variable-or-property:not([non-header]) > .variables-view-element-details {
   -moz-margin-start: 10px;
 }
 
 /* Custom variables and properties traits */
 
 .variables-view-variable {
   -moz-margin-start: 1px;
   -moz-margin-end: 1px;
 }
 
 .variables-view-variable:not(:last-child) {
-  border-bottom: 1px solid #eee;
+  border-bottom: 1px solid rgba(128, 128, 128, .15);
 }
 
 .variables-view-variable > .title > .name {
   font-weight: 600;
 }
 
-.variables-view-variable:not(:focus) > .title > .name {
-  color: #048;
-}
-
-.variables-view-property:not(:focus) > .title > .name {
-  color: #881090;
-}
-
-/* Token value colors */
-
-.variable-or-property:not(:focus) > .title > .token-undefined {
-  color: #bbb;
-}
-
-.variable-or-property:not(:focus) > .title > .token-null {
-  color: #999;
-}
-
-.variable-or-property:not(:focus) > .title > .token-boolean {
-  color: #10c;
-}
-
-.variable-or-property:not(:focus) > .title > .token-number {
-  color: #c00;
-}
-
-.variable-or-property:not(:focus) > .title > .token-string {
-  color: #282;
-}
-
-.variable-or-property:not(:focus) > .title > .token-other {
-  color: #333;
+.variable-or-property:focus > .title > label {
+  color: inherit !important;
 }
 
 /* Custom configurable/enumerable/writable or frozen/sealed/extensible
  * variables and properties */
 
 .variable-or-property[non-enumerable]:not([self]):not([exception]):not([return]):not([scope]) > .title > .name {
-  opacity: 0.5;
+  opacity: 0.6;
 }
 
 .variable-or-property[non-configurable] > .title > .name {
   border-bottom: 1px dashed #99f;
 }
 
 .variable-or-property[non-writable] > .title > .name {
   border-bottom: 1px dashed #f99;
 }
 
+.variable-or-property[safe-getter] > .title > .name {
+  border-bottom: 1px dashed #8b0;
+}
+
 .variable-or-property-non-writable-icon {
   background: url("chrome://browser/skin/identity-icons-https.png") no-repeat;
   width: 16px;
   height: 16px;
   opacity: 0.5;
 }
 
 @media (min-resolution: 2dppx) {
@@ -566,37 +524,16 @@
 }
 
 .variable-or-property:not(:focus) > .title > .variable-or-property-frozen-label,
 .variable-or-property:not(:focus) > .title > .variable-or-property-sealed-label,
 .variable-or-property:not(:focus) > .title > .variable-or-property-non-extensible-label {
   color: #666;
 }
 
-/* Special variables and properties */
-
-.variable-or-property[safe-getter] > .title > .name {
-  border-bottom: 1px dashed #8b0;
-}
-
-.variable-or-property[exception]:not(:focus) > .title > .name {
-  color: #a00;
-  text-shadow: 0 0 8px #fcc;
-}
-
-.variable-or-property[return]:not(:focus) > .title > .name {
-  color: #0a0;
-  text-shadow: 0 0 8px #cfc;
-}
-
-.variable-or-property[scope]:not(:focus) > .title > .name {
-  color: #00a;
-  text-shadow: 0 0 8px #ccf;
-}
-
 /* Aligned values */
 
 .variables-view-container[aligned-values] .title > .separator {
   -moz-box-flex: 1;
 }
 
 .variables-view-container[aligned-values] .title > .value {
   -moz-box-flex: 0;
@@ -669,24 +606,23 @@
 .element-value-input {
   -moz-margin-start: 4px !important;
   -moz-margin-end: 2px !important;
 }
 
 .element-name-input {
   -moz-margin-start: -2px !important;
   -moz-margin-end: 2px !important;
-  color: #048;
   font-weight: 600;
 }
 
 .element-value-input,
 .element-name-input {
-  border: 1px solid #999 !important;
-  box-shadow: 1px 2px 4px #aaa;
+  border: 1px solid rgba(128, 128, 128, .5) !important;
+  color: inherit;
 }
 
 /* Variables and properties searching */
 
 .variables-view-searchinput {
   min-height: 24px;
 }
 
--- a/browser/themes/shared/devtools/dark-theme.css
+++ b/browser/themes/shared/devtools/dark-theme.css
@@ -46,17 +46,18 @@
 .theme-selected {
   background: #26394D;
 }
 
 .theme-bg-darker {
   background-color: rgba(0,0,0,0.5);
 }
 
-.theme-bg-contrast { /* contrast bg color to attract attention on a container */
+.theme-bg-contrast,
+.variable-or-property[changed] { /* contrast bg color to attract attention on a container */
   background: #a18650;
 }
 
 .theme-link,
 .cm-s-mozilla .cm-link { /* blue */
   color: #3689b2;
 }
 
@@ -68,71 +69,82 @@
 .cm-s-mozilla .cm-link:visited { /* blue */
   color: #3689b2;
 }
 
 
 .theme-comment,
 .cm-s-mozilla .cm-meta,
 .cm-s-mozilla .cm-hr,
-.cm-s-mozilla .cm-comment { /* grey */
+.cm-s-mozilla .cm-comment,
+.variable-or-property .token-undefined,
+.variable-or-property .token-null { /* grey */
   color: #5c6773;
 }
 
 .theme-gutter {
   background-color: #0f171f;
   color: #667380;
   border-color: #303b47;
 }
 
 .theme-separator { /* grey */
   border-color: #303b47;
 }
 
 .theme-fg-color1,
-.cm-s-mozilla .cm-number { /* green */
+.cm-s-mozilla .cm-number,
+.variable-or-property .token-number,
+.variable-or-property[return] > .title > .name { /* green */
   color: #5c9966;
 }
 
 .theme-fg-color2,
 .cm-s-mozilla .cm-attribute,
 .cm-s-mozilla .cm-variable,
 .cm-s-mozilla .cm-def,
 .cm-s-mozilla .cm-property,
-.cm-s-mozilla .cm-qualifier { /* blue */
+.cm-s-mozilla .cm-qualifier,
+.variables-view-variable > .title > .name,
+.variable-or-property[scope] > .title > .name { /* blue */
   color: #3689b2;
 }
 
 .theme-fg-color3,
 .cm-s-mozilla .cm-builtin,
 .cm-s-mozilla .cm-tag,
-.cm-s-mozilla .cm-header { /* pink/lavender */
+.cm-s-mozilla .cm-header,
+.variables-view-property > .title > .name,
+.variable-or-property[safe-getter] > .title > .name { /* pink/lavender */
   color: #a673bf;
 }
 
 .theme-fg-color4 { /* purple/violet */
   color: #6270b2;
 }
 
 .theme-fg-color5,
 .cm-s-mozilla .cm-bracket,
 .cm-s-mozilla .cm-keyword { /* Yellow */
   color: #a18650;
 }
 
 .theme-fg-color6,
 .cm-s-mozilla .cm-string,
-.cm-s-mozilla .cm-string-2 { /* Orange */
+.cm-s-mozilla .cm-string-2,
+.variable-or-property .token-string { /* Orange */
   color: #b26b47;
 }
 
 .theme-fg-color7,
 .cm-s-mozilla .cm-atom,
 .cm-s-mozilla .cm-quote,
-.cm-s-mozilla .cm-error { /* Red */
+.cm-s-mozilla .cm-error,
+.variable-or-property .token-boolean,
+.variable-or-property[exception] > .title > .name { /* Red */
   color: #bf5656;
 }
 
 .theme-toolbar,
 .devtools-toolbar { /* General toolbar styling */
   color: hsl(210,30%,85%);
   background-color: #343c45;
   border-color: #060a0d;
@@ -143,16 +155,22 @@
 }
 
 .ruleview-colorswatch,
 .computedview-colorswatch,
 .markupview-colorswatch {
   box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
 }
 
+.variables-view-scope:focus > .title,
+.variable-or-property:focus > .title {
+  background:  #3689b2; /* fg-color2 */
+  color: white;
+}
+
 /* CodeMirror specific styles.
  * Best effort to match the existing theme, some of the colors
  * are duplicated here to prevent weirdness in the main theme. */
 
 .CodeMirror { /* Inherit platform specific font sizing and styles */
   font-family: inherit;
   font-size: inherit;
   background: transparent;
--- a/browser/themes/shared/devtools/light-theme.css
+++ b/browser/themes/shared/devtools/light-theme.css
@@ -46,17 +46,18 @@
 .theme-selected {
   background-color: #CCC;
 }
 
 .theme-bg-darker {
   background: #EFEFEF;
 }
 
-.theme-bg-contrast { /* contrast bg color to attract attention on a container */
+.theme-bg-contrast,
+.variable-or-property[changed] { /* contrast bg color to attract attention on a container */
   background: #a18650;
 }
 
 .theme-link,
 .cm-s-mozilla .cm-link { /* blue */
   color: hsl(208,56%,40%);
 }
 
@@ -67,71 +68,82 @@
 .theme-link:visited,
 .cm-s-mozilla .cm-link:visited { /* blue */
   color: hsl(208,56%,40%);
 }
 
 .theme-comment,
 .cm-s-mozilla .cm-meta,
 .cm-s-mozilla .cm-hr,
-.cm-s-mozilla .cm-comment { /* grey */
+.cm-s-mozilla .cm-comment,
+.variable-or-property .token-undefined,
+.variable-or-property .token-null { /* grey */
   color: hsl(90,2%,46%);
 }
 
 .theme-gutter {
   background-color: hsl(0,0%,90%);
   color: #667380;
   border-color: hsl(0,0%,65%);
 }
 
 .theme-separator { /* grey */
   border-color: #cddae5;
 }
 
 .theme-fg-color1,
-.cm-s-mozilla .cm-number { /* green */
+.cm-s-mozilla .cm-number,
+.variable-or-property .token-number,
+.variable-or-property[return] > .title > .name { /* green */
   color: hsl(72,100%,27%);
 }
 
 .theme-fg-color2,
 .cm-s-mozilla .cm-attribute,
 .cm-s-mozilla .cm-builtin,
 .cm-s-mozilla .cm-def,
 .cm-s-mozilla .cm-property,
-.cm-s-mozilla .cm-qualifier { /* blue */
+.cm-s-mozilla .cm-qualifier,
+.variables-view-variable > .title > .name,
+.variable-or-property[scope] > .title > .name { /* blue */
   color: hsl(208,56%,40%);
 }
 
 .theme-fg-color3,
 .cm-s-mozilla .cm-variable,
 .cm-s-mozilla .cm-tag,
-.cm-s-mozilla .cm-header { /* dark blue */
+.cm-s-mozilla .cm-header,
+.variables-view-property > .title > .name,
+.variable-or-property[safe-getter] > .title > .name { /* dark blue */
   color: hsl(208,81%,21%)
 }
 
 .theme-fg-color4 { /* Orange */
   color: hsl(24,85%,39%);
 }
 
 .theme-fg-color5,
 .cm-s-mozilla .cm-bracket,
 .cm-s-mozilla .cm-keyword { /* Yellow */
   color: #a18650;
 }
 
 .theme-fg-color6,
 .cm-s-mozilla .cm-string,
-.cm-s-mozilla .cm-string-2 { /* Orange */
+.cm-s-mozilla .cm-string-2,
+.variable-or-property .token-string { /* Orange */
   color: hsl(24,85%,39%);
 }
 
 .theme-fg-color7,
 .cm-s-mozilla .cm-atom,
 .cm-s-mozilla .cm-quote,
-.cm-s-mozilla .cm-error { /* Red */
+.cm-s-mozilla .cm-error,
+.variable-or-property .token-boolean,
+.variable-or-property[exception] > .title > .name { /* Red */
   color: #bf5656;
 }
 
 .theme-fg-contrast { /* To be used for text on theme-bg-contrast */
   color: black;
 }
 
 .theme-toolbar,
@@ -142,16 +154,22 @@
 }
 
 .ruleview-colorswatch,
 .computedview-colorswatch,
 .markupview-colorswatch {
   box-shadow: 0 0 0 1px #EFEFEF;
 }
 
+.variables-view-scope:focus > .title,
+.variable-or-property:focus > .title {
+  background:  hsl(208,56%,40%); /* fg-color2 */
+  color: white;
+}
+
 /* CodeMirror specific styles.
  * Best effort to match the existing theme, some of the colors
  * are duplicated here to prevent weirdness in the main theme. */
 
 .CodeMirror { /* Inherit platform specific font sizing and styles */
   font-family: inherit;
   font-size: inherit;
   background: transparent;
--- a/browser/themes/shared/devtools/toolbars.inc.css
+++ b/browser/themes/shared/devtools/toolbars.inc.css
@@ -255,21 +255,25 @@
   -moz-margin-start: -1px;
 }
 
 .devtools-sidebar-tabs > tabs > tab:first-of-type {
   -moz-margin-start: -3px;
 }
 
 .devtools-sidebar-tabs > tabs > tab {
-  background-size: calc(100% - 2px) 100%, 1px 100%;
+  background-size: calc(100% - 1px) 100%, 1px 100%;
   background-repeat: no-repeat;
   background-position: 1px, 0;
 }
 
+.devtools-sidebar-tabs > tabs > tab:not(:last-of-type) {
+  background-size: calc(100% - 2px) 100%, 1px 100%;
+}
+
 .devtools-sidebar-tabs:-moz-locale-dir(rtl) > tabs > tab {
   background-position: calc(100% - 1px), 100%;
 }
 
 .devtools-sidebar-tabs > tabs > tab {
   background-color: transparent;
   background-image: linear-gradient(transparent, transparent), @smallSeparator@;
 }
--- a/browser/themes/shared/devtools/webconsole.inc.css
+++ b/browser/themes/shared/devtools/webconsole.inc.css
@@ -286,21 +286,21 @@ a {
 
 .inlined-variables-view .body {
   display: flex;
   flex-direction: column;
 }
 .inlined-variables-view iframe {
   display: block;
   flex: 1;
+  margin-top: 5px;
   margin-bottom: 15px;
   -moz-margin-end: 15px;
-  border: 1px solid #ccc;
-  border-radius: 4px;
-  box-shadow: 0 0 12px #dfdfdf;
+  border: 1px solid rgba(128, 128, 128, .5);
+  border-radius: 3px;
 }
 
 #webconsole-sidebar > tabs {
   height: 0;
   border: none;
 }
 
 /* Security styles */
@@ -349,24 +349,33 @@ a {
 .theme-dark .jsterm-complete-node {
   color: #5c6773; /* commentColor */
 }
 
 .theme-dark .navigation-marker .url {
   background: #131c26; /* mainBackgroundColor */
 }
 
+.theme-dark .inlined-variables-view iframe {
+  border-color: #333;
+}
+
 .theme-light .jsterm-input-container {
   background-color: #fff; /* mainBackgroundColor */
   border-color: ThreeDShadow;
 }
 
 .theme-light .jsterm-input-node {
   color: black; /* textColor */
 }
 
 .theme-light .jsterm-complete-node {
   color: hsl(90,2%,46%); /* commentColor */
 }
 
 .theme-light .navigation-marker .url {
   background: #fff; /* mainBackgroundColor */
 }
+
+.theme-light .inlined-variables-view iframe {
+  border-color: #ccc;
+}
+
--- a/browser/themes/windows/devtools/debugger.css
+++ b/browser/themes/windows/devtools/debugger.css
@@ -4,28 +4,32 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Sources and breakpoints pane */
 
 #sources-pane {
   min-width: 50px;
 }
 
+#sources-pane > tabs {
+  -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
+}
+
 #sources-and-editor-splitter {
   -moz-border-start-color: transparent;
 }
 
 /* Sources toolbar */
 
 #sources-toolbar {
   border: none; /* Remove the devtools-toolbar's black bottom border. */
   -moz-border-end: 1px solid #222426; /* Match the sources list's dark margin. */
 }
 
-#sources-toolbar .devtools-toolbarbutton {
+#sources-toolbar > #sources-controls > .devtools-toolbarbutton {
   min-width: 32px;
 }
 
 #pretty-print {
   font-weight: bold;
 }
 
 #black-box {
@@ -82,50 +86,89 @@
 }
 
 /* ListWidget items */
 
 .list-widget-item {
   padding: 2px;
 }
 
-.list-widget-item:not(.selected):not(.empty):hover {
+.theme-light .list-widget-item:not(.selected):not(.empty):hover {
   background: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.85)), Highlight;
 }
 
-.list-widget-item.selected.light {
+.theme-light .list-widget-item.selected.light {
   background: linear-gradient(rgba(255,255,255,0.85), rgba(255,255,255,0.8)), Highlight;
   color: #000;
 }
 
+.theme-dark .list-widget-item:not(.selected):not(.empty):hover {
+  background: linear-gradient(rgba(255,255,255,0.1), rgba(255,255,255,0.05));
+}
+
+.theme-dark .list-widget-item.selected.light {
+  background: linear-gradient(rgba(255,255,255,0.05), rgba(255,255,255,0.025));
+}
+
 .list-widget-item.selected {
   background: Highlight;
   color: HighlightText;
 }
 
 .list-widget-item.empty {
   color: GrayText;
   padding: 2px;
 }
 
-/* Stack frames view */
+/* Breadcrumbs stack frames view */
+
+.breadcrumbs-widget-item {
+  max-width: none;
+}
 
 .dbg-stackframe-details {
   -moz-padding-start: 4px;
 }
 
-.dbg-stackframe-menuitem[checked] {
-  margin-top: 3px;
-  margin-bottom: 3px;
-  outline: 1px solid #eee;
+/* Classic stack frames view */
+
+.dbg-classic-stackframe {
+  display: block;
+  padding: 4px;
+}
+
+.dbg-classic-stackframe-title {
   font-weight: 600;
+  color: #046;
+}
+
+.dbg-classic-stackframe-details:-moz-locale-dir(ltr) {
+  float: right;
 }
 
-.dbg-stackframe-menuitem-details {
-  -moz-padding-start: 16px;
+.dbg-classic-stackframe-details:-moz-locale-dir(rtl) {
+  float: left;
+}
+
+.dbg-classic-stackframe-details-url {
+  max-width: 90%;
+  text-align: end;
+  color: #666;
+}
+
+.dbg-classic-stackframe-details-sep {
+  color: #aaa;
+}
+
+.dbg-classic-stackframe-details-line {
+  color: #58b;
+}
+
+#callstack-list .side-menu-widget-item.selected label {
+  color: #fff;
 }
 
 /* Sources and breakpoints view */
 
 .dbg-breakpoint {
   -moz-margin-start: 4px;
 }
 
@@ -175,18 +218,19 @@
 
 /* Instruments pane (watch expressions, variables, event listeners...) */
 
 #instruments-pane > tabs > tab {
   min-height: 25px !important;
   padding: 0 !important;
 }
 
-#instruments-pane > tabpanels > tabpanel {
-  background: #fff;
+#instruments-pane .side-menu-widget-container,
+#instruments-pane .side-menu-widget-empty-notice-container {
+  box-shadow: none !important;
 }
 
 /* Watch expressions view */
 
 #expressions {
   min-height: 10px;
   max-height: 125px;
 }
@@ -198,16 +242,17 @@
 .dbg-expression-arrow {
   width: 16px;
   height: auto;
   background: -moz-image-rect(url(commandline-icon.png), 0, 32, 16, 16);
 }
 
 .dbg-expression-input {
   -moz-padding-start: 2px !important;
+  color: inherit;
 }
 
 /* Event listeners view */
 
 .dbg-event-listener {
   padding: 4px 8px;
 }
 
@@ -222,16 +267,20 @@
 .dbg-event-listener-targets {
   color: #046;
 }
 
 .dbg-event-listener-location {
   color: #666;
 }
 
+#event-listeners .side-menu-widget-item.selected {
+  background: none !important;
+}
+
 /* Searchbox and the search operations help panel */
 
 #searchbox {
   min-width: 220px;
   -moz-margin-start: 1px;
 }
 
 #filter-label {
@@ -375,16 +424,21 @@
 
 .dbg-results-line-contents-string[match=true][focused] {
   transition-duration: 0.1s;
   transform: scale(1.75, 1.75);
 }
 
 /* Toolbar controls */
 
+.devtools-sidebar-tabs > tabs > tab {
+  min-height: 25px !important;
+  padding: 0 !important;
+}
+
 #resumption-panel-desc {
   width: 200px;
 }
 
 #resumption-order-panel {
   -moz-margin-start: -8px;
 }
 
@@ -468,22 +522,27 @@
 
 /* Horizontal vs. vertical layout */
 
 #vertical-layout-panes-container {
   min-height: 35vh;
   max-height: 80vh;
 }
 
+#body[layout=vertical] #sources-pane > tabs {
+  -moz-border-end: none;
+}
+
 #body[layout=vertical] #instruments-pane {
   margin: 0 !important;
   /* To prevent all the margin hacks to hide the sidebar. */
 }
 
-#body[layout=vertical] .side-menu-widget-container {
+#body[layout=vertical] .side-menu-widget-container,
+#body[layout=vertical] .side-menu-widget-empty-notice-container {
   box-shadow: none !important;
 }
 
 #body[layout=vertical] .side-menu-widget-item-arrow {
   background-image: none !important;
 }
 
 #body[layout=vertical] .side-menu-widget-group,
--- a/browser/themes/windows/devtools/netmonitor.css
+++ b/browser/themes/windows/devtools/netmonitor.css
@@ -307,16 +307,20 @@ box.requests-menu-status[code^="5"] {
 .requests-menu-timings-cap.receive {
   background-color: rgba(255,255,255,1.0);
   box-shadow: 0 0 8px 0 rgba(128,255,255,1.0),
               0 0 4px 0 rgba(255,255,255,1.0) inset;
 }
 
 /* SideMenuWidget */
 
+.side-menu-widget-container {
+  box-shadow: none !important;
+}
+
 .side-menu-widget-item[odd] {
   background: rgba(255,255,255,0.05);
 }
 
 /* Network request details */
 
 #details-pane {
   background: hsl(208,11%,27%);
@@ -358,20 +362,16 @@ box.requests-menu-status[code^="5"] {
   text-shadow: 0 1px 0 #000;
   color: hsl(210,30%,85%);
 }
 
 .tabpanel-summary-value {
   -moz-padding-start: 3px;
 }
 
-.variable-or-property:not(:focus) > .title > .token-string {
-  color: #10c !important;
-}
-
 /* Headers tabpanel */
 
 #headers-summary-status,
 #headers-summary-version {
   padding-bottom: 2px;
 }
 
 #headers-summary-size {
--- a/browser/themes/windows/devtools/widgets.css
+++ b/browser/themes/windows/devtools/widgets.css
@@ -280,25 +280,27 @@
 
 .side-menu-widget-container[theme="light"] {
   background: #fff;
   color: #000;
 }
 
 /* SideMenuWidget container */
 
-.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(ltr) {
+.side-menu-widget-container:-moz-locale-dir(ltr),
+.side-menu-widget-empty-notice-container:-moz-locale-dir(ltr) {
   box-shadow: inset -1px 0 0 #222426;
 }
 
-.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(rtl) {
+.side-menu-widget-container:-moz-locale-dir(rtl),
+.side-menu-widget-empty-notice-container:-moz-locale-dir(rtl) {
   box-shadow: inset 1px 0 0 #222426;
 }
 
-.side-menu-widget-container[with-arrows=true] .side-menu-widget-group {
+.side-menu-widget-group {
   /* To allow visibility of the dark margin shadow. */
   -moz-margin-end: 1px;
 }
 
 .side-menu-widget-container[with-arrows=true] .side-menu-widget-item {
   /* To compensate for the arrow image's dark margin. */
   -moz-margin-end: -1px;
 }
@@ -327,36 +329,34 @@
   border-top: 1px solid hsla(210,8%,5%,.25);
   border-bottom: 1px solid hsla(210,16%,76%,.1);
   margin-top: -1px;
   margin-bottom: -1px;
 }
 
 .side-menu-widget-item[theme="light"] {
   border-top: 1px solid hsla(210,8%,75%,.25);
+  border-bottom: 1px solid hsla(210,16%,76%,.1);
   margin-top: -1px;
+  margin-bottom: -1px;
 }
 
 .side-menu-widget-item[theme="dark"]:last-of-type {
   box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25);
 }
 
 .side-menu-widget-item[theme="light"]:last-of-type {
   box-shadow: inset 0 -1px 0 hsla(210,8%,75%,.25);
 }
 
-.side-menu-widget-item[theme="dark"].selected {
+.side-menu-widget-item.selected {
   background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left !important;
   box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15);
 }
 
-.side-menu-widget-item[theme="light"].selected {
-  /* Nothing here yet */
-}
-
 .side-menu-widget-item.selected > .side-menu-widget-item-arrow {
   background-size: auto, 1px 100%;
   background-repeat: no-repeat;
 }
 
 .side-menu-widget-item.selected > .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
   background-image: url(itemArrow-ltr.png), linear-gradient(to right, #222426, #222426);
   background-position: center right, top right;
@@ -424,135 +424,93 @@
 .side-menu-widget-empty-notice-container[theme="light"] {
   background: #fff;
   padding: 4px 8px;
   color: GrayText;
 }
 
 /* VariablesView */
 
-.variables-view-container {
-  background: #fff;
-}
-
 .variables-view-empty-notice {
   color: GrayText;
   padding: 2px;
 }
 
 .variables-view-scope > .title {
   color: #fff;
 }
 
-.variables-view-scope:focus > .title {
-  background: Highlight;
-  color: HighlightText;
-}
-
 .variables-view-scope > .variables-view-element-details:not(:empty) {
   -moz-margin-start: 2px;
   -moz-margin-end: 1px;
 }
 
 /* Generic traits applied to both variables and properties */
 
 .variable-or-property {
-  transition: background 1s ease-in-out;
-  color: #000;
+  transition: background 1s ease-in-out, color 1s ease-in-out;
 }
 
 .variable-or-property[changed] {
-  background: rgba(255,255,0,0.65);
-  transition-duration: 0.4s;
+  color: black;
+  transition-duration: .4s;
 }
 
 .variable-or-property > .title > .value {
   -moz-box-flex: 1;
   -moz-padding-start: 6px;
   -moz-padding-end: 4px;
 }
 
-.variable-or-property:focus > .title {
-  background: Highlight;
-  color: HighlightText;
-  border-radius: 4px;
-}
-
 .variable-or-property[editable] > .title > .value {
   cursor: text;
 }
 
 .variable-or-property:not([non-header]) > .variables-view-element-details {
   -moz-margin-start: 10px;
 }
 
 /* Custom variables and properties traits */
 
 .variables-view-variable {
   -moz-margin-start: 1px;
   -moz-margin-end: 1px;
 }
 
 .variables-view-variable:not(:last-child) {
-  border-bottom: 1px solid #eee;
+  border-bottom: 1px solid rgba(128, 128, 128, .15);
 }
 
 .variables-view-variable > .title > .name {
   font-weight: 600;
 }
 
-.variables-view-variable:not(:focus) > .title > .name {
-  color: #048;
-}
-
-.variables-view-property:not(:focus) > .title > .name {
-  color: #881090;
-}
-
-/* Token value colors */
-
-.variable-or-property:not(:focus) > .title > .token-undefined {
-  color: #bbb;
-}
-
-.variable-or-property:not(:focus) > .title > .token-null {
-  color: #999;
-}
-
-.variable-or-property:not(:focus) > .title > .token-boolean {
-  color: #10c;
-}
-
-.variable-or-property:not(:focus) > .title > .token-number {
-  color: #c00;
-}
-
-.variable-or-property:not(:focus) > .title > .token-string {
-  color: #282;
-}
-
-.variable-or-property:not(:focus) > .title > .token-other {
-  color: #333;
+.variable-or-property:focus > .title > label {
+  color: inherit !important;
 }
 
 /* Custom configurable/enumerable/writable or frozen/sealed/extensible
  * variables and properties */
 
 .variable-or-property[non-enumerable]:not([self]):not([exception]):not([return]):not([scope]) > .title > .name {
-  opacity: 0.5;
+  opacity: 0.6;
 }
 
 .variable-or-property[non-configurable] > .title > .name {
   border-bottom: 1px dashed #99f;
 }
 
 .variable-or-property[non-writable] > .title > .name {
   border-bottom: 1px dashed #f99;
 }
 
+.variable-or-property[safe-getter] > .title > .name {
+  border-bottom: 1px dashed #8b0;
+}
+
 .variable-or-property-non-writable-icon {
   background: url("chrome://browser/skin/identity-icons-https.png") no-repeat;
   width: 16px;
   height: 16px;
   opacity: 0.5;
 }
 
 @media (min-resolution: 2dppx) {
@@ -569,37 +527,16 @@
 }
 
 .variable-or-property:not(:focus) > .title > .variable-or-property-frozen-label,
 .variable-or-property:not(:focus) > .title > .variable-or-property-sealed-label,
 .variable-or-property:not(:focus) > .title > .variable-or-property-non-extensible-label {
   color: #666;
 }
 
-/* Special variables and properties */
-
-.variable-or-property[safe-getter] > .title > .name {
-  border-bottom: 1px dashed #8b0;
-}
-
-.variable-or-property[exception]:not(:focus) > .title > .name {
-  color: #a00;
-  text-shadow: 0 0 8px #fcc;
-}
-
-.variable-or-property[return]:not(:focus) > .title > .name {
-  color: #0a0;
-  text-shadow: 0 0 8px #cfc;
-}
-
-.variable-or-property[scope]:not(:focus) > .title > .name {
-  color: #00a;
-  text-shadow: 0 0 8px #ccf;
-}
-
 /* Aligned values */
 
 .variables-view-container[aligned-values] .title > .separator {
   -moz-box-flex: 1;
 }
 
 .variables-view-container[aligned-values] .title > .value {
   -moz-box-flex: 0;
@@ -672,24 +609,23 @@
 .element-value-input {
   -moz-margin-start: 4px !important;
   -moz-margin-end: 2px !important;
 }
 
 .element-name-input {
   -moz-margin-start: -2px !important;
   -moz-margin-end: 2px !important;
-  color: #048;
   font-weight: 600;
 }
 
 .element-value-input,
 .element-name-input {
-  border: 1px solid #999 !important;
-  box-shadow: 1px 2px 4px #aaa;
+  border: 1px solid rgba(128, 128, 128, .5) !important;
+  color: inherit;
 }
 
 /* Variables and properties searching */
 
 .variables-view-searchinput {
   min-height: 24px;
 }
 
--- a/build/mobile/robocop/FennecNativeActions.java
+++ b/build/mobile/robocop/FennecNativeActions.java
@@ -1,150 +1,102 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.lang.reflect.InvocationHandler;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.ArrayList;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.gfx.GeckoLayerClient;
+import org.mozilla.gecko.gfx.GeckoLayerClient.DrawListener;
+import org.mozilla.gecko.mozglue.GeckoLoader;
+import org.mozilla.gecko.sqlite.SQLiteBridge;
+import org.mozilla.gecko.util.GeckoEventListener;
 
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.database.Cursor;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.ArrayList;
+import org.json.JSONObject;
 
 import com.jayway.android.robotium.solo.Solo;
 
 import static org.mozilla.gecko.FennecNativeDriver.LogLevel;
 
 public class FennecNativeActions implements Actions {
+    private static final String LOGTAG = "FennecNativeActions";
+
     private Solo mSolo;
     private Instrumentation mInstr;
-    private Activity mGeckoApp;
     private Assert mAsserter;
 
-    // Objects for reflexive access of fennec classes.
-    private ClassLoader mClassLoader;
-    private Class mApiClass;
-    private Class mEventListenerClass;
-    private Class mDrawListenerClass;
-    private Method mRegisterEventListener;
-    private Method mUnregisterEventListener;
-    private Method mBroadcastEvent;
-    private Method mPreferencesGetEvent;
-    private Method mPreferencesObserveEvent;
-    private Method mPreferencesRemoveObserversEvent;
-    private Method mSetDrawListener;
-    private Method mQuerySql;
-    private Object mRobocopApi;
-
-    private static final String LOGTAG = "FennecNativeActions";
-
     public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation, Assert asserter) {
         mSolo = robocop;
         mInstr = instrumentation;
-        mGeckoApp = activity;
         mAsserter = asserter;
-        // Set up reflexive access of java classes and methods.
-        try {
-            mClassLoader = activity.getClassLoader();
-
-            mApiClass = mClassLoader.loadClass("org.mozilla.gecko.RobocopAPI");
-            mEventListenerClass = mClassLoader.loadClass("org.mozilla.gecko.util.GeckoEventListener");
-            mDrawListenerClass = mClassLoader.loadClass("org.mozilla.gecko.gfx.GeckoLayerClient$DrawListener");
-
-            mRegisterEventListener = mApiClass.getMethod("registerEventListener", String.class, mEventListenerClass);
-            mUnregisterEventListener = mApiClass.getMethod("unregisterEventListener", String.class, mEventListenerClass);
-            mBroadcastEvent = mApiClass.getMethod("broadcastEvent", String.class, String.class);
-            mPreferencesGetEvent = mApiClass.getMethod("preferencesGetEvent", Integer.TYPE, String[].class);
-            mPreferencesObserveEvent = mApiClass.getMethod("preferencesObserveEvent", Integer.TYPE, String[].class);
-            mPreferencesRemoveObserversEvent = mApiClass.getMethod("preferencesRemoveObserversEvent", Integer.TYPE);
-            mSetDrawListener = mApiClass.getMethod("setDrawListener", mDrawListenerClass);
-            mQuerySql = mApiClass.getMethod("querySql", String.class, String.class);
-
-            mRobocopApi = mApiClass.getConstructor(Activity.class).newInstance(activity);
-        } catch (Exception e) {
-            FennecNativeDriver.log(LogLevel.ERROR, e);
-        }
-    }
 
-    class wakeInvocationHandler implements InvocationHandler {
-        private final GeckoEventExpecter mEventExpecter;
-
-        public wakeInvocationHandler(GeckoEventExpecter expecter) {
-            mEventExpecter = expecter;
-        }
-
-        public Object invoke(Object proxy, Method method, Object[] args) {
-            String methodName = method.getName();
-            //Depending on the method, return a completely different type.
-            if(methodName.equals("toString")) {
-                return this.toString();
-            }
-            if(methodName.equals("equals")) {
-                return
-                    args[0] == null ? false :
-                    this.toString().equals(args[0].toString());
-            }
-            if(methodName.equals("clone")) {
-                return this;
-            }
-            if(methodName.equals("hashCode")) {
-                return 314;
-            }
-            FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, 
-                "Waking up on "+methodName);
-            mEventExpecter.notifyOfEvent(args);
-            return null;
-        }
+        GeckoLoader.loadSQLiteLibs(activity, activity.getApplication().getPackageResourcePath());
     }
 
     class GeckoEventExpecter implements RepeatedEventExpecter {
+        private static final int MAX_WAIT_MS = 90000;
+
+        private volatile boolean mIsRegistered;
+
         private final String mGeckoEvent;
-        private Object[] mRegistrationParams;
-        private boolean mEventEverReceived;
+        private final GeckoEventListener mListener;
+
+        private volatile boolean mEventEverReceived;
         private String mEventData;
         private BlockingQueue<String> mEventDataQueue;
-        private static final int MAX_WAIT_MS = 90000;
 
-        GeckoEventExpecter(String geckoEvent, Object[] registrationParams) {
+        GeckoEventExpecter(final String geckoEvent) {
             if (TextUtils.isEmpty(geckoEvent)) {
                 throw new IllegalArgumentException("geckoEvent must not be empty");
             }
-            if (registrationParams == null || registrationParams.length == 0) {
-                throw new IllegalArgumentException("registrationParams must not be empty");
-            }
 
             mGeckoEvent = geckoEvent;
-            mRegistrationParams = registrationParams;
             mEventDataQueue = new LinkedBlockingQueue<String>();
+
+            final GeckoEventExpecter expecter = this;
+            mListener = new GeckoEventListener() {
+                @Override
+                public void handleMessage(final String event, final JSONObject message) {
+                    FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
+                            "handleMessage called for: " + event + "; expecting: " + mGeckoEvent);
+                    mAsserter.is(event, mGeckoEvent, "Given message occurred for registered event");
+
+                    expecter.notifyOfEvent(message);
+                }
+            };
+
+            GeckoAppShell.registerEventListener(mGeckoEvent, mListener);
+            mIsRegistered = true;
         }
 
         public void blockForEvent() {
             blockForEvent(MAX_WAIT_MS, true);
         }
 
         private void blockForEvent(long millis, boolean failOnTimeout) {
-            if (mRegistrationParams == null) {
+            if (!mIsRegistered) {
                 throw new IllegalStateException("listener not registered");
             }
+
             try {
                 mEventData = mEventDataQueue.poll(millis, TimeUnit.MILLISECONDS);
             } catch (InterruptedException ie) {
                 FennecNativeDriver.log(LogLevel.ERROR, ie);
             }
             if (mEventData == null) {
                 if (failOnTimeout) {
                     FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR);
@@ -156,22 +108,23 @@ public class FennecNativeActions impleme
                 }
             } else {
                 FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
                     "unblocked on expecter for " + mGeckoEvent);
             }
         }
 
         public void blockUntilClear(long millis) {
-            if (mRegistrationParams == null) {
+            if (!mIsRegistered) {
                 throw new IllegalStateException("listener not registered");
             }
             if (millis <= 0) {
                 throw new IllegalArgumentException("millis must be > 0");
             }
+
             // wait for at least one event
             try {
                 mEventData = mEventDataQueue.poll(MAX_WAIT_MS, TimeUnit.MILLISECONDS);
             } catch (InterruptedException ie) {
                 FennecNativeDriver.log(LogLevel.ERROR, ie);
             }
             if (mEventData == null) {
                 FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR);
@@ -200,149 +153,92 @@ public class FennecNativeActions impleme
         }
 
         public String blockForEventDataWithTimeout(long millis) {
             blockForEvent(millis, false);
             return mEventData;
         }
 
         public void unregisterListener() {
-            if (mRegistrationParams == null) {
+            if (!mIsRegistered) {
                 throw new IllegalStateException("listener not registered");
             }
-            try {
-                FennecNativeDriver.log(LogLevel.INFO, "EventExpecter: no longer listening for "+mGeckoEvent);
-                mUnregisterEventListener.invoke(mRobocopApi, mRegistrationParams);
-                mRegistrationParams = null;
-            } catch (IllegalAccessException e) {
-                FennecNativeDriver.log(LogLevel.ERROR, e);
-            } catch (InvocationTargetException e) {
-                FennecNativeDriver.log(LogLevel.ERROR, e);
-            }
+
+            FennecNativeDriver.log(LogLevel.INFO,
+                    "EventExpecter: no longer listening for " + mGeckoEvent);
+
+            GeckoAppShell.unregisterEventListener(mGeckoEvent, mListener);
+            mIsRegistered = false;
         }
 
-        public synchronized boolean eventReceived() {
+        public boolean eventReceived() {
             return mEventEverReceived;
         }
 
-        void notifyOfEvent(Object[] args) {
+        void notifyOfEvent(final JSONObject message) {
             FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
-                "received event " + mGeckoEvent);
-            synchronized (this) {
-                mEventEverReceived = true;
-            }
+                    "received event " + mGeckoEvent);
+
+            mEventEverReceived = true;
+
             try {
-                mEventDataQueue.put(args[1].toString());
+                mEventDataQueue.put(message.toString());
             } catch (InterruptedException e) {
                 FennecNativeDriver.log(LogLevel.ERROR,
-                    "EventExpecter dropped event: "+args[1].toString());
-                FennecNativeDriver.log(LogLevel.ERROR, e);
+                    "EventExpecter dropped event: " + message.toString(), e);
             }
         }
     }
 
-    public RepeatedEventExpecter expectGeckoEvent(String geckoEvent) {
-        FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
-            "waiting for "+geckoEvent);
-        try {
-            Object[] finalParams = new Object[2];
-            finalParams[0] = geckoEvent;
-            GeckoEventExpecter expecter = new GeckoEventExpecter(geckoEvent, finalParams);
-            wakeInvocationHandler wIH = new wakeInvocationHandler(expecter);
-            Object proxy = Proxy.newProxyInstance(mClassLoader, new Class[] { mEventListenerClass }, wIH);
-            finalParams[1] = proxy;
-
-            mRegisterEventListener.invoke(mRobocopApi, finalParams);
-            return expecter;
-        } catch (IllegalAccessException e) {
-            FennecNativeDriver.log(LogLevel.ERROR, e);
-        } catch (InvocationTargetException e) {
-            FennecNativeDriver.log(LogLevel.ERROR, e);
-        }
-        return null;
+    public RepeatedEventExpecter expectGeckoEvent(final String geckoEvent) {
+        FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, "waiting for " + geckoEvent);
+        return new GeckoEventExpecter(geckoEvent);
     }
 
-    public void sendGeckoEvent(String geckoEvent, String data) {
-        try {
-            mBroadcastEvent.invoke(mRobocopApi, geckoEvent, data);
-        } catch (IllegalAccessException e) {
-            FennecNativeDriver.log(LogLevel.ERROR, e);
-        } catch (InvocationTargetException e) {
-            FennecNativeDriver.log(LogLevel.ERROR, e);
-        }
-    }
-
-    private void sendPreferencesEvent(Method method, int requestId, String[] prefNames) {
-        try {
-            method.invoke(mRobocopApi, requestId, prefNames);
-        } catch (IllegalAccessException e) {
-            FennecNativeDriver.log(LogLevel.ERROR, e);
-        } catch (InvocationTargetException e) {
-            FennecNativeDriver.log(LogLevel.ERROR, e);
-        }
+    public void sendGeckoEvent(final String geckoEvent, final String data) {
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(geckoEvent, data));
     }
 
     public void sendPreferencesGetEvent(int requestId, String[] prefNames) {
-        sendPreferencesEvent(mPreferencesGetEvent, requestId, prefNames);
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesGetEvent(requestId, prefNames));
     }
 
     public void sendPreferencesObserveEvent(int requestId, String[] prefNames) {
-        sendPreferencesEvent(mPreferencesObserveEvent, requestId, prefNames);
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesObserveEvent(requestId, prefNames));
     }
 
     public void sendPreferencesRemoveObserversEvent(int requestId) {
-        try {
-            mPreferencesRemoveObserversEvent.invoke(mRobocopApi, requestId);
-        } catch (IllegalAccessException e) {
-            FennecNativeDriver.log(LogLevel.ERROR, e);
-        } catch (InvocationTargetException e) {
-            FennecNativeDriver.log(LogLevel.ERROR, e);
-        } 
-    }
-
-    class DrawListenerProxy implements InvocationHandler {
-        private final PaintExpecter mPaintExpecter;
-
-        DrawListenerProxy(PaintExpecter paintExpecter) {
-            mPaintExpecter = paintExpecter;
-        }
-
-        public Object invoke(Object proxy, Method method, Object[] args) {
-            String methodName = method.getName();
-            if ("drawFinished".equals(methodName)) {
-                FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
-                    "Received drawFinished notification");
-                mPaintExpecter.notifyOfEvent(args);
-            } else if ("toString".equals(methodName)) {
-                return "DrawListenerProxy";
-            } else if ("equals".equals(methodName)) {
-                return false;
-            } else if ("hashCode".equals(methodName)) {
-                return 0;
-            }
-            return null;
-        }
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesRemoveObserversEvent(requestId));
     }
 
     class PaintExpecter implements RepeatedEventExpecter {
-        private boolean mPaintDone;
-        private boolean mListening;
         private static final int MAX_WAIT_MS = 90000;
 
-        PaintExpecter() throws IllegalAccessException, InvocationTargetException {
-            Object proxy = Proxy.newProxyInstance(mClassLoader, new Class[] { mDrawListenerClass }, new DrawListenerProxy(this));
-            mSetDrawListener.invoke(mRobocopApi, proxy);
+        private boolean mPaintDone;
+        private boolean mListening;
+
+        private final GeckoLayerClient mLayerClient;
+
+        PaintExpecter() {
+            final PaintExpecter expecter = this;
+            mLayerClient = GeckoAppShell.getLayerView().getLayerClient();
+            mLayerClient.setDrawListener(new DrawListener() {
+                @Override
+                public void drawFinished() {
+                    FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
+                            "Received drawFinished notification");
+                    expecter.notifyOfEvent();
+                }
+            });
             mListening = true;
         }
 
-        void notifyOfEvent(Object[] args) {
-            synchronized (this) {
-                mPaintDone = true;
-                this.notifyAll();
-            }
+        private synchronized void notifyOfEvent() {
+            mPaintDone = true;
+            this.notifyAll();
         }
 
         private synchronized void blockForEvent(long millis, boolean failOnTimeout) {
             if (!mListening) {
                 throw new IllegalStateException("draw listener not registered");
             }
             long startTime = SystemClock.uptimeMillis();
             long endTime = 0;
@@ -426,33 +322,26 @@ public class FennecNativeActions impleme
                 startTime = endTime;
             }
         }
 
         public synchronized void unregisterListener() {
             if (!mListening) {
                 throw new IllegalStateException("listener not registered");
             }
-            try {
-                FennecNativeDriver.log(LogLevel.INFO, "PaintExpecter: no longer listening for events");
-                mListening = false;
-                mSetDrawListener.invoke(mRobocopApi, (Object)null);
-            } catch (Exception e) {
-                FennecNativeDriver.log(LogLevel.ERROR, e);
-            }
+
+            FennecNativeDriver.log(LogLevel.INFO,
+                    "PaintExpecter: no longer listening for events");
+            mLayerClient.setDrawListener(null);
+            mListening = false;
         }
     }
 
     public RepeatedEventExpecter expectPaint() {
-        try {
-            return new PaintExpecter();
-        } catch (Exception e) {
-            FennecNativeDriver.log(LogLevel.ERROR, e);
-            return null;
-        }
+        return new PaintExpecter();
     }
 
     public void sendSpecialKey(SpecialKey button) {
         switch(button) {
             case DOWN:
                 sendKeyCode(KeyEvent.KEYCODE_DPAD_DOWN);
                 break;
             case UP:
@@ -490,19 +379,12 @@ public class FennecNativeActions impleme
     public void sendKeys(String input) {
         mInstr.sendStringSync(input);
     }
 
     public void drag(int startingX, int endingX, int startingY, int endingY) {
         mSolo.drag(startingX, endingX, startingY, endingY, 10);
     }
 
-    public Cursor querySql(String dbPath, String sql) {
-        try {
-            return (Cursor)mQuerySql.invoke(mRobocopApi, dbPath, sql);
-        } catch(InvocationTargetException ex) {
-            Log.e(LOGTAG, "Error invoking method", ex);
-        } catch(IllegalAccessException ex) {
-            Log.e(LOGTAG, "Error using field", ex);
-        }
-        return null;
+    public Cursor querySql(final String dbPath, final String sql) {
+        return new SQLiteBridge(dbPath).rawQuery(sql, null);
     }
 }
--- a/build/mobile/robocop/FennecNativeDriver.java
+++ b/build/mobile/robocop/FennecNativeDriver.java
@@ -1,35 +1,34 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.gfx.LayerView;
+import org.mozilla.gecko.gfx.PanningPerfAPI;
+import org.mozilla.gecko.util.GeckoEventListener;
+
 import java.io.BufferedOutputStream;
 import java.io.BufferedReader;
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.nio.IntBuffer;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.lang.reflect.InvocationHandler;
-
 import android.app.Activity;
 import android.opengl.GLSurfaceView;
 import android.view.View;
 import android.util.Log;
 
 import org.json.*;
 
 import com.jayway.android.robotium.solo.Solo;
@@ -41,28 +40,16 @@ public class FennecNativeDriver implemen
     private HashMap mLocators = null;
     private Activity mActivity;
     private Solo mSolo;
     private String mRootPath;
 
     private static String mLogFile = null;
     private static LogLevel mLogLevel = LogLevel.INFO;
 
-    // Objects for reflexive access of fennec classes.
-    private ClassLoader mClassLoader;
-    private Class mApiClass;
-    private Class mEventListenerClass;
-    private Method mRegisterEventListener;
-    private Method mGetPixels;
-    private Method mStartFrameRecording;
-    private Method mStopFrameRecording;
-    private Method mStartCheckerboardRecording;
-    private Method mStopCheckerboardRecording;
-    private Object mRobocopApi;
-
     public enum LogLevel {
         DEBUG(1),
         INFO(2),
         WARN(3),
         ERROR(4);
 
         private int mValue;
         LogLevel(int value) {
@@ -78,35 +65,16 @@ public class FennecNativeDriver implemen
 
     public FennecNativeDriver(Activity activity, Solo robocop, String rootPath) {
         mActivity = activity;
         mSolo = robocop;
         mRootPath = rootPath;
 
         // Set up table of fennec_ids.
         mLocators = convertTextToTable(getFile(mRootPath + "/fennec_ids.txt"));
-
-        // Set up reflexive access of java classes and methods.
-        try {
-            mClassLoader = activity.getClassLoader();
-
-            mApiClass = mClassLoader.loadClass("org.mozilla.gecko.RobocopAPI");
-            mEventListenerClass = mClassLoader.loadClass("org.mozilla.gecko.util.GeckoEventListener");
-
-            mRegisterEventListener = mApiClass.getMethod("registerEventListener", String.class, mEventListenerClass);
-            mGetPixels = mApiClass.getMethod("getViewPixels", View.class);
-            mStartFrameRecording = mApiClass.getDeclaredMethod("startFrameTimeRecording");
-            mStopFrameRecording = mApiClass.getDeclaredMethod("stopFrameTimeRecording");
-            mStartCheckerboardRecording = mApiClass.getDeclaredMethod("startCheckerboardRecording");
-            mStopCheckerboardRecording = mApiClass.getDeclaredMethod("stopCheckerboardRecording");
-
-            mRobocopApi = mApiClass.getConstructor(Activity.class).newInstance(activity);
-        } catch (Exception e) {
-            log(LogLevel.ERROR, e);
-        }
     }
 
     //Information on the location of the Gecko Frame.
     private boolean mGeckoInfo = false;
     private int mGeckoTop = 100;
     private int mGeckoLeft = 0;
     private int mGeckoHeight= 700;
     private int mGeckoWidth = 1024;
@@ -167,109 +135,69 @@ public class FennecNativeDriver implemen
             return new FennecNativeElement(Integer.decode((String)mLocators.get(name)), activity, mSolo);
         }
         FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR,
             "findElement: Element '"+name+"' does not exist in the list");
         return null;
     }
 
     public void startFrameRecording() {
-        try {
-            mStartFrameRecording.invoke(null);
-        } catch (IllegalAccessException e) {
-            log(LogLevel.ERROR, e);
-        } catch (InvocationTargetException e) {
-            log(LogLevel.ERROR, e);
-        }
+        PanningPerfAPI.startFrameTimeRecording();
     }
 
     public int stopFrameRecording() {
-        try {
-            List<Long> frames = (List<Long>)mStopFrameRecording.invoke(null);
-            int badness = 0;
-            for (int i = 1; i < frames.size(); i++) {
-                long frameTime = frames.get(i) - frames.get(i - 1);
-                int delay = (int)(frameTime - FRAME_TIME_THRESHOLD);
-                // for each frame we miss, add the square of the delay. This
-                // makes large delays much worse than small delays.
-                if (delay > 0) {
-                    badness += delay * delay;
-                }
+        final List<Long> frames = PanningPerfAPI.stopFrameTimeRecording();
+        int badness = 0;
+        for (int i = 1; i < frames.size(); i++) {
+            long frameTime = frames.get(i) - frames.get(i - 1);
+            int delay = (int)(frameTime - FRAME_TIME_THRESHOLD);
+            // for each frame we miss, add the square of the delay. This
+            // makes large delays much worse than small delays.
+            if (delay > 0) {
+                badness += delay * delay;
             }
-            // Don't do any averaging of the numbers because really we want to
-            // know how bad the jank was at its worst
-            return badness;
-        } catch (IllegalAccessException e) {
-            log(LogLevel.ERROR, e);
-        } catch (InvocationTargetException e) {
-            log(LogLevel.ERROR, e);
         }
 
-        // higher values are worse, and the test failing is the worst!
-        return Integer.MAX_VALUE;
+        // Don't do any averaging of the numbers because really we want to
+        // know how bad the jank was at its worst
+        return badness;
     }
 
     public void startCheckerboardRecording() {
-        try {
-            mStartCheckerboardRecording.invoke(null);
-        } catch (IllegalAccessException e) {
-            log(LogLevel.ERROR, e);
-        } catch (InvocationTargetException e) {
-            log(LogLevel.ERROR, e);
-        }
+        PanningPerfAPI.startCheckerboardRecording();
     }
 
     public float stopCheckerboardRecording() {
-        try {
-            List<Float> checkerboard = (List<Float>)mStopCheckerboardRecording.invoke(null);
-            float total = 0;
-            for (float val : checkerboard) {
-                total += val;
-            }
-            return total * 100.0f;
-        } catch (IllegalAccessException e) {
-            log(LogLevel.ERROR, e);
-        } catch (InvocationTargetException e) {
-            log(LogLevel.ERROR, e);
+        final List<Float> checkerboard = PanningPerfAPI.stopCheckerboardRecording();
+        float total = 0;
+        for (float val : checkerboard) {
+            total += val;
         }
-
-        return 0.0f;
+        return total * 100.0f;
     }
 
-    private View getSurfaceView() {
-        ArrayList<View> views = mSolo.getCurrentViews();
-        try {
-            Class c = Class.forName("org.mozilla.gecko.gfx.LayerView");
-            for (View v : views) {
-                if (c.isInstance(v)) {
-                    return v;
-                }
+    private LayerView getSurfaceView() {
+        final LayerView layerView = mSolo.getView(LayerView.class, 0);
+
+        if (layerView == null) {
+            log(LogLevel.WARN, "getSurfaceView could not find LayerView");
+            for (final View v : mSolo.getViews()) {
+                log(LogLevel.WARN, "  View: " + v);
             }
-        } catch (ClassNotFoundException e) {
-            log(LogLevel.ERROR, e);
         }
-        log(LogLevel.WARN, "getSurfaceView could not find LayerView");
-        for (View v : views) {
-            log(LogLevel.WARN, v.toString());
-        }
-        return null;
+        return layerView;
     }
 
     public PaintedSurface getPaintedSurface() {
-        View view = getSurfaceView();
+        final LayerView view = getSurfaceView();
         if (view == null) {
             return null;
         }
-        IntBuffer pixelBuffer;
-        try {
-            pixelBuffer = (IntBuffer)mGetPixels.invoke(mRobocopApi, view);
-        } catch (Exception e) {
-            log(LogLevel.ERROR, e);
-            return null;
-        }
+
+        final IntBuffer pixelBuffer = view.getPixels();
 
         // now we need to (1) flip the image, because GL likes to do things up-side-down,
         // and (2) rearrange the bits from AGBR-8888 to ARGB-8888.
         int w = view.getWidth();
         int h = view.getHeight();
         pixelBuffer.position(0);
         String mapFile = mRootPath + "/pixels.map";
 
@@ -307,62 +235,44 @@ public class FennecNativeDriver implemen
         }
         return new PaintedSurface(mapFile, w, h);
     }
 
     public int mHeight=0;
     public int mScrollHeight=0;
     public int mPageHeight=10;
 
-    class scrollHandler implements InvocationHandler {
-        public scrollHandler(){};
-        public Object invoke(Object proxy, Method method, Object[] args) {
-            try {
-                // Disect the JSON object into the appropriate variables 
-                JSONObject jo = ((JSONObject)args[1]);
-                mScrollHeight = jo.getInt("y");
-                mHeight = jo.getInt("cheight");
-                // We don't want a height of 0. That means it's a bad response.
-                if (mHeight > 0) {
-                    mPageHeight = jo.getInt("height");
-                }
-
-            } catch( Throwable e) {
-                FennecNativeDriver.log(FennecNativeDriver.LogLevel.WARN, 
-                    "WARNING: ScrollReceived, but read wrong!");
-            }
-            return null;
-        }
-    }
-
     public int getScrollHeight() {
         return mScrollHeight;
     }
     public int getPageHeight() {
         return mPageHeight;
     }
     public int getHeight() {
         return mHeight;
     }
 
     public void setupScrollHandling() {
-        //Setup scrollHandler to catch "robocop:scroll" events. 
-        try {
-            Class [] interfaces = new Class[1];
-            interfaces[0] = mEventListenerClass;
-            Object[] finalParams = new Object[2];
-            finalParams[0] = "robocop:scroll";
-            finalParams[1] = Proxy.newProxyInstance(mClassLoader, interfaces, new scrollHandler());
-            mRegisterEventListener.invoke(mRobocopApi, finalParams);
-        } catch (IllegalAccessException e) {
-            log(LogLevel.ERROR, e);
-        } catch (InvocationTargetException e) {
-            log(LogLevel.ERROR, e);
-        }
-
+        GeckoAppShell.registerEventListener("robocop:scroll", new GeckoEventListener() {
+            @Override
+            public void handleMessage(final String event, final JSONObject message) {
+                try {
+                    mScrollHeight = message.getInt("y");
+                    mHeight = message.getInt("cheight");
+                    // We don't want a height of 0. That means it's a bad response.
+                    if (mHeight > 0) {
+                        mPageHeight = message.getInt("height");
+                    }
+                } catch (JSONException e) {
+                    FennecNativeDriver.log(FennecNativeDriver.LogLevel.WARN,
+                            "WARNING: ScrollReceived, but message does not contain " +
+                            "expected fields: " + e);
+                }
+            }
+        });
     }
 
     /**
      *  Takes a filename, loads the file, and returns a string version of the entire file.
      */
     public static String getFile(String filename)
     {
         StringBuilder text = new StringBuilder();
--- a/dom/bluetooth/ObexBase.cpp
+++ b/dom/bluetooth/ObexBase.cpp
@@ -73,52 +73,62 @@ AppendHeaderConnectionId(uint8_t* aRetBu
 void
 SetObexPacketInfo(uint8_t* aRetBuf, uint8_t aOpcode, int aPacketLength)
 {
   aRetBuf[0] = aOpcode;
   aRetBuf[1] = (aPacketLength & 0xFF00) >> 8;
   aRetBuf[2] = aPacketLength & 0x00FF;
 }
 
-void
+bool
 ParseHeaders(const uint8_t* aHeaderStart,
              int aTotalLength,
              ObexHeaderSet* aRetHandlerSet)
 {
   const uint8_t* ptr = aHeaderStart;
 
   while (ptr - aHeaderStart < aTotalLength) {
     ObexHeaderId headerId = (ObexHeaderId)*ptr++;
 
-    int contentLength = 0;
+    uint16_t contentLength = 0;
     uint8_t highByte, lowByte;
 
     // Defined in 2.1 OBEX Headers, IrOBEX 1.2
     switch (headerId >> 6)
     {
       case 0x00:
         // Null-terminated Unicode text, length prefixed with 2-byte
         // unsigned integer.
       case 0x01:
         // byte sequence, length prefixed with 2 byte unsigned integer.
         highByte = *ptr++;
         lowByte = *ptr++;
-        contentLength = (((int)highByte << 8) | lowByte) - 3;
+        contentLength = (((uint16_t)highByte << 8) | lowByte) - 3;
         break;
 
       case 0x02:
         // 1 byte quantity
         contentLength = 1;
         break;
 
       case 0x03:
         // 4 byte quantity
         contentLength = 4;
         break;
     }
 
+    // Length check to prevent from memory pollusion.
+    if (ptr + contentLength > aHeaderStart + aTotalLength) {
+      // Severe error occurred. We can't even believe the received data, so
+      // clear all headers.
+      MOZ_ASSERT(false);
+      aRetHandlerSet->ClearHeaders();
+      return false;
+    }
+
     aRetHandlerSet->AddHeader(new ObexHeader(headerId, contentLength, ptr));
-
     ptr += contentLength;
   }
+
+  return true;
 }
 
 END_BLUETOOTH_NAMESPACE
--- a/dom/bluetooth/ObexBase.h
+++ b/dom/bluetooth/ObexBase.h
@@ -97,17 +97,18 @@ enum ObexResponseCode {
   ServiceUnavailable = 0xD3,
   GatewayTimeout = 0xD4,
   HttpVersionNotSupported = 0xD5,
 
   DatabaseFull = 0xE0,
   DatabaseLocked = 0xE1,
 };
 
-class ObexHeader {
+class ObexHeader
+{
 public:
   ObexHeader(ObexHeaderId aId, int aDataLength, const uint8_t* aData)
     : mId(aId)
     , mDataLength(aDataLength)
     , mData(nullptr)
   {
     mData = new uint8_t[mDataLength];
     memcpy(mData, aData, aDataLength);
@@ -117,21 +118,19 @@ public:
   {
   }
 
   ObexHeaderId mId;
   int mDataLength;
   nsAutoArrayPtr<uint8_t> mData;
 };
 
-class ObexHeaderSet {
+class ObexHeaderSet
+{
 public:
-  uint8_t mOpcode;
-  nsTArray<nsAutoPtr<ObexHeader> > mHeaders;
-
   ObexHeaderSet(uint8_t aOpcode) : mOpcode(aOpcode)
   {
   }
 
   ~ObexHeaderSet()
   {
   }
 
@@ -236,23 +235,36 @@ public:
     for (int i = 0; i < length; ++i) {
       if (mHeaders[i]->mId == aId) {
         return true;
       }
     }
 
     return false;
   }
+
+  void ClearHeaders()
+  {
+    mHeaders.Clear();
+  }
+
+private:
+  uint8_t mOpcode;
+  nsTArray<nsAutoPtr<ObexHeader> > mHeaders;
 };
 
 int AppendHeaderName(uint8_t* aRetBuf, const char* aName, int aLength);
 int AppendHeaderBody(uint8_t* aRetBuf, uint8_t* aData, int aLength);
 int AppendHeaderEndOfBody(uint8_t* aRetBuf);
 int AppendHeaderLength(uint8_t* aRetBuf, int aObjectLength);
 int AppendHeaderConnectionId(uint8_t* aRetBuf, int aConnectionId);
 void SetObexPacketInfo(uint8_t* aRetBuf, uint8_t aOpcode, int aPacketLength);
-void ParseHeaders(const uint8_t* aHeaderStart,
+
+/**
+ * @return true when the message was parsed without any error, false otherwise.
+ */
+bool ParseHeaders(const uint8_t* aHeaderStart,
                   int aTotalLength,
                   ObexHeaderSet* aRetHanderSet);
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/bluedroid/BluetoothOppManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothOppManager.cpp
@@ -798,35 +798,41 @@ BluetoothOppManager::ServerDataHandler(U
     }
   }
 
   ObexHeaderSet pktHeaders(opCode);
   if (opCode == ObexRequestCode::Connect) {
     // Section 3.3.1 "Connect", IrOBEX 1.2
     // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
     // [Headers:var]
-    ParseHeaders(&aMessage->mData[7],
-                 receivedLength - 7,
-                 &pktHeaders);
+    if (!ParseHeaders(&aMessage->mData[7], receivedLength - 7, &pktHeaders)) {
+      ReplyError(ObexResponseCode::BadRequest);
+      return;
+    }
+
     ReplyToConnect();
     AfterOppConnected();
   } else if (opCode == ObexRequestCode::Abort) {
     // Section 3.3.5 "Abort", IrOBEX 1.2
     // [opcode:1][length:2][Headers:var]
-    ParseHeaders(&aMessage->mData[3],
-                receivedLength - 3,
-                &pktHeaders);
+    if (!ParseHeaders(&aMessage->mData[3], receivedLength - 3, &pktHeaders)) {
+      ReplyError(ObexResponseCode::BadRequest);
+      return;
+    }
+
     ReplyToDisconnectOrAbort();
     DeleteReceivedFile();
   } else if (opCode == ObexRequestCode::Disconnect) {
     // Section 3.3.2 "Disconnect", IrOBEX 1.2
     // [opcode:1][length:2][Headers:var]
-    ParseHeaders(&aMessage->mData[3],
-                receivedLength - 3,
-                &pktHeaders);
+    if (!ParseHeaders(&aMessage->mData[3], receivedLength - 3, &pktHeaders)) {
+      ReplyError(ObexResponseCode::BadRequest);
+      return;
+    }
+
     ReplyToDisconnectOrAbort();
     AfterOppDisconnected();
     FileTransferComplete();
   } else if (opCode == ObexRequestCode::Put ||
              opCode == ObexRequestCode::PutFinal) {
     if (!ComposePacket(opCode, aMessage)) {
       return;
     }
@@ -1192,17 +1198,17 @@ BluetoothOppManager::ReplyToPut(bool aFi
   }
 
   SendObexData(req, opcode, index);
 }
 
 void
 BluetoothOppManager::ReplyError(uint8_t aError)
 {
-  if (!mConnected) return;
+  BT_LOGR("error: %d", aError);
 
   // Section 3.2 "Response Format", IrOBEX 1.2
   // [opcode:1][length:2][Headers:var]
   uint8_t req[255];
   int index = 3;
 
   SendObexData(req, aError, index);
 }
--- a/dom/bluetooth/bluez/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluez/BluetoothHfpManager.cpp
@@ -336,19 +336,22 @@ Call::IsActive()
 {
   return (mState == nsITelephonyProvider::CALL_STATE_CONNECTED);
 }
 #endif // MOZ_B2G_RIL
 
 /**
  *  BluetoothHfpManager
  */
-BluetoothHfpManager::BluetoothHfpManager() : mPhoneType(PhoneType::NONE)
-                                           , mController(nullptr)
+BluetoothHfpManager::BluetoothHfpManager() : mController(nullptr)
 {
+#ifdef MOZ_B2G_RIL
+  mPhoneType = PhoneType::NONE;
+#endif // MOZ_B2G_RIL
+
   Reset();
 }
 
 #ifdef MOZ_B2G_RIL
 void
 BluetoothHfpManager::ResetCallArray()
 {
   mCurrentCallArray.Clear();
--- a/dom/bluetooth/bluez/BluetoothOppManager.cpp
+++ b/dom/bluetooth/bluez/BluetoothOppManager.cpp
@@ -814,35 +814,41 @@ BluetoothOppManager::ServerDataHandler(U
     }
   }
 
   ObexHeaderSet pktHeaders(opCode);
   if (opCode == ObexRequestCode::Connect) {
     // Section 3.3.1 "Connect", IrOBEX 1.2
     // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
     // [Headers:var]
-    ParseHeaders(&aMessage->mData[7],
-                 receivedLength - 7,
-                 &pktHeaders);
+    if (!ParseHeaders(&aMessage->mData[7], receivedLength - 7, &pktHeaders)) {
+      ReplyError(ObexResponseCode::BadRequest);
+      return;
+    }
+
     ReplyToConnect();
     AfterOppConnected();
   } else if (opCode == ObexRequestCode::Abort) {
     // Section 3.3.5 "Abort", IrOBEX 1.2
     // [opcode:1][length:2][Headers:var]
-    ParseHeaders(&aMessage->mData[3],
-                receivedLength - 3,
-                &pktHeaders);
+    if (!ParseHeaders(&aMessage->mData[3], receivedLength - 3, &pktHeaders)) {
+      ReplyError(ObexResponseCode::BadRequest);
+      return;
+    }
+
     ReplyToDisconnectOrAbort();
     DeleteReceivedFile();
   } else if (opCode == ObexRequestCode::Disconnect) {
     // Section 3.3.2 "Disconnect", IrOBEX 1.2
     // [opcode:1][length:2][Headers:var]
-    ParseHeaders(&aMessage->mData[3],
-                receivedLength - 3,
-                &pktHeaders);
+    if (!ParseHeaders(&aMessage->mData[3], receivedLength - 3, &pktHeaders)) {
+      ReplyError(ObexResponseCode::BadRequest);
+      return;
+    }
+
     ReplyToDisconnectOrAbort();
     AfterOppDisconnected();
     FileTransferComplete();
   } else if (opCode == ObexRequestCode::Put ||
              opCode == ObexRequestCode::PutFinal) {
     if (!ComposePacket(opCode, aMessage)) {
       return;
     }
@@ -1208,17 +1214,17 @@ BluetoothOppManager::ReplyToPut(bool aFi
   }
 
   SendObexData(req, opcode, index);
 }
 
 void
 BluetoothOppManager::ReplyError(uint8_t aError)
 {
-  if (!mConnected) return;
+  BT_LOGR("error: %d", aError);
 
   // Section 3.2 "Response Format", IrOBEX 1.2
   // [opcode:1][length:2][Headers:var]
   uint8_t req[255];
   int index = 3;
 
   SendObexData(req, aError, index);
 }
--- a/dom/camera/CameraCommon.h
+++ b/dom/camera/CameraCommon.h
@@ -74,16 +74,17 @@ enum {
   CAMERA_PARAM_FOCALLENGTH,
   CAMERA_PARAM_FOCUSDISTANCENEAR,
   CAMERA_PARAM_FOCUSDISTANCEOPTIMUM,
   CAMERA_PARAM_FOCUSDISTANCEFAR,
   CAMERA_PARAM_EXPOSURECOMPENSATION,
   CAMERA_PARAM_PICTURESIZE,
   CAMERA_PARAM_THUMBNAILSIZE,
   CAMERA_PARAM_THUMBNAILQUALITY,
+  CAMERA_PARAM_SENSORANGLE,
 
   CAMERA_PARAM_SUPPORTED_PREVIEWSIZES,
   CAMERA_PARAM_SUPPORTED_VIDEOSIZES,
   CAMERA_PARAM_SUPPORTED_PICTURESIZES,
   CAMERA_PARAM_SUPPORTED_PICTUREFORMATS,
   CAMERA_PARAM_SUPPORTED_WHITEBALANCES,
   CAMERA_PARAM_SUPPORTED_SCENEMODES,
   CAMERA_PARAM_SUPPORTED_EFFECTS,
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -263,16 +263,24 @@ CameraControlImpl::Set(uint32_t aKey, co
 
 nsresult
 CameraControlImpl::Get(uint32_t aKey, idl::CameraSize& aSize)
 {
   GetParameter(aKey, aSize);
   return NS_OK;
 }
 
+nsresult
+CameraControlImpl::Get(uint32_t aKey, int32_t* aValue)
+{
+  MOZ_ASSERT(aValue);
+  *aValue = GetParameterInt32(aKey);
+  return NS_OK;
+}
+
 already_AddRefed<RecorderProfileManager>
 CameraControlImpl::GetRecorderProfileManager()
 {
   return GetRecorderProfileManagerImpl();
 }
 
 void
 CameraControlImpl::Shutdown()
--- a/dom/camera/CameraControlImpl.h
+++ b/dom/camera/CameraControlImpl.h
@@ -70,16 +70,17 @@ public:
   nsresult Set(nsICameraClosedCallback* aOnClosed);
   nsresult Get(nsICameraClosedCallback** aOnClosed);
   nsresult Set(nsICameraRecorderStateChange* aOnRecorderStateChange);
   nsresult Get(nsICameraRecorderStateChange** aOnRecorderStateChange);
   nsresult Set(nsICameraPreviewStateChange* aOnPreviewStateChange);
   nsresult Get(nsICameraPreviewStateChange** aOnPreviewStateChange);
   nsresult Set(uint32_t aKey, const idl::CameraSize& aSize);
   nsresult Get(uint32_t aKey, idl::CameraSize& aSize);
+  nsresult Get(uint32_t aKey, int32_t* aValue);
 
   nsresult SetFocusAreas(JSContext* aCx, const JS::Value& aValue)
   {
     return Set(aCx, CAMERA_PARAM_FOCUSAREAS, aValue, mMaxFocusAreas);
   }
 
   nsresult SetMeteringAreas(JSContext* aCx, const JS::Value& aValue)
   {
@@ -87,16 +88,17 @@ public:
   }
 
   already_AddRefed<RecorderProfileManager> GetRecorderProfileManager();
   uint32_t GetCameraId() { return mCameraId; }
 
   virtual const char* GetParameter(const char* aKey) = 0;
   virtual const char* GetParameterConstChar(uint32_t aKey) = 0;
   virtual double GetParameterDouble(uint32_t aKey) = 0;
+  virtual int32_t GetParameterInt32(uint32_t aKey) = 0;
   virtual void GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions) = 0;
   virtual void GetParameter(uint32_t aKey, idl::CameraSize& aSize) = 0;
   virtual void SetParameter(const char* aKey, const char* aValue) = 0;
   virtual void SetParameter(uint32_t aKey, const char* aValue) = 0;
   virtual void SetParameter(uint32_t aKey, double aValue) = 0;
   virtual void SetParameter(uint32_t aKey, const nsTArray<idl::CameraRegion>& aRegions) = 0;
   virtual void SetParameter(uint32_t aKey, const idl::CameraSize& aSize) = 0;
   virtual nsresult GetVideoSizes(nsTArray<idl::CameraSize>& aVideoSizes) = 0;
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -280,16 +280,24 @@ nsDOMCameraControl::SetExposureCompensat
 double
 nsDOMCameraControl::GetExposureCompensation(ErrorResult& aRv)
 {
   double compensation;
   aRv = mCameraControl->Get(CAMERA_PARAM_EXPOSURECOMPENSATION, &compensation);
   return compensation;
 }
 
+int32_t
+nsDOMCameraControl::SensorAngle()
+{
+  int32_t angle;
+  mCameraControl->Get(CAMERA_PARAM_SENSORANGLE, &angle);
+  return angle;
+}
+
 already_AddRefed<nsICameraShutterCallback>
 nsDOMCameraControl::GetOnShutter(ErrorResult& aRv)
 {
   nsCOMPtr<nsICameraShutterCallback> cb;
   aRv = mCameraControl->Get(getter_AddRefs(cb));
   return cb.forget();
 }
 
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -71,16 +71,17 @@ public:
   JS::Value GetThumbnailSize(JSContext* aCx, ErrorResult& aRv);
   void SetThumbnailSize(JSContext* aCx, JS::Handle<JS::Value> aSize, ErrorResult& aRv);
   double GetFocalLength(ErrorResult& aRv);
   double GetFocusDistanceNear(ErrorResult& aRv);
   double GetFocusDistanceOptimum(ErrorResult& aRv);
   double GetFocusDistanceFar(ErrorResult& aRv);
   void SetExposureCompensation(const dom::Optional<double>& aCompensation, ErrorResult& aRv);
   double GetExposureCompensation(ErrorResult& aRv);
+  int32_t SensorAngle();
   already_AddRefed<nsICameraShutterCallback> GetOnShutter(ErrorResult& aRv);
   void SetOnShutter(nsICameraShutterCallback* aCb, ErrorResult& aRv);
   already_AddRefed<nsICameraClosedCallback> GetOnClosed(ErrorResult& aRv);
   void SetOnClosed(nsICameraClosedCallback* aCb, ErrorResult& aRv);
   already_AddRefed<nsICameraRecorderStateChange> GetOnRecorderStateChange(ErrorResult& aRv);
   void SetOnRecorderStateChange(nsICameraRecorderStateChange* aCb, ErrorResult& aRv);
   void AutoFocus(nsICameraAutoFocusCallback* aOnSuccess, const dom::Optional<nsICameraErrorCallback*>& aOnErro, ErrorResult& aRvr);
   void TakePicture(JSContext* aCx, const dom::CameraPictureOptions& aOptions,
--- a/dom/camera/FallbackCameraControl.cpp
+++ b/dom/camera/FallbackCameraControl.cpp
@@ -19,16 +19,17 @@ class RecorderProfileManager;
 class nsFallbackCameraControl : public CameraControlImpl
 {
 public:
   nsFallbackCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
 
   const char* GetParameter(const char* aKey);
   const char* GetParameterConstChar(uint32_t aKey);
   double GetParameterDouble(uint32_t aKey);
+  int32_t GetParameterInt32(uint32_t aKey);
   void GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions);
   void GetParameter(uint32_t aKey, idl::CameraSize& aSize);
   void SetParameter(const char* aKey, const char* aValue);
   void SetParameter(uint32_t aKey, const char* aValue);
   void SetParameter(uint32_t aKey, double aValue);
   void SetParameter(uint32_t aKey, const nsTArray<idl::CameraRegion>& aRegions);
   void SetParameter(uint32_t aKey, const idl::CameraSize& aSize);
   nsresult GetVideoSizes(nsTArray<idl::CameraSize>& aVideoSizes);
@@ -96,16 +97,22 @@ nsFallbackCameraControl::GetParameterCon
 }
 
 double
 nsFallbackCameraControl::GetParameterDouble(uint32_t aKey)
 {
   return NAN;
 }
 
+int32_t
+nsFallbackCameraControl::GetParameterInt32(uint32_t aKey)
+{
+  return 0;
+}
+
 void
 nsFallbackCameraControl::GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions)
 {
 }
 
 void
 nsFallbackCameraControl::GetParameter(uint32_t aKey, idl::CameraSize& aSize)
 {
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -429,16 +429,35 @@ nsGonkCameraControl::GetParameterDouble(
       DOM_CAMERA_LOGI("index = %d --> compensation = %f\n", index, val);
       return val;
 
     default:
       return mParams.getFloat(key);
   }
 }
 
+int32_t
+nsGonkCameraControl::GetParameterInt32(uint32_t aKey)
+{
+  if (aKey == CAMERA_PARAM_SENSORANGLE) {
+    if (!mCameraHw.get()) {
+      return 0;
+    }
+    return mCameraHw->GetSensorOrientation();
+  }
+
+  const char* key = getKeyText(aKey);
+  if (!key) {
+    return 0;
+  }
+
+  RwAutoLockRead lock(mRwLock);
+  return mParams.getInt(key);
+}
+
 void
 nsGonkCameraControl::GetParameter(uint32_t aKey,
                                   nsTArray<idl::CameraRegion>& aRegions)
 {
   aRegions.Clear();
 
   const char* key = getKeyText(aKey);
   if (!key) {
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -48,16 +48,17 @@ class nsGonkCameraControl : public Camer
 public:
   nsGonkCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
   void DispatchInit(nsDOMCameraControl* aDOMCameraControl, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId);
   nsresult Init();
 
   const char* GetParameter(const char* aKey);
   const char* GetParameterConstChar(uint32_t aKey);
   double GetParameterDouble(uint32_t aKey);
+  int32_t GetParameterInt32(uint32_t aKey);
   void GetParameter(uint32_t aKey, nsTArray<idl::CameraRegion>& aRegions);
   void GetParameter(uint32_t aKey, nsTArray<idl::CameraSize>& aSizes);
   void GetParameter(uint32_t aKey, idl::CameraSize& aSize);
   void SetParameter(const char* aKey, const char* aValue);
   void SetParameter(uint32_t aKey, const char* aValue);
   void SetParameter(uint32_t aKey, double aValue);
   void SetParameter(uint32_t aKey, const nsTArray<idl::CameraRegion>& aRegions);
   void SetParameter(uint32_t aKey, int aValue);
--- a/dom/camera/ICameraControl.h
+++ b/dom/camera/ICameraControl.h
@@ -41,16 +41,17 @@ public:
   virtual nsresult Set(nsICameraClosedCallback* aOnClosed) = 0;
   virtual nsresult Get(nsICameraClosedCallback** aOnClosed) = 0;
   virtual nsresult Set(nsICameraRecorderStateChange* aOnRecorderStateChange) = 0;
   virtual nsresult Get(nsICameraRecorderStateChange** aOnRecorderStateChange) = 0;
   virtual nsresult Set(nsICameraPreviewStateChange* aOnPreviewStateChange) = 0;
   virtual nsresult Get(nsICameraPreviewStateChange** aOnPreviewStateChange) = 0;
   virtual nsresult Set(uint32_t aKey, const idl::CameraSize& aSize) = 0;
   virtual nsresult Get(uint32_t aKey, idl::CameraSize& aSize) = 0;
+  virtual nsresult Get(uint32_t aKey, int32_t* aValue) = 0;
   virtual nsresult SetFocusAreas(JSContext* aCx, const JS::Value& aValue) = 0;
   virtual nsresult SetMeteringAreas(JSContext* aCx, const JS::Value& aValue) = 0;
   virtual nsresult GetVideoSizes(nsTArray<idl::CameraSize>& aVideoSizes) = 0;
   virtual already_AddRefed<RecorderProfileManager> GetRecorderProfileManager() = 0;
   virtual uint32_t GetCameraId() = 0;
 
   virtual const char* GetParameter(const char* aKey) = 0;
   virtual const char* GetParameterConstChar(uint32_t aKey) = 0;
--- a/dom/messages/SystemMessagePermissionsChecker.jsm
+++ b/dom/messages/SystemMessagePermissionsChecker.jsm
@@ -57,16 +57,19 @@ this.SystemMessagePermissionsTable = {
   },
   "connection": { },
   "dummy-system-message": { }, // for system message testing framework
   "headset-button": { },
   "icc-stkcommand": {
     "settings": ["read", "write"]
   },
   "media-button": { },
+  "networkstats-alarm": {
+    "networkstats-manage": []
+  },
   "notification": {
     "desktop-notification": []
   },
   "push": {
   	"push": []
   },
   "push-register": {
   	"push": []
--- a/dom/network/interfaces/nsIDOMNetworkStatsManager.idl
+++ b/dom/network/interfaces/nsIDOMNetworkStatsManager.idl
@@ -15,17 +15,32 @@ interface nsIDOMMozNetworkStatsInterface
   readonly attribute long type;
 
   /**
    * Id value is '0' for wifi or the iccid for mobile (SIM).
    */
   readonly attribute DOMString id;
 };
 
-[scriptable, uuid(5f033d31-c9a2-4e2d-83aa-6a807f1e0c11)]
+dictionary NetworkStatsAlarmOptions
+{
+  jsval startTime; // Date object
+  jsval data;
+};
+
+[scriptable, builtinclass, uuid(063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c)]
+interface nsIDOMMozNetworkStatsAlarm : nsISupports
+{
+  readonly attribute unsigned long alarmId;
+  readonly attribute nsIDOMMozNetworkStatsInterface network;
+  readonly attribute long threshold;
+  readonly attribute jsval data;
+};
+
+[scriptable,  uuid(50d109b8-0d7f-4208-81fe-5f07a759f159)]
 interface nsIDOMMozNetworkStatsManager : nsISupports
 {
   /**
    * Constants for known interface types.
    */
   const long WIFI = 0;
   const long MOBILE = 1;
 
@@ -38,16 +53,51 @@ interface nsIDOMMozNetworkStatsManager :
    * If success, the request result will be an nsIDOMMozNetworkStats object.
    */
   nsIDOMDOMRequest getSamples(in nsIDOMMozNetworkStatsInterface network,
                               in jsval start,
                               in jsval end,
                               [optional] in DOMString manifestURL);
 
   /**
+   * Install an alarm on a network. The network must be in the return of
+   * getAvailableNetworks() otherwise an "InvalidNetwork" exception will
+   * be raised.
+   *
+   * When total data usage reaches threshold bytes, a "networkstats-alarm"
+   * system message is sent to the application, where the optional parameter
+   * |data| must be a cloneable object.
+   *
+   * If success, the |result| field of the DOMRequest keeps the alarm Id.
+   */
+  nsIDOMDOMRequest addAlarm(in nsIDOMMozNetworkStatsInterface network,
+                            in long threshold,
+                            [optional] in jsval options /* NetworkStatsAlarmOptions */);
+
+  /**
+   * Obtain all alarms for those networks returned by getAvailableNetworks().
+   * If a network is provided, only retrieves the alarms for that network.
+   * The network must be one of those returned by getAvailebleNetworks() or an
+   * "InvalidNetwork" exception will be raised.
+   *
+   * Each alarm object has the same fields as that in the system message:
+   *  - alarmId
+   *  - network
+   *  - threshold
+   *  - data
+   */
+  nsIDOMDOMRequest getAllAlarms([optional] in nsIDOMMozNetworkStatsInterface network);
+
+  /**
+   * Remove all network alarms. If an |alarmId| is provided, then only that
+   * alarm is removed.
+   */
+  nsIDOMDOMRequest removeAlarms([optional] in long alarmId);
+
+  /**
    * Remove all stats related with the provided network from DB.
    */
   nsIDOMDOMRequest clearStats(in nsIDOMMozNetworkStatsInterface network);
 
   /**
    * Remove all stats in the database.
    */
   nsIDOMDOMRequest clearAllStats();
--- a/dom/network/src/NetworkStatsDB.jsm
+++ b/dom/network/src/NetworkStatsDB.jsm
@@ -11,59 +11,60 @@ function debug(s) { dump("-*- NetworkSta
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 Cu.importGlobalProperties(["indexedDB"]);
 
 const DB_NAME = "net_stats";
-const DB_VERSION = 4;
-const STORE_NAME = "net_stats";
+const DB_VERSION = 5;
+const STATS_STORE_NAME = "net_stats";
+const ALARMS_STORE_NAME = "net_alarm";
 
 // Constant defining the maximum values allowed per interface. If more, older
 // will be erased.
 const VALUES_MAX_LENGTH = 6 * 30;
 
 // Constant defining the rate of the samples. Daily.
 const SAMPLE_RATE = 1000 * 60 * 60 * 24;
 
 this.NetworkStatsDB = function NetworkStatsDB() {
   if (DEBUG) {
     debug("Constructor");
   }
-  this.initDBHelper(DB_NAME, DB_VERSION, [STORE_NAME]);
+  this.initDBHelper(DB_NAME, DB_VERSION, [STATS_STORE_NAME, ALARMS_STORE_NAME]);
 }
 
 NetworkStatsDB.prototype = {
   __proto__: IndexedDBHelper.prototype,
 
-  dbNewTxn: function dbNewTxn(txn_type, callback, txnCb) {
+  dbNewTxn: function dbNewTxn(store_name, txn_type, callback, txnCb) {
     function successCb(result) {
       txnCb(null, result);
     }
     function errorCb(error) {
       txnCb(error, null);
     }
-    return this.newTxn(txn_type, STORE_NAME, callback, successCb, errorCb);
+    return this.newTxn(txn_type, store_name, callback, successCb, errorCb);
   },
 
   upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
     if (DEBUG) {
       debug("upgrade schema from: " + aOldVersion + " to " + aNewVersion + " called!");
     }
     let db = aDb;
     let objectStore;
     for (let currVersion = aOldVersion; currVersion < aNewVersion; currVersion++) {
       if (currVersion == 0) {
         /**
          * Create the initial database schema.
          */
 
-        objectStore = db.createObjectStore(STORE_NAME, { keyPath: ["connectionType", "timestamp"] });
+        objectStore = db.createObjectStore(STATS_STORE_NAME, { keyPath: ["connectionType", "timestamp"] });
         objectStore.createIndex("connectionType", "connectionType", { unique: false });
         objectStore.createIndex("timestamp", "timestamp", { unique: false });
         objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
         objectStore.createIndex("txBytes", "txBytes", { unique: false });
         objectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
         objectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
         if (DEBUG) {
           debug("Created object stores and indexes");
@@ -72,34 +73,34 @@ NetworkStatsDB.prototype = {
         // In order to support per-app traffic data storage, the original
         // objectStore needs to be replaced by a new objectStore with new
         // key path ("appId") and new index ("appId").
         // Also, since now networks are identified by their
         // [networkId, networkType] not just by their connectionType,
         // to modify the keyPath is mandatory to delete the object store
         // and create it again. Old data is going to be deleted because the
         // networkId for each sample can not be set.
-        db.deleteObjectStore(STORE_NAME);
+        db.deleteObjectStore(STATS_STORE_NAME);
 
-        objectStore = db.createObjectStore(STORE_NAME, { keyPath: ["appId", "network", "timestamp"] });
+        objectStore = db.createObjectStore(STATS_STORE_NAME, { keyPath: ["appId", "network", "timestamp"] });
         objectStore.createIndex("appId", "appId", { unique: false });
         objectStore.createIndex("network", "network", { unique: false });
         objectStore.createIndex("networkType", "networkType", { unique: false });
         objectStore.createIndex("timestamp", "timestamp", { unique: false });
         objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
         objectStore.createIndex("txBytes", "txBytes", { unique: false });
         objectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
         objectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
 
         if (DEBUG) {
           debug("Created object stores and indexes for version 3");
         }
       } else if (currVersion == 3) {
         // Delete redundent indexes (leave "network" only).
-        objectStore = aTransaction.objectStore(STORE_NAME);
+        objectStore = aTransaction.objectStore(STATS_STORE_NAME);
         if (objectStore.indexNames.contains("appId")) {
           objectStore.deleteIndex("appId");
         }
         if (objectStore.indexNames.contains("networkType")) {
           objectStore.deleteIndex("networkType");
         }
         if (objectStore.indexNames.contains("timestamp")) {
           objectStore.deleteIndex("timestamp");
@@ -115,28 +116,92 @@ NetworkStatsDB.prototype = {
         }
         if (objectStore.indexNames.contains("txTotalBytes")) {
           objectStore.deleteIndex("txTotalBytes");
         }
 
         if (DEBUG) {
           debug("Deleted redundent indexes for version 4");
         }
+      } else if (currVersion == 4) {
+        // In order to manage alarms, it is necessary to use a global counter
+        // (totalBytes) that will increase regardless of the system reboot.
+        objectStore = aTransaction.objectStore(STATS_STORE_NAME);
+
+        // Now, systemBytes will hold the old totalBytes and totalBytes will
+        // keep the increasing counter. |counters| will keep the track of
+        // accumulated values.
+        let counters = {};
+
+        objectStore.openCursor().onsuccess = function(event) {
+          let cursor = event.target.result;
+          if (!cursor){
+            return;
+          }
+
+          cursor.value.rxSystemBytes = cursor.value.rxTotalBytes;
+          cursor.value.txSystemBytes = cursor.value.txTotalBytes;
+
+          if (cursor.value.appId == 0) {
+            let netId = cursor.value.network[0] + '' + cursor.value.network[1];
+            if (!counters[netId]) {
+              counters[netId] = {
+                rxCounter: 0,
+                txCounter: 0,
+                lastRx: 0,
+                lastTx: 0
+              };
+            }
+
+            let rxDiff = cursor.value.rxSystemBytes - counters[netId].lastRx;
+            let txDiff = cursor.value.txSystemBytes - counters[netId].lastTx;
+            if (rxDiff < 0 || txDiff < 0) {
+              // System reboot between samples, so take the current one.
+              rxDiff = cursor.value.rxSystemBytes;
+              txDiff = cursor.value.txSystemBytes;
+            }
+
+            counters[netId].rxCounter += rxDiff;
+            counters[netId].txCounter += txDiff;
+            cursor.value.rxTotalBytes = counters[netId].rxCounter;
+            cursor.value.txTotalBytes = counters[netId].txCounter;
+
+            counters[netId].lastRx = cursor.value.rxSystemBytes;
+            counters[netId].lastTx = cursor.value.txSystemBytes;
+          } else {
+            cursor.value.rxTotalBytes = cursor.value.rxSystemBytes;
+            cursor.value.txTotalBytes = cursor.value.txSystemBytes;
+          }
+
+          cursor.update(cursor.value);
+          cursor.continue();
+        };
+
+        // Create object store for alarms.
+        objectStore = db.createObjectStore(ALARMS_STORE_NAME, { keyPath: "id", autoIncrement: true });
+        objectStore.createIndex("alarm", ['networkId','threshold'], { unique: false });
+        objectStore.createIndex("manifestURL", "manifestURL", { unique: false });
+
+        if (DEBUG) {
+          debug("Created alarms store for version 5");
+        }
       }
     }
   },
 
   importData: function importData(aStats) {
-    let stats = { appId:        aStats.appId,
-                  network:      [aStats.networkId, aStats.networkType],
-                  timestamp:    aStats.timestamp,
-                  rxBytes:      aStats.rxBytes,
-                  txBytes:      aStats.txBytes,
-                  rxTotalBytes: aStats.rxTotalBytes,
-                  txTotalBytes: aStats.txTotalBytes };
+    let stats = { appId:         aStats.appId,
+                  network:       [aStats.networkId, aStats.networkType],
+                  timestamp:     aStats.timestamp,
+                  rxBytes:       aStats.rxBytes,
+                  txBytes:       aStats.txBytes,
+                  rxSystemBytes: aStats.rxSystemBytes,
+                  txSystemBytes: aStats.txSystemBytes,
+                  rxTotalBytes:  aStats.rxTotalBytes,
+                  txTotalBytes:  aStats.txTotalBytes };
 
     return stats;
   },
 
   exportData: function exportData(aStats) {
     let stats = { appId:        aStats.appId,
                   networkId:    aStats.network[0],
                   networkType:  aStats.network[1],
@@ -155,28 +220,30 @@ NetworkStatsDB.prototype = {
     let timestamp = aDate.getTime() - aDate.getTimezoneOffset() * 60 * 1000;
     timestamp = Math.floor(timestamp / SAMPLE_RATE) * SAMPLE_RATE;
     return timestamp;
   },
 
   saveStats: function saveStats(aStats, aResultCb) {
     let timestamp = this.normalizeDate(aStats.date);
 
-    let stats = { appId:        aStats.appId,
-                  networkId:    aStats.networkId,
-                  networkType:  aStats.networkType,
-                  timestamp:    timestamp,
-                  rxBytes:      (aStats.appId == 0) ? 0 : aStats.rxBytes,
-                  txBytes:      (aStats.appId == 0) ? 0 : aStats.txBytes,
-                  rxTotalBytes: (aStats.appId == 0) ? aStats.rxBytes : 0,
-                  txTotalBytes: (aStats.appId == 0) ? aStats.txBytes : 0 };
+    let stats = { appId:         aStats.appId,
+                  networkId:     aStats.networkId,
+                  networkType:   aStats.networkType,
+                  timestamp:     timestamp,
+                  rxBytes:       (aStats.appId == 0) ? 0 : aStats.rxBytes,
+                  txBytes:       (aStats.appId == 0) ? 0 : aStats.txBytes,
+                  rxSystemBytes: (aStats.appId == 0) ? aStats.rxBytes : 0,
+                  txSystemBytes: (aStats.appId == 0) ? aStats.txBytes : 0,
+                  rxTotalBytes:  (aStats.appId == 0) ? aStats.rxBytes : 0,
+                  txTotalBytes:  (aStats.appId == 0) ? aStats.txBytes : 0 };
 
     stats = this.importData(stats);
 
-    this.dbNewTxn("readwrite", function(aTxn, aStore) {
+    this.dbNewTxn(STATS_STORE_NAME, "readwrite", function(aTxn, aStore) {
       if (DEBUG) {
         debug("Filtered time: " + new Date(timestamp));
         debug("New stats: " + JSON.stringify(stats));
       }
 
     let request = aStore.index("network").openCursor(stats.network, "prev");
       request.onsuccess = function onsuccess(event) {
         let cursor = event.target.result;
@@ -236,31 +303,39 @@ NetworkStatsDB.prototype = {
             lastSample.timestamp + " - diff: " + diff);
     }
 
     // If the incoming data is obtained from netd (|newSample.appId| is 0),
     // the new |txBytes|/|rxBytes| is assigend by the differnce between the new
     // |txTotalBytes|/|rxTotalBytes| and the last |txTotalBytes|/|rxTotalBytes|.
     // Else, the incoming data is per-app data (|newSample.appId| is not 0),
     // the |txBytes|/|rxBytes| is directly the new |txBytes|/|rxBytes|.
+    let rxDiff = 0;
+    let txDiff = 0;
     if (aNewSample.appId == 0) {
-      let rxDiff = aNewSample.rxTotalBytes - lastSample.rxTotalBytes;
-      let txDiff = aNewSample.txTotalBytes - lastSample.txTotalBytes;
+      rxDiff = aNewSample.rxSystemBytes - lastSample.rxSystemBytes;
+      txDiff = aNewSample.txSystemBytes - lastSample.txSystemBytes;
       if (rxDiff < 0 || txDiff < 0) {
-        rxDiff = aNewSample.rxTotalBytes;
-        txDiff = aNewSample.txTotalBytes;
+        rxDiff = aNewSample.rxSystemBytes;
+        txDiff = aNewSample.txSystemBytes;
       }
       aNewSample.rxBytes = rxDiff;
       aNewSample.txBytes = txDiff;
+
+      aNewSample.rxTotalBytes = lastSample.rxTotalBytes + rxDiff;
+      aNewSample.txTotalBytes = lastSample.txTotalBytes + txDiff;
+    } else {
+      rxDiff = aNewSample.rxBytes;
+      txDiff = aNewSample.txBytes;
     }
 
     if (diff == 1) {
       // New element.
 
-      // If the incoming data is per-data data, new |rxTotalBytes|/|txTotalBytes|
+      // If the incoming data is per-app data, new |rxTotalBytes|/|txTotalBytes|
       // needs to be obtained by adding new |rxBytes|/|txBytes| to last
       // |rxTotalBytes|/|txTotalBytes|.
       if (aNewSample.appId != 0) {
         aNewSample.rxTotalBytes = aNewSample.rxBytes + lastSample.rxTotalBytes;
         aNewSample.txTotalBytes = aNewSample.txBytes + lastSample.txTotalBytes;
       }
 
       this._saveStats(aTxn, aStore, aNewSample);
@@ -272,53 +347,48 @@ NetworkStatsDB.prototype = {
       // Add lost samples with 0 bytes and the actual one.
       if (diff > VALUES_MAX_LENGTH) {
         diff = VALUES_MAX_LENGTH;
       }
 
       let data = [];
       for (let i = diff - 2; i >= 0; i--) {
         let time = aNewSample.timestamp - SAMPLE_RATE * (i + 1);
-        let sample = { appId:        aNewSample.appId,
-                       network:      aNewSample.network,
-                       timestamp:    time,
-                       rxBytes:      0,
-                       txBytes:      0,
-                       rxTotalBytes: lastSample.rxTotalBytes,
-                       txTotalBytes: lastSample.txTotalBytes };
+        let sample = { appId:         aNewSample.appId,
+                       network:       aNewSample.network,
+                       timestamp:     time,
+                       rxBytes:       0,
+                       txBytes:       0,
+                       rxSystemBytes: lastSample.rxSystemBytes,
+                       txSystemBytes: lastSample.txSystemBytes,
+                       rxTotalBytes:  lastSample.rxTotalBytes,
+                       txTotalBytes:  lastSample.txTotalBytes };
 
         data.push(sample);
       }
 
       data.push(aNewSample);
       this._saveStats(aTxn, aStore, data);
       return;
     }
     if (diff == 0 || diff < 0) {
-      // New element received before samplerate period.
-      // It means that device has been restarted (or clock / timezone change).
-      // Update element.
-
-      // If diff < 0, clock or timezone changed back. Place data in the last sample.
-
-      lastSample.rxBytes += aNewSample.rxBytes;
-      lastSample.txBytes += aNewSample.txBytes;
+      // New element received before samplerate period. It means that device has
+      // been restarted (or clock / timezone change).
+      // Update element. If diff < 0, clock or timezone changed back. Place data
+      // in the last sample.
 
-      // If incoming data is obtained from netd, last |rxTotalBytes|/|txTotalBytes|
-      // needs to get updated by replacing the new |rxTotalBytes|/|txTotalBytes|.
-      if (aNewSample.appId == 0) {
-        lastSample.rxTotalBytes = aNewSample.rxTotalBytes;
-        lastSample.txTotalBytes = aNewSample.txTotalBytes;
-      } else {
-        // Else, the incoming data is per-app data, old |rxTotalBytes|/
-        // |txTotalBytes| needs to get updated by adding the new
-        // |rxBytes|/|txBytes| to last |rxTotalBytes|/|txTotalBytes|.
-        lastSample.rxTotalBytes += aNewSample.rxBytes;
-        lastSample.txTotalBytes += aNewSample.txBytes;
-      }
+      // Old |rxTotalBytes|/|txTotalBytes| needs to get updated by adding the
+      // last |rxTotalBytes|/|txTotalBytes|.
+      lastSample.rxBytes += rxDiff;
+      lastSample.txBytes += txDiff;
+      lastSample.rxSystemBytes = aNewSample.rxSystemBytes;
+      lastSample.txSystemBytes = aNewSample.txSystemBytes;
+      lastSample.rxTotalBytes += rxDiff;
+      lastSample.txTotalBytes += txDiff;
+
       if (DEBUG) {
         debug("Update: " + JSON.stringify(lastSample));
       }
       let req = aLastSampleCursor.update(lastSample);
     }
   },
 
   _saveStats: function _saveStats(aTxn, aStore, aNetworkStats) {
@@ -374,17 +444,17 @@ NetworkStatsDB.prototype = {
     };
   },
 
   clearInterfaceStats: function clearInterfaceStats(aNetwork, aResultCb) {
     let network = [aNetwork.id, aNetwork.type];
     let self = this;
 
     // Clear and save an empty sample to keep sync with system counters
-    this.dbNewTxn("readwrite", function(aTxn, aStore) {
+    this.dbNewTxn(STATS_STORE_NAME, "readwrite", function(aTxn, aStore) {
       let sample = null;
       let request = aStore.index("network").openCursor(network, "prev");
       request.onsuccess = function onsuccess(event) {
         let cursor = event.target.result;
         if (cursor) {
           if (!sample && cursor.value.appId == 0) {
             sample = cursor.value;
           }
@@ -426,29 +496,56 @@ NetworkStatsDB.prototype = {
 
     if (!aNetworks[index]) {
       aResultCb(null, true);
       return;
     }
     this.clearInterfaceStats(aNetworks[index], callback);
   },
 
+  getCurrentStats: function getCurrentStats(aNetwork, aDate, aResultCb) {
+    if (DEBUG) {
+      debug("Get current stats for " + JSON.stringify(aNetwork) + " since " + aDate);
+    }
+
+    this.dbNewTxn(STATS_STORE_NAME, "readonly", function(txn, store) {
+      let request = null;
+      let network = [aNetwork.id, aNetwork.type];
+      if (aDate) {
+        let start = this.normalizeDate(aDate);
+        let lowerFilter = [0, network, start];
+        let range = this.dbGlobal.IDBKeyRange.lowerBound(lowerFilter, false);
+        request = store.openCursor(range);
+      } else {
+        request = store.index("network").openCursor(network, "prev");
+      }
+
+      request.onsuccess = function onsuccess(event) {
+        txn.result = null;
+        let cursor = event.target.result;
+        if (cursor) {
+          txn.result = cursor.value;
+        }
+      };
+    }.bind(this), aResultCb);
+  },
+
   find: function find(aResultCb, aNetwork, aStart, aEnd, aAppId, aManifestURL) {
     let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
     let start = this.normalizeDate(aStart);
     let end = this.normalizeDate(aEnd);
 
     if (DEBUG) {
       debug("Find samples for appId: " + aAppId + " network " +
             JSON.stringify(aNetwork) + " from " + start + " until " + end);
       debug("Start time: " + new Date(start));
       debug("End time: " + new Date(end));
     }
 
-    this.dbNewTxn("readonly", function(aTxn, aStore) {
+    this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
       let network = [aNetwork.id, aNetwork.type];
       let lowerFilter = [aAppId, network, start];
       let upperFilter = [aAppId, network, end];
       let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
 
       let data = [];
 
       if (!aTxn.result) {
@@ -498,17 +595,17 @@ NetworkStatsDB.prototype = {
     while (aEnd > aData[aData.length - 1].date.getTime()) {
       aData.push({ rxBytes: undefined,
                    txBytes: undefined,
                    date: new Date(aData[aData.length - 1].date.getTime() + SAMPLE_RATE) });
     }
   },
 
   getAvailableNetworks: function getAvailableNetworks(aResultCb) {
-    this.dbNewTxn("readonly", function(aTxn, aStore) {
+    this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
       if (!aTxn.result) {
         aTxn.result = [];
       }
 
       let request = aStore.index("network").openKeyCursor(null, "nextunique");
       request.onsuccess = function onsuccess(event) {
         let cursor = event.target.result;
         if (cursor) {
@@ -517,17 +614,17 @@ NetworkStatsDB.prototype = {
           cursor.continue();
           return;
         }
       };
     }, aResultCb);
   },
 
   isNetworkAvailable: function isNetworkAvailable(aNetwork, aResultCb) {
-    this.dbNewTxn("readonly", function(aTxn, aStore) {
+    this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
       if (!aTxn.result) {
         aTxn.result = false;
       }
 
       var network = [aNetwork.id, aNetwork.type];
       let request = aStore.index("network").openKeyCursor(IDBKeyRange.only(network));
       request.onsuccess = function onsuccess(event) {
         if (event.target.result) {
@@ -541,15 +638,164 @@ NetworkStatsDB.prototype = {
     return SAMPLE_RATE;
   },
 
   get maxStorageSamples () {
     return VALUES_MAX_LENGTH;
   },
 
   logAllRecords: function logAllRecords(aResultCb) {
-    this.dbNewTxn("readonly", function(aTxn, aStore) {
+    this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
       aStore.mozGetAll().onsuccess = function onsuccess(event) {
         aTxn.result = event.target.result;
       };
     }, aResultCb);
   },
+
+  alarmToRecord: function alarmToRecord(aAlarm) {
+    let record = { networkId: aAlarm.networkId,
+                   threshold: aAlarm.threshold,
+                   data: aAlarm.data,
+                   manifestURL: aAlarm.manifestURL,
+                   pageURL: aAlarm.pageURL };
+
+    if (aAlarm.id) {
+      record.id = aAlarm.id;
+    }
+
+    return record;
+  },
+
+  recordToAlarm: function recordToalarm(aRecord) {
+    let alarm = { networkId: aRecord.networkId,
+                  threshold: aRecord.threshold,
+                  data: aRecord.data,
+                  manifestURL: aRecord.manifestURL,
+                  pageURL: aRecord.pageURL };
+
+    if (aRecord.id) {
+      alarm.id = aRecord.id;
+    }
+
+    return alarm;
+  },
+
+  addAlarm: function addAlarm(aAlarm, aResultCb) {
+    this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+      if (DEBUG) {
+        debug("Going to add " + JSON.stringify(aAlarm));
+      }
+
+      let record = this.alarmToRecord(aAlarm);
+      store.put(record).onsuccess = function setResult(aEvent) {
+        txn.result = aEvent.target.result;
+        if (DEBUG) {
+          debug("Request successful. New record ID: " + txn.result);
+        }
+      };
+    }.bind(this), aResultCb);
+  },
+
+  getFirstAlarm: function getFirstAlarm(aNetworkId, aResultCb) {
+    let self = this;
+
+    this.dbNewTxn(ALARMS_STORE_NAME, "readonly", function(txn, store) {
+      if (DEBUG) {
+        debug("Get first alarm for network " + aNetworkId);
+      }
+
+      let lowerFilter = [aNetworkId, 0];
+      let upperFilter = [aNetworkId, ""];
+      let range = IDBKeyRange.bound(lowerFilter, upperFilter);
+
+      store.index("alarm").openCursor(range).onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        txn.result = null;
+        if (cursor) {
+          txn.result = self.recordToAlarm(cursor.value);
+        }
+      };
+    }, aResultCb);
+  },
+
+  removeAlarm: function removeAlarm(aAlarmId, aManifestURL, aResultCb) {
+    this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+      if (DEBUG) {
+        debug("Remove alarm " + aAlarmId);
+      }
+
+      store.get(aAlarmId).onsuccess = function onsuccess(event) {
+        let record = event.target.result;
+        txn.result = false;
+        if (!record || (aManifestURL && record.manifestURL != aManifestURL)) {
+          return;
+        }
+
+        store.delete(aAlarmId);
+        txn.result = true;
+      }
+    }, aResultCb);
+  },
+
+  removeAlarms: function removeAlarms(aManifestURL, aResultCb) {
+    this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+      if (DEBUG) {
+        debug("Remove alarms of " + aManifestURL);
+      }
+
+      store.index("manifestURL").openCursor(aManifestURL)
+                                .onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          cursor.delete();
+          cursor.continue();
+        }
+      }
+    }, aResultCb);
+  },
+
+  updateAlarm: function updateAlarm(aAlarm, aResultCb) {
+    let self = this;
+    this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+      if (DEBUG) {
+        debug("Update alarm " + aAlarm.id);
+      }
+
+      let record = self.alarmToRecord(aAlarm);
+      store.openCursor(record.id).onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        txn.result = false;
+        if (cursor) {
+          cursor.update(record);
+          txn.result = true;
+        }
+      }
+    }, aResultCb);
+  },
+
+  getAlarms: function getAlarms(aNetworkId, aManifestURL, aResultCb) {
+    this.dbNewTxn(ALARMS_STORE_NAME, "readonly", function(txn, store) {
+      if (DEBUG) {
+        debug("Get alarms for " + aManifestURL);
+      }
+
+      txn.result = [];
+      store.index("manifestURL").openCursor(aManifestURL)
+                                .onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        if (!cursor) {
+          return;
+        }
+
+        if (!aNetworkId || cursor.value.networkId == aNetworkId) {
+          let alarm = { id: cursor.value.id,
+                        networkId: cursor.value.networkId,
+                        threshold: cursor.value.threshold,
+                        data: cursor.value.data };
+
+          txn.result.push(alarm);
+        }
+
+        cursor.continue();
+      }
+    }, aResultCb);
+  }
 };
--- a/dom/network/src/NetworkStatsManager.js
+++ b/dom/network/src/NetworkStatsManager.js
@@ -25,74 +25,74 @@ if (isParentProcess) {
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsISyncMessageSender");
 
 // NetworkStatsData
 const nsIClassInfo              = Ci.nsIClassInfo;
 const NETWORKSTATSDATA_CID      = Components.ID("{3b16fe17-5583-483a-b486-b64a3243221c}");
-const nsIDOMMozNetworkStatsData = Components.interfaces.nsIDOMMozNetworkStatsData;
+const nsIDOMMozNetworkStatsData = Ci.nsIDOMMozNetworkStatsData;
 
 function NetworkStatsData(aData) {
   this.rxBytes = aData.rxBytes;
   this.txBytes = aData.txBytes;
   this.date = aData.date;
 }
 
 NetworkStatsData.prototype = {
   __exposedProps__: {
-                      rxBytes: 'r',
-                      txBytes: 'r',
-                      date:  'r',
-                     },
+    rxBytes: 'r',
+    txBytes: 'r',
+    date:  'r',
+  },
 
   classID : NETWORKSTATSDATA_CID,
   classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSDATA_CID,
                                      contractID:"@mozilla.org/networkstatsdata;1",
                                      classDescription: "NetworkStatsData",
                                      interfaces: [nsIDOMMozNetworkStatsData],
                                      flags: nsIClassInfo.DOM_OBJECT}),
 
   QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsData])
 };
 
 // NetworkStatsInterface
 const NETWORKSTATSINTERFACE_CONTRACTID = "@mozilla.org/networkstatsinterface;1";
 const NETWORKSTATSINTERFACE_CID        = Components.ID("{f540615b-d803-43ff-8200-2a9d145a5645}");
-const nsIDOMMozNetworkStatsInterface   = Components.interfaces.nsIDOMMozNetworkStatsInterface;
+const nsIDOMMozNetworkStatsInterface   = Ci.nsIDOMMozNetworkStatsInterface;
 
 function NetworkStatsInterface(aNetwork) {
   if (DEBUG) {
     debug("NetworkStatsInterface Constructor");
   }
   this.type = aNetwork.type;
   this.id = aNetwork.id;
 }
 
 NetworkStatsInterface.prototype = {
   __exposedProps__: {
-                      id: 'r',
-                      type: 'r',
-                    },
+    id: 'r',
+    type: 'r',
+  },
 
   classID : NETWORKSTATSINTERFACE_CID,
   classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSINTERFACE_CID,
                                      contractID: NETWORKSTATSINTERFACE_CONTRACTID,
                                      classDescription: "NetworkStatsInterface",
                                      interfaces: [nsIDOMMozNetworkStatsInterface],
                                      flags: nsIClassInfo.DOM_OBJECT}),
 
   QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsInterface])
 }
 
 // NetworkStats
 const NETWORKSTATS_CONTRACTID = "@mozilla.org/networkstats;1";
 const NETWORKSTATS_CID        = Components.ID("{b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}");
-const nsIDOMMozNetworkStats   = Components.interfaces.nsIDOMMozNetworkStats;
+const nsIDOMMozNetworkStats   = Ci.nsIDOMMozNetworkStats;
 
 function NetworkStats(aWindow, aStats) {
   if (DEBUG) {
     debug("NetworkStats Constructor");
   }
   this.manifestURL = aStats.manifestURL || null;
   this.network = new NetworkStatsInterface(aStats.network);
   this.start = aStats.start || null;
@@ -101,40 +101,69 @@ function NetworkStats(aWindow, aStats) {
   let samples = this.data = Cu.createArrayIn(aWindow);
   for (let i = 0; i < aStats.data.length; i++) {
     samples.push(new NetworkStatsData(aStats.data[i]));
   }
 }
 
 NetworkStats.prototype = {
   __exposedProps__: {
-                      manifestURL: 'r',
-                      network: 'r',
-                      start: 'r',
-                      end:  'r',
-                      data:  'r',
-                    },
+    manifestURL: 'r',
+    network: 'r',
+    start: 'r',
+    end:  'r',
+    data:  'r',
+  },
 
   classID : NETWORKSTATS_CID,
   classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATS_CID,
                                      contractID: NETWORKSTATS_CONTRACTID,
                                      classDescription: "NetworkStats",
                                      interfaces: [nsIDOMMozNetworkStats],
                                      flags: nsIClassInfo.DOM_OBJECT}),
 
   QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStats,
                                           nsIDOMMozNetworkStatsData,
                                           nsIDOMMozNetworkStatsInterface])
 }
 
+// NetworkStatsAlarm
+const NETWORKSTATSALARM_CID      = Components.ID("{063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c}");
+const nsIDOMMozNetworkStatsAlarm = Ci.nsIDOMMozNetworkStatsAlarm;
+
+function NetworkStatsAlarm(aAlarm) {
+  this.alarmId = aAlarm.id;
+  this.network = new NetworkStatsInterface(aAlarm.network);
+  this.threshold = aAlarm.threshold;
+  this.data = aAlarm.data;
+}
+
+NetworkStatsAlarm.prototype = {
+  __exposedProps__: {
+    alarmId: 'r',
+    network: 'r',
+    threshold: 'r',
+    data: 'r',
+  },
+
+  classID : NETWORKSTATSALARM_CID,
+  classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSALARM_CID,
+                                     contractID:"@mozilla.org/networkstatsalarm;1",
+                                     classDescription: "NetworkStatsAlarm",
+                                     interfaces: [nsIDOMMozNetworkStatsAlarm],
+                                     flags: nsIClassInfo.DOM_OBJECT}),
+
+  QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsAlarm])
+};
+
 // NetworkStatsManager
 
 const NETWORKSTATSMANAGER_CONTRACTID = "@mozilla.org/networkStatsManager;1";
-const NETWORKSTATSMANAGER_CID        = Components.ID("{5f033d31-c9a2-4e2d-83aa-6a807f1e0c11}");
-const nsIDOMMozNetworkStatsManager   = Components.interfaces.nsIDOMMozNetworkStatsManager;
+const NETWORKSTATSMANAGER_CID        = Components.ID("{50d109b8-0d7f-4208-81fe-5f07a759f159}");
+const nsIDOMMozNetworkStatsManager   = Ci.nsIDOMMozNetworkStatsManager;
 
 function NetworkStatsManager() {
   if (DEBUG) {
     debug("Constructor");
   }
 }
 
 NetworkStatsManager.prototype = {
@@ -184,16 +213,62 @@ NetworkStatsManager.prototype = {
     this.checkPrivileges();
 
     let request = this.createRequest();
     cpmm.sendAsyncMessage("NetworkStats:ClearAll",
                           {id: this.getRequestId(request)});
     return request;
   },
 
+  addAlarm: function addAlarm(aNetwork, aThreshold, aOptions) {
+    this.checkPrivileges();
+
+    if (!aOptions) {
+      aOptions = Object.create(null);
+    }
+
+    let request = this.createRequest();
+    cpmm.sendAsyncMessage("NetworkStats:SetAlarm",
+                          {id: this.getRequestId(request),
+                           data: {network: aNetwork,
+                                  threshold: aThreshold,
+                                  startTime: aOptions.startTime,
+                                  data: aOptions.data,
+                                  manifestURL: this.manifestURL,
+                                  pageURL: this.pageURL}});
+    return request;
+  },
+
+  getAllAlarms: function getAllAlarms(aNetwork) {
+    this.checkPrivileges();
+
+    let request = this.createRequest();
+    cpmm.sendAsyncMessage("NetworkStats:GetAlarms",
+                          {id: this.getRequestId(request),
+                           data: {network: aNetwork,
+                                  manifestURL: this.manifestURL}});
+    return request;
+  },
+
+  removeAlarms: function removeAlarms(aAlarmId) {
+    this.checkPrivileges();
+
+    if (aAlarmId == 0) {
+      aAlarmId = -1;
+    }
+
+    let request = this.createRequest();
+    cpmm.sendAsyncMessage("NetworkStats:RemoveAlarms",
+                          {id: this.getRequestId(request),
+                           data: {alarmId: aAlarmId,
+                                  manifestURL: this.manifestURL}});
+
+    return request;
+  },
+
   getAvailableNetworks: function getAvailableNetworks() {
     this.checkPrivileges();
 
     let request = this.createRequest();
     cpmm.sendAsyncMessage("NetworkStats:GetAvailableNetworks",
                           { id: this.getRequestId(request) });
     return request;
   },
@@ -207,18 +282,18 @@ NetworkStatsManager.prototype = {
     this.checkPrivileges();
     return cpmm.sendSyncMessage("NetworkStats:MaxStorageAge")[0];
   },
 
   receiveMessage: function(aMessage) {
     if (DEBUG) {
       debug("NetworkStatsmanager::receiveMessage: " + aMessage.name);
     }
+
     let msg = aMessage.json;
-
     let req = this.takeRequest(msg.id);
     if (!req) {
       if (DEBUG) {
         debug("No request stored with id " + msg.id);
       }
       return;
     }
 
@@ -255,16 +330,40 @@ NetworkStatsManager.prototype = {
         if (msg.error) {
           Services.DOMRequest.fireError(req, msg.error);
           return;
         }
 
         Services.DOMRequest.fireSuccess(req, true);
         break;
 
+      case "NetworkStats:SetAlarm:Return":
+      case "NetworkStats:RemoveAlarms:Return":
+        if (msg.error) {
+          Services.DOMRequest.fireError(req, msg.error);
+          return;
+        }
+
+        Services.DOMRequest.fireSuccess(req, msg.result);
+        break;
+
+      case "NetworkStats:GetAlarms:Return":
+        if (msg.error) {
+          Services.DOMRequest.fireError(req, msg.error);
+          return;
+        }
+
+        let alarms = Cu.createArrayIn(this._window);
+        for (let i = 0; i < msg.result.length; i++) {
+          alarms.push(new NetworkStatsAlarm(msg.result[i]));
+        }
+
+        Services.DOMRequest.fireSuccess(req, alarms);
+        break;
+
       default:
         if (DEBUG) {
           debug("Wrong message: " + aMessage.name);
         }
     }
   },
 
   init: function(aWindow) {
@@ -288,17 +387,31 @@ NetworkStatsManager.prototype = {
 
     if (!this.hasPrivileges) {
       return null;
     }
 
     this.initDOMRequestHelper(aWindow, ["NetworkStats:Get:Return",
                                         "NetworkStats:GetAvailableNetworks:Return",
                                         "NetworkStats:Clear:Return",
-                                        "NetworkStats:ClearAll:Return"]);
+                                        "NetworkStats:ClearAll:Return",
+                                        "NetworkStats:SetAlarm:Return",
+                                        "NetworkStats:GetAlarms:Return",
+                                        "NetworkStats:RemoveAlarms:Return"]);
+
+    // Init app properties.
+    let appsService = Cc["@mozilla.org/AppsService;1"]
+                        .getService(Ci.nsIAppsService);
+
+    this.manifestURL = appsService.getManifestURLByLocalId(principal.appId);
+
+    let isApp = !!this.manifestURL.length;
+    if (isApp) {
+      this.pageURL = principal.URI.spec;
+    }
   },
 
   // Called from DOMRequestIpcHelper
   uninit: function uninit() {
     if (DEBUG) {
       debug("uninit call");
     }
   },
@@ -311,12 +424,13 @@ NetworkStatsManager.prototype = {
 
   classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSMANAGER_CID,
                                      contractID: NETWORKSTATSMANAGER_CONTRACTID,
                                      classDescription: "NetworkStatsManager",
                                      interfaces: [nsIDOMMozNetworkStatsManager],
                                      flags: nsIClassInfo.DOM_OBJECT})
 }
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsData,
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsAlarm,
+                                                     NetworkStatsData,
                                                      NetworkStatsInterface,
                                                      NetworkStats,
                                                      NetworkStatsManager]);
--- a/dom/network/src/NetworkStatsManager.manifest
+++ b/dom/network/src/NetworkStatsManager.manifest
@@ -2,11 +2,14 @@ component {3b16fe17-5583-483a-b486-b64a3
 contract @mozilla.org/networkStatsdata;1 {3b16fe17-5583-483a-b486-b64a3243221c}
 
 component {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe} NetworkStatsManager.js
 contract @mozilla.org/networkStats;1 {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}
 
 component {f540615b-d803-43ff-8200-2a9d145a5645} NetworkStatsManager.js
 contract @mozilla.org/networkstatsinterface;1 {f540615b-d803-43ff-8200-2a9d145a5645}
 
-component {5f033d31-c9a2-4e2d-83aa-6a807f1e0c11} NetworkStatsManager.js
-contract @mozilla.org/networkStatsManager;1 {5f033d31-c9a2-4e2d-83aa-6a807f1e0c11}
+component {063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c} NetworkStatsManager.js
+contract @mozilla.org/networkstatsalarm;1 {063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c}
+
+component {50d109b8-0d7f-4208-81fe-5f07a759f159} NetworkStatsManager.js
+contract @mozilla.org/networkStatsManager;1 {50d109b8-0d7f-4208-81fe-5f07a759f159}
 category JavaScript-navigator-property mozNetworkStats @mozilla.org/networkStatsManager;1
--- a/dom/network/src/NetworkStatsService.jsm
+++ b/dom/network/src/NetworkStatsService.jsm
@@ -17,16 +17,18 @@ this.EXPORTED_SYMBOLS = ["NetworkStatsSe
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
 
 const NET_NETWORKSTATSSERVICE_CONTRACTID = "@mozilla.org/network/netstatsservice;1";
 const NET_NETWORKSTATSSERVICE_CID = Components.ID("{18725604-e9ac-488a-8aa0-2471e7f6c0a4}");
 
+const TOPIC_BANDWIDTH_CONTROL = "netd-bandwidth-control"
+
 const TOPIC_INTERFACE_REGISTERED   = "network-interface-registered";
 const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
 const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
 const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
 
 // The maximum traffic amount can be saved in the |cachedAppStats|.
 const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB
 
@@ -45,23 +47,28 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
                                    "@mozilla.org/AppsService;1",
                                    "nsIAppsService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
                                    "@mozilla.org/settingsService;1",
                                    "nsISettingsService");
 
+XPCOMUtils.defineLazyServiceGetter(this, "messenger",
+                                   "@mozilla.org/system-message-internal;1",
+                                   "nsISystemMessagesInternal");
+
 this.NetworkStatsService = {
   init: function() {
     debug("Service started");
 
     Services.obs.addObserver(this, "xpcom-shutdown", false);
     Services.obs.addObserver(this, TOPIC_INTERFACE_REGISTERED, false);
     Services.obs.addObserver(this, TOPIC_INTERFACE_UNREGISTERED, false);
+    Services.obs.addObserver(this, TOPIC_BANDWIDTH_CONTROL, false);
     Services.obs.addObserver(this, "profile-after-change", false);
 
     this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
     // Object to store network interfaces, each network interface is composed
     // by a network object (network type and network Id) and a interfaceName
     // that contains the name of the physical interface (wlan0, rmnet0, etc.).
     // The network type can be 0 for wifi or 1 for mobile. On the other hand,
@@ -82,16 +89,19 @@ this.NetworkStatsService = {
     let netId = this.getNetworkId('0', NET_TYPE_WIFI);
     this._networks[netId] = { network:       { id: '0',
                                                type: NET_TYPE_WIFI },
                               interfaceName: null };
 
     this.messages = ["NetworkStats:Get",
                      "NetworkStats:Clear",
                      "NetworkStats:ClearAll",
+                     "NetworkStats:SetAlarm",
+                     "NetworkStats:GetAlarms",
+                     "NetworkStats:RemoveAlarms",
                      "NetworkStats:GetAvailableNetworks",
                      "NetworkStats:SampleRate",
                      "NetworkStats:MaxStorageAge"];
 
     this.messages.forEach(function(aMsgName) {
       ppmm.addMessageListener(aMsgName, this);
     }, this);
 
@@ -102,16 +112,19 @@ this.NetworkStatsService = {
                                 Ci.nsITimer.TYPE_REPEATING_PRECISE);
 
     // App stats are firstly stored in the cached.
     this.cachedAppStats = Object.create(null);
     this.cachedAppStatsDate = new Date();
 
     this.updateQueue = [];
     this.isQueueRunning = false;
+
+    this._currentAlarms = {};
+    this.initAlarms();
   },
 
   receiveMessage: function(aMessage) {
     if (!aMessage.target.assertPermission("networkstats-manage")) {
       return;
     }
 
     debug("receiveMessage " + aMessage.name);
@@ -124,16 +137,25 @@ this.NetworkStatsService = {
         this.getSamples(mm, msg);
         break;
       case "NetworkStats:Clear":
         this.clearInterfaceStats(mm, msg);
         break;
       case "NetworkStats:ClearAll":
         this.clearDB(mm, msg);
         break;
+      case "NetworkStats:SetAlarm":
+        this.setAlarm(mm, msg);
+        break;
+      case "NetworkStats:GetAlarms":
+        this.getAlarms(mm, msg);
+        break;
+      case "NetworkStats:RemoveAlarms":
+        this.removeAlarms(mm, msg);
+        break;
       case "NetworkStats:GetAvailableNetworks":
         this.getAvailableNetworks(mm, msg);
         break;
       case "NetworkStats:SampleRate":
         // This message is sync.
         return this._db.sampleRate;
       case "NetworkStats:MaxStorageAge":
         // This message is sync.
@@ -141,42 +163,61 @@ this.NetworkStatsService = {
     }
   },
 
   observe: function observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       case TOPIC_INTERFACE_REGISTERED:
       case TOPIC_INTERFACE_UNREGISTERED:
 
-        // If new interface is registered (notified from NetworkManager),
+        // If new interface is registered (notified from NetworkService),
         // the stats are updated for the new interface without waiting to
         // complete the updating period.
 
         let network = aSubject.QueryInterface(Ci.nsINetworkInterface);
         debug("Network " + network.name + " of type " + network.type + " status change");
 
         let netId = this.convertNetworkInterface(network);
         if (!netId) {
           break;
         }
 
+        this._updateCurrentAlarm(netId);
+
         debug("NetId: " + netId);
         this.updateStats(netId);
         break;
+
+      case TOPIC_BANDWIDTH_CONTROL:
+        debug("Bandwidth message from netd: " + JSON.stringify(aData));
+
+        let interfaceName = aData.substring(aData.lastIndexOf(" ") + 1);
+        for (let networkId in this._networks) {
+          if (interfaceName == this._networks[networkId].interfaceName) {
+            let currentAlarm = this._currentAlarms[networkId];
+            if (Object.getOwnPropertyNames(currentAlarm).length !== 0) {
+              this._fireAlarm(currentAlarm.alarm);
+            }
+            break;
+          }
+        }
+        break;
+
       case "xpcom-shutdown":
         debug("Service shutdown");
 
         this.messages.forEach(function(aMsgName) {
           ppmm.removeMessageListener(aMsgName, this);
         }, this);
 
         Services.obs.removeObserver(this, "xpcom-shutdown");
         Services.obs.removeObserver(this, "profile-after-change");
         Services.obs.removeObserver(this, TOPIC_INTERFACE_REGISTERED);
         Services.obs.removeObserver(this, TOPIC_INTERFACE_UNREGISTERED);
+        Services.obs.removeObserver(this, TOPIC_BANDWIDTH_CONTROL);
 
         this.timer.cancel();
         this.timer = null;
 
         // Update stats before shutdown
         this.updateAllStats();
         break;
     }
@@ -261,16 +302,35 @@ this.NetworkStatsService = {
         }
       }
 
       mm.sendAsyncMessage("NetworkStats:GetAvailableNetworks:Return",
                           { id: msg.id, error: aError, result: aResult });
     });
   },
 
+  initAlarms: function initAlarms() {
+    debug("Init usage alarms");
+    let self = this;
+
+    for (let netId in this._networks) {
+      this._currentAlarms[netId] = Object.create(null);
+
+      this._db.getFirstAlarm(netId, function getResult(error, result) {
+        if (!error && result) {
+          self._setAlarm(result, function onSet(error, success) {
+            if (error == "InvalidStateError") {
+              self._fireAlarm(result);
+            }
+          });
+        }
+      });
+    }
+  },
+
   /*
    * Function called from manager to get stats from database.
    * In order to return updated stats, first is performed a call to
    * updateAllStats function, which will get last stats from netd
    * and update the database.
    * Then, depending on the request (stats per appId or total stats)
    * it retrieve them from database and return to the manager.
    */
@@ -477,18 +537,18 @@ this.NetworkStatsService = {
       let item = this.updateQueue.shift();
       for (let callback of item.callbacks) {
         if(callback) {
           callback(aResult, aMessage);
         }
       }
     } else {
       // The caller is a function that has pushed new elements to the queue,
-      // if isQueueRunning is false it means there is no processing currently being
-      // done, so start.
+      // if isQueueRunning is false it means there is no processing currently
+      // being done, so start.
       if (this.isQueueRunning) {
         if(this.updateQueue.length > 1) {
           return;
         }
       } else {
         this.isQueueRunning = true;
       }
     }
@@ -510,17 +570,17 @@ this.NetworkStatsService = {
         aCallback(false, "Invalid network " + aNetId);
       }
       return;
     }
 
     let interfaceName = this._networks[aNetId].interfaceName;
     debug("Update stats for " + interfaceName);
 
-    // Request stats to NetworkManager, which will get stats from netd, passing
+    // Request stats to NetworkService, which will get stats from netd, passing
     // 'networkStatsAvailable' as a callback.
     if (interfaceName) {
       networkService.getNetworkInterfaceStats(interfaceName,
                 this.networkStatsAvailable.bind(this, aCallback, aNetId));
       return;
     }
 
     if (aCallback) {
@@ -701,11 +761,267 @@ this.NetworkStatsService = {
         return;
       }
 
       debug("===== LOG =====");
       debug("There are " + aResult.length + " items");
       debug(JSON.stringify(aResult));
     });
   },
+
+  getAlarms: function getAlarms(mm, msg) {
+    let network = msg.data.network;
+    let manifestURL = msg.data.manifestURL;
+
+    let netId = null;
+    if (network) {
+      netId = this.getNetworkId(network.id, network.type);
+      if (!this._networks[netId]) {
+        mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
+                            { id: msg.id, error: "InvalidInterface", result: null });
+        return;
+      }
+    }
+
+    let self = this;
+    this._db.getAlarms(netId, manifestURL, function onCompleted(error, result) {
+      if (error) {
+        mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
+                            { id: msg.id, error: error, result: result });
+        return;
+      }
+
+      let alarms = []
+      // NetworkStatsManager must return the network instead of the networkId.
+      for (let i = 0; i < result.length; i++) {
+        let alarm = result[i];
+        alarms.push({ id: alarm.id,
+                      network: self._networks[alarm.networkId].network,
+                      threshold: alarm.threshold,
+                      data: alarm.data });
+      }
+
+      mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
+                          { id: msg.id, error: null, result: alarms });
+    });
+  },
+
+  removeAlarms: function removeAlarms(mm, msg) {
+    let alarmId = msg.data.alarmId;
+    let manifestURL = msg.data.manifestURL;
+
+    let self = this;
+    let callback = function onRemove(error, result) {
+      if (error) {
+        mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
+                            { id: msg.id, error: error, result: result });
+        return;
+      }
+
+      for (let i in self._currentAlarms) {
+        let currentAlarm = self._currentAlarms[i].alarm;
+        if (currentAlarm && ((alarmId == currentAlarm.id) ||
+            (alarmId == -1 && currentAlarm.manifestURL == manifestURL))) {
+
+          self._updateCurrentAlarm(currentAlarm.networkId);
+        }
+      }
+
+      mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
+                          { id: msg.id, error: error, result: true });
+    };
+
+    if (alarmId == -1) {
+      this._db.removeAlarms(manifestURL, callback);
+    } else {
+      this._db.removeAlarm(alarmId, manifestURL, callback);
+    }
+  },
+
+  /*
+   * Function called from manager to set an alarm.
+   */
+  setAlarm: function setAlarm(mm, msg) {
+    let options = msg.data;
+    let network = options.network;
+    let threshold = options.threshold;
+
+    debug("Set alarm at " + threshold + " for " + JSON.stringify(network));
+
+    if (threshold < 0) {
+      mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+                          { id: msg.id, error: "InvalidThresholdValue", result: null });
+      return;
+    }
+
+    let netId = this.getNetworkId(network.id, network.type);
+    if (!this._networks[netId]) {
+      mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+                          { id: msg.id, error: "InvalidiConnectionType", result: null });
+      return;
+    }
+
+    let newAlarm = {
+      id: null,
+      networkId: netId,
+      threshold: threshold,
+      absoluteThreshold: null,
+      startTime: options.startTime,
+      data: options.data,
+      pageURL: options.pageURL,
+      manifestURL: options.manifestURL
+    };
+
+    let self = this;
+    this._updateThreshold(newAlarm, function onUpdate(error, _threshold) {
+      if (error) {
+        mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+                            { id: msg.id, error: error, result: null });
+        return;
+      }
+
+      newAlarm.absoluteThreshold = _threshold.absoluteThreshold;
+      self._db.addAlarm(newAlarm, function addSuccessCb(error, newId) {
+        if (error) {
+          mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+                              { id: msg.id, error: error, result: null });
+          return;
+        }
+
+        newAlarm.id = newId;
+        self._setAlarm(newAlarm, function onSet(error, success) {
+          mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+                              { id: msg.id, error: error, result: newId });
+
+          if (error == "InvalidStateError") {
+            self._fireAlarm(newAlarm);
+          }
+        });
+      });
+    });
+  },
+
+  _setAlarm: function _setAlarm(aAlarm, aCallback) {
+    let currentAlarm = this._currentAlarms[aAlarm.networkId];
+    if (Object.getOwnPropertyNames(currentAlarm).length !== 0 &&
+        aAlarm.absoluteThreshold > currentAlarm.alarm.absoluteThreshold) {
+      aCallback(null, true);
+      return;
+    }
+
+    let self = this;
+
+    this._updateThreshold(aAlarm, function onUpdate(aError, aThreshold) {
+      if (aError) {
+        aCallback(aError, null);
+        return;
+      }
+
+      let callback = function onAlarmSet(aError) {
+        if (aError) {
+          debug("Set alarm error: " + aError);
+          aCallback("netdError", null);
+          return;
+        }
+
+        self._currentAlarms[aAlarm.networkId].alarm = aAlarm;
+
+        aCallback(null, true);
+      };
+
+      debug("Set alarm " + JSON.stringify(aAlarm));
+      let interfaceName = self._networks[aAlarm.networkId].interfaceName;
+      if (interfaceName) {
+        networkService.setNetworkInterfaceAlarm(interfaceName,
+                                                aThreshold.systemThreshold,
+                                                callback);
+        return;
+      }
+
+      aCallback(null, true);
+    });
+  },
+
+  _updateThreshold: function _updateThreshold(aAlarm, aCallback) {
+    let self = this;
+    this.updateStats(aAlarm.networkId, function onStatsUpdated(aResult, aMessage) {
+      self._db.getCurrentStats(self._networks[aAlarm.networkId].network,
+                               aAlarm.startTime,
+                               function onStatsFound(error, result) {
+        if (error) {
+          debug("Error getting stats for " +
+                JSON.stringify(self._networks[aAlarm.networkId]) + ": " + error);
+          aCallback(error, result);
+          return;
+        }
+
+        let offset = aAlarm.threshold - result.rxTotalBytes - result.txTotalBytes;
+
+        // Alarm set to a threshold lower than current rx/tx bytes.
+        if (offset <= 0) {
+          aCallback("InvalidStateError", null);
+          return;
+        }
+
+        let threshold = {
+          systemThreshold: result.rxSystemBytes + result.txSystemBytes + offset,
+          absoluteThreshold: result.rxTotalBytes + result.txTotalBytes + offset
+        };
+
+        aCallback(null, threshold);
+      });
+    });
+  },
+
+  _fireAlarm: function _fireAlarm(aAlarm) {
+    debug("Fire alarm");
+
+    let self = this;
+    this._db.removeAlarm(aAlarm.id, null, function onRemove(aError, aResult){
+      if (!aError && !aResult) {
+        return;
+      }
+
+      self._fireSystemMessage(aAlarm);
+      self._updateCurrentAlarm(aAlarm.networkId);
+    });
+  },
+
+  _updateCurrentAlarm: function _updateCurrentAlarm(aNetworkId) {
+    this._currentAlarms[aNetworkId] = Object.create(null);
+
+    let self = this;
+    this._db.getFirstAlarm(aNetworkId, function onGet(error, result){
+      if (error) {
+        debug("Error getting the first alarm");
+        return;
+      }
+
+      if (!result) {
+        let interfaceName = self._networks[aNetworkId].interfaceName;
+        networkService.setNetworkInterfaceAlarm(interfaceName, -1,
+                                                function onComplete(){});
+        return;
+      }
+
+      self._setAlarm(result, function onSet(error, success){
+        if (error == "InvalidStateError") {
+          self._fireAlarm(result);
+          return;
+        }
+      });
+    });
+  },
+
+  _fireSystemMessage: function _fireSystemMessage(aAlarm) {
+    debug("Fire system message: " + JSON.stringify(aAlarm));
+
+    let manifestURI = Services.io.newURI(aAlarm.manifestURL, null, null);
+    let pageURI = Services.io.newURI(aAlarm.pageURL, null, null);
+
+    let alarm = { "id":        aAlarm.id,
+                  "threshold": aAlarm.threshold,
+                  "data":      aAlarm.data };
+    messenger.sendMessage("networkstats-alarm", alarm, pageURI, manifestURI);
+  }
 };
 
 NetworkStatsService.init();
--- a/dom/network/tests/Makefile.in
+++ b/dom/network/tests/Makefile.in
@@ -10,10 +10,11 @@ MOCHITEST_FILES = \
   $(NULL)
 
 ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
 MOCHITEST_FILES = \
   test_networkstats_basics.html \
   test_networkstats_disabled.html \
   test_networkstats_enabled_no_perm.html \
   test_networkstats_enabled_perm.html \
+  test_networkstats_alarms.html \
   $(NULL)
 endif
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/test_networkstats_alarms.html
@@ -0,0 +1,175 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for NetworkStats alarms</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">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+function test() {
+  ok(true, "Checking if no alarms are set.");
+
+  req = navigator.mozNetworkStats.getAllAlarms();
+
+  req.onsuccess = function () {
+    ok(true, "Succeeded to get alarms.");
+    ok(Array.isArray(req.result) && req.result.length == 0,
+       "There are no alarms set.");
+    next();
+  };
+
+  req.onerror = function () {
+    ok(false, "getAllAlarms() shouldn't fail!");
+  }
+}
+
+var req;
+var index = -1;
+
+var wifi = {'type': 0, 'id': '0'};
+var mobile = {'type': 1, 'id': '1'};
+
+var steps = [
+  function () {
+    ok(true, "Calling getAllAlarms() with invalid network.");
+
+    req = navigator.mozNetworkStats.getAllAlarms(mobile);
+
+    req.onsuccess = function () {
+      ok(false, "getAllAlarms() shouldn't succeed!");
+    };
+
+    req.onerror = function () {
+      ok(req.error.name == "InvalidInterface", "Get InvalidInterface error");
+      next();
+    }
+  },
+  function () {
+    ok(true, "Calling addAlarm() with invalid network or parameters.");
+
+    try {
+      navigator.mozNetworkStats.addAlarm();
+    } catch(ex) {
+      ok(ex.result == SpecialPowers.Cr.NS_ERROR_XPC_NOT_ENOUGH_ARGS,
+         "addAlarm() throws NS_ERROR_XPC_NOT_ENOUGH_ARGS exception when no parameters");
+    }
+
+    try {
+      navigator.mozNetworkStats.addAlarm(100000);
+    } catch(ex) {
+      ok(ex.result == SpecialPowers.Cr.NS_ERROR_XPC_NOT_ENOUGH_ARGS,
+         "addAlarm() throws NS_ERROR_XPC_NOT_ENOUGH_ARGS exception when no network");
+    }
+
+    try {
+      navigator.mozNetworkStats.addAlarm(wifi);
+    } catch(ex) {
+      ok(ex.result == SpecialPowers.Cr.NS_ERROR_XPC_NOT_ENOUGH_ARGS,
+         "addAlarm() throws NS_ERROR_XPC_NOT_ENOUGH_ARGS exception when no threshold");
+    }
+
+    req = navigator.mozNetworkStats.addAlarm(mobile, -100000);
+
+    req.onsuccess = function () {
+      ok(false, "addAlarm() shouldn't succeed with negative threshold.");
+    };
+
+    req.onerror = function () {
+      ok(req.error.name == "InvalidThresholdValue", "Get InvalidThresholdValue error");
+      next();
+    }
+  },
+  function () {
+    ok(true, "Calling addAlarm()");
+
+    req = navigator.mozNetworkStats.addAlarm(wifi, 1000000);
+
+    req.onsuccess = function () {
+      ok(true, "Succeeded to add alarm. AlarmId: " + req.result);
+      next();
+    };
+    req.onerror = function () {
+      ok(false, "addAlarm() shouldn't fail.");
+    };
+  },
+  function () {
+    ok(true, "Calling getAllAlarms()");
+
+    req = navigator.mozNetworkStats.getAllAlarms(wifi);
+
+    req.onsuccess = function () {
+      ok(req.result.length == 1, "Only one alarm");
+      ok(req.result[0].alarmId == 1, "Get correct alarmId");
+      next();
+    };
+
+    req.onerror = function () {
+      ok(false, "getAllAlarms() shouldn't fail.");
+    }
+  },
+  function () {
+    ok(true, "Calling removeAlarms() to remove alarms.");
+
+    req = navigator.mozNetworkStats.removeAlarms();
+
+    req.onsuccess = function () {
+      ok(req.result, "Succeeded to remove alarms.");
+      next();
+    };
+
+    req.onerror = function () {
+      ok(false, "removeAlarms() shouldn't fail.");
+    }
+  },
+  function () {
+    ok(true, "Checking if all alarms are removed.");
+
+    req = navigator.mozNetworkStats.getAllAlarms();
+
+    req.onsuccess = function () {
+      ok(Array.isArray(req.result) && req.result.length == 0,
+         "Succeeded to remove all alarms.");
+      next();
+    };
+
+    req.onerror = function () {
+      ok(false, "getAllAlarms() shouldn't fail.");
+    }
+  },
+  function () {
+    ok(true, "all done!\n");
+    SpecialPowers.removePermission("networkstats-manage", document);
+    SimpleTest.finish();
+    return;
+  }
+];
+
+function next() {
+  index += 1;
+  if (index >= steps.length) {
+    ok(false, "Shouldn't get here!");
+    return;
+  }
+  try {
+    steps[index]();
+  } catch(ex) {
+    ok(false, "Caught exception", ex);
+  }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.addPermission("networkstats-manage", true, document);
+SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]}, test);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/network/tests/test_networkstats_basics.html
+++ b/dom/network/tests/test_networkstats_basics.html
@@ -8,20 +8,18 @@
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 function test() {
-  ok('mozNetworkStats' in navigator, "navigator.mozMozNetworkStats should exist");
-  ok(navigator.mozNetworkStats, "navigator.mozNetworkStats returns an object");
-
-  netStats = navigator.mozNetworkStats;
+  netStats = window.navigator.mozNetworkStats;
+  ok(netStats, "mozNetworkStats exists");
 
   // Test IDL attributes
   ok('sampleRate' in netStats,
    "sampleRate should be a NetworkStats attribute");
   ok(netStats.sampleRate > 0,
    "sampleRate is greater than 0.");
 
   ok('maxStorageAge' in netStats,
--- a/dom/network/tests/unit_stats/test_networkstats_db.js
+++ b/dom/network/tests/unit_stats/test_networkstats_db.js
@@ -2,22 +2,38 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
 
 const netStatsDb = new NetworkStatsDB();
 
-function clearWholeDB(callback) {
-  netStatsDb.dbNewTxn("readwrite", function(aTxn, aStore) {
-    aStore.delete();
+function clearStore(store, callback) {
+  netStatsDb.dbNewTxn(store, "readwrite", function(aTxn, aStore) {
+    aStore.openCursor().onsuccess = function (event) {
+      let cursor = event.target.result;
+      if (cursor){
+        cursor.delete();
+        cursor.continue();
+      }
+    };
   }, callback);
 }
 
+add_test(function prepareDatabase() {
+  // Clear whole database to avoid starting tests with unknown state
+  // due to the previous tests.
+  clearStore('net_stats', function() {
+    clearStore('net_alarm', function() {
+      run_next_test();
+    });
+  });
+});
+
 function filterTimestamp(date) {
   var sampleRate = netStatsDb.sampleRate;
   var offset = date.getTimezoneOffset() * 60 * 1000;
   return Math.floor((date.getTime() - offset) / sampleRate) * sampleRate;
 }
 
 function getNetworks() {
   return [{ id: '0', type: Ci.nsIDOMMozNetworkStatsManager.WIFI },
@@ -117,37 +133,41 @@ add_test(function test_clear_interface()
     do_check_eq(error, null);
     run_next_test();
   });
 });
 
 add_test(function test_internalSaveStats_singleSample() {
   var networks = getNetworks();
 
-  var stats = { appId:        0,
-                network:      [networks[0].id, networks[0].type],
-                timestamp:    Date.now(),
-                rxBytes:      0,
-                txBytes:      0,
-                rxTotalBytes: 1234,
-                txTotalBytes: 1234 };
+  var stats = { appId:         0,
+                network:       [networks[0].id, networks[0].type],
+                timestamp:     Date.now(),
+                rxBytes:       0,
+                txBytes:       0,
+                rxSystemBytes: 1234,
+                txSystemBytes: 1234,
+                rxTotalBytes:  1234,
+                txTotalBytes:  1234 };
 
-  netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+  netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
     netStatsDb._saveStats(txn, store, stats);
   }, function(error, result) {
     do_check_eq(error, null);
 
     netStatsDb.logAllRecords(function(error, result) {
       do_check_eq(error, null);
       do_check_eq(result.length, 1);
       do_check_eq(result[0].appId, stats.appId);
       do_check_true(compareNetworks(result[0].network, stats.network));
       do_check_eq(result[0].timestamp, stats.timestamp);
       do_check_eq(result[0].rxBytes, stats.rxBytes);
       do_check_eq(result[0].txBytes, stats.txBytes);
+      do_check_eq(result[0].rxSystemBytes, stats.rxSystemBytes);
+      do_check_eq(result[0].txSystemBytes, stats.txSystemBytes);
       do_check_eq(result[0].rxTotalBytes, stats.rxTotalBytes);
       do_check_eq(result[0].txTotalBytes, stats.txTotalBytes);
       run_next_test();
     });
   });
 });
 
 add_test(function test_internalSaveStats_arraySamples() {
@@ -156,45 +176,48 @@ add_test(function test_internalSaveStats
   netStatsDb.clearStats(networks, function (error, result) {
     do_check_eq(error, null);
 
     var network = [networks[0].id, networks[0].type];
 
     var samples = 2;
     var stats = [];
     for (var i = 0; i < samples; i++) {
-      stats.push({ appId:        0,
-                   network:      network,
-                   timestamp:    Date.now() + (10 * i),
-                   rxBytes:      0,
-                   txBytes:      0,
-                   rxTotalBytes: 1234,
-                   txTotalBytes: 1234 });
+      stats.push({ appId:         0,
+                   network:       network,
+                   timestamp:     Date.now() + (10 * i),
+                   rxBytes:       0,
+                   txBytes:       0,
+                   rxSystemBytes: 1234,
+                   txSystemBytes: 1234,
+                   rxTotalBytes:  1234,
+                   txTotalBytes:  1234 });
     }
 
-    netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+    netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
       netStatsDb._saveStats(txn, store, stats);
     }, function(error, result) {
       do_check_eq(error, null);
 
       netStatsDb.logAllRecords(function(error, result) {
         do_check_eq(error, null);
 
         // Result has one sample more than samples because clear inserts
         // an empty sample to keep totalBytes synchronized with netd counters
         result.shift();
         do_check_eq(result.length, samples);
-
         var success = true;
         for (var i = 1; i < samples; i++) {
           if (result[i].appId != stats[i].appId ||
               !compareNetworks(result[i].network, stats[i].network) ||
               result[i].timestamp != stats[i].timestamp ||
               result[i].rxBytes != stats[i].rxBytes ||
               result[i].txBytes != stats[i].txBytes ||
+              result[i].rxSystemBytes != stats[i].rxSystemBytes ||
+              result[i].txSystemBytes != stats[i].txSystemBytes ||
               result[i].rxTotalBytes != stats[i].rxTotalBytes ||
               result[i].txTotalBytes != stats[i].txTotalBytes) {
             success = false;
             break;
           }
         }
         do_check_true(success);
         run_next_test();
@@ -208,28 +231,30 @@ add_test(function test_internalRemoveOld
 
   netStatsDb.clearStats(networks, function (error, result) {
     do_check_eq(error, null);
 
     var network = [networks[0].id, networks[0].type];
     var samples = 10;
     var stats = [];
     for (var i = 0; i < samples - 1; i++) {
-      stats.push({ appId:              0,
-                   network:      network, timestamp:    Date.now() + (10 * i),
-                   rxBytes:            0, txBytes:      0,
-                   rxTotalBytes:    1234, txTotalBytes: 1234 });
+      stats.push({ appId:               0,
+                   network:       network, timestamp:     Date.now() + (10 * i),
+                   rxBytes:             0, txBytes:       0,
+                   rxSystemBytes:    1234, txSystemBytes: 1234,
+                   rxTotalBytes:     1234, txTotalBytes:  1234 });
     }
 
-    stats.push({ appId:              0,
-                 network:      network, timestamp:    Date.now() + (10 * samples),
-                 rxBytes:            0, txBytes:      0,
-                 rxTotalBytes:    1234, txTotalBytes: 1234 });
+    stats.push({ appId:               0,
+                 network:       network, timestamp:     Date.now() + (10 * samples),
+                 rxBytes:             0, txBytes:       0,
+                 rxSystemBytes:    1234, txSystemBytes: 1234,
+                 rxTotalBytes:     1234, txTotalBytes:  1234 });
 
-    netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+    netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
       netStatsDb._saveStats(txn, store, stats);
       var date = stats[stats.length - 1].timestamp
                  + (netStatsDb.sampleRate * netStatsDb.maxStorageSamples - 1) - 1;
       netStatsDb._removeOldStats(txn, store, 0, network, date);
     }, function(error, result) {
       do_check_eq(error, null);
 
       netStatsDb.logAllRecords(function(error, result) {
@@ -240,20 +265,20 @@ add_test(function test_internalRemoveOld
       });
     });
   });
 });
 
 function processSamplesDiff(networks, lastStat, newStat, callback) {
   netStatsDb.clearStats(networks, function (error, result){
     do_check_eq(error, null);
-    netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+    netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
       netStatsDb._saveStats(txn, store, lastStat);
     }, function(error, result) {
-      netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+      netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
         let request = store.index("network").openCursor(newStat.network, "prev");
         request.onsuccess = function onsuccess(event) {
           let cursor = event.target.result;
           do_check_neq(cursor, null);
           netStatsDb._processSamplesDiff(txn, store, cursor, newStat);
         };
       }, function(error, result) {
         do_check_eq(error, null);
@@ -268,122 +293,135 @@ function processSamplesDiff(networks, la
 
 add_test(function test_processSamplesDiffSameSample() {
   var networks = getNetworks();
   var network = [networks[0].id, networks[0].type];
 
   var sampleRate = netStatsDb.sampleRate;
   var date = filterTimestamp(new Date());
 
-  var lastStat = { appId:              0,
-                   network:      network, timestamp:    date,
-                   rxBytes:            0, txBytes:      0,
-                   rxTotalBytes:    1234, txTotalBytes: 1234 };
+  var lastStat = { appId:               0,
+                   network:       network, timestamp:     date,
+                   rxBytes:             0, txBytes:       0,
+                   rxSystemBytes:    1234, txSystemBytes: 1234,
+                   rxTotalBytes:     2234, txTotalBytes:  2234 };
 
-  var newStat = { appId:              0,
-                  network:      network, timestamp:    date,
-                  rxBytes:            0, txBytes:      0,
-                  rxTotalBytes:    2234, txTotalBytes: 2234 };
+  var newStat = { appId:               0,
+                  network:       network, timestamp:     date,
+                  rxBytes:             0, txBytes:       0,
+                  rxSystemBytes:    2234, txSystemBytes: 2234,
+                  rxTotalBytes:     2234, txTotalBytes:  2234 };
 
   processSamplesDiff(networks, lastStat, newStat, function(result) {
     do_check_eq(result.length, 1);
     do_check_eq(result[0].appId, newStat.appId);
     do_check_true(compareNetworks(result[0].network, newStat.network));
     do_check_eq(result[0].timestamp, newStat.timestamp);
-    do_check_eq(result[0].rxBytes, newStat.rxTotalBytes - lastStat.rxTotalBytes);
-    do_check_eq(result[0].txBytes, newStat.txTotalBytes - lastStat.txTotalBytes);
-    do_check_eq(result[0].rxTotalBytes, newStat.rxTotalBytes);
-    do_check_eq(result[0].txTotalBytes, newStat.txTotalBytes);
+    do_check_eq(result[0].rxBytes, newStat.rxSystemBytes - lastStat.rxSystemBytes);
+    do_check_eq(result[0].txBytes, newStat.txSystemBytes - lastStat.txSystemBytes);
+    do_check_eq(result[0].rxTotalBytes, lastStat.rxTotalBytes + newStat.rxSystemBytes - lastStat.rxSystemBytes);
+    do_check_eq(result[0].txTotalBytes, lastStat.txTotalBytes + newStat.txSystemBytes - lastStat.txSystemBytes);
+    do_check_eq(result[0].rxSystemBytes, newStat.rxSystemBytes);
+    do_check_eq(result[0].txSystemBytes, newStat.txSystemBytes);
     run_next_test();
   });
 });
 
 add_test(function test_processSamplesDiffNextSample() {
   var networks = getNetworks();
   var network = [networks[0].id, networks[0].type];
 
   var sampleRate = netStatsDb.sampleRate;
   var date = filterTimestamp(new Date());
 
-  var lastStat = { appId:              0,
-                   network:      network, timestamp:    date,
-                   rxBytes:            0, txBytes:      0,
-                   rxTotalBytes:    1234, txTotalBytes: 1234 };
+  var lastStat = { appId:               0,
+                   network:       network, timestamp:     date,
+                   rxBytes:             0, txBytes:       0,
+                   rxSystemBytes:    1234, txSystemBytes: 1234,
+                   rxTotalBytes:     2234, txTotalBytes:  2234 };
 
-  var newStat = { appId:              0,
-                  network:      network, timestamp:    date + sampleRate,
-                  rxBytes:            0, txBytes:      0,
-                  rxTotalBytes:     500, txTotalBytes: 500 };
+  var newStat = { appId:               0,
+                  network:       network, timestamp:     date + sampleRate,
+                  rxBytes:             0, txBytes:       0,
+                  rxSystemBytes:    1734, txSystemBytes: 1734,
+                  rxTotalBytes:        0, txTotalBytes:  0 };
 
   processSamplesDiff(networks, lastStat, newStat, function(result) {
     do_check_eq(result.length, 2);
     do_check_eq(result[1].appId, newStat.appId);
     do_check_true(compareNetworks(result[1].network, newStat.network));
     do_check_eq(result[1].timestamp, newStat.timestamp);
-    do_check_eq(result[1].rxBytes, newStat.rxTotalBytes);
-    do_check_eq(result[1].txBytes, newStat.txTotalBytes);
-    do_check_eq(result[1].rxTotalBytes, newStat.rxTotalBytes);
-    do_check_eq(result[1].txTotalBytes, newStat.txTotalBytes);
+    do_check_eq(result[1].rxBytes, newStat.rxSystemBytes - lastStat.rxSystemBytes);
+    do_check_eq(result[1].txBytes, newStat.txSystemBytes - lastStat.txSystemBytes);
+    do_check_eq(result[1].rxSystemBytes, newStat.rxSystemBytes);
+    do_check_eq(result[1].txSystemBytes, newStat.txSystemBytes);
+    do_check_eq(result[1].rxTotalBytes, lastStat.rxTotalBytes + newStat.rxSystemBytes - lastStat.rxSystemBytes);
+    do_check_eq(result[1].txTotalBytes, lastStat.txTotalBytes + newStat.txSystemBytes - lastStat.txSystemBytes);
     run_next_test();
   });
 });
 
 add_test(function test_processSamplesDiffSamplesLost() {
   var networks = getNetworks();
   var network = [networks[0].id, networks[0].type];
   var samples = 5;
   var sampleRate = netStatsDb.sampleRate;
   var date = filterTimestamp(new Date());
-  var lastStat = { appId:             0,
-                   network:     network, timestamp:    date,
-                   rxBytes:           0, txBytes:      0,
-                   rxTotalBytes:   1234, txTotalBytes: 1234 };
+  var lastStat = { appId:              0,
+                   network:      network, timestamp:     date,
+                   rxBytes:            0, txBytes:       0,
+                   rxSystemBytes:   1234, txSystemBytes: 1234,
+                   rxTotalBytes:    2234, txTotalBytes:  2234};
 
-  var newStat = { appId:              0,
-                  network:      network, timestamp:    date + (sampleRate * samples),
-                  rxBytes:            0, txBytes:      0,
-                  rxTotalBytes:    2234, txTotalBytes: 2234 };
+  var newStat = { appId:               0,
+                  network:       network, timestamp:     date + (sampleRate * samples),
+                  rxBytes:             0, txBytes:       0,
+                  rxSystemBytes:    2234, txSystemBytes: 2234,
+                  rxTotalBytes:        0, txTotalBytes:  0 };
 
   processSamplesDiff(networks, lastStat, newStat, function(result) {
     do_check_eq(result.length, samples + 1);
     do_check_eq(result[0].appId, newStat.appId);
     do_check_true(compareNetworks(result[samples].network, newStat.network));
     do_check_eq(result[samples].timestamp, newStat.timestamp);
     do_check_eq(result[samples].rxBytes, newStat.rxTotalBytes - lastStat.rxTotalBytes);
     do_check_eq(result[samples].txBytes, newStat.txTotalBytes - lastStat.txTotalBytes);
-    do_check_eq(result[samples].rxTotalBytes, newStat.rxTotalBytes);
-    do_check_eq(result[samples].txTotalBytes, newStat.txTotalBytes);
+    do_check_eq(result[samples].rxSystemBytes, newStat.rxSystemBytes);
+    do_check_eq(result[samples].txSystemBytes, newStat.txSystemBytes);
+    do_check_eq(result[samples].rxTotalBytes, lastStat.rxTotalBytes + newStat.rxSystemBytes - lastStat.rxSystemBytes);
+    do_check_eq(result[samples].txTotalBytes, lastStat.txTotalBytes + newStat.txSystemBytes - lastStat.txSystemBytes);
     run_next_test();
   });
 });
 
 add_test(function test_saveStats() {
   var networks = getNetworks();
   var network = [networks[0].id, networks[0].type];
 
   var stats = { appId:       0,
                 networkId:   networks[0].id,
                 networkType: networks[0].type,
                 date:        new Date(),
                 rxBytes:     2234,
                 txBytes:     2234};
 
-  netStatsDb.clearStats(networks, function (error, result) {
-    do_check_eq(error, null);
+  clearStore('net_stats', function() {
     netStatsDb.saveStats(stats, function(error, result) {
       do_check_eq(error, null);
       netStatsDb.logAllRecords(function(error, result) {
         do_check_eq(error, null);
         do_check_eq(result.length, 1);
         do_check_eq(result[0].appId, stats.appId);
         do_check_true(compareNetworks(result[0].network, network));
         let timestamp = filterTimestamp(stats.date);
         do_check_eq(result[0].timestamp, timestamp);
-        do_check_eq(result[0].rxBytes, 0);
-        do_check_eq(result[0].txBytes, 0);
+        do_check_eq(result[0].rxBytes, stats.rxBytes);
+        do_check_eq(result[0].txBytes, stats.txBytes);
+        do_check_eq(result[0].rxSystemBytes, stats.rxBytes);
+        do_check_eq(result[0].txSystemBytes, stats.txBytes);
         do_check_eq(result[0].rxTotalBytes, stats.rxBytes);
         do_check_eq(result[0].txTotalBytes, stats.txBytes);
         run_next_test();
       });
     });
   });
 });
 
@@ -411,28 +449,30 @@ add_test(function test_saveAppStats() {
         // past tests and the new one for appId 1
         do_check_eq(result.length, 2);
         do_check_eq(result[1].appId, stats.appId);
         do_check_true(compareNetworks(result[1].network, network));
         let timestamp = filterTimestamp(stats.date);
         do_check_eq(result[1].timestamp, timestamp);
         do_check_eq(result[1].rxBytes, stats.rxBytes);
         do_check_eq(result[1].txBytes, stats.txBytes);
+        do_check_eq(result[1].rxSystemBytes, 0);
+        do_check_eq(result[1].txSystemBytes, 0);
         do_check_eq(result[1].rxTotalBytes, 0);
         do_check_eq(result[1].txTotalBytes, 0);
         run_next_test();
       });
     });
   });
 });
 
 function prepareFind(network, stats, callback) {
   netStatsDb.clearStats(network, function (error, result) {
     do_check_eq(error, null);
-    netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+    netStatsDb.dbNewTxn("net_stats", "readwrite", function(txn, store) {
       netStatsDb._saveStats(txn, store, stats);
     }, function(error, result) {
         callback(error, result);
     });
   });
 }
 
 add_test(function test_find () {
@@ -444,25 +484,28 @@ add_test(function test_find () {
   var samples = 5;
   var sampleRate = netStatsDb.sampleRate;
   var start = Date.now();
   var saveDate = filterTimestamp(new Date());
   var end = new Date(start + (sampleRate * (samples - 1)));
   start = new Date(start - sampleRate);
   var stats = [];
   for (var i = 0; i < samples; i++) {
-    stats.push({ appId:              appId,
-                 network:      networkWifi, timestamp:    saveDate + (sampleRate * i),
-                 rxBytes:                0, txBytes:      10,
-                 rxTotalBytes:           0, txTotalBytes: 0 });
+    stats.push({ appId:               appId,
+                 network:       networkWifi, timestamp:     saveDate + (sampleRate * i),
+                 rxBytes:                 0, txBytes:       10,
+                 rxSystemBytes:           0, txSystemBytes: 0,
+                 rxTotalBytes:            0, txTotalBytes:  0});
 
-    stats.push({ appId:                appId,
-                 network:      networkMobile, timestamp:    saveDate + (sampleRate * i),
-                 rxBytes:                  0, txBytes:      10,
-                 rxTotalBytes:             0, txTotalBytes: 0 });
+
+    stats.push({ appId:                 appId,
+                 network:       networkMobile, timestamp:     saveDate + (sampleRate * i),
+                 rxBytes:                   0, txBytes:       10,
+                 rxSystemBytes:             0, txSystemBytes: 0,
+                 rxTotalBytes:              0, txTotalBytes:  0});
   }
 
   prepareFind(networks[0], stats, function(error, result) {
     do_check_eq(error, null);
     netStatsDb.find(function (error, result) {
       do_check_eq(error, null);
       do_check_eq(result.network.id, networks[0].id);
       do_check_eq(result.network.type, networks[0].type);
@@ -549,40 +592,217 @@ add_test(function test_saveMultipleAppSt
   netStatsDb.clearStats(networks, function (error, result) {
     do_check_eq(error, null);
     netStatsDb.saveStats(cached[keys[index]],
       function callback(error, result) {
         do_check_eq(error, null);
 
         if (index == keys.length - 1) {
           netStatsDb.logAllRecords(function(error, result) {
-          // Again, result has two samples more than expected samples because
-          // clear inserts one empty sample for each network to keep totalBytes
-          // synchronized with netd counters. so the first two samples have to
-          // be discarted.
-          result.shift();
-          result.shift();
+            // Again, result has two samples more than expected samples because
+            // clear inserts one empty sample for each network to keep totalBytes
+            // synchronized with netd counters. so the first two samples have to
+            // be discarted.
+            result.shift();
+            result.shift();
 
-          do_check_eq(error, null);
-          do_check_eq(result.length, 4);
-          do_check_eq(result[0].appId, 1);
-          do_check_true(compareNetworks(result[0].network,[networkWifi.id, networkWifi.type]));
-          do_check_eq(result[0].rxBytes, 0);
-          do_check_eq(result[0].txBytes, 10);
-          run_next_test();
+            do_check_eq(error, null);
+            do_check_eq(result.length, 4);
+            do_check_eq(result[0].appId, 1);
+            do_check_true(compareNetworks(result[0].network, [networkWifi.id, networkWifi.type]));
+            do_check_eq(result[0].rxBytes, 0);
+            do_check_eq(result[0].txBytes, 10);
+            run_next_test();
           });
+         return;
         }
 
         index += 1;
         netStatsDb.saveStats(cached[keys[index]], callback);
     });
   });
 });
 
+var networkWifi = '00';
+var networkMobile = '11';
+
+var examplePageURL = "http://example.com/index.html";
+var exampleManifestURL = "http://example.com/manifest.webapp";
+
+var testPageURL = "http://test.com/index.html";
+var testManifestURL = "http://test.com/manifest.webapp";
+
+var alarms = [{ id:             null,
+                networkId:      networkWifi,
+                threshold:      10000,
+                data:           {foo: "something"},
+                pageURL:        examplePageURL,
+                manifestURL:    exampleManifestURL },
+              { id:             null,
+                networkId:      networkWifi,
+                threshold:      1000,
+                data:           {foo: "else"},
+                pageURL:        examplePageURL,
+                manifestURL:    exampleManifestURL },
+              { id:             null,
+                networkId:      networkMobile,
+                threshold:      100,
+                data:           {foo: "to"},
+                pageURL:        examplePageURL,
+                manifestURL:    exampleManifestURL },
+              { id:             null,
+                networkId:      networkMobile,
+                threshold:      10,
+                data:           {foo: "test"},
+                pageURL:        testPageURL,
+                manifestURL:    testManifestURL }];
+
+var alarmsDbId = 1;
+
+add_test(function test_addAlarm() {
+  // Add alarms[0] -> DB: [ alarms[0] (id: 1) ]
+  // Check the insertion is OK.
+  netStatsDb.addAlarm(alarms[0], function(error, result) {
+    do_check_eq(error, null);
+    alarmsDbId = result;
+    netStatsDb.getAlarms(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, exampleManifestURL, function(error, result) {
+      do_check_eq(error, null);
+      do_check_eq(result.length, 1);
+      do_check_eq(result[0].id, alarmsDbId);
+      do_check_eq(result[0].networkId, alarms[0].networkId);
+      do_check_eq(result[0].threshold, alarms[0].threshold);
+      do_check_eq(result[0].data.foo, alarms[0].data.foo);
+      run_next_test();
+    });
+  });
+});
+
+add_test(function test_getFirstAlarm() {
+  // Add alarms[1] -> DB: [ alarms[0] (id: 1), alarms[1] (id: 2) ]
+  // Check first alarm is alarms[1] because threshold is lower.
+  alarmsDbId += 1;
+  netStatsDb.addAlarm(alarms[1], function (error, result) {
+    do_check_eq(error, null);
+    do_check_eq(result, alarmsDbId);
+    netStatsDb.getFirstAlarm(networkWifi, function(error, result) {
+      do_check_eq(error, null);
+      do_check_eq(result.id, alarmsDbId);
+      do_check_eq(result.networkId, alarms[1].networkId);
+      do_check_eq(result.threshold, alarms[1].threshold);
+      do_check_eq(result.data.foo, alarms[1].data.foo);
+      do_check_eq(result.pageURL, alarms[1].pageURL);
+      do_check_eq(result.manifestURL, alarms[1].manifestURL);
+      run_next_test();
+    });
+  });
+});
+
+add_test(function test_removeAlarm() {
+  // Remove alarms[1] (id: 2) -> DB: [ alarms[0] (id: 1) ]
+  // Check get first return alarms[0].
+  netStatsDb.removeAlarm(alarmsDbId, alarms[0].manifestURL, function (error, result) {
+    do_check_eq(error, null);
+    netStatsDb.getFirstAlarm(networkWifi, function(error, result) {
+      do_check_eq(error, null);
+      do_check_eq(result.id, alarmsDbId - 1);
+      do_check_eq(result.networkId, alarms[0].networkId);
+      do_check_eq(result.threshold, alarms[0].threshold);
+      do_check_eq(result.data.foo, alarms[0].data.foo);
+      do_check_eq(result.pageURL, alarms[0].pageURL);
+      do_check_eq(result.manifestURL, alarms[0].manifestURL);
+      run_next_test();
+    });
+  });
+});
+
+add_test(function test_removeAppAlarm() {
+  // Remove alarms[0] (id: 1) -> DB: [ ]
+  netStatsDb.removeAlarm(alarmsDbId - 1, alarms[0].manifestURL, function (error, result) {
+    do_check_eq(error, null);
+    netStatsDb.getAlarms(networkWifi, exampleManifestURL, function(error, result) {
+      do_check_eq(error, null);
+      do_check_eq(result.length, 0);
+      run_next_test();
+    });
+  });
+});
+
+add_test(function test_getAlarms() {
+  // Add all alarms -> DB: [ alarms[0] (id: 3),
+  //                         alarms[1] (id: 4),
+  //                         alarms[2] (id: 5),
+  //                         alarms[3] (id: 6) ]
+  // Check that getAlarms for wifi returns 2 alarms.
+  // Check that getAlarms for all connections returns 3 alarms.
+
+  var callback = function () {
+    netStatsDb.getAlarms(networkWifi, exampleManifestURL, function(error, result) {
+      do_check_eq(error, null);
+      do_check_eq(result.length, 2);
+      netStatsDb.getAlarms(null, exampleManifestURL, function(error, result) {
+        do_check_eq(error, null);
+        do_check_eq(result.length, 3);
+        run_next_test();
+      });
+    });
+  };
+
+  var index = 0;
+
+  var addFunction = function () {
+    alarmsDbId += 1;
+    netStatsDb.addAlarm(alarms[index], function (error, result) {
+      do_check_eq(error, null);
+      index += 1;
+      do_check_eq(result, alarmsDbId);
+      if (index >= alarms.length) {
+        callback();
+        return;
+      }
+      addFunction();
+    });
+  };
+
+  addFunction();
+});
+
+add_test(function test_removeAppAllAlarms() {
+  // Remove all alarms for exampleManifestURL -> DB: [ alarms[3] (id: 6) ]
+  netStatsDb.removeAlarms(exampleManifestURL, function (error, result) {
+    do_check_eq(error, null);
+    netStatsDb.getAlarms(null, exampleManifestURL, function(error, result) {
+      do_check_eq(error, null);
+      do_check_eq(result.length, 0);
+      netStatsDb.getAlarms(null, testManifestURL, function(error, result) {
+        do_check_eq(error, null);
+        do_check_eq(result.length, 1);
+        run_next_test();
+      });
+    });
+  });
+});
+
+add_test(function test_updateAlarm() {
+  // Update alarms[3] (id: 6) -> DB: [ alarms[3]* (id: 6) ]
+
+  var updatedAlarm = alarms[1];
+  updatedAlarm.id = alarmsDbId;
+  updatedAlarm.threshold = 10;
+
+  netStatsDb.updateAlarm(updatedAlarm, function (error, result) {
+    do_check_eq(error, null);
+    netStatsDb.getFirstAlarm(networkWifi, function(error, result) {
+      do_check_eq(error, null);
+      do_check_eq(result.id, updatedAlarm.id);
+      do_check_eq(result.networkId, updatedAlarm.networkId);
+      do_check_eq(result.threshold, updatedAlarm.threshold);
+      do_check_eq(result.data.foo, updatedAlarm.data.foo);
+      do_check_eq(result.pageURL, updatedAlarm.pageURL);
+      do_check_eq(result.manifestURL, updatedAlarm.manifestURL);
+      run_next_test();
+    });
+  });
+});
+
 function run_test() {
   do_get_profile();
-
-  // Clear whole database to avoid start tests with unknown state
-  // due to previous tests.
-  clearWholeDB(function(){
-    run_next_test();
-  });
+  run_next_test();
 }
--- a/dom/network/tests/unit_stats/test_networkstats_service.js
+++ b/dom/network/tests/unit_stats/test_networkstats_service.js
@@ -134,17 +134,105 @@ add_test(function test_queue() {
   NetworkStatsService.updateStats(netId1, callback);
   NetworkStatsService.updateStats(netId2, callback);
 
   do_check_eq(NetworkStatsService.updateQueue.length, 2);
   do_check_eq(NetworkStatsService.updateQueue[0].callbacks.length, 2);
   do_check_eq(NetworkStatsService.updateQueue[0].callbacks[0], null);
   do_check_neq(NetworkStatsService.updateQueue[0].callbacks[1], null);
 
+  // Clear queue because in test environment requests for mobile networks
+  // can not be handled.
+  NetworkStatsService.updateQueue =  [];
   run_next_test();
 });
 
+var wifiId = '00';
+
+add_test(function test_updateThreshold() {
+  let alarm = { networkId: wifiId, threshold: 10000 };
+
+  NetworkStatsService._updateThreshold(alarm, function onSet(error, threshold){
+    do_check_eq(error, null);
+    do_check_neq(threshold.systemThreshold, undefined);
+    do_check_neq(threshold.absoluteThreshold, undefined);
+    run_next_test();
+  });
+});
+
+var testPageURL = "http://test.com";
+var testManifestURL = "http://test.com/manifest.webapp";
+
+add_test(function test_setAlarm() {
+  let alarm = { id: null,
+                networkId: wifiId,
+                threshold: 10000,
+                absoluteThreshold: null,
+                alarmStart: null,
+                alarmEnd: null,
+                data: null,
+                pageURL: testPageURL,
+                manifestURL: testManifestURL };
+
+  NetworkStatsService._setAlarm(alarm, function onSet(error, result) {
+    do_check_eq(result, 1);
+    run_next_test();
+  });
+});
+
+add_test(function test_setAlarm_invalid_threshold() {
+  let alarm = { id: null,
+                networkId: wifiId,
+                threshold: -10000,
+                absoluteThreshold: null,
+                alarmStart: null,
+                alarmEnd: null,
+                data: null,
+                pageURL: testPageURL,
+                manifestURL: testManifestURL };
+
+  NetworkStatsService._setAlarm(alarm, function onSet(error, result) {
+    do_check_eq(error, "InvalidStateError");
+    run_next_test();
+  });
+});
+
+add_test(function test_fireAlarm() {
+  // Add a fake alarm into database.
+  let alarm = { id: null,
+                networkId: wifiId,
+                threshold: 10000,
+                absoluteThreshold: null,
+                alarmStart: null,
+                alarmEnd: null,
+                data: null,
+                pageURL: testPageURL,
+                manifestURL: testManifestURL };
+
+  NetworkStatsService._db.addAlarm(alarm, function addSuccessCb(error, newId) {
+    NetworkStatsService._db.getAlarms(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+                                      testManifestURL, function onGet(error, result) {
+      do_check_eq(error, null);
+      do_check_eq(result.length, 1);
+
+      // Result of getAlarms is based on expected child's data format, so
+      // some changes are needed to be able to use it.
+      result[0].networkId = wifiId;
+      result[0].pageURL = testPageURL;
+      result[0].manifestURL = testManifestURL;
+
+      NetworkStatsService._fireAlarm(result[0], false);
+      NetworkStatsService._db.getAlarms(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+                                        testManifestURL, function onGet(error, result) {
+        do_check_eq(error, undefined);
+        do_check_eq(result.length, 0);
+        run_next_test();
+      });
+    });
+  });
+});
+
 function run_test() {
   do_get_profile();
 
   Cu.import("resource://gre/modules/NetworkStatsService.jsm");
   run_next_test();
 }
--- a/dom/nfc/MozNdefRecord.cpp
+++ b/dom/nfc/MozNdefRecord.cpp
@@ -3,60 +3,112 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Copyright © 2013 Deutsche Telekom, Inc. */
 
 #include "MozNdefRecord.h"
 #include "mozilla/dom/MozNdefRecordBinding.h"
+#include "mozilla/HoldDropJSObjects.h"
 #include "nsContentUtils.h"
 
+
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(MozNdefRecord, mWindow)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MozNdefRecord)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MozNdefRecord)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MozNdefRecord)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(MozNdefRecord)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mType)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mId)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPayload)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MozNdefRecord)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MozNdefRecord)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MozNdefRecord)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
+void
+MozNdefRecord::HoldData()
+{
+  mozilla::HoldJSObjects(this);
+}
+
+void
+MozNdefRecord::DropData()
+{
+  if (mType) {
+    mType = nullptr;
+  }
+  if (mId) {
+    mId = nullptr;
+  }
+  if (mPayload) {
+    mPayload = nullptr;
+  }
+  mozilla::DropJSObjects(this);
+}
+
 /* static */
 already_AddRefed<MozNdefRecord>
 MozNdefRecord::Constructor(const GlobalObject& aGlobal,
-                           uint8_t aTnf, const nsAString& aType,
-                           const nsAString& aId, const nsAString& aPayload,
+                           uint8_t aTnf, const Uint8Array& aType,
+                           const Uint8Array& aId, const Uint8Array& aPayload,
                            ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aGlobal.GetAsSupports());
   if (!win) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
-  nsRefPtr<MozNdefRecord> ndefrecord =
-    new MozNdefRecord(win, aTnf, aType, aId, aPayload);
+
+  nsRefPtr<MozNdefRecord> ndefrecord = new MozNdefRecord(aGlobal.GetContext(),
+                                                         win, aTnf, aType, aId,
+                                                         aPayload);
+  if (!ndefrecord) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
   return ndefrecord.forget();
 }
 
-MozNdefRecord::MozNdefRecord(nsPIDOMWindow* aWindow,
-                             uint8_t aTnf, const nsAString& aType,
-                             const nsAString& aId, const nsAString& aPayload)
+MozNdefRecord::MozNdefRecord(JSContext* aCx, nsPIDOMWindow* aWindow,
+                             uint8_t aTnf, const Uint8Array& aType,
+                             const Uint8Array& aId, const Uint8Array& aPayload)
   : mTnf(aTnf)
-  , mType(aType)
-  , mId(aId)
-  , mPayload(aPayload)
 {
-  mWindow = aWindow;
+  mWindow = aWindow; // For GetParentObject()
+
+  mType = Uint8Array::Create(aCx, this, aType.Length(), aType.Data());
+  mId = Uint8Array::Create(aCx, this, aId.Length(), aId.Data());
+  mPayload = Uint8Array::Create(aCx, this, aPayload.Length(), aPayload.Data());
+
   SetIsDOMBinding();
+  HoldData();
 }
 
 MozNdefRecord::~MozNdefRecord()
 {
+  DropData();
 }
 
 JSObject*
 MozNdefRecord::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return MozNdefRecordBinding::Wrap(aCx, aScope, this);
 }
 
--- a/dom/nfc/MozNdefRecord.h
+++ b/dom/nfc/MozNdefRecord.h
@@ -12,77 +12,96 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "jsapi.h"
 
 #include "nsIDocument.h"
 
+#include "mozilla/dom/TypedArray.h"
+#include "jsfriendapi.h"
+#include "js/GCAPI.h"
+
 struct JSContext;
 
 namespace mozilla {
 namespace dom {
 
 class MozNdefRecord MOZ_FINAL : public nsISupports,
                                 public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MozNdefRecord)
 
 public:
 
-  MozNdefRecord(nsPIDOMWindow* aWindow,
-                uint8_t aTnf, const nsAString& aType,
-                const nsAString& aId, const nsAString& aPlayload);
+  MozNdefRecord(JSContext* aCx, nsPIDOMWindow* aWindow, uint8_t aTnf,
+                const Uint8Array& aType, const Uint8Array& aId,
+                const Uint8Array& aPlayload);
 
   ~MozNdefRecord();
 
   nsIDOMWindow* GetParentObject() const
   {
     return mWindow;
   }
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
-  static already_AddRefed<MozNdefRecord> Constructor(
-                                           const GlobalObject& aGlobal,
-                                           uint8_t aTnf, const nsAString& aType,
-                                           const nsAString& aId,
-                                           const nsAString& aPayload,
-                                           ErrorResult& aRv);
+  static already_AddRefed<MozNdefRecord>
+                  Constructor(const GlobalObject& aGlobal, uint8_t aTnf,
+                              const Uint8Array& aType, const Uint8Array& aId,
+                              const Uint8Array& aPayload, ErrorResult& aRv);
 
   uint8_t Tnf() const
   {
     return mTnf;
   }
 
-  void GetType(nsString& aType) const
+  JSObject* Type(JSContext* cx) const
   {
-    aType = mType;
+    return GetTypeObject();
+  }
+  JSObject* GetTypeObject() const
+  {
+    JS::ExposeObjectToActiveJS(mType);
+    return mType;
   }
 
-  void GetId(nsString& aId) const
+  JSObject* Id(JSContext* cx) const
   {
-    aId = mId;
+    return GetIdObject();
+  }
+  JSObject* GetIdObject() const
+  {
+    JS::ExposeObjectToActiveJS(mId);
+    return mId;
   }
 
-  void GetPayload(nsString& aPayload) const
+  JSObject* Payload(JSContext* cx) const
   {
-    aPayload = mPayload;
+    return GetPayloadObject();
+  }
+  JSObject* GetPayloadObject() const
+  {
+    JS::ExposeObjectToActiveJS(mPayload);
+    return mPayload;
   }
 
 private:
   MozNdefRecord() MOZ_DELETE;
   nsRefPtr<nsPIDOMWindow> mWindow;
+  void HoldData();
+  void DropData();
 
   uint8_t mTnf;
-  nsString mType;
-  nsString mId;
-  nsString mPayload;
+  JS::Heap<JSObject*> mType;
+  JS::Heap<JSObject*> mId;
+  JS::Heap<JSObject*> mPayload;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_MozNdefRecord_h__
--- a/dom/system/gonk/NetworkService.js
+++ b/dom/system/gonk/NetworkService.js
@@ -5,17 +5,17 @@
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 const NETWORKSERVICE_CONTRACTID = "@mozilla.org/network/service;1";
-const NETWORKSERVICE_CID = Components.ID("{a6c58260-46df-11e3-8f96-0800200c9a66}");
+const NETWORKSERVICE_CID = Components.ID("{c14cabaf-bb8e-470d-a2f1-2cb6de6c5e5c}");
 
 // 1xx - Requested action is proceeding
 const NETD_COMMAND_PROCEEDING   = 100;
 // 2xx - Requested action has been successfully completed
 const NETD_COMMAND_OKAY         = 200;
 // 4xx - The command is accepted but the requested action didn't
 // take place.
 const NETD_COMMAND_FAIL         = 400;
@@ -112,23 +112,99 @@ NetworkService.prototype = {
       cmd: "getNetworkInterfaceStats",
       ifname: networkName
     };
 
     params.report = true;
     params.isAsync = true;
 
     this.controlMessage(params, function(result) {
-      let success = result.resultCode >= NETD_COMMAND_OKAY &&
-                    result.resultCode < NETD_COMMAND_ERROR;
+      let success = !isError(result.resultCode);
       callback.networkStatsAvailable(success, result.rxBytes,
                                      result.txBytes, result.date);
     });
   },
 
+  setNetworkInterfaceAlarm: function setNetworkInterfaceAlarm(networkName, threshold, callback) {
+    if (!networkName) {
+      callback.networkUsageAlarmResult(-1);
+      return;
+    }
+
+    if (threshold < 0) {
+      this._disableNetworkInterfaceAlarm(networkName, callback);
+      return;
+    }
+
+    this._setNetworkInterfaceAlarm(networkName, threshold, callback);
+  },
+
+  _setNetworkInterfaceAlarm: function _setNetworkInterfaceAlarm(networkName, threshold, callback) {
+    debug("setNetworkInterfaceAlarm for " + networkName + " at " + threshold + "bytes");
+
+    let params = {
+      cmd: "setNetworkInterfaceAlarm",
+      ifname: networkName,
+      threshold: threshold
+    };
+
+    params.report = true;
+    params.isAsync = true;
+
+    this.controlMessage(params, function(result) {
+      if (!isError(result.resultCode)) {
+        callback.networkUsageAlarmResult(null);
+        return;
+      }
+
+      this._enableNetworkInterfaceAlarm(networkName, threshold, callback);
+    });
+  },
+
+  _enableNetworkInterfaceAlarm: function _enableNetworkInterfaceAlarm(networkName, threshold, callback) {
+    debug("enableNetworkInterfaceAlarm for " + networkName + " at " + threshold + "bytes");
+
+    let params = {
+      cmd: "enableNetworkInterfaceAlarm",
+      ifname: networkName,
+      threshold: threshold
+    };
+
+    params.report = true;
+    params.isAsync = true;
+
+    this.controlMessage(params, function(result) {
+      if (!isError(result.resultCode)) {
+        callback.networkUsageAlarmResult(null);
+        return;
+      }
+      callback.networkUsageAlarmResult(result.reason);
+    });
+  },
+
+  _disableNetworkInterfaceAlarm: function _disableNetworkInterfaceAlarm(networkName, callback) {
+    debug("disableNetworkInterfaceAlarm for " + networkName);
+
+    let params = {
+      cmd: "disableNetworkInterfaceAlarm",
+      ifname: networkName,
+    };
+
+    params.report = true;
+    params.isAsync = true;
+
+    this.controlMessage(params, function(result) {
+      if (!isError(result.resultCode)) {
+        callback.networkUsageAlarmResult(null);
+        return;
+      }
+      callback.networkUsageAlarmResult(result.reason);
+    });
+  },
+
   setWifiOperationMode: function setWifiOperationMode(interfaceName, mode, callback) {
     if(DEBUG) debug("setWifiOperationMode on " + interfaceName + " to " + mode);
 
     let params = {
       cmd: "setWifiOperationMode",
       ifname: interfaceName,
       mode: mode
     };
--- a/dom/system/gonk/NetworkService.manifest
+++ b/dom/system/gonk/NetworkService.manifest
@@ -1,3 +1,3 @@
 # NetworkService.js
-component {a6c58260-46df-11e3-8f96-0800200c9a66} NetworkService.js
-contract @mozilla.org/network/service;1 {a6c58260-46df-11e3-8f96-0800200c9a66}
+component {c14cabaf-bb8e-470d-a2f1-2cb6de6c5e5c} NetworkService.js
+contract @mozilla.org/network/service;1 {c14cabaf-bb8e-470d-a2f1-2cb6de6c5e5c}
--- a/dom/system/gonk/net_worker.js
+++ b/dom/system/gonk/net_worker.js
@@ -146,17 +146,29 @@ function networkInterfaceStatsFail(param
   // Notify the main thread.
   postMessage(params);
   return true;
 }
 
 function networkInterfaceStatsSuccess(params) {
   // Notify the main thread.
   params.txBytes = parseFloat(params.resultReason);
+  postMessage(params);
+  return true;
+}
 
+function networkInterfaceAlarmFail(params) {
+  // Notify the main thread.
+  postMessage(params);
+  return true;
+}
+
+function networkInterfaceAlarmSuccess(params) {
+  // Notify the main thread.
+  params.error = parseFloat(params.resultReason);
   postMessage(params);
   return true;
 }
 
 function updateUpStreamSuccess(params) {
   // Notify the main thread.
   postMessage(params);
   return true;
@@ -602,16 +614,41 @@ function getRxBytes(params, callback) {
 
 function getTxBytes(params, callback) {
   params.rxBytes = parseFloat(params.resultReason);
 
   let command = "interface readtxcounter " + params.ifname;
   return doCommand(command, callback);
 }
 
+function enableAlarm(params, callback) {
+  let command = "bandwidth enable";
+  return doCommand(command, callback);
+}
+
+function disableAlarm(params, callback) {
+  let command = "bandwidth disable";
+  return doCommand(command, callback);
+}
+
+function setQuota(params, callback) {
+  let command = "bandwidth setiquota " + params.ifname + " " + parseInt('0xffffffffffffffff');
+  return doCommand(command, callback);
+}
+
+function removeQuota(params, callback) {
+  let command = "bandwidth removeiquota " + params.ifname;
+  return doCommand(command, callback);
+}
+
+function setAlarm(params, callback) {
+  let command = "bandwidth setinterfacealert " + params.ifname + " " + params.threshold;
+  return doCommand(command, callback);
+}
+
 function escapeQuote(str) {
   str = str.replace(/\\/g, "\\\\");
   return str.replace(/"/g, "\\\"");
 }
 
 /**
  * Command format for sdk version < 16
  *   Arguments:
@@ -909,16 +946,49 @@ function getNetworkInterfaceStats(params
   params.rxBytes = -1;
   params.txBytes = -1;
   params.date = new Date();
 
   chain(params, gNetworkInterfaceStatsChain, networkInterfaceStatsFail);
   return true;
 }
 
+let gNetworkInterfaceEnableAlarmChain = [enableAlarm,
+                                         setQuota,
+                                         setAlarm,
+                                         networkInterfaceAlarmSuccess];
+
+function enableNetworkInterfaceAlarm(params) {
+  debug("enableNetworkInterfaceAlarms: " + params.ifname);
+
+  chain(params, gNetworkInterfaceEnableAlarmChain, networkInterfaceAlarmFail);
+  return true;
+}
+
+let gNetworkInterfaceDisableAlarmChain = [removeQuota,
+                                          disableAlarm,
+                                          networkInterfaceAlarmSuccess];
+
+function disableNetworkInterfaceAlarm(params) {
+  debug("disableNetworkInterfaceAlarms: " + params.ifname);
+
+  chain(params, gNetworkInterfaceDisableAlarmChain, networkInterfaceAlarmFail);
+  return true;
+}
+
+let gNetworkInterfaceSetAlarmChain = [setAlarm,
+                                      networkInterfaceAlarmSuccess];
+
+function setNetworkInterfaceAlarm(params) {
+  debug("setNetworkInterfaceAlarms: " + params.ifname);
+
+  chain(params, gNetworkInterfaceSetAlarmChain, networkInterfaceAlarmFail);
+  return true;
+}
+
 let gWifiOperationModeChain = [wifiFirmwareReload,
                                wifiOperationModeSuccess];
 
 /**
  * handling main thread's reload Wifi firmware request
  */
 function setWifiOperationMode(params) {
   debug("setWifiOperationMode: " + params.ifname + " " + params.mode);
--- a/dom/system/gonk/nfc_consts.js
+++ b/dom/system/gonk/nfc_consts.js
@@ -43,18 +43,17 @@ this.NFC_RESPONSE_READ_NDEF = 1003;
 this.NFC_NOTIFICATION_INITIALIZED = 2000;
 this.NFC_NOTIFICATION_TECH_DISCOVERED = 2001;
 this.NFC_NOTIFICATION_TECH_LOST = 2002;
 
 this.NFC_TECHS = {
   0:'NDEF',
   1:'NDEF_WRITEABLE',
   2:'NDEF_FORMATABLE',
-  3:'P2P',
-  4:'NFC_A'
+  3:'P2P'
 };
 
 // TODO: Bug 933595. Fill-in all error codes for Gonk/nfcd protocol
 this.GECKO_NFC_ERROR_SUCCESS             = 0;
 this.GECKO_NFC_ERROR_GENERIC_FAILURE     = 1;
 
 // NFC powerlevels must match config PDUs.
 this.NFC_POWER_LEVEL_UNKNOWN        = -1;
--- a/dom/system/gonk/nfc_worker.js
+++ b/dom/system/gonk/nfc_worker.js
@@ -107,43 +107,34 @@ let NfcWorker = {
     let numOfRecords = Buf.readInt32();
     debug("numOfRecords = " + numOfRecords);
     if (numOfRecords <= 0) {
       return null;
     }
     let records = [];
 
     for (let i = 0; i < numOfRecords; i++) {
-      let tnf        = Buf.readInt32();
+      let tnf        = Buf.readInt32() & 0xff;
       let typeLength = Buf.readInt32();
-      let type = [];
-      for (let i = 0; i < typeLength; i++) {
-        type.push(Buf.readUint8());
-      }
+      let type       = Buf.readUint8Array(typeLength);
       let padding    = getPaddingLen(typeLength);
       for (let i = 0; i < padding; i++) {
         Buf.readUint8();
       }
 
       let idLength = Buf.readInt32();
-      let id = [];
-      for (let i = 0; i < idLength; i++) {
-        id.push(Buf.readUint8());
-      }
+      let id       = Buf.readUint8Array(idLength);
       padding      = getPaddingLen(idLength);
       for (let i = 0; i < padding; i++) {
         Buf.readUint8();
       }
 
       let payloadLength = Buf.readInt32();
-      let payload = [];
-      for (let i = 0; i < payloadLength; i++) {
-        payload.push(Buf.readUint8());
-      }
-      padding = getPaddingLen(payloadLength);
+      let payload       = Buf.readUint8Array(payloadLength);
+      padding           = getPaddingLen(payloadLength);
       for (let i = 0; i < padding; i++) {
         Buf.readUint8();
       }
       records.push({tnf: tnf,
                     type: type,
                     id: id,
                     payload: payload});
     }
@@ -191,40 +182,40 @@ let NfcWorker = {
     Buf.writeInt32(message.sessionId);
     let records    = message.records;
     let numRecords = records.length;
     Buf.writeInt32(numRecords);
     for (let i = 0; i < numRecords; i++) {
       let record = records[i];
       Buf.writeInt32(record.tnf);
 
-      let typeLength = record.type.length;
+      let typeLength = record.type ? record.type.length : 0;
       Buf.writeInt32(typeLength);
       for (let j = 0; j < typeLength; j++) {
-        Buf.writeUint8(record.type.charCodeAt(j));
+        Buf.writeUint8(record.type[j]);
       }
       let padding = getPaddingLen(typeLength);
       for (let i = 0; i < padding; i++) {
         Buf.writeUint8(0x00);
       }
 
-      let idLength = record.id.length;
+      let idLength = record.id ? record.id.length : 0;
       Buf.writeInt32(idLength);
       for (let j = 0; j < idLength; j++) {
-        Buf.writeUint8(record.id.charCodeAt(j));
+        Buf.writeUint8(record.id[j]);
       }
       padding = getPaddingLen(idLength);
       for (let i = 0; i < padding; i++) {
         Buf.writeUint8(0x00);
       }
 
-      let payloadLength = record.payload && record.payload.length;
+      let payloadLength = record.payload ? record.payload.length : 0;
       Buf.writeInt32(payloadLength);
       for (let j = 0; j < payloadLength; j++) {
-        Buf.writeUint8(record.payload.charCodeAt(j));
+        Buf.writeUint8(record.payload[j]);
       }
       padding = getPaddingLen(payloadLength);
       for (let i = 0; i < padding; i++) {
         Buf.writeUint8(0x00);
       }
     }
 
     Buf.sendParcel();
@@ -377,17 +368,20 @@ NfcWorker[NFC_NOTIFICATION_INITIALIZED] 
 NfcWorker[NFC_NOTIFICATION_TECH_DISCOVERED] = function NFC_NOTIFICATION_TECH_DISCOVERED() {
   debug("NFC_NOTIFICATION_TECH_DISCOVERED");
   let techs     = [];
   let ndefMsgs  = [];
 
   let sessionId = Buf.readInt32();
   let techCount = Buf.readInt32();
   for (let count = 0; count < techCount; count++) {
-    techs.push(NFC_TECHS[Buf.readUint8()]);
+    let tech = NFC_TECHS[Buf.readUint8()];
+    if (tech) {
+      techs.push(tech);
+    }
   }
 
   let padding   = getPaddingLen(techCount);
   for (let i = 0; i < padding; i++) {
     Buf.readUint8();
   }
 
   let ndefMsgCount = Buf.readInt32();
--- a/dom/system/gonk/nsINetworkService.idl
+++ b/dom/system/gonk/nsINetworkService.idl
@@ -23,16 +23,22 @@ interface nsIWifiTetheringCallback : nsI
 interface nsINetworkStatsCallback : nsISupports
 {
   void networkStatsAvailable(in boolean success,
                              in unsigned long rxBytes,
                              in unsigned long txBytes,
                              in jsval date);
 };
 
+[scriptable, function, uuid(0706bfa2-ac2d-11e2-9a8d-7b6d988d4767)]
+interface nsINetworkUsageAlarmCallback : nsISupports
+{
+  void networkUsageAlarmResult(in jsval error);
+};
+
 [scriptable, function, uuid(9ede8720-f8bc-11e2-b778-0800200c9a66)]
 interface nsIWifiOperationModeCallback : nsISupports
 {
   /**
    * Callback function used to report result to WifiManager.
    *
    * @param error
    *        An error message if the operation wasn't successful,
@@ -93,17 +99,17 @@ interface nsIUpdateUpStreamCallback : ns
    *        The external interface name.
    */
   void updateUpStreamResult(in boolean success, in DOMString externalIfname);
 };
 
 /**
  * Provide network services.
  */
-[scriptable, uuid(a6c58260-46df-11e3-8f96-0800200c9a66)]
+[scriptable, uuid(c14cabaf-bb8e-470d-a2f1-2cb6de6c5e5c)]
 interface nsINetworkService : nsISupports
 {
   /**
    * Enable or disable Wifi Tethering
    *
    * @param enabled
    *        Boolean that indicates whether tethering should be enabled (true) or disabled (false).
    * @param config
@@ -147,16 +153,34 @@ interface nsINetworkService : nsISupport
    *
    * @param callback
    *        Callback to notify result and provide stats, connectionType
    *        and the date when stats are retrieved
    */
   void getNetworkInterfaceStats(in DOMString networkName, in nsINetworkStatsCallback callback);
 
   /**
+   * Set Alarm of usage per interface
+   *
+   * @param networkName
+   *        Select the Network interface to set an alarm.
+   *
+   * @param threshold
+   *        Amount of data that will trigger the alarm.
+   *
+   * @param callback
+   *        Callback to notify the result.
+   *
+   * @return false if there is no interface registered for the networkType param.
+   */
+  boolean setNetworkInterfaceAlarm(in DOMString networkName,
+                                   in long threshold,
+                                   in nsINetworkUsageAlarmCallback callback);
+
+  /**
    * Reload Wifi firmware to specific operation mode.
    *
    * @param interfaceName
    *        Wifi Network interface name.
    *
    * @param mode
    *        AP  - Access pointer mode.
    *        P2P - Peer to peer connection mode.
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -3009,17 +3009,16 @@ let RIL = {
       // Other types of ICC we can send Terminal_Profile immediately.
       if (this.appType == CARD_APPTYPE_SIM) {
         SimRecordHelper.readSimPhase();
       } else if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD) {
         this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
       }
 
       ICCRecordHelper.fetchICCRecords();
-      this.reportStkServiceIsRunning();
     }
 
     this.cardState = newCardState;
     this.sendChromeMessage({rilMessageType: "cardstatechange",
                             cardState: this.cardState});
   },
 
    /**
@@ -11220,16 +11219,17 @@ let ICCRecordHelper = {
       let strLen = Buf.readInt32();
       let octetLen = strLen / 2;
       RIL.iccInfo.iccid = GsmPDUHelper.readSwappedNibbleBcdString(octetLen);
       Buf.readStringDelimiter(strLen);
 
       if (DEBUG) debug("ICCID: " + RIL.iccInfo.iccid);
       if (RIL.iccInfo.iccid) {
         ICCUtilsHelper.handleICCInfoChange();
+        RIL.reportStkServiceIsRunning();
       }
     }
 
     ICCIOHelper.loadTransparentEF({fileId: ICC_EF_ICCID,
                                    callback: callback.bind(this)});
   },
 
   /**
--- a/dom/webidl/CameraControl.webidl
+++ b/dom/webidl/CameraControl.webidl
@@ -147,16 +147,22 @@ interface CameraControl {
        one; an object with 'height' and 'width' properties that corresponds
        to one of the options returned by capabilities.pictureSizes.
        
        this setting should be considered a hint: the implementation will
        respect it when possible, and override it if necessary. */
     [Throws]
     attribute any thumbnailSize;
 
+    /* the angle, in degrees, that the image sensor is mounted relative
+       to the display; e.g. if 'sensorAngle' is 270 degrees (or -90 degrees),
+       then the preview stream needs to be rotated +90 degrees to have the
+       same orientation as the real world. */
+    readonly attribute long sensorAngle;
+
     /* tell the camera to attempt to focus the image */
     [Throws]
     void autoFocus(CameraAutoFocusCallback onSuccess, optional CameraErrorCallback onError);
 
     /* capture an image and return it as a blob to the 'onSuccess' callback;
        if the camera supports it, this may be invoked while the camera is
        already recording video.
 
--- a/dom/webidl/MozNdefRecord.webidl
+++ b/dom/webidl/MozNdefRecord.webidl
@@ -1,38 +1,43 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Copyright © 2013 Deutsche Telekom, Inc. */
 
-[Constructor(octet tnf, DOMString type, DOMString id, DOMString payload)]
+[Constructor(octet tnf, Uint8Array type, Uint8Array id, Uint8Array payload)]
 interface MozNdefRecord
 {
   /**
    * Type Name Field (3-bits) - Specifies the NDEF record type in general.
    *   tnf_empty: 0x00
    *   tnf_well_known: 0x01
    *   tnf_mime_media: 0x02
    *   tnf_absolute_uri: 0x03
    *   tnf_external type: 0x04
    *   tnf_unknown: 0x05
    *   tnf_unchanged: 0x06
    *   tnf_reserved: 0x07
    */
+  [Constant]
   readonly attribute octet tnf;
 
   /**
    * type - Describes the content of the payload. This can be a mime type.
    */
-  readonly attribute DOMString type;
+  [Constant]
+  readonly attribute Uint8Array type;
 
   /**
    * id - Identifer is application dependent.
    */
-  readonly attribute DOMString id;
+  [Constant]
+  readonly attribute Uint8Array id;
 
   /**
-   * payload - Binary data blob. The meaning of this field is application dependent.
+   * payload - Binary data blob. The meaning of this field is application
+   * dependent.
    */
-  readonly attribute DOMString payload;
+  [Constant]
+  readonly attribute Uint8Array payload;
 };
--- a/gfx/layers/ipc/Axis.cpp
+++ b/gfx/layers/ipc/Axis.cpp
@@ -26,17 +26,17 @@ namespace layers {
  * or we get a touch point very far away from the previous position for some
  * reason.
  */
 static float gMaxEventAcceleration = 999.0f;
 
 /**
  * Amount of friction applied during flings.
  */
-static float gFlingFriction = 0.006f;
+static float gFlingFriction = 0.002f;
 
 /**
  * Threshold for velocity beneath which we turn off any acceleration we had
  * during repeated flings.
  */
 static float gVelocityThreshold = 0.14f;
 
 /**
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -320,16 +320,17 @@ public class GeckoAppShell
     }
 
     private static LayerView sLayerView;
 
     public static void setLayerView(LayerView lv) {
         sLayerView = lv;
     }
 
+    @RobocopTarget
     public static LayerView getLayerView() {
         return sLayerView;
     }
 
     public static void runGecko(String apkPath, String args, String url, String type) {
         // Preparation for pumpMessageLoop()
         MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
             @Override public boolean queueIdle() {
@@ -387,16 +388,17 @@ public class GeckoAppShell
         try {
             while (!gPendingEvents.isEmpty()) {
                 GeckoEvent e = gPendingEvents.removeFirst();
                 notifyGeckoOfEvent(e);
             }
         } catch (NoSuchElementException e) {}
     }
 
+    @RobocopTarget
     public static void sendEventToGecko(GeckoEvent e) {
         if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
             notifyGeckoOfEvent(e);
         } else {
             gPendingEvents.addLast(e);
         }
     }
 
@@ -2285,31 +2287,33 @@ public class GeckoAppShell
 
     /**
      * Adds a listener for a gecko event.
      * This method is thread-safe and may be called at any time. In particular, calling it
      * with an event that is currently being processed has the properly-defined behaviour that
      * any added listeners will not be invoked on the event currently being processed, but
      * will be invoked on future events of that type.
      */
+    @RobocopTarget
     public static void registerEventListener(String event, GeckoEventListener listener) {
         sEventDispatcher.registerEventListener(event, listener);
     }
 
     public static EventDispatcher getEventDispatcher() {
         return sEventDispatcher;
     }
 
     /**
      * Remove a previously-registered listener for a gecko event.
      * This method is thread-safe and may be called at any time. In particular, calling it
      * with an event that is currently being processed has the properly-defined behaviour that
      * any removed listeners will still be invoked on the event currently being processed, but
      * will not be invoked on future events of that type.
      */
+    @RobocopTarget
     public static void unregisterEventListener(String event, GeckoEventListener listener) {
         sEventDispatcher.unregisterEventListener(event, listener);
     }
 
     /*
      * Battery API related methods.
      */
     @WrapElementForJNI
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -5,16 +5,17 @@
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.DisplayPortMetrics;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.mozglue.JNITarget;
 import org.mozilla.gecko.mozglue.generatorannotations.GeneratorOptions;
 import org.mozilla.gecko.mozglue.generatorannotations.WrapEntireClassForJNI;
+import org.mozilla.gecko.mozglue.RobocopTarget;
 
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorManager;
@@ -607,16 +608,17 @@ public class GeckoEvent {
     public static GeckoEvent createSizeChangedEvent(int w, int h, int screenw, int screenh) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.SIZE_CHANGED);
         event.mPoints = new Point[2];
         event.mPoints[0] = new Point(w, h);
         event.mPoints[1] = new Point(screenw, screenh);
         return event;
     }
 
+    @RobocopTarget
     public static GeckoEvent createBroadcastEvent(String subject, String data) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.BROADCAST);
         event.mCharacters = subject;
         event.mCharactersExtra = data;
         return event;
     }
 
     public static GeckoEvent createViewportEvent(ImmutableViewportMetrics metrics, DisplayPortMetrics displayPort) {
@@ -697,30 +699,33 @@ public class GeckoEvent {
     }
 
     public static GeckoEvent createRemoveObserverEvent(String observerKey) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.REMOVE_OBSERVER);
         event.mCharacters = observerKey;
         return event;
     }
 
+    @RobocopTarget
     public static GeckoEvent createPreferencesObserveEvent(int requestId, String[] prefNames) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.PREFERENCES_OBSERVE);
         event.mCount = requestId;
         event.mPrefNames = prefNames;
         return event;
     }
 
+    @RobocopTarget
     public static GeckoEvent createPreferencesGetEvent(int requestId, String[] prefNames) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.PREFERENCES_GET);
         event.mCount = requestId;
         event.mPrefNames = prefNames;
         return event;
     }
 
+    @RobocopTarget
     public static GeckoEvent createPreferencesRemoveObserversEvent(int requestId) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.PREFERENCES_REMOVE_OBSERVERS);
         event.mCount = requestId;
         return event;
     }
 
     public static GeckoEvent createLowMemoryEvent(int level) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.LOW_MEMORY);
deleted file mode 100644
--- a/mobile/android/base/RobocopAPI.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.gfx.GeckoLayerClient;
-import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.gfx.PanningPerfAPI;
-import org.mozilla.gecko.mozglue.GeckoLoader;
-import org.mozilla.gecko.mozglue.RobocopTarget;
-import org.mozilla.gecko.sqlite.SQLiteBridge;
-import org.mozilla.gecko.util.GeckoEventListener;
-
-import android.app.Activity;
-import android.database.Cursor;
-import android.view.View;
-
-import java.nio.IntBuffer;
-import java.util.List;
-
-/**
- * Class to provide wrapper methods around methods wanted by Robocop.
- *
- * This class provides fixed entry points into code that is liable to be optimised by Proguard without
- * needing to prevent Proguard from optimising the wrapped methods.
- * Wrapping in this way still slightly hinders Proguard's ability to optimise.
- *
- * If you find yourself wanting to add a method to this class - proceed with caution. If you're writing
- * a test that's not about manipulating the UI, you might be better off using JUnit (Or similar)
- * instead of Robocop.
- *
- * Alternatively, you might be able to get what you want by reflecting on a method annotated for the
- * benefit of the C++ wrapper generator - these methods are sure to not disappear at compile-time.
- * 
- * Finally, you might be able to get what you want via Reflection on Android's libraries. Those are
- * also not prone to vanishing at compile-time, but doing this might substantially complicate your
- * work, ultimately not proving worth the extra effort to avoid making a slight mess here.
- */
-@RobocopTarget
-public class RobocopAPI {
-    private final GeckoApp mGeckoApp;
-
-    public RobocopAPI(Activity activity) {
-        mGeckoApp = (GeckoApp)activity;
-    }
-
-    public void registerEventListener(String event, GeckoEventListener listener) {
-        GeckoAppShell.registerEventListener(event, listener);
-    }
-
-    public void unregisterEventListener(String event, GeckoEventListener listener) {
-        GeckoAppShell.unregisterEventListener(event, listener);
-    }
-
-    public void broadcastEvent(String subject, String data) {
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(subject, data));
-    }
-
-    public void preferencesGetEvent(int requestId, String[] prefNames) {
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesGetEvent(requestId, prefNames));
-    }
-
-    public void preferencesObserveEvent(int requestId, String[] prefNames) {
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesObserveEvent(requestId, prefNames));
-    }
-
-    public void preferencesRemoveObserversEvent(int requestId) {
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesRemoveObserversEvent(requestId));
-    }
-
-    public void setDrawListener(GeckoLayerClient.DrawListener listener) {
-        GeckoAppShell.getLayerView().getLayerClient().setDrawListener(listener);
-    }
-
-    public Cursor querySql(String dbPath, String query) {
-        GeckoLoader.loadSQLiteLibs(mGeckoApp, mGeckoApp.getApplication().getPackageResourcePath());
-        return new SQLiteBridge(dbPath).rawQuery(query, null);
-    }
-
-    public IntBuffer getViewPixels(View view) {
-        return ((LayerView)view).getPixels();
-    }
-
-    // PanningPerfAPI.
-    public static void startFrameTimeRecording() {
-        PanningPerfAPI.startFrameTimeRecording();
-    }
-
-    public static List<Long> stopFrameTimeRecording() {
-        return PanningPerfAPI.stopFrameTimeRecording();
-    }
-
-    public static void startCheckerboardRecording() {
-        PanningPerfAPI.startCheckerboardRecording();
-    }
-
-    public static List<Float> stopCheckerboardRecording() {
-        return PanningPerfAPI.stopCheckerboardRecording();
-    }
-}
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -10,16 +10,17 @@ import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.TouchEventInterceptor;
 import org.mozilla.gecko.ZoomConstraints;
 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.util.EventDispatcher;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
@@ -319,16 +320,17 @@ public class LayerView extends FrameLayo
             addView(mSurfaceView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
 
             SurfaceHolder holder = mSurfaceView.getHolder();
             holder.addCallback(new SurfaceListener());
             holder.setFormat(PixelFormat.RGB_565);
         }
     }
 
+    @RobocopTarget
     public GeckoLayerClient getLayerClient() { return mLayerClient; }
     public PanZoomController getPanZoomController() { return mPanZoomController; }
     public LayerMarginsAnimator getLayerMarginsAnimator() { return mMarginsAnimator; }
 
     public ImmutableViewportMetrics getViewportMetrics() {
         return mLayerClient.getViewportMetrics();
     }
 
@@ -451,16 +453,17 @@ public class LayerView extends FrameLayo
         mRenderer.removeRenderTask(task);
     }
 
     public int getMaxTextureSize() {
         return mRenderer.getMaxTextureSize();
     }
 
     /** Used by robocop for testing purposes. Not for production use! */
+    @RobocopTarget
     public IntBuffer getPixels() {
         return mRenderer.getPixels();
     }
 
     /* paintState must be a PAINT_xxx constant. */
     public void setPaintState(int paintState) {
         mPaintState = paintState;
     }
--- a/mobile/android/base/gfx/PanningPerfAPI.java
+++ b/mobile/android/base/gfx/PanningPerfAPI.java
@@ -1,15 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
+import org.mozilla.gecko.mozglue.RobocopTarget;
+
 import android.os.SystemClock;
 import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.List;
 
 public class PanningPerfAPI {
     private static final String LOGTAG = "GeckoPanningPerfAPI";
@@ -35,26 +37,28 @@ public class PanningPerfAPI {
         }
         if (mCheckerboardAmounts == null) {
             mCheckerboardAmounts = new ArrayList<Float>(EXPECTED_FRAME_COUNT);
         } else {
             mCheckerboardAmounts.clear();
         }
     }
 
+    @RobocopTarget
     public static void startFrameTimeRecording() {
         if (mRecordingFrames || mRecordingCheckerboard) {
             Log.e(LOGTAG, "Error: startFrameTimeRecording() called while already recording!");
             return;
         }
         mRecordingFrames = true;
         initialiseRecordingArrays();
         mFrameStartTime = SystemClock.uptimeMillis();
     }
 
+    @RobocopTarget
     public static List<Long> stopFrameTimeRecording() {
         if (!mRecordingFrames) {
             Log.e(LOGTAG, "Error: stopFrameTimeRecording() called when not recording!");
             return null;
         }
         mRecordingFrames = false;
         return mFrameTimes;
     }
@@ -65,26 +69,28 @@ public class PanningPerfAPI {
             mFrameTimes.add(SystemClock.uptimeMillis() - mFrameStartTime);
         }
     }
 
     public static boolean isRecordingCheckerboard() {
         return mRecordingCheckerboard;
     }
 
+    @RobocopTarget
     public static void startCheckerboardRecording() {
         if (mRecordingCheckerboard || mRecordingFrames) {
             Log.e(LOGTAG, "Error: startCheckerboardRecording() called while already recording!");
             return;
         }
         mRecordingCheckerboard = true;
         initialiseRecordingArrays();
         mCheckerboardStartTime = SystemClock.uptimeMillis();
     }
 
+    @RobocopTarget
     public static List<Float> stopCheckerboardRecording() {
         if (!mRecordingCheckerboard) {
             Log.e(LOGTAG, "Error: stopCheckerboardRecording() called when not recording!");
             return null;
         }
         mRecordingCheckerboard = false;
 
         // We take the number of values in mCheckerboardAmounts here, as there's
--- a/mobile/android/base/home/BrowserSearch.java
+++ b/mobile/android/base/home/BrowserSearch.java
@@ -320,66 +320,140 @@ public class BrowserSearch extends HomeF
     }
 
     @Override
     protected void load() {
         SearchLoader.init(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm);
     }
 
     private void handleAutocomplete(String searchTerm, Cursor c) {
-        if (TextUtils.isEmpty(mSearchTerm) || c == null || mAutocompleteHandler == null) {
+        if (c == null ||
+            mAutocompleteHandler == null ||
+            TextUtils.isEmpty(searchTerm)) {
             return;
         }
 
         // Avoid searching the path if we don't have to. Currently just
-        // decided by if there is a '/' character in the string.
-        final boolean searchPath = (searchTerm.indexOf("/") > 0);
+        // decided by whether there is a '/' character in the string.
+        final boolean searchPath = searchTerm.indexOf('/') > 0;
         final String autocompletion = findAutocompletion(searchTerm, c, searchPath);
 
-        if (autocompletion != null && mAutocompleteHandler != null) {
-            mAutocompleteHandler.onAutocomplete(autocompletion);
-            mAutocompleteHandler = null;
+        if (autocompletion == null || mAutocompleteHandler == null) {
+            return;
         }
+
+        // Prefetch auto-completed domain since it's a likely target
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Session:Prefetch", "http://" + autocompletion));
+
+        mAutocompleteHandler.onAutocomplete(autocompletion);
+        mAutocompleteHandler = null;
+    }
+
+    /**
+     * Returns the substring of a provided URI, starting at the given offset,
+     * and extending up to the end of the path segment in which the provided
+     * index is found.
+     *
+     * For example, given
+     *
+     *   "www.reddit.com/r/boop/abcdef", 0, ?
+     *
+     * this method returns
+     *
+     *   ?=2:  "www.reddit.com/"
+     *   ?=17: "www.reddit.com/r/boop/"
+     *   ?=21: "www.reddit.com/r/boop/"
+     *   ?=22: "www.reddit.com/r/boop/abcdef"
+     *
+     */
+    private static String uriSubstringUpToMatchedPath(final String url, final int offset, final int begin) {
+        final int afterEnd = url.length();
+
+        // We want to include the trailing slash, but not other characters.
+        int chop = url.indexOf('/', begin);
+        if (chop != -1) {
+            ++chop;
+            if (chop < offset) {
+                // This isn't supposed to happen. Fall back to returning the whole damn thing.
+                return url;
+            }
+        } else {
+            chop = url.indexOf('?', begin);
+            if (chop == -1) {
+                chop = url.indexOf('#', begin);
+            }
+            if (chop == -1) {
+                chop = afterEnd;
+            }
+        }