Merge m-c to inbound
authorWes Kocher <wkocher@mozilla.com>
Wed, 04 Sep 2013 18:04:50 -0700
changeset 145622 a40169f091ea751e074d6cc21015500b38036fce
parent 145621 7035eecefd2565b822e9f236926b7c5b33a129c0 (current diff)
parent 145552 77ed46bf4c1a98ebdd79e45ffce947d3a92642c3 (diff)
child 145623 20658bd801b1c94d8a8f85107e93c213ca25d38c
push id2502
push useremorley@mozilla.com
push dateThu, 05 Sep 2013 13:54:42 +0000
treeherderfx-team@efe592efe709 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone26.0a1
Merge m-c to inbound
CLOBBER
browser/app/profile/firefox.js
browser/base/content/test/social/Makefile.in
browser/devtools/scratchpad/test/Makefile.in
browser/devtools/styleinspector/test/Makefile.in
dom/mobilemessage/src/Makefile.in
dom/mobilemessage/src/MobileMessageCallback.cpp
dom/mobilemessage/src/MobileMessageManager.cpp
dom/system/gonk/Makefile.in
dom/telephony/Makefile.in
layout/build/Makefile.in
mobile/android/base/Makefile.in
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -1,72 +1,90 @@
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* 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;"
+"use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 
 #ifdef MOZ_WIDGET_GONK
 XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
   Cu.import("resource://gre/modules/systemlibs.js");
   return libcutils;
 });
 #endif
 
-// Once Bug 731746 - Allow chrome JS object to implement nsIDOMEventTarget
-// is resolved this helper could be removed.
 var SettingsListener = {
-  _callbacks: {},
+  // Timer to remove the lock.
+  _timer: null,
+
+  // lock stores here
+  _lock: null,
 
-  init: function sl_init() {
-    if ('mozSettings' in navigator && navigator.mozSettings) {
-      navigator.mozSettings.onsettingchange = this.onchange.bind(this);
-    }
-  },
+  /**
+   * getSettingsLock: create a lock or retrieve one that we saved.
+   * mozSettings.createLock() is expensive and lock should be reused
+   * whenever possible.
+   */
+  getSettingsLock: function sl_getSettingsLock() {
+    // Each time there is a getSettingsLock call, we postpone the removal.
+    clearTimeout(this._timer);
+    this._timer = setTimeout((function() {
+      this._lock = null;
+    }).bind(this), 0);
 
-  onchange: function sl_onchange(evt) {
-    var callback = this._callbacks[evt.settingName];
-    if (callback) {
-      callback(evt.settingValue);
+    // If there is a lock present we return that.
+    if (this._lock) {
+      return this._lock;
     }
+
+    // If there isn't we create a new one.
+    let settings = window.navigator.mozSettings;
+
+    return (this._lock = settings.createLock());
   },
 
   observe: function sl_observe(name, defaultValue, callback) {
-    var settings = window.navigator.mozSettings;
-    if (!settings) {
-      window.setTimeout(function() { callback(defaultValue); });
-      return;
+    let settings = window.navigator.mozSettings;
+
+    let req;
+    try {
+      req = this.getSettingsLock().get(name);
+    } catch (e) {
+      // It is possible (but rare) for getSettingsLock() to return
+      // a SettingsLock object that is no longer valid.
+      // Until https://bugzilla.mozilla.org/show_bug.cgi?id=793239
+      // is fixed, we just catch the resulting exception and try
+      // again with a fresh lock
+      console.warn('Stale lock in settings.js.',
+                   'See https://bugzilla.mozilla.org/show_bug.cgi?id=793239');
+      this._lock = null;
+      req = this.getSettingsLock().get(name);
     }
 
-    if (!callback || typeof callback !== 'function') {
-      throw new Error('Callback is not a function');
-    }
-
-    var req = settings.createLock().get(name);
     req.addEventListener('success', (function onsuccess() {
       callback(typeof(req.result[name]) != 'undefined' ?
         req.result[name] : defaultValue);
     }));
 
-    this._callbacks[name] = callback;
+    settings.addObserver(name, function settingChanged(evt) {
+      callback(evt.settingValue);
+    });
   }
 };
 
-SettingsListener.init();
-
 // =================== Console ======================
 
 SettingsListener.observe('debug.console.enabled', true, function(value) {
   Services.prefs.setBoolPref('consoleservice.enabled', value);
   Services.prefs.setBoolPref('layout.css.report_errors', value);
 });
 
 // =================== Languages ====================
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "752ced5b3cc3208f79806eccf8d8768910f17f2b", 
+    "revision": "4296bbf526e4ed8d0ae443f20947bd2d7189aa0e", 
     "repo_path": "/integration/gaia-central"
 }
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1074,16 +1074,17 @@ pref("devtools.toolbox.selectedTool", "w
 pref("devtools.toolbox.toolbarSpec", '["paintflashing toggle","tilt toggle","scratchpad","resize toggle"]');
 pref("devtools.toolbox.sideEnabled", true);
 
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 pref("devtools.inspector.activeSidebar", "ruleview");
 pref("devtools.inspector.markupPreview", false);
 pref("devtools.inspector.remote", false);
+pref("devtools.inspector.show_pseudo_elements", true);
 
 // Enable the Layout View
 pref("devtools.layoutview.enabled", true);
 pref("devtools.layoutview.open", false);
 
 // Enable the Responsive UI tool
 pref("devtools.responsiveUI.enabled", true);
 pref("devtools.responsiveUI.no-reload-notification", false);
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -32,16 +32,20 @@ SocialUI = {
   init: function SocialUI_init() {
     Services.obs.addObserver(this, "social:ambient-notification-changed", false);
     Services.obs.addObserver(this, "social:profile-changed", false);
     Services.obs.addObserver(this, "social:page-mark-config", false);
     Services.obs.addObserver(this, "social:frameworker-error", false);
     Services.obs.addObserver(this, "social:provider-set", false);
     Services.obs.addObserver(this, "social:providers-changed", false);
     Services.obs.addObserver(this, "social:provider-reload", false);
+    Services.obs.addObserver(this, "social:provider-installed", false);
+    Services.obs.addObserver(this, "social:provider-uninstalled", false);
+    Services.obs.addObserver(this, "social:provider-enabled", false);
+    Services.obs.addObserver(this, "social:provider-disabled", false);
 
     Services.prefs.addObserver("social.sidebar.open", this, false);
     Services.prefs.addObserver("social.toast-notifications.enabled", this, false);
 
     gBrowser.addEventListener("ActivateSocialFeature", this._activationEventHandler.bind(this), true, true);
 
     if (!Social.initialized) {
       Social.init();
@@ -57,30 +61,46 @@ SocialUI = {
   uninit: function SocialUI_uninit() {
     Services.obs.removeObserver(this, "social:ambient-notification-changed");
     Services.obs.removeObserver(this, "social:profile-changed");
     Services.obs.removeObserver(this, "social:page-mark-config");
     Services.obs.removeObserver(this, "social:frameworker-error");
     Services.obs.removeObserver(this, "social:provider-set");
     Services.obs.removeObserver(this, "social:providers-changed");
     Services.obs.removeObserver(this, "social:provider-reload");
+    Services.obs.removeObserver(this, "social:provider-installed");
+    Services.obs.removeObserver(this, "social:provider-uninstalled");
+    Services.obs.removeObserver(this, "social:provider-enabled");
+    Services.obs.removeObserver(this, "social:provider-disabled");
 
     Services.prefs.removeObserver("social.sidebar.open", this);
     Services.prefs.removeObserver("social.toast-notifications.enabled", this);
   },
 
   _matchesCurrentProvider: function (origin) {
     return Social.provider && Social.provider.origin == origin;
   },
 
   observe: function SocialUI_observe(subject, topic, data) {
     // Exceptions here sometimes don't get reported properly, report them
     // manually :(
     try {
       switch (topic) {
+        case "social:provider-installed":
+          SocialStatus.setPosition(data);
+          break;
+        case "social:provider-uninstalled":
+          SocialStatus.removePosition(data);
+          break;
+        case "social:provider-enabled":
+          SocialStatus.populateToolbarPalette();
+          break;
+        case "social:provider-disabled":
+          SocialStatus.removeProvider(data);
+          break;
         case "social:provider-reload":
           // if the reloaded provider is our current provider, fall through
           // to social:provider-set so the ui will be reset
           if (!Social.provider || Social.provider.origin != data)
             return;
           // be sure to unload the sidebar as it will not reload if the origin
           // has not changed, it will be loaded in provider-set below. Other
           // panels will be unloaded or handle reload.
@@ -93,28 +113,31 @@ SocialUI = {
           this._updateMenuItems();
 
           SocialFlyout.unload();
           SocialChatBar.update();
           SocialShare.update();
           SocialSidebar.update();
           SocialMark.update();
           SocialToolbar.update();
+          SocialStatus.populateToolbarPalette();
           SocialMenu.populate();
           break;
         case "social:providers-changed":
           // the list of providers changed - this may impact the "active" UI.
           this._updateActiveUI();
           // and the multi-provider menu
           SocialToolbar.populateProviderMenus();
           SocialShare.populateProviderMenu();
+          SocialStatus.populateToolbarPalette();
           break;
 
         // Provider-specific notifications
         case "social:ambient-notification-changed":
+          SocialStatus.updateNotification(data);
           if (this._matchesCurrentProvider(data)) {
             SocialToolbar.updateButton();
             SocialMenu.populate();
           }
           break;
         case "social:profile-changed":
           if (this._matchesCurrentProvider(data)) {
             SocialToolbar.updateProvider();
@@ -1051,16 +1074,25 @@ SocialToolbar = {
         (!Social.provider.haveLoggedInUser() && Social.provider.profile !== undefined)) {
       // Either no enabled provider, or there is a provider and it has
       // responded with a profile and the user isn't loggedin.  The icons
       // etc have already been removed by updateButtonHiddenState, so we want
       // to nuke any cached icons we have and get out of here!
       Services.prefs.clearUserPref(CACHE_PREF_NAME);
       return;
     }
+
+    // If the provider uses the new SocialStatus button, then they do not get
+    // to use the ambient icons in the old toolbar button.  Since the status
+    // button depends on multiple workers, if not enabled we will ignore this
+    // limitation.  That allows a provider to migrate to the new functionality
+    // once we enable multiple workers.
+    if (Social.provider.statusURL && Social.allowMultipleWorkers)
+      return;
+
     let icons = Social.provider.ambientNotificationIcons;
     let iconNames = Object.keys(icons);
 
     if (Social.provider.profile === undefined) {
       // provider has not told us about the login state yet - see if we have
       // a cached version for this provider.
       let cached;
       try {
@@ -1149,19 +1181,18 @@ SocialToolbar = {
         ariaLabel = gNavigatorBundle.getFormattedString("social.aria.toolbarButtonBadgeText",
                                                         [ariaLabel, badge]);
       toolbarButton.setAttribute("aria-label", ariaLabel);
     }
     let socialToolbarItem = document.getElementById("social-toolbar-item");
     socialToolbarItem.insertBefore(toolbarButtons, SocialMark.button);
 
     for (let frame of createdFrames) {
-      if (frame.socialErrorListener) {
+      if (frame.socialErrorListener)
         frame.socialErrorListener.remove();
-      }
       if (frame.docShell) {
         frame.docShell.isActive = false;
         Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this));
       }
     }
   },
 
   showAmbientPopup: function SocialToolbar_showAmbientPopup(aToolbarButton) {
@@ -1196,21 +1227,20 @@ SocialToolbar = {
       aToolbarButton.parentNode.removeAttribute("open");
       dynamicResizer.stop();
       notificationFrame.docShell.isActive = false;
       dispatchPanelEvent("socialFrameHide");
     });
 
     panel.addEventListener("popupshown", function onpopupshown() {
       panel.removeEventListener("popupshown", onpopupshown);
-      // This attribute is needed on both the button and the
-      // containing toolbaritem since the buttons on OS X have
-      // moz-appearance:none, while their container gets
-      // moz-appearance:toolbarbutton due to the way that toolbar buttons
-      // get combined on OS X.
+      // The "open" attribute is needed on both the button and the containing
+      // toolbaritem since the buttons on OS X have moz-appearance:none, while
+      // their container gets moz-appearance:toolbarbutton due to the way that
+      // toolbar buttons get combined on OS X.
       aToolbarButton.setAttribute("open", "true");
       aToolbarButton.parentNode.setAttribute("open", "true");
       notificationFrame.docShell.isActive = true;
       notificationFrame.docShell.isAppTab = true;
       if (notificationFrame.contentDocument.readyState == "complete" && wasAlive) {
         dynamicResizer.start(panel, notificationFrame);
         dispatchPanelEvent("socialFrameShow");
       } else {
@@ -1387,9 +1417,358 @@ SocialSidebar = {
       sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure");
     } else {
       let url = encodeURIComponent(Social.provider.sidebarURL);
       sbrowser.loadURI("about:socialerror?mode=tryAgain&url=" + url, null, null);
     }
   }
 }
 
+// this helper class is used by removable/customizable buttons to handle
+// location persistence and insertion into palette and/or toolbars
+
+// When a provider is installed we show all their UI so the user will see the
+// functionality of what they installed. The user can later customize the UI,
+// moving buttons around or off the toolbar.
+//
+// To make this happen, on install we add a button id to the navbar currentset.
+// On enabling the provider (happens just after install) we insert the button
+// into the toolbar as well. The button is then persisted on restart (assuming
+// it was not removed).
+//
+// When a provider is disabled, we do not remove the buttons from currentset.
+// That way, if the provider is re-enabled during the same session, the buttons
+// will reappear where they were before. When a provider is uninstalled, we make
+// sure that the id is removed from currentset.
+//
+// On startup, we insert the buttons of any enabled provider into either the
+// apropriate toolbar or the palette.
+function ToolbarHelper(type, createButtonFn) {
+  this._createButton = createButtonFn;
+  this._type = type;
+}
+
+ToolbarHelper.prototype = {
+  idFromOrgin: function(origin) {
+    return this._type + "-" + origin;
+  },
+
+  // find a button either in the document or the palette
+  _getExistingButton: function(id) {
+    let button = document.getElementById(id);
+    if (button)
+      return button;
+    let palette = document.getElementById("navigator-toolbox").palette;
+    let paletteItem = palette.firstChild;
+    while (paletteItem) {
+      if (paletteItem.id == id)
+        return paletteItem;
+      paletteItem = paletteItem.nextSibling;
+    }
+    return null;
+  },
+
+  setPersistentPosition: function(id) {
+    // called when a provider is installed.  add provider buttons to nav-bar
+    let toolbar = document.getElementById("nav-bar");
+    // first startups will not have a currentset attribute, always rely on
+    // currentSet since it will be derived from the defaultset in that case.
+    let currentset = toolbar.currentSet;
+    if (currentset == "__empty")
+      currentset = []
+    else
+      currentset = currentset.split(",");
+    if (currentset.indexOf(id) >= 0)
+      return;
+    // we do not set toolbar.currentSet since that will try to add the button,
+    // and we have not added it yet (happens on provider being enabled)
+    currentset.push(id);
+    toolbar.setAttribute("currentset", currentset.join(","));
+    document.persist(toolbar.id, "currentset");
+  },
+
+  removeProviderButton: function(origin) {
+    // this will remove the button from the palette or the toolbar
+    let button = this._getExistingButton(this.idFromOrgin(origin));
+    if (button)
+      button.parentNode.removeChild(button);
+  },
+
+  removePersistence: function(id) {
+    let persisted = document.querySelectorAll("*[currentset]");
+    for (let pent of persisted) {
+      // the button will have been removed, but left in the currentset attribute
+      // in case the user re-enables (e.g. undo in addon manager). So we only
+      // check the attribute here.
+      let currentset = pent.getAttribute("currentset").split(",");
+
+      let pos = currentset.indexOf(id);
+      if (pos >= 0) {
+        currentset.splice(pos, 1);
+        pent.setAttribute("currentset", currentset.join(","));
+        document.persist(pent.id, "currentset");
+        return;
+      }
+    }
+  },
+
+  // if social is entirely disabled, we need to clear the palette, but leave
+  // the persisted id's in place
+  clearPalette: function() {
+    [this.removeProviderButton(p.origin) for (p of Social.providers)];
+  },
+
+  // should be called on startup of each window, otherwise the addon manager
+  // listener will handle new activations, or enable/disabling of a provider
+  // XXX we currently call more regularly, will fix during refactoring
+  populatePalette: function() {
+    if (!Social.enabled) {
+      this.clearPalette();
+      return;
+    }
+    let persisted = document.querySelectorAll("*[currentset]");
+    let persistedById = {};
+    for (let pent of persisted) {
+      let pset = pent.getAttribute("currentset").split(',');
+      for (let id of pset)
+        persistedById[id] = pent;
+    }
+
+    // create any buttons that do not exist yet if they have been persisted
+    // as a part of the UI (otherwise they belong in the palette).
+    for (let provider of Social.providers) {
+      let id = this.idFromOrgin(provider.origin);
+      if (this._getExistingButton(id))
+        return;
+      let button = this._createButton(provider);
+      if (button && persistedById.hasOwnProperty(id)) {
+        let parent = persistedById[id];
+        let pset = persistedById[id].getAttribute("currentset").split(',');
+        let pi = pset.indexOf(id) + 1;
+        let next = document.getElementById(pset[pi]);
+        parent.insertItem(id, next, null, false);
+      }
+    }
+  }
+}
+
+SocialStatus = {
+  populateToolbarPalette: function() {
+    if (!Social.allowMultipleWorkers)
+      return;
+    this._toolbarHelper.populatePalette();
+  },
+
+  setPosition: function(origin) {
+    if (!Social.allowMultipleWorkers)
+      return;
+    // this is called during install, before the provider is enabled so we have
+    // to use the manifest rather than the provider instance as we do elsewhere.
+    let manifest = Social.getManifestByOrigin(origin);
+    if (!manifest.statusURL)
+      return;
+    let tbh = this._toolbarHelper;
+    tbh.setPersistentPosition(tbh.idFromOrgin(origin));
+  },
+
+  removePosition: function(origin) {
+    if (!Social.allowMultipleWorkers)
+      return;
+    let tbh = this._toolbarHelper;
+    tbh.removePersistence(tbh.idFromOrgin(origin));
+  },
+
+  removeProvider: function(origin) {
+    if (!Social.allowMultipleWorkers)
+      return;
+    this._toolbarHelper.removeProviderButton(origin);
+  },
+
+  get _toolbarHelper() {
+    delete this._toolbarHelper;
+    this._toolbarHelper = new ToolbarHelper("social-status-button", this._createButton.bind(this));
+    return this._toolbarHelper;
+  },
+
+  get _dynamicResizer() {
+    delete this._dynamicResizer;
+    this._dynamicResizer = new DynamicResizeWatcher();
+    return this._dynamicResizer;
+  },
+
+  _createButton: function(provider) {
+    if (!provider.statusURL)
+      return null;
+    let palette = document.getElementById("navigator-toolbox").palette;
+    let button = document.createElement("toolbarbutton");
+    button.setAttribute("class", "toolbarbutton-1 social-status-button");
+    button.setAttribute("type", "badged");
+    button.setAttribute("removable", "true");
+    button.setAttribute("image", provider.iconURL);
+    button.setAttribute("label", provider.name);
+    button.setAttribute("tooltiptext", provider.name);
+    button.setAttribute("origin", provider.origin);
+    button.setAttribute("oncommand", "SocialStatus.showPopup(this);");
+    button.setAttribute("id", this._toolbarHelper.idFromOrgin(provider.origin));
+    palette.appendChild(button);
+    return button;
+  },
+
+  // status panels are one-per button per-process, we swap the docshells between
+  // windows when necessary
+  _attachNotificatonPanel: function(aButton, provider) {
+    let panel = document.getElementById("social-notification-panel");
+    panel.hidden = !SocialUI.enabled;
+    let notificationFrameId = "social-status-" + provider.origin;
+    let frame = document.getElementById(notificationFrameId);
+
+    if (!frame) {
+      frame = SharedFrame.createFrame(
+        notificationFrameId, /* frame name */
+        panel, /* parent */
+        {
+          "type": "content",
+          "mozbrowser": "true",
+          "class": "social-panel-frame",
+          "id": notificationFrameId,
+          "tooltip": "aHTMLTooltip",
+
+          // work around bug 793057 - by making the panel roughly the final size
+          // we are more likely to have the anchor in the correct position.
+          "style": "width: " + PANEL_MIN_WIDTH + "px;",
+
+          "origin": provider.origin,
+          "src": provider.statusURL
+        }
+      );
+
+      if (frame.socialErrorListener)
+        frame.socialErrorListener.remove();
+      if (frame.docShell) {
+        frame.docShell.isActive = false;
+        Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this));
+      }
+    } else {
+      frame.setAttribute("origin", provider.origin);
+      SharedFrame.updateURL(notificationFrameId, provider.statusURL);
+    }
+    aButton.setAttribute("notificationFrameId", notificationFrameId);
+  },
+
+  updateNotification: function(origin) {
+    if (!Social.allowMultipleWorkers)
+      return;
+    let provider = Social._getProviderFromOrigin(origin);
+    let button = document.getElementById(this._toolbarHelper.idFromOrgin(provider.origin));
+    if (button) {
+      // we only grab the first notification, ignore all others
+      let icons = provider.ambientNotificationIcons;
+      let iconNames = Object.keys(icons);
+      let notif = icons[iconNames[0]];
+      if (!notif) {
+        button.setAttribute("badge", "");
+        button.setAttribute("aria-label", "");
+        button.setAttribute("tooltiptext", "");
+        return;
+      }
+
+      button.style.listStyleImage = "url(" + notif.iconURL || provider.iconURL + ")";
+      button.setAttribute("tooltiptext", notif.label);
+
+      let badge = notif.counter || "";
+      button.setAttribute("badge", badge);
+      let ariaLabel = notif.label;
+      // if there is a badge value, we must use a localizable string to insert it.
+      if (badge)
+        ariaLabel = gNavigatorBundle.getFormattedString("social.aria.toolbarButtonBadgeText",
+                                                        [ariaLabel, badge]);
+      button.setAttribute("aria-label", ariaLabel);
+    }
+  },
+
+  showPopup: function(aToolbarButton) {
+    if (!Social.allowMultipleWorkers)
+      return;
+    // attach our notification panel if necessary
+    let origin = aToolbarButton.getAttribute("origin");
+    let provider = Social._getProviderFromOrigin(origin);
+    this._attachNotificatonPanel(aToolbarButton, provider);
+
+    let panel = document.getElementById("social-notification-panel");
+    let notificationFrameId = aToolbarButton.getAttribute("notificationFrameId");
+    let notificationFrame = document.getElementById(notificationFrameId);
+
+    let wasAlive = SharedFrame.isGroupAlive(notificationFrameId);
+    SharedFrame.setOwner(notificationFrameId, notificationFrame);
+
+    // Clear dimensions on all browsers so the panel size will
+    // only use the selected browser.
+    let frameIter = panel.firstElementChild;
+    while (frameIter) {
+      frameIter.collapsed = (frameIter != notificationFrame);
+      frameIter = frameIter.nextElementSibling;
+    }
+
+    function dispatchPanelEvent(name) {
+      let evt = notificationFrame.contentDocument.createEvent("CustomEvent");
+      evt.initCustomEvent(name, true, true, {});
+      notificationFrame.contentDocument.documentElement.dispatchEvent(evt);
+    }
+
+    let dynamicResizer = this._dynamicResizer;
+    panel.addEventListener("popuphidden", function onpopuphiding() {
+      panel.removeEventListener("popuphidden", onpopuphiding);
+      aToolbarButton.removeAttribute("open");
+      dynamicResizer.stop();
+      notificationFrame.docShell.isActive = false;
+      dispatchPanelEvent("socialFrameHide");
+    });
+
+    panel.addEventListener("popupshown", function onpopupshown() {
+      panel.removeEventListener("popupshown", onpopupshown);
+      // This attribute is needed on both the button and the
+      // containing toolbaritem since the buttons on OS X have
+      // moz-appearance:none, while their container gets
+      // moz-appearance:toolbarbutton due to the way that toolbar buttons
+      // get combined on OS X.
+      aToolbarButton.setAttribute("open", "true");
+      notificationFrame.docShell.isActive = true;
+      notificationFrame.docShell.isAppTab = true;
+      if (notificationFrame.contentDocument.readyState == "complete" && wasAlive) {
+        dynamicResizer.start(panel, notificationFrame);
+        dispatchPanelEvent("socialFrameShow");
+      } else {
+        // first time load, wait for load and dispatch after load
+        notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
+          notificationFrame.removeEventListener("load", panelBrowserOnload, true);
+          dynamicResizer.start(panel, notificationFrame);
+          dispatchPanelEvent("socialFrameShow");
+        }, true);
+      }
+    });
+
+    let navBar = document.getElementById("nav-bar");
+    let anchor = navBar.getAttribute("mode") == "text" ?
+                   document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-text") :
+                   document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-badge-container");
+    // Bug 849216 - open the popup in a setTimeout so we avoid the auto-rollup
+    // handling from preventing it being opened in some cases.
+    setTimeout(function() {
+      panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
+    }, 0);
+  },
+
+  setPanelErrorMessage: function(aNotificationFrame) {
+    if (!aNotificationFrame)
+      return;
+
+    let src = aNotificationFrame.getAttribute("src");
+    aNotificationFrame.removeAttribute("src");
+    aNotificationFrame.webNavigation.loadURI("about:socialerror?mode=tryAgainOnly&url=" +
+                                             encodeURIComponent(src),
+                                             null, null, null, null);
+    let panel = aNotificationFrame.parentNode;
+    sizeSocialPanelToContent(panel, aNotificationFrame);
+  },
+
+};
+
 })();
--- a/browser/base/content/test/social/Makefile.in
+++ b/browser/base/content/test/social/Makefile.in
@@ -18,16 +18,17 @@ MOCHITEST_BROWSER_FILES = \
                  browser_social_mozSocial_API.js \
                  browser_social_isVisible.js \
                  browser_social_chatwindow.js \
                  browser_social_chatwindow_resize.js \
                  browser_social_chatwindowfocus.js \
                  browser_social_multiprovider.js \
                  browser_social_multiworker.js \
                  browser_social_errorPage.js \
+                 browser_social_status.js \
                  browser_social_window.js \
                  social_activate.html \
                  social_activate_iframe.html \
                  browser_share.js \
                  social_panel.html \
                  social_mark_image.png \
                  social_sidebar.html \
                  social_chat.html \
--- a/browser/base/content/test/social/browser_addons.js
+++ b/browser/base/content/test/social/browser_addons.js
@@ -45,19 +45,20 @@ function test() {
     finish();
   });
 }
 
 function installListener(next, aManifest) {
   let expectEvent = "onInstalling";
   let prefname = getManifestPrefname(aManifest);
   // wait for the actual removal to call next
-  SocialService.registerProviderListener(function providerListener(topic, data) {
-    if (topic == "provider-removed") {
+  SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
+    if (topic == "provider-disabled") {
       SocialService.unregisterProviderListener(providerListener);
+      is(origin, aManifest.origin, "provider disabled");
       executeSoon(next);
     }
   });
 
   return {
     onInstalling: function(addon) {
       is(expectEvent, "onInstalling", "install started");
       is(addon.manifest.origin, aManifest.origin, "provider about to be installed");
@@ -290,24 +291,25 @@ var tests = {
       let installFrom = doc.nodePrincipal.origin;
       Services.prefs.setCharPref("social.whitelist", installFrom);
       Social.installProvider(doc, manifest2, function(addonManifest) {
         SocialService.addBuiltinProvider(addonManifest.origin, function(provider) {
           is(provider.manifest.version, 1, "manifest version is 1");
           Social.enabled = true;
 
           // watch for the provider-update and test the new version
-          SocialService.registerProviderListener(function providerListener(topic, data) {
+          SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
             if (topic != "provider-update")
               return;
+            is(origin, addonManifest.origin, "provider updated")
             SocialService.unregisterProviderListener(providerListener);
             Services.prefs.clearUserPref("social.whitelist");
-            let provider = Social._getProviderFromOrigin(addonManifest.origin);
+            let provider = Social._getProviderFromOrigin(origin);
             is(provider.manifest.version, 2, "manifest version is 2");
-            Social.uninstallProvider(addonManifest.origin, function() {
+            Social.uninstallProvider(origin, function() {
               gBrowser.removeTab(tab);
               next();
             });
           });
 
           let port = provider.getWorkerPort();
           port.onmessage = function (e) {
             let topic = e.data.topic;
--- a/browser/base/content/test/social/browser_blocklist.js
+++ b/browser/base/content/test/social/browser_blocklist.js
@@ -150,24 +150,25 @@ var tests = {
       onWindowTitleChange: function(aXULWindow, aNewTitle) { }
     };
 
     Services.wm.addListener(listener);
 
     setManifestPref("social.manifest.blocked", manifest_bad);
     try {
       SocialService.addProvider(manifest_bad, function(provider) {
-        // the act of blocking should cause a 'provider-removed' notification
+        // the act of blocking should cause a 'provider-disabled' notification
         // from SocialService.
-        SocialService.registerProviderListener(function providerListener(topic) {
-          if (topic != "provider-removed")
+        SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
+          if (topic != "provider-disabled")
             return;
           SocialService.unregisterProviderListener(providerListener);
+          is(origin, provider.origin, "provider disabled");
           SocialService.getProvider(provider.origin, function(p) {
-            ok(p==null, "blocklisted provider removed");
+            ok(p == null, "blocklisted provider disabled");
             Services.prefs.clearUserPref("social.manifest.blocked");
             resetBlocklist(finish);
           });
         });
         // no callback - the act of updating should cause the listener above
         // to fire.
         setAndUpdateBlocklist(blocklistURL);
       });
--- a/browser/base/content/test/social/browser_social_mozSocial_API.js
+++ b/browser/base/content/test/social/browser_social_mozSocial_API.js
@@ -14,16 +14,23 @@ function test() {
   };
   runSocialTestWithProvider(manifest, function (finishcb) {
     runSocialTests(tests, undefined, undefined, finishcb);
   });
 }
 
 var tests = {
   testStatusIcons: function(next) {
+    let icon = {
+      name: "testIcon",
+      iconURL: "chrome://browser/skin/Info.png",
+      contentPanel: "https://example.com/browser/browser/base/content/test/social/social_panel.html",
+      counter: 1
+    };
+
     let iconsReady = false;
     let gotSidebarMessage = false;
 
     function checkNext() {
       if (iconsReady && gotSidebarMessage)
         triggerIconPanel();
     }
 
@@ -66,16 +73,16 @@ var tests = {
             next();
           }
           break;
         case "got-sidebar-message":
           // The sidebar message will always come first, since it loads by default
           ok(true, "got sidebar message");
           gotSidebarMessage = true;
           // load a status panel
-          port.postMessage({topic: "test-ambient-notification"});
+          port.postMessage({topic: "test-ambient-notification", data: icon});
           checkNext();
           break;
       }
     }
     port.postMessage({topic: "test-init"});
   }
 }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_status.js
@@ -0,0 +1,220 @@
+/* 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/. */
+
+let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+
+let manifest = { // builtin provider
+  name: "provider example.com",
+  origin: "https://example.com",
+  sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+  workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+  iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+};
+let manifest2 = { // used for testing install
+  name: "provider test1",
+  origin: "https://test1.example.com",
+  workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
+  statusURL: "https://test1.example.com/browser/browser/base/content/test/social/social_panel.html",
+  iconURL: "https://test1.example.com/browser/browser/base/content/test/moz.png",
+  version: 1
+};
+let manifest3 = { // used for testing install
+  name: "provider test2",
+  origin: "https://test2.example.com",
+  sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html",
+  iconURL: "https://test2.example.com/browser/browser/base/content/test/moz.png",
+  version: 1
+};
+
+
+function openWindowAndWaitForInit(callback) {
+  let topic = "browser-delayed-startup-finished";
+  let w = OpenBrowserWindow();
+  Services.obs.addObserver(function providerSet(subject, topic, data) {
+    Services.obs.removeObserver(providerSet, topic);
+    executeSoon(() => callback(w));
+  }, topic, false);
+}
+
+function test() {
+  waitForExplicitFinish();
+
+  Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
+  let toolbar = document.getElementById("nav-bar");
+  let currentsetAtStart = toolbar.currentSet;
+  info("tb0 "+currentsetAtStart);
+  runSocialTestWithProvider(manifest, function () {
+    runSocialTests(tests, undefined, undefined, function () {
+      Services.prefs.clearUserPref("social.remote-install.enabled");
+      // just in case the tests failed, clear these here as well
+      Services.prefs.clearUserPref("social.allowMultipleWorkers");
+      Services.prefs.clearUserPref("social.whitelist");
+      
+      // This post-test test ensures that a new window maintains the same
+      // toolbar button set as when we started. That means our insert/removal of
+      // persistent id's is working correctly
+      is(currentsetAtStart, toolbar.currentSet, "toolbar currentset unchanged");
+      openWindowAndWaitForInit(function(w1) {
+        checkSocialUI(w1);
+        // Sometimes the new window adds other buttons to currentSet that are
+        // outside the scope of what we're checking. So we verify that all
+        // buttons from startup are in currentSet for a new window, and that the
+        // provider buttons are properly removed. (e.g on try, window-controls
+        // was not present in currentsetAtStart, but present on the second
+        // window)
+        let tb1 = w1.document.getElementById("nav-bar");
+        info("tb0 "+toolbar.currentSet);
+        info("tb1 "+tb1.currentSet);
+        let startupSet = Set(toolbar.currentSet.split(','));
+        let newSet = Set(tb1.currentSet.split(','));
+        let intersect = Set([x for (x of startupSet) if (newSet.has(x))]);
+        info("intersect "+intersect);
+        let difference = Set([x for (x of newSet) if (!startupSet.has(x))]);
+        info("difference "+difference);
+        is(startupSet.size, intersect.size, "new window toolbar same as old");
+        // verify that our provider buttons are not in difference
+        let id = SocialStatus._toolbarHelper.idFromOrgin(manifest2.origin);
+        ok(!difference.has(id), "status button not persisted at end");
+        w1.close();
+        finish();
+      });
+    });
+  });
+}
+
+var tests = {
+  testNoButtonOnInstall: function(next) {
+    // we expect the addon install dialog to appear, we need to accept the
+    // install from the dialog.
+    info("Waiting for install dialog");
+    let panel = document.getElementById("servicesInstall-notification");
+    PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
+      PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
+      info("servicesInstall-notification panel opened");
+      panel.button.click();
+    })
+
+    let id = "social-status-button-" + manifest3.origin;
+    let toolbar = document.getElementById("nav-bar");
+    let currentset = toolbar.getAttribute("currentset").split(',');
+    ok(currentset.indexOf(id) < 0, "button is not part of currentset at start");
+
+    let activationURL = manifest3.origin + "/browser/browser/base/content/test/social/social_activate.html"
+    addTab(activationURL, function(tab) {
+      let doc = tab.linkedBrowser.contentDocument;
+      Social.installProvider(doc, manifest3, function(addonManifest) {
+        // enable the provider so we know the button would have appeared
+        SocialService.addBuiltinProvider(manifest3.origin, function(provider) {
+          ok(provider, "provider is installed");
+          currentset = toolbar.getAttribute("currentset").split(',');
+          ok(currentset.indexOf(id) < 0, "button was not added to currentset");
+          Social.uninstallProvider(manifest3.origin, function() {
+            gBrowser.removeTab(tab);
+            next();
+          });
+        });
+      });
+    });
+  },
+  testButtonOnInstall: function(next) {
+    // we expect the addon install dialog to appear, we need to accept the
+    // install from the dialog.
+    info("Waiting for install dialog");
+    let panel = document.getElementById("servicesInstall-notification");
+    PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
+      PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
+      info("servicesInstall-notification panel opened");
+      panel.button.click();
+    })
+
+    let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
+    addTab(activationURL, function(tab) {
+      let doc = tab.linkedBrowser.contentDocument;
+      Social.installProvider(doc, manifest2, function(addonManifest) {
+          // at this point, we should have a button id in the currentset for our provider
+          let id = "social-status-button-" + manifest2.origin;
+          let toolbar = document.getElementById("nav-bar");
+
+          waitForCondition(function() {
+                             let currentset = toolbar.getAttribute("currentset").split(',');
+                             return currentset.indexOf(id) >= 0;
+                           },
+                           function() {
+                             // no longer need the tab
+                             gBrowser.removeTab(tab);
+                             next();
+                           }, "status button added to currentset");
+      });
+    });
+  },
+  testButtonOnEnable: function(next) {
+    // enable the provider now
+    SocialService.addBuiltinProvider(manifest2.origin, function(provider) {
+      ok(provider, "provider is installed");
+      let id = "social-status-button-" + manifest2.origin;
+      waitForCondition(function() { return document.getElementById(id) },
+                       next, "button exists after enabling social");
+    });
+  },
+  testStatusPanel: function(next) {
+    let icon = {
+      name: "testIcon",
+      iconURL: "chrome://browser/skin/Info.png",
+      counter: 1
+    };
+    // click on panel to open and wait for visibility
+    let provider = Social._getProviderFromOrigin(manifest2.origin);
+    let id = "social-status-button-" + provider.origin;
+    let btn = document.getElementById(id)
+    ok(btn, "got a status button");
+    let port = provider.getWorkerPort();
+
+    port.onmessage = function (e) {
+      let topic = e.data.topic;
+      switch (topic) {
+        case "test-init-done":
+          ok(true, "test-init-done received");
+          ok(provider.profile.userName, "profile was set by test worker");
+          btn.click();
+          break;
+        case "got-social-panel-visibility":
+          ok(true, "got the panel message " + e.data.result);
+          if (e.data.result == "shown") {
+            let panel = document.getElementById("social-notification-panel");
+            panel.hidePopup();
+          } else {
+            port.postMessage({topic: "test-ambient-notification", data: icon});
+            port.close();
+            waitForCondition(function() { return btn.getAttribute("badge"); },
+                       function() {
+                         is(btn.style.listStyleImage, "url(\"" + icon.iconURL + "\")", "notification icon updated");
+                         next();
+                       }, "button updated by notification");
+          }
+          break;
+      }
+    };
+    port.postMessage({topic: "test-init"});
+  },
+  testButtonOnDisable: function(next) {
+    // enable the provider now
+    let provider = Social._getProviderFromOrigin(manifest2.origin);
+    ok(provider, "provider is installed");
+    SocialService.removeProvider(manifest2.origin, function() {
+      let id = "social-status-button-" + manifest2.origin;
+      waitForCondition(function() { return !document.getElementById(id) },
+                       next, "button does not exist after disabling the provider");
+    });
+  },
+  testButtonOnUninstall: function(next) {
+    Social.uninstallProvider(manifest2.origin, function() {
+      // test that the button is no longer persisted
+      let id = "social-status-button-" + manifest2.origin;
+      let toolbar = document.getElementById("nav-bar");
+      let currentset = toolbar.getAttribute("currentset").split(',');
+      is(currentset.indexOf(id), -1, "button no longer in currentset");
+      next();
+    });
+  }
+}
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -172,17 +172,17 @@ function runSocialTests(tests, cbPreTest
     // We run on a timeout as the frameworker also makes use of timeouts, so
     // this helps keep the debug messages sane.
     executeSoon(function() {
       function cleanupAndRunNextTest() {
         info("sub-test " + name + " complete");
         cbPostTest(runNextTest);
       }
       cbPreTest(function() {
-        is(providersAtStart, Social.providers.length, "pre-test: no new providers left enabled");
+        info("pre-test: starting with " + Social.providers.length + " providers");
         info("sub-test " + name + " starting");
         try {
           func.call(tests, cleanupAndRunNextTest);
         } catch (ex) {
           ok(false, "sub-test " + name + " failed: " + ex.toString() +"\n"+ex.stack);
           cleanupAndRunNextTest();
         }
       })
--- a/browser/base/content/test/social/social_activate.html
+++ b/browser/base/content/test/social/social_activate.html
@@ -9,16 +9,17 @@ var data = {
   "name": "Demo Social Service",
   "iconURL": "chrome://branding/content/icon16.png",
   "icon32URL": "chrome://branding/content/favicon32.png",
   "icon64URL": "chrome://branding/content/icon64.png",
 
   // at least one of these must be defined
   "sidebarURL": "/browser/browser/base/content/test/social/social_sidebar.html",
   "workerURL": "/browser/browser/base/content/test/social/social_worker.js",
+  "statusURL": "/browser/browser/base/content/test/social/social_panel.html",
 
   // should be available for display purposes
   "description": "A short paragraph about this provider",
   "author": "Shane Caraveo, Mozilla",
 
   // optional
   "version": 1
 }
--- a/browser/base/content/test/social/social_worker.js
+++ b/browser/base/content/test/social/social_worker.js
@@ -114,23 +114,17 @@ onconnect = function(e) {
               markedTooltip: "Unmark this page",
               unmarkedLabel: "Mark",
               markedLabel: "Unmark",
             }
           }
         });
         break;
       case "test-ambient-notification":
-        let icon = {
-          name: "testIcon",
-          iconURL: "chrome://browser/skin/Info.png",
-          contentPanel: "https://example.com/browser/browser/base/content/test/social/social_panel.html",
-          counter: 1
-        };
-        apiPort.postMessage({topic: "social.ambient-notification", data: icon});
+        apiPort.postMessage({topic: "social.ambient-notification", data: event.data.data});
         break;
       case "test-isVisible":
         sidebarPort.postMessage({topic: "test-isVisible"});
         break;
       case "test-isVisible-response":
         testPort.postMessage({topic: "got-isVisible-response", result: event.data.result});
         break;
       case "share-data-message":
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -15,27 +15,30 @@
 "use strict";
 
 let require = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 
 let { Cc, Ci, Cu } = require("chrome");
 let promise = require("sdk/core/promise");
 let Telemetry = require("devtools/shared/telemetry");
 let TargetFactory = require("devtools/framework/target").TargetFactory;
+const escodegen = require("escodegen/escodegen");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource:///modules/source-editor.jsm");
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
 Cu.import("resource:///modules/devtools/scratchpad-manager.jsm");
 Cu.import("resource://gre/modules/jsdebugger.jsm");
 Cu.import("resource:///modules/devtools/gDevTools.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
+Cu.import("resource://gre/modules/reflect.jsm");
+Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
   "resource:///modules/devtools/VariablesView.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "VariablesViewController",
   "resource:///modules/devtools/VariablesViewController.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ObjectClient",
@@ -517,16 +520,30 @@ var Scratchpad = {
         });
       }
     }, reject);
 
     return deferred.promise;
   },
 
   /**
+   * Pretty print the source text inside the scratchpad.
+   */
+  prettyPrint: function SP_prettyPrint() {
+    const uglyText = this.getText();
+    try {
+      const ast = Reflect.parse(uglyText);
+      const prettyText = escodegen.generate(ast);
+      this.setText(prettyText);
+    } catch (e) {
+      this.writeAsErrorComment(DevToolsUtils.safeErrorString(e));
+    }
+  },
+
+  /**
    * Writes out a primitive value as a comment. This handles values which are
    * to be printed directly (number, string) as well as grips to values
    * (null, undefined, longString).
    *
    * @param any aValue
    *        The value to print.
    * @return Promise
    *         The promise that resolves after the value has been printed.
--- a/browser/devtools/scratchpad/scratchpad.xul
+++ b/browser/devtools/scratchpad/scratchpad.xul
@@ -41,16 +41,17 @@
   <!-- TODO: bug 650340 - implement printFile()
   <command id="sp-cmd-printFile" oncommand="Scratchpad.printFile();" disabled="true"/>
  -->
 
   <command id="sp-cmd-close" oncommand="Scratchpad.close();"/>
   <command id="sp-cmd-run" oncommand="Scratchpad.run();"/>
   <command id="sp-cmd-inspect" oncommand="Scratchpad.inspect();"/>
   <command id="sp-cmd-display" oncommand="Scratchpad.display();"/>
+  <command id="sp-cmd-pprint" oncommand="Scratchpad.prettyPrint();"/>
   <command id="sp-cmd-contentContext" oncommand="Scratchpad.setContentContext();"/>
   <command id="sp-cmd-browserContext" oncommand="Scratchpad.setBrowserContext();" disabled="true"/>
   <command id="sp-cmd-reloadAndRun" oncommand="Scratchpad.reloadAndRun();"/>
   <command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/>
   <command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/>
   <command id="sp-cmd-documentationLink" oncommand="Scratchpad.openDocumentationPage();"/>
   <command id="sp-cmd-hideSidebar" oncommand="Scratchpad.sidebar.hide();"/>
 </commandset>
@@ -89,16 +90,20 @@
   <key id="sp-key-inspect"
        key="&inspect.key;"
        command="sp-cmd-inspect"
        modifiers="accel"/>
   <key id="sp-key-display"
        key="&display.key;"
        command="sp-cmd-display"
        modifiers="accel"/>
+  <key id="sp-key-pprint"
+       key="&pprint.key;"
+       command="sp-cmd-pprint"
+       modifiers="accel"/>
   <key id="sp-key-reloadAndRun"
        key="&reloadAndRun.key;"
        command="sp-cmd-reloadAndRun"
        modifiers="accel,shift"/>
   <key id="sp-key-errorConsole"
        key="&errorConsoleCmd.commandkey;"
        command="sp-cmd-errorConsole"
        modifiers="accel,shift"/>
@@ -267,16 +272,21 @@
   <toolbarbutton id="sp-toolbar-inspect"
                  class="devtools-toolbarbutton"
                  label="&inspect.label;"
                  command="sp-cmd-inspect"/>
   <toolbarbutton id="sp-toolbar-display"
                  class="devtools-toolbarbutton"
                  label="&display.label;"
                  command="sp-cmd-display"/>
+  <toolbarspacer/>
+  <toolbarbutton id="sp-toolbar-pprint"
+                 class="devtools-toolbarbutton"
+                 label="&pprint.label;"
+                 command="sp-cmd-pprint"/>
 </toolbar>
 
 
 <popupset id="scratchpad-popups">
   <menupopup id="scratchpad-text-popup"
              onpopupshowing="goUpdateSourceEditorMenuItems()">
     <menuitem id="se-cMenu-cut"/>
     <menuitem id="se-cMenu-copy"/>
--- a/browser/devtools/scratchpad/test/Makefile.in
+++ b/browser/devtools/scratchpad/test/Makefile.in
@@ -26,12 +26,13 @@ MOCHITEST_BROWSER_FILES = \
 		browser_scratchpad_bug756681_display_non_error_exceptions.js \
 		browser_scratchpad_bug_751744_revert_to_saved.js \
 		browser_scratchpad_bug740948_reload_and_run.js \
 		browser_scratchpad_bug_661762_wrong_window_focus.js \
 		browser_scratchpad_bug_644413_modeline.js \
 		browser_scratchpad_bug807924_cannot_convert_to_string.js \
 		browser_scratchpad_long_string.js \
 		browser_scratchpad_open_error_console.js \
+		browser_scratchpad_pprint.js \
 		head.js \
 
 # Disable test due to bug 807234 becoming basically permanent
 #		browser_scratchpad_bug_653427_confirm_close.js \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_pprint.js
@@ -0,0 +1,26 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+    openScratchpad(runTests);
+  }, true);
+
+  content.location = "data:text/html;charset=utf8,test Scratchpad pretty print.";
+}
+
+function runTests(sw)
+{
+  const sp = sw.Scratchpad;
+  sp.setText("function main() { console.log(5); }");
+  sp.prettyPrint();
+  const prettyText = sp.getText();
+  ok(prettyText.contains("\n"));
+  finish();
+}
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -6,17 +6,17 @@
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 const promise = require("sdk/core/promise");
 
 let {CssLogic} = require("devtools/styleinspector/css-logic");
 let {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor");
-let {ELEMENT_STYLE} = require("devtools/server/actors/styles");
+let {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 /**
@@ -198,29 +198,41 @@ ElementStyle.prototype = {
 
         this.rules = [];
 
         for (let entry of entries) {
           this._maybeAddRule(entry);
         }
 
         // Mark overridden computed styles.
-        this.markOverridden();
+        this.markOverriddenAll();
+
+        this._sortRulesForPseudoElement();
 
         // We're done with the previous list of rules.
         delete this._refreshRules;
 
         return null;
       });
     }).then(null, promiseWarn);
     this.populated = populated;
     return this.populated;
   },
 
   /**
+   * Put pseudo elements in front of others.
+   */
+   _sortRulesForPseudoElement: function ElementStyle_sortRulesForPseudoElement()
+   {
+      this.rules = this.rules.sort((a, b) => {
+        return (a.pseudoElement || "z") > (b.pseudoElement || "z");
+      });
+   },
+
+  /**
    * Add a rule if it's one we care about.  Filters out duplicates and
    * inherited styles with no inherited properties.
    *
    * @param {object} aOptions
    *        Options for creating the Rule, see the Rule constructor.
    *
    * @return {bool} true if we added the rule.
    */
@@ -261,32 +273,48 @@ ElementStyle.prototype = {
       return false;
     }
 
     this.rules.push(rule);
     return true;
   },
 
   /**
-   * Mark the properties listed in this.rules with an overridden flag
-   * if an earlier property overrides it.
+   * Calls markOverridden with all supported pseudo elements
    */
-  markOverridden: function ElementStyle_markOverridden()
+  markOverriddenAll: function ElementStyle_markOverriddenAll()
+  {
+    this.markOverridden();
+    for (let pseudo of PSEUDO_ELEMENTS) {
+      this.markOverridden(pseudo);
+    }
+  },
+
+  /**
+   * Mark the properties listed in this.rules for a given pseudo element
+   * with an overridden flag if an earlier property overrides it.
+   * @param {string} pseudo
+   *        Which pseudo element to flag as overridden.
+   *        Empty string or undefined will default to no pseudo element.
+   */
+  markOverridden: function ElementStyle_markOverridden(pseudo="")
   {
     // Gather all the text properties applied by these rules, ordered
     // from more- to less-specific.
     let textProps = [];
-    for each (let rule in this.rules) {
-      textProps = textProps.concat(rule.textProps.slice(0).reverse());
+    for (let rule of this.rules) {
+      if (rule.pseudoElement == pseudo) {
+        textProps = textProps.concat(rule.textProps.slice(0).reverse());
+      }
     }
 
     // Gather all the computed properties applied by those text
     // properties.
     let computedProps = [];
-    for each (let textProp in textProps) {
+    for (let textProp of textProps) {
       computedProps = computedProps.concat(textProp.computed);
     }
 
     // Walk over the computed properties.  As we see a property name
     // for the first time, mark that property's name as taken by this
     // property.
     //
     // If we come across a property whose name is already taken, check
@@ -297,17 +325,17 @@ ElementStyle.prototype = {
     //   the new property.
     //
     //   If the new property is a lower or equal priority, mark it as
     //   overridden.
     //
     // _overriddenDirty will be set on each prop, indicating whether its
     // dirty status changed during this pass.
     let taken = {};
-    for each (let computedProp in computedProps) {
+    for (let computedProp of computedProps) {
       let earlier = taken[computedProp.name];
       let overridden;
       if (earlier
           && computedProp.priority === "important"
           && earlier.priority !== "important") {
         // New property is higher priority.  Mark the earlier property
         // overridden (which will reverse its dirty state).
         earlier._overriddenDirty = !earlier._overriddenDirty;
@@ -323,17 +351,17 @@ ElementStyle.prototype = {
         taken[computedProp.name] = computedProp;
       }
     }
 
     // For each TextProperty, mark it overridden if all of its
     // computed properties are marked overridden.  Update the text
     // property's associated editor, if any.  This will clear the
     // _overriddenDirty state on all computed properties.
-    for each (let textProp in textProps) {
+    for (let textProp of textProps) {
       // _updatePropertyOverridden will return true if the
       // overridden state has changed for the text property.
       if (this._updatePropertyOverridden(textProp)) {
         textProp.updateEditor();
       }
     }
   },
 
@@ -379,16 +407,17 @@ ElementStyle.prototype = {
  * @constructor
  */
 function Rule(aElementStyle, aOptions)
 {
   this.elementStyle = aElementStyle;
   this.domRule = aOptions.rule || null;
   this.style = aOptions.rule;
   this.matchedSelectors = aOptions.matchedSelectors || [];
+  this.pseudoElement = aOptions.pseudoElement || "";
 
   this.inherited = aOptions.inherited || null;
   this._modificationDepth = 0;
 
   if (this.domRule) {
     let parentRule = this.domRule.parentRule;
     if (parentRule && parentRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) {
       this.mediaText = parentRule.mediaText;
@@ -553,17 +582,17 @@ Rule.prototype = {
             this.style, textProp.name,
             null,
             cssProp.value,
             textProp.value);
         }
         textProp.priority = cssProp.priority;
       }
 
-      this.elementStyle.markOverridden();
+      this.elementStyle.markOverriddenAll();
 
       if (promise === this._applyingModifications) {
         this._applyingModifications = null;
       }
 
       this.elementStyle._changed();
     }).then(null, promiseWarn);
     this._applyingModifications = promise;
@@ -637,17 +666,16 @@ Rule.prototype = {
   },
 
   _parseCSSText: function Rule_parseProperties(aCssText)
   {
     let lines = aCssText.match(CSS_LINE_RE);
     let props = [];
 
     for (let line of lines) {
-      dump("line: " + line + "\n");
       let [, name, value, priority] = CSS_PROP_RE.exec(line) || []
       if (!name || !value) {
         continue;
       }
 
       props.push({
         name: name,
         value: value,
@@ -1073,16 +1101,17 @@ CssRuleView.prototype = {
   _populate: function() {
     let elementStyle = this._elementStyle;
     return this._elementStyle.populate().then(() => {
       if (this._elementStyle != elementStyle) {
         return promise.reject("element changed");
       }
       this._createEditors();
 
+
       // Notify anyone that cares that we refreshed.
       var evt = this.doc.createEvent("Events");
       evt.initEvent("CssRuleViewRefreshed", true, false);
       this.element.dispatchEvent(evt);
       return undefined;
     }).then(null, promiseWarn);
   },
 
@@ -1128,43 +1157,128 @@ CssRuleView.prototype = {
   _changed: function CssRuleView_changed()
   {
     var evt = this.doc.createEvent("Events");
     evt.initEvent("CssRuleViewChanged", true, false);
     this.element.dispatchEvent(evt);
   },
 
   /**
+   * Text for header that shows above rules for this element
+   */
+  get selectedElementLabel ()
+  {
+    if (this._selectedElementLabel) {
+      return this._selectedElementLabel;
+    }
+    this._selectedElementLabel = CssLogic.l10n("rule.selectedElement");
+    return this._selectedElementLabel;
+  },
+
+  /**
+   * Text for header that shows above rules for pseudo elements
+   */
+  get pseudoElementLabel ()
+  {
+    if (this._pseudoElementLabel) {
+      return this._pseudoElementLabel;
+    }
+    this._pseudoElementLabel = CssLogic.l10n("rule.pseudoElement");
+    return this._pseudoElementLabel;
+  },
+
+  togglePseudoElementVisibility: function(value)
+  {
+    this._showPseudoElements = !!value;
+    let isOpen = this.showPseudoElements;
+
+    Services.prefs.setBoolPref("devtools.inspector.show_pseudo_elements",
+      isOpen);
+
+    this.element.classList.toggle("show-pseudo-elements", isOpen);
+
+    if (this.pseudoElementTwisty) {
+      if (isOpen) {
+        this.pseudoElementTwisty.setAttribute("open", "true");
+      }
+      else {
+        this.pseudoElementTwisty.removeAttribute("open");
+      }
+    }
+  },
+
+  get showPseudoElements ()
+  {
+    if (this._showPseudoElements === undefined) {
+      this._showPseudoElements =
+        Services.prefs.getBoolPref("devtools.inspector.show_pseudo_elements");
+    }
+    return this._showPseudoElements;
+  },
+
+  /**
    * Creates editor UI for each of the rules in _elementStyle.
    */
   _createEditors: function CssRuleView_createEditors()
   {
     // Run through the current list of rules, attaching
     // their editors in order.  Create editors if needed.
     let lastInheritedSource = "";
+    let seenPseudoElement = false;
+    let seenNormalElement = false;
+
     for (let rule of this._elementStyle.rules) {
       if (rule.domRule.system) {
         continue;
       }
 
+      // Only print header for this element if there are pseudo elements
+      if (seenPseudoElement && !seenNormalElement && !rule.pseudoElement) {
+        seenNormalElement = true;
+        let div = this.doc.createElementNS(HTML_NS, "div");
+        div.className = "theme-gutter ruleview-header";
+        div.textContent = this.selectedElementLabel;
+        this.element.appendChild(div);
+      }
+
       let inheritedSource = rule.inheritedSource;
       if (inheritedSource != lastInheritedSource) {
-        let h2 = this.doc.createElementNS(HTML_NS, "div");
-        h2.className = "ruleview-rule-inheritance theme-gutter";
-        h2.textContent = inheritedSource;
+        let div = this.doc.createElementNS(HTML_NS, "div");
+        div.className = "theme-gutter ruleview-header";
+        div.textContent = inheritedSource;
         lastInheritedSource = inheritedSource;
-        this.element.appendChild(h2);
+        this.element.appendChild(div);
+      }
+
+      if (!seenPseudoElement && rule.pseudoElement) {
+        seenPseudoElement = true;
+
+        let div = this.doc.createElementNS(HTML_NS, "div");
+        div.className = "theme-gutter ruleview-header";
+        div.textContent = this.pseudoElementLabel;
+
+        let twisty = this.pseudoElementTwisty =
+          this.doc.createElementNS(HTML_NS, "span");
+        twisty.className = "ruleview-expander theme-twisty";
+        twisty.addEventListener("click", () => {
+          this.togglePseudoElementVisibility(!this.showPseudoElements);
+        }, false);
+
+        div.insertBefore(twisty, div.firstChild);
+        this.element.appendChild(div);
       }
 
       if (!rule.editor) {
         new RuleEditor(this, rule);
       }
 
       this.element.appendChild(rule.editor.element);
     }
+
+    this.togglePseudoElementVisibility(this.showPseudoElements);
   },
 
   /**
    * Copy selected text from the rule view.
    *
    * @param {Event} aEvent
    *        The event object.
    */
@@ -1222,16 +1336,19 @@ function RuleEditor(aRuleView, aRule)
 }
 
 RuleEditor.prototype = {
   _create: function RuleEditor_create()
   {
     this.element = this.doc.createElementNS(HTML_NS, "div");
     this.element.className = "ruleview-rule theme-separator";
     this.element._ruleEditor = this;
+    if (this.rule.pseudoElement) {
+      this.element.classList.add("ruleview-rule-pseudo-element");
+    }
 
     // Give a relative position for the inplace editor's measurement
     // span to be placed absolutely against.
     this.element.style.position = "relative";
 
     // Add the source link.
     let source = createChild(this.element, "div", {
       class: "ruleview-rule-source theme-link"
@@ -1353,22 +1470,25 @@ RuleEditor.prototype = {
    * Programatically add a new property to the rule.
    *
    * @param {string} aName
    *        Property name.
    * @param {string} aValue
    *        Property value.
    * @param {string} aPriority
    *        Property priority.
+   * @return {TextProperty}
+   *        The new property
    */
   addProperty: function RuleEditor_addProperty(aName, aValue, aPriority)
   {
     let prop = this.rule.createProperty(aName, aValue, aPriority);
     let editor = new TextPropertyEditor(this, prop);
     this.propertyList.appendChild(editor.element);
+    return prop;
   },
 
   /**
    * Create a text input for a property name.  If a non-empty property
    * name is given, we'll create a real TextProperty and add it to the
    * rule.
    */
   newProperty: function RuleEditor_newProperty()
--- a/browser/devtools/styleinspector/ruleview.css
+++ b/browser/devtools/styleinspector/ruleview.css
@@ -31,8 +31,26 @@
 .ruleview-propertycontainer a {
   cursor: pointer;
 }
 
 .ruleview-computedlist:not(.styleinspector-open),
 .ruleview-warning[hidden] {
   display: none;
 }
+
+.ruleview-rule-pseudo-element {
+  display: none;
+}
+
+.show-pseudo-elements .ruleview-rule-pseudo-element {
+  display: block;
+}
+
+.ruleview .ruleview-expander {
+  vertical-align: middle;
+}
+
+.ruleview-header {
+  vertical-align:middle;
+  height: 1.5em;
+  line-height: 1.5em;
+}
\ No newline at end of file
--- a/browser/devtools/styleinspector/test/Makefile.in
+++ b/browser/devtools/styleinspector/test/Makefile.in
@@ -30,16 +30,17 @@ MOCHITEST_BROWSER_FILES = \
   browser_computedview_734259_style_editor_link.js \
   browser_computedview_copy.js\
   browser_styleinspector_bug_677930_urls_clickable.js \
   browser_bug893965_css_property_completion_new_property.js \
   browser_bug893965_css_property_completion_existing_property.js \
   browser_bug894376_css_value_completion_new_property_value_pair.js \
   browser_bug894376_css_value_completion_existing_property_value_pair.js \
   browser_ruleview_bug_902966_revert_value_on_ESC.js \
+  browser_ruleview_pseudoelement.js \
   head.js \
   $(NULL)
 
 MOCHITEST_BROWSER_FILES += \
   browser_bug683672.html \
   browser_bug705707_is_content_stylesheet.html \
   browser_bug705707_is_content_stylesheet_imported.css \
   browser_bug705707_is_content_stylesheet_imported2.css \
@@ -47,9 +48,10 @@ MOCHITEST_BROWSER_FILES += \
   browser_bug705707_is_content_stylesheet_script.css \
   browser_bug705707_is_content_stylesheet.xul \
   browser_bug705707_is_content_stylesheet_xul.css \
   browser_bug722196_identify_media_queries.html \
   browser_styleinspector_bug_677930_urls_clickable.html \
   browser_styleinspector_bug_677930_urls_clickable \
   browser_styleinspector_bug_677930_urls_clickable/browser_styleinspector_bug_677930_urls_clickable.css \
   test-image.png \
+  browser_ruleview_pseudoelement.html \
   $(NULL)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_ruleview_pseudoelement.html
@@ -0,0 +1,115 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <style>
+
+body {
+    color: #333;
+}
+
+.box {
+    float:left;
+    width: 128px;
+    height: 128px;
+    background: #ddd;
+    padding: 32px;
+    margin: 32px;
+    position:relative;
+}
+
+* {
+    cursor: default;
+}
+
+nothing {
+    cursor: pointer;
+}
+
+p::-moz-selection {
+    color: white;
+    background: black;
+}
+p::selection {
+    color: white;
+    background: black;
+}
+
+p:first-line {
+   background: blue;
+}
+p:first-letter {
+  color: red;
+  font-size: 130%;
+}
+
+.box:before {
+    background: green;
+    content: " ";
+    position: absolute;
+    height:32px;
+    width:32px;
+}
+
+.box:after {
+    background: red;
+    content: " ";
+    position: absolute;
+    border-radius: 50%;
+    height:32px;
+    width:32px;
+    top: 50%;
+    left: 50%;
+    margin-top: -16px;
+    margin-left: -16px;
+}
+
+.topleft:before {
+    top:0;
+    left:0;
+}
+
+.topright:before {
+    top:0;
+    right:0;
+}
+
+.bottomright:before {
+    bottom:10px;
+    right:10px;
+    color: red;
+}
+
+.bottomright:before {
+    bottom:0;
+    right:0;
+}
+
+.bottomleft:before {
+    bottom:0;
+    left:0;
+}
+
+    </style>
+  </head>
+  <body>
+    <h1>ruleview pseudoelement($("test"));</h1>
+
+    <div id="topleft" class="box topleft">
+        <p>Top Left<br />Position</p>
+    </div>
+
+    <div id="topright" class="box topright">
+        <p>Top Right<br />Position</p>
+    </div>
+
+    <div id="bottomright" class="box bottomright">
+        <p>Bottom Right<br />Position</p>
+    </div>
+
+    <div id="bottomleft" class="box bottomleft">
+        <p>Bottom Left<br />Position</p>
+    </div>
+
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_ruleview_pseudoelement.js
@@ -0,0 +1,317 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let doc;
+let inspector;
+let view;
+
+const TEST_URI = "http://example.com/browser/browser/" +
+                 "devtools/styleinspector/test/" +
+                 "browser_ruleview_pseudoelement.html";
+
+function testPseudoElements(aInspector, aRuleView)
+{
+  inspector = aInspector;
+  view = aRuleView;
+
+  testTopLeft();
+}
+
+function testTopLeft()
+{
+  testNode(doc.querySelector("#topleft"), (element, elementStyle) => {
+    let elementRules = elementStyle.rules.filter((rule) => { return !rule.pseudoElement; });
+    let afterRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":after"; });
+    let beforeRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":before"; });
+    let firstLineRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":first-line"; });
+    let firstLetterRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":first-letter"; });
+    let selectionRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":-moz-selection"; });
+
+    is(elementRules.length, 4, "TopLeft has the correct number of non psuedo element rules");
+    is(afterRules.length, 1, "TopLeft has the correct number of :after rules");
+    is(beforeRules.length, 2, "TopLeft has the correct number of :before rules");
+    is(firstLineRules.length, 0, "TopLeft has the correct number of :first-line rules");
+    is(firstLetterRules.length, 0, "TopLeft has the correct number of :first-letter rules");
+    is(selectionRules.length, 0, "TopLeft has the correct number of :selection rules");
+
+    let gutters = view.element.querySelectorAll(".theme-gutter");
+    is (gutters.length, 3, "There are three gutter headings");
+    is (gutters[0].textContent, "Pseudo-elements", "Gutter heading is correct");
+    is (gutters[1].textContent, "This Element", "Gutter heading is correct");
+    is (gutters[2].textContent, "Inherited from body", "Gutter heading is correct");
+
+    // Make sure that clicking on the twisty hides pseudo elements
+    let expander = gutters[0].querySelector(".ruleview-expander");
+    ok (view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements are expanded");
+    expander.click();
+    ok (!view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements are collapsed by twisty");
+    expander.click();
+    ok (view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements are expanded again");
+    expander.click();
+
+    let defaultView = element.ownerDocument.defaultView;
+    let elementRule = elementRules[0];
+    let elementRuleView = [].filter.call(view.element.children, (e) => {
+      return e._ruleEditor && e._ruleEditor.rule === elementRule;
+    })[0]._ruleEditor;
+
+    let elementAfterRule = afterRules[0];
+    let elementAfterRuleView = [].filter.call(view.element.children, (e) => {
+      return e._ruleEditor && e._ruleEditor.rule === elementAfterRule;
+    })[0]._ruleEditor;
+
+    is
+    (
+      convertTextPropsToString(elementAfterRule.textProps),
+      "background: none repeat scroll 0% 0% red; content: \" \"; position: absolute; " +
+      "border-radius: 50%; height: 32px; width: 32px; top: 50%; left: 50%; margin-top: -16px; margin-left: -16px",
+      "TopLeft after properties are correct"
+    );
+
+    let elementBeforeRule = beforeRules[0];
+    let elementBeforeRuleView = [].filter.call(view.element.children, (e) => {
+      return e._ruleEditor && e._ruleEditor.rule === elementBeforeRule;
+    })[0]._ruleEditor;
+
+    is
+    (
+      convertTextPropsToString(elementBeforeRule.textProps),
+      "top: 0px; left: 0px",
+      "TopLeft before properties are correct"
+    );
+
+    let firstProp = elementAfterRuleView.addProperty("background-color", "rgb(0, 255, 0)", "");
+    let secondProp = elementAfterRuleView.addProperty("padding", "100px", "");
+
+    is (firstProp, elementAfterRule.textProps[elementAfterRule.textProps.length - 2],
+        "First added property is on back of array");
+    is (secondProp, elementAfterRule.textProps[elementAfterRule.textProps.length - 1],
+        "Second added property is on back of array");
+
+    promiseDone(elementAfterRule._applyingModifications.then(() => {
+      is(defaultView.getComputedStyle(element, ":after").getPropertyValue("background-color"),
+        "rgb(0, 255, 0)", "Added property should have been used.");
+      is(defaultView.getComputedStyle(element, ":after").getPropertyValue("padding-top"),
+        "100px", "Added property should have been used.");
+      is(defaultView.getComputedStyle(element).getPropertyValue("padding-top"),
+        "32px", "Added property should not apply to element");
+
+      secondProp.setEnabled(false);
+
+      return elementAfterRule._applyingModifications;
+    }).then(() => {
+      is(defaultView.getComputedStyle(element, ":after").getPropertyValue("padding-top"), "0px",
+        "Disabled property should have been used.");
+      is(defaultView.getComputedStyle(element).getPropertyValue("padding-top"), "32px",
+        "Added property should not apply to element");
+
+      secondProp.setEnabled(true);
+
+      return elementAfterRule._applyingModifications;
+    }).then(() => {
+      is(defaultView.getComputedStyle(element, ":after").getPropertyValue("padding-top"), "100px",
+        "Enabled property should have been used.");
+      is(defaultView.getComputedStyle(element).getPropertyValue("padding-top"), "32px",
+        "Added property should not apply to element");
+
+      let firstProp = elementRuleView.addProperty("background-color", "rgb(0, 0, 255)", "");
+
+      return elementRule._applyingModifications;
+    }).then(() => {
+      is(defaultView.getComputedStyle(element).getPropertyValue("background-color"), "rgb(0, 0, 255)",
+        "Added property should have been used.");
+      is(defaultView.getComputedStyle(element, ":after").getPropertyValue("background-color"), "rgb(0, 255, 0)",
+        "Added prop does not apply to pseudo");
+
+      testTopRight();
+    }));
+  });
+}
+
+function testTopRight()
+{
+  testNode(doc.querySelector("#topright"), (element, elementStyle) => {
+
+    let elementRules = elementStyle.rules.filter((rule) => { return !rule.pseudoElement; });
+    let afterRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":after"; });
+    let beforeRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":before"; });
+    let firstLineRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":first-line"; });
+    let firstLetterRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":first-letter"; });
+    let selectionRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":-moz-selection"; });
+
+    is(elementRules.length, 4, "TopRight has the correct number of non psuedo element rules");
+    is(afterRules.length, 1, "TopRight has the correct number of :after rules");
+    is(beforeRules.length, 2, "TopRight has the correct number of :before rules");
+    is(firstLineRules.length, 0, "TopRight has the correct number of :first-line rules");
+    is(firstLetterRules.length, 0, "TopRight has the correct number of :first-letter rules");
+    is(selectionRules.length, 0, "TopRight has the correct number of :selection rules");
+
+    let gutters = view.element.querySelectorAll(".theme-gutter");
+    is (gutters.length, 3, "There are three gutter headings");
+    is (gutters[0].textContent, "Pseudo-elements", "Gutter heading is correct");
+    is (gutters[1].textContent, "This Element", "Gutter heading is correct");
+    is (gutters[2].textContent, "Inherited from body", "Gutter heading is correct");
+
+    let expander = gutters[0].querySelector(".ruleview-expander");
+    ok (!view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements remain collapsed after switching element");
+    expander.scrollIntoView();
+    expander.click();
+    ok (view.element.classList.contains("show-pseudo-elements"), "Pseudo Elements are shown again after clicking twisty");
+
+    testBottomRight();
+  });
+}
+
+function testBottomRight()
+{
+  testNode(doc.querySelector("#bottomright"), (element, elementStyle) => {
+
+    let elementRules = elementStyle.rules.filter((rule) => { return !rule.pseudoElement; });
+    let afterRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":after"; });
+    let beforeRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":before"; });
+    let firstLineRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":first-line"; });
+    let firstLetterRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":first-letter"; });
+    let selectionRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":-moz-selection"; });
+
+    is(elementRules.length, 4, "BottomRight has the correct number of non psuedo element rules");
+    is(afterRules.length, 1, "BottomRight has the correct number of :after rules");
+    is(beforeRules.length, 3, "BottomRight has the correct number of :before rules");
+    is(firstLineRules.length, 0, "BottomRight has the correct number of :first-line rules");
+    is(firstLetterRules.length, 0, "BottomRight has the correct number of :first-letter rules");
+    is(selectionRules.length, 0, "BottomRight has the correct number of :selection rules");
+
+    testBottomLeft();
+  });
+}
+
+function testBottomLeft()
+{
+  testNode(doc.querySelector("#bottomleft"), (element, elementStyle) => {
+
+    let elementRules = elementStyle.rules.filter((rule) => { return !rule.pseudoElement; });
+    let afterRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":after"; });
+    let beforeRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":before"; });
+    let firstLineRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":first-line"; });
+    let firstLetterRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":first-letter"; });
+    let selectionRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":-moz-selection"; });
+
+    is(elementRules.length, 4, "BottomLeft has the correct number of non psuedo element rules");
+    is(afterRules.length, 1, "BottomLeft has the correct number of :after rules");
+    is(beforeRules.length, 2, "BottomLeft has the correct number of :before rules");
+    is(firstLineRules.length, 0, "BottomLeft has the correct number of :first-line rules");
+    is(firstLetterRules.length, 0, "BottomLeft has the correct number of :first-letter rules");
+    is(selectionRules.length, 0, "BottomLeft has the correct number of :selection rules");
+
+    testParagraph();
+  });
+}
+
+function testParagraph()
+{
+  testNode(doc.querySelector("#bottomleft p"), (element, elementStyle) => {
+
+    let elementRules = elementStyle.rules.filter((rule) => { return !rule.pseudoElement; });
+    let afterRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":after"; });
+    let beforeRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":before"; });
+    let firstLineRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":first-line"; });
+    let firstLetterRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":first-letter"; });
+    let selectionRules = elementStyle.rules.filter((rule) => { return rule.pseudoElement === ":-moz-selection"; });
+
+    is(elementRules.length, 3, "Paragraph has the correct number of non psuedo element rules");
+    is(afterRules.length, 0, "Paragraph has the correct number of :after rules");
+    is(beforeRules.length, 0, "Paragraph has the correct number of :before rules");
+    is(firstLineRules.length, 1, "Paragraph has the correct number of :first-line rules");
+    is(firstLetterRules.length, 1, "Paragraph has the correct number of :first-letter rules");
+    is(selectionRules.length, 1, "Paragraph has the correct number of :selection rules");
+
+    let gutters = view.element.querySelectorAll(".theme-gutter");
+    is (gutters.length, 3, "There are three gutter headings");
+    is (gutters[0].textContent, "Pseudo-elements", "Gutter heading is correct");
+    is (gutters[1].textContent, "This Element", "Gutter heading is correct");
+    is (gutters[2].textContent, "Inherited from body", "Gutter heading is correct");
+
+    let elementFirstLineRule = firstLineRules[0];
+    let elementFirstLineRuleView = [].filter.call(view.element.children, (e) => {
+      return e._ruleEditor && e._ruleEditor.rule === elementFirstLineRule;
+    })[0]._ruleEditor;
+
+    is
+    (
+      convertTextPropsToString(elementFirstLineRule.textProps),
+      "background: none repeat scroll 0% 0% blue",
+      "Paragraph first-line properties are correct"
+    );
+
+    let elementFirstLetterRule = firstLetterRules[0];
+    let elementFirstLetterRuleView = [].filter.call(view.element.children, (e) => {
+      return e._ruleEditor && e._ruleEditor.rule === elementFirstLetterRule;
+    })[0]._ruleEditor;
+
+    is
+    (
+      convertTextPropsToString(elementFirstLetterRule.textProps),
+      "color: red; font-size: 130%",
+      "Paragraph first-letter properties are correct"
+    );
+
+    let elementSelectionRule = selectionRules[0];
+    let elementSelectionRuleView = [].filter.call(view.element.children, (e) => {
+      return e._ruleEditor && e._ruleEditor.rule === elementSelectionRule;
+    })[0]._ruleEditor;
+
+    is
+    (
+      convertTextPropsToString(elementSelectionRule.textProps),
+      "color: white; background: none repeat scroll 0% 0% black",
+      "Paragraph first-letter properties are correct"
+    );
+
+    testBody();
+  });
+}
+
+function testBody() {
+
+  testNode(doc.querySelector("body"), (element, elementStyle) => {
+
+    let gutters = view.element.querySelectorAll(".theme-gutter");
+    is (gutters.length, 0, "There are no gutter headings");
+
+    finishTest();
+  });
+
+}
+function convertTextPropsToString(textProps) {
+  return textProps.map((t) => {
+    return t.name + ": " + t.value;
+  }).join("; ");
+}
+
+function testNode(node, cb)
+{
+  inspector.once("inspector-updated", () => {
+    cb(node, view._elementStyle)
+  });
+  inspector.selection.setNode(node);
+}
+
+function finishTest()
+{
+  doc = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
+    doc = content.document;
+    waitForFocus(() => openRuleView(testPseudoElements), content);
+  }, true);
+
+  content.location = TEST_URI;
+}
--- a/browser/locales/en-US/chrome/browser/devtools/scratchpad.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/scratchpad.dtd
@@ -60,16 +60,19 @@
 <!ENTITY inspect.label                "Inspect">
 <!ENTITY inspect.accesskey            "I">
 <!ENTITY inspect.key                  "i">
 
 <!ENTITY display.label                "Display">
 <!ENTITY display.accesskey            "D">
 <!ENTITY display.key                  "l">
 
+<!ENTITY pprint.label                 "Pretty Print">
+<!ENTITY pprint.key                   "p">
+
 <!-- LOCALIZATION NOTE (environmentMenu.label, accesskey): This menu item was
   -  renamed from "Context" to avoid confusion with the right-click context
   -  menu in the text area. It refers to the JavaScript Environment (or context)
   -  the user is evaluating against. I.e., Content (current tab) or Chrome
   -  (browser).
   -->
 <!ENTITY environmentMenu.label        "Environment">
 <!ENTITY environmentMenu.accesskey    "N">
--- a/browser/locales/en-US/chrome/browser/devtools/styleinspector.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/styleinspector.properties
@@ -30,16 +30,24 @@ rule.sourceInline=inline
 rule.sourceElement=element
 
 # LOCALIZATION NOTE (rule.inheritedFrom): Shown for CSS rules
 # that were inherited from a parent node. Will be passed a node
 # identifier of the parent node.
 # e.g "Inherited from body#bodyID"
 rule.inheritedFrom=Inherited from %S
 
+# LOCALIZATION NOTE (rule.pseudoElement): Shown for CSS rules
+# pseudo element header
+rule.pseudoElement=Pseudo-elements
+
+# LOCALIZATION NOTE (rule.pseudoElement): Shown for CSS rules
+# pseudo element header
+rule.selectedElement=This Element
+
 # LOCALIZATION NOTE (helpLinkTitle): For each style property
 # the user can hover it and get a help link button which allows one to
 # quickly jump to the documentation from the Mozilla Developer Network site.
 # This is the link title shown in the hover tooltip.
 helpLinkTitle=Read the documentation for this property
 
 # LOCALIZATION NOTE (rule.warning.title): When an invalid property value is
 # entered into the rule view a warning icon is displayed. This text is used for
--- a/browser/metro/modules/CrossSlide.jsm
+++ b/browser/metro/modules/CrossSlide.jsm
@@ -37,42 +37,38 @@ let CrossSlidingStateNames = [
 // --------------------------------
 // module helpers
 //
 
 function isSelectable(aElement) {
   // placeholder logic
   return aElement.nodeName == 'richgriditem';
 }
-
 function withinCone(aLen, aHeight) {
   // check pt falls within 45deg either side of the cross axis
   return aLen > aHeight;
 }
+function getScrollAxisFromElement(aElement) {
+  // keeping it simple - just return apparent scroll axis for the document
+  let win = aElement.ownerDocument.defaultView;
+  let scrollX = win.scrollMaxX,
+      scrollY = win.scrollMaxY;
+  // determine scroll axis from scrollable content when possible
+  if (scrollX || scrollY)
+    return scrollX >= scrollY ? 'x' : 'y';
 
-function getScrollAxisFromElement(aElement) {
-  let elem = aElement,
-      win = elem.ownerDocument.defaultView;
-  let scrollX, scrollY;
-  for (; elem && 1==elem.nodeType; elem = elem.parentNode) {
-    let cs = win.getComputedStyle(elem);
-    scrollX = (cs.overflowX=='scroll' || cs.overflowX=='auto');
-    scrollY = (cs.overflowX=='scroll' || cs.overflowX=='auto');
-    if (scrollX || scrollY) {
-      break;
-    }
-  }
-  return scrollX ? 'x' : 'y';
+  // fall back to guessing at scroll axis from document aspect ratio
+  let docElem = aElement.ownerDocument.documentElement;
+  return  docElem.clientWidth >= docElem.clientHeight ?
+          'x' : 'y';
 }
-
 function pointFromTouchEvent(aEvent) {
   let touch = aEvent.touches[0];
   return { x: touch.clientX, y: touch.clientY };
 }
-
 // This damping function has these important properties:
 // f(0) = 0
 // f'(0) = 1
 // limit as x -> Infinity of f(x) = 1
 function damp(aX) {
   return 2 / (1 + Math.exp(-2 * aX)) - 1;
 }
 function speedbump(aDelta, aStart, aEnd) {
--- a/browser/modules/Social.jsm
+++ b/browser/modules/Social.jsm
@@ -164,34 +164,38 @@ this.Social = {
       // Retrieve the current set of providers, and set the current provider.
       SocialService.getOrderedProviderList(function (providers) {
         Social._updateProviderCache(providers);
         Social._updateWorkerState(true);
       });
     }
 
     // Register an observer for changes to the provider list
-    SocialService.registerProviderListener(function providerListener(topic, data) {
+    SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
       // An engine change caused by adding/removing a provider should notify.
       // any providers we receive are enabled in the AddonsManager
-      if (topic == "provider-added" || topic == "provider-removed") {
-        Social._updateProviderCache(data);
+      if (topic == "provider-installed" || topic == "provider-uninstalled") {
+        // installed/uninstalled do not send the providers param
+        Services.obs.notifyObservers(null, "social:" + topic, origin);
+        return;
+      }
+      if (topic == "provider-enabled" || topic == "provider-disabled") {
+        Social._updateProviderCache(providers);
         Social._updateWorkerState(true);
         Services.obs.notifyObservers(null, "social:providers-changed", null);
+        Services.obs.notifyObservers(null, "social:" + topic, origin);
         return;
       }
       if (topic == "provider-update") {
         // a provider has self-updated its manifest, we need to update our cache
         // and reload the provider.
-        let provider = data;
-        SocialService.getOrderedProviderList(function(providers) {
-          Social._updateProviderCache(providers);
-          provider.reload();
-          Services.obs.notifyObservers(null, "social:providers-changed", null);
-        });
+        Social._updateProviderCache(providers);
+        let provider = Social._getProviderFromOrigin(origin);
+        provider.reload();
+        Services.obs.notifyObservers(null, "social:providers-changed", null);
       }
     });
   },
 
   _updateWorkerState: function(enable) {
     // ensure that our providers are all disabled, and enabled if we allow
     // multiple workers
     if (enable && !Social.allowMultipleWorkers)
@@ -254,16 +258,20 @@ this.Social = {
     for (let p of this.providers) {
       if (p.origin == origin) {
         return p;
       }
     }
     return null;
   },
 
+  getManifestByOrigin: function(origin) {
+    return SocialService.getManifestByOrigin(origin);
+  },
+
   installProvider: function(doc, data, installCallback) {
     SocialService.installProvider(doc, data, installCallback);
   },
 
   uninstallProvider: function(origin, aCallback) {
     SocialService.uninstallProvider(origin, aCallback);
   },
 
--- a/browser/themes/linux/devtools/ruleview.css
+++ b/browser/themes/linux/devtools/ruleview.css
@@ -9,17 +9,17 @@
 .ruleview-rule-source {
   -moz-padding-start: 5px;
   cursor: pointer;
   text-align: right;
   float: right;
   -moz-user-select: none;
 }
 
-.ruleview-rule-inheritance {
+.ruleview-header {
   border-top-width: 1px;
   border-bottom-width: 1px;
   border-top-style: solid;
   border-bottom-style: solid;
   padding: 1px 4px;
   margin-top: 4px;
   -moz-user-select: none;
 }
--- a/browser/themes/osx/devtools/ruleview.css
+++ b/browser/themes/osx/devtools/ruleview.css
@@ -9,26 +9,30 @@
 .ruleview-rule-source {
   -moz-padding-start: 5px;
   cursor: pointer;
   text-align: right;
   float: right;
   -moz-user-select: none;
 }
 
-.ruleview-rule-inheritance {
+.ruleview-header {
   border-top-width: 1px;
   border-bottom-width: 1px;
   border-top-style: solid;
   border-bottom-style: solid;
   padding: 1px 4px;
-  margin-top: 4px;
   -moz-user-select: none;
 }
 
+.ruleview-rule-pseudo-element {
+  padding-left:20px;
+  border-left: solid 10px;
+}
+
 .ruleview-rule-source:hover {
   text-decoration: underline;
 }
 
 .ruleview-rule,
 #noResults {
   padding: 2px 4px;
 }
--- a/browser/themes/windows/devtools/ruleview.css
+++ b/browser/themes/windows/devtools/ruleview.css
@@ -9,17 +9,17 @@
 .ruleview-rule-source {
   -moz-padding-start: 5px;
   cursor: pointer;
   text-align: right;
   float: right;
   -moz-user-select: none;
 }
 
-.ruleview-rule-inheritance {
+.ruleview-header {
   border-top-width: 1px;
   border-bottom-width: 1px;
   border-top-style: solid;
   border-bottom-style: solid;
   padding: 1px 4px;
   margin-top: 4px;
   -moz-user-select: none;
 }
--- a/content/html/document/src/ImageDocument.cpp
+++ b/content/html/document/src/ImageDocument.cpp
@@ -708,18 +708,18 @@ ImageDocument::UpdateTitleAndCharset()
                                         formatString, 1,
                                         getter_Copies(status));
   }
 
   static const char* const formatNames[4] = 
   {
     "ImageTitleWithNeitherDimensionsNorFile",
     "ImageTitleWithoutDimensions",
-    "ImageTitleWithDimensions",
-    "ImageTitleWithDimensionsAndFile",
+    "ImageTitleWithDimensions2",
+    "ImageTitleWithDimensions2AndFile",
   };
 
   MediaDocument::UpdateTitleAndCharset(typeStr, formatNames,
                                        mImageWidth, mImageHeight, status);
 }
 
 void
 ImageDocument::ResetZoomLevel()
--- a/content/html/document/src/MediaDocument.h
+++ b/content/html/document/src/MediaDocument.h
@@ -47,17 +47,17 @@ protected:
 
   nsresult LinkStylesheet(const nsAString& aStylesheet);
 
   // |aFormatNames[]| needs to have four elements in the following order: 
   // a format name with neither dimension nor file, a format name with
   // filename but w/o dimension, a format name with dimension but w/o filename,
   // a format name with both of them.  For instance, it can have
   // "ImageTitleWithNeitherDimensionsNorFile", "ImageTitleWithoutDimensions",
-  // "ImageTitleWithDimesions",  "ImageTitleWithDimensionsAndFile".
+  // "ImageTitleWithDimesions2",  "ImageTitleWithDimensions2AndFile".
   //
   // Also see MediaDocument.properties if you want to define format names
   // for a new subclass. aWidth and aHeight are pixels for |ImageDocument|,
   // but could be in other units for other 'media', in which case you have to 
   // define format names accordingly. 
   void UpdateTitleAndCharset(const nsACString&  aTypeStr,
                              const char* const* aFormatNames = sFormatNames,
                              int32_t            aWidth = 0,
--- a/dom/bluetooth/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/BluetoothHfpManager.cpp
@@ -641,17 +641,20 @@ BluetoothHfpManager::HandleIccInfoChange
 {
   nsCOMPtr<nsIIccProvider> icc =
     do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
   NS_ENSURE_TRUE_VOID(icc);
 
   nsCOMPtr<nsIDOMMozIccInfo> iccInfo;
   icc->GetIccInfo(getter_AddRefs(iccInfo));
   NS_ENSURE_TRUE_VOID(iccInfo);
-  iccInfo->GetMsisdn(mMsisdn);
+
+  nsCOMPtr<nsIDOMMozGsmIccInfo> gsmIccInfo = do_QueryInterface(iccInfo);
+  NS_ENSURE_TRUE_VOID(gsmIccInfo);
+  gsmIccInfo->GetMsisdn(mMsisdn);
 }
 
 void
 BluetoothHfpManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   sInShutdown = true;
   Disconnect();
--- a/dom/bluetooth/BluetoothService.cpp
+++ b/dom/bluetooth/BluetoothService.cpp
@@ -20,16 +20,17 @@
 #include "jsapi.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
 #include "mozilla/Util.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/ipc/UnixSocket.h"
+#include "mozilla/LazyIdleThread.h"
 #include "nsContentUtils.h"
 #include "nsCxPusher.h"
 #include "nsIObserverService.h"
 #include "nsISettingsService.h"
 #include "nsISystemMessagesInternal.h"
 #include "nsITimer.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOM.h"
@@ -49,16 +50,17 @@
 #endif
 
 #define MOZSETTINGS_CHANGED_ID      "mozsettings-changed"
 #define BLUETOOTH_ENABLED_SETTING   "bluetooth.enabled"
 #define BLUETOOTH_DEBUGGING_SETTING "bluetooth.debugging.enabled"
 
 #define PROP_BLUETOOTH_ENABLED      "bluetooth.isEnabled"
 
+#define DEFAULT_THREAD_TIMEOUT_MS 3000
 #define DEFAULT_SHUTDOWN_TIMER_MS 5000
 
 bool gBluetoothDebugFlag = false;
 
 using namespace mozilla;
 using namespace mozilla::dom;
 USING_BLUETOOTH_NAMESPACE
 
@@ -140,29 +142,18 @@ public:
       } else {
         signalName = NS_LITERAL_STRING("Disabled");
       }
       signalPath = NS_LITERAL_STRING(KEY_MANAGER);
       BluetoothSignal signal(signalName, signalPath, v);
       gBluetoothService->DistributeSignal(signal);
     }
 
-    if (!mEnabled || gInShutdown) {
-      // Shut down the command thread if it still exists.
-      if (gBluetoothService->mBluetoothCommandThread) {
-        nsCOMPtr<nsIThread> thread;
-        gBluetoothService->mBluetoothCommandThread.swap(thread);
-        if (NS_FAILED(thread->Shutdown())) {
-          NS_WARNING("Failed to shut down the bluetooth command thread!");
-        }
-      }
-
-      if (gInShutdown) {
-        gBluetoothService = nullptr;
-      }
+    if (gInShutdown) {
+      gBluetoothService = nullptr;
     }
 
     return NS_OK;
   }
 
 private:
   bool mEnabled;
 };
@@ -456,42 +447,39 @@ BluetoothService::StartStopBluetooth(boo
 
   if (gInShutdown) {
     if (aStart) {
       // Don't try to start if we're already shutting down.
       MOZ_ASSERT(false, "Start called while in shutdown!");
       return NS_ERROR_FAILURE;
     }
 
-    if (!mBluetoothCommandThread) {
+    if (!mBluetoothThread) {
       // Don't create a new thread after we've begun shutdown since bluetooth
       // can't be running.
       return NS_OK;
     }
   }
 
-  nsresult rv;
-  if (!mBluetoothCommandThread) {
-    MOZ_ASSERT(!gInShutdown);
-
-    rv = NS_NewNamedThread("BluetoothCmd",
-                           getter_AddRefs(mBluetoothCommandThread));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
   if (!aStart) {
     BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
     hfp->Disconnect();
 
     BluetoothOppManager* opp = BluetoothOppManager::Get();
     opp->Disconnect();
   }
 
+  if (!mBluetoothThread) {
+    mBluetoothThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
+                                          NS_LITERAL_CSTRING("Bluetooth"),
+                                          LazyIdleThread::ManualShutdown);
+  }
+
   nsCOMPtr<nsIRunnable> runnable = new ToggleBtTask(aStart, aIsStartup);
-  rv = mBluetoothCommandThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+  nsresult rv = mBluetoothThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 void
 BluetoothService::SetEnabled(bool aEnabled)
 {
@@ -809,18 +797,8 @@ BluetoothService::Notify(const Bluetooth
   nsCOMPtr<nsISystemMessagesInternal> systemMessenger =
     do_GetService("@mozilla.org/system-message-internal;1");
   NS_ENSURE_TRUE_VOID(systemMessenger);
 
   systemMessenger->BroadcastMessage(type,
                                     OBJECT_TO_JSVAL(obj),
                                     JS::UndefinedValue());
 }
-
-void
-BluetoothService::DispatchToCommandThread(nsRunnable* aRunnable)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aRunnable);
-  MOZ_ASSERT(mBluetoothCommandThread);
-
-  mBluetoothCommandThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
-}
--- a/dom/bluetooth/BluetoothService.h
+++ b/dom/bluetooth/BluetoothService.h
@@ -42,48 +42,48 @@ class BluetoothService : public nsIObser
 
   class StartupTask;
   friend class StartupTask;
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
-  /** 
+  /**
    * Add a message handler object from message distribution observer.
    * Must be called from the main thread.
    *
    * @param aNodeName Node name of the object
    * @param aMsgHandler Weak pointer to the object
    */
   virtual void
   RegisterBluetoothSignalHandler(const nsAString& aNodeName,
                                  BluetoothSignalObserver* aMsgHandler);
 
-  /** 
+  /**
    * Remove a message handler object from message distribution observer.
    * Must be called from the main thread.
    *
    * @param aNodeName Node name of the object
    * @param aMsgHandler Weak pointer to the object
    */
   virtual void
   UnregisterBluetoothSignalHandler(const nsAString& aNodeName,
                                    BluetoothSignalObserver* aMsgHandler);
 
-  /** 
+  /**
    * Remove a message handlers for the given observer.
    * Must be called from the main thread.
    *
    * @param aMsgHandler Weak pointer to the object
    */
   void
   UnregisterAllSignalHandlers(BluetoothSignalObserver* aMsgHandler);
 
-  /** 
+  /**
    * Distribute a signal to the observer list
    *
    * @param aSignal Signal object to distribute
    *
    * @return NS_OK if signal distributed, NS_ERROR_FAILURE on error
    */
   void
   DistributeSignal(const BluetoothSignal& aEvent);
@@ -145,17 +145,17 @@ public:
   /**
    * Stop device discovery (platform specific implementation)
    *
    * @return NS_OK if discovery stopped correctly, false otherwise
    */
   virtual nsresult
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
 
-  /** 
+  /**
    * Start device discovery (platform specific implementation)
    *
    * @return NS_OK if discovery stopped correctly, false otherwise
    */
   virtual nsresult
   StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
@@ -302,19 +302,16 @@ public:
   }
 
   bool
   IsToggling() const;
 
   void
   RemoveObserverFromTable(const nsAString& key);
 
-  void
-  DispatchToCommandThread(nsRunnable* aRunnable);
-
 protected:
   BluetoothService()
   : mEnabled(false)
   {
     mBluetoothSignalObserverTable.Init();
   }
 
   virtual ~BluetoothService();
@@ -323,17 +320,17 @@ protected:
   Init();
 
   void
   Cleanup();
 
   nsresult
   StartStopBluetooth(bool aStart, bool aIsStartup);
 
-  /** 
+  /**
    * Platform specific startup functions go here. Usually deals with member
    * variables, so not static. Guaranteed to be called outside of main thread.
    *
    * @return NS_OK on correct startup, NS_ERROR_FAILURE otherwise
    */
   virtual nsresult
   StartInternal() = 0;
 
@@ -382,33 +379,28 @@ protected:
   // Called by ToggleBtAck.
   void
   SetEnabled(bool aEnabled);
 
   // Called by Get().
   static BluetoothService*
   Create();
 
-  /**
-   * Due to the fact that some operations require multiple calls, a
-   * CommandThread is created that can run blocking, platform-specific calls
-   * where either no asynchronous equivilent exists, or else where multiple
-   * asynchronous calls would require excessive runnable bouncing between main
-   * thread and IO thread.
-   *
-   * For instance, when we retrieve an Adapter object, we would like it to come
-   * with all of its properties filled in and registered as an agent, which
-   * requires a minimum of 3 calls to platform specific code on some platforms.
-   *
-   */
-  nsCOMPtr<nsIThread> mBluetoothCommandThread;
-
   typedef nsClassHashtable<nsStringHashKey, BluetoothSignalObserverList >
   BluetoothSignalObserverTable;
 
   BluetoothSignalObserverTable mBluetoothSignalObserverTable;
 
   bool mEnabled;
+
+private:
+  /**
+   * Due to the fact that the startup and shutdown of the Bluetooth system
+   * can take an indefinite amount of time, a command thread is created
+   * that can run blocking calls. The thread is not intended for regular
+   * Bluetooth operations though.
+   */
+  nsCOMPtr<nsIThread> mBluetoothThread;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/browser-element/BrowserElementPanning.js
+++ b/dom/browser-element/BrowserElementPanning.js
@@ -178,16 +178,22 @@ const ContentPanning = {
       KineticPanning.stop();
 
       if (oldTarget && oldTarget == this.target)
         this.preventNextClick = true;
     }
 
     this.position.set(screenX, screenY);
     KineticPanning.record(new Point(0, 0), evt.timeStamp);
+
+    // We prevent start events to avoid sending a focus event at the end of this
+    // touch series. See bug 889717.
+    if (this.panning || this.preventNextClick) {
+      evt.preventDefault();
+    }
   },
 
   onTouchEnd: function cp_onTouchEnd(evt) {
     let touch = null;
     if (!this.dragging ||
         (this.watchedEventsType == 'touch' &&
          !(touch = this.findPrimaryPointer(evt.changedTouches)))) {
       return;
@@ -209,20 +215,25 @@ const ContentPanning = {
     // same element.
     if (this.hybridEvents) {
       let target =
         content.document.elementFromPoint(touch.clientX, touch.clientY);
       click |= (target === this.pointerDownTarget);
     }
 
     if (this.target && click && (this.panning || this.preventNextClick)) {
-      let target = this.target;
-      let view = target.ownerDocument ? target.ownerDocument.defaultView
-                                      : target;
-      view.addEventListener('click', this, true, true);
+      if (this.hybridEvents) {
+        let target = this.target;
+        let view = target.ownerDocument ? target.ownerDocument.defaultView
+                                        : target;
+        view.addEventListener('click', this, true, true);
+      } else {
+        // We prevent end events to avoid sending a focus event. See bug 889717.
+        evt.preventDefault();
+      }
     }
 
     this._finishPanning();
 
     // Now that we're done, avoid entraining the thing we just panned.
     this.pointerDownTarget = null;
   },
 
--- a/dom/icc/interfaces/nsIDOMIccInfo.idl
+++ b/dom/icc/interfaces/nsIDOMIccInfo.idl
@@ -1,18 +1,25 @@
 /* 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/. */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(a45c0fe0-c911-11e2-8b8b-0800200c9a66)]
+[scriptable, uuid(dd9f229c-e5a6-453a-8388-950af0ff9918)]
 interface nsIDOMMozIccInfo : nsISupports
 {
-   /**
+  /**
+   * Integrated Circuit Card Type.
+   *
+   * Possible values: "sim", "usim", "ruim".
+   */
+  readonly attribute DOMString iccType;
+
+  /**
    * Integrated Circuit Card Identifier.
    */
   readonly attribute DOMString iccid;
 
   /**
    * Mobile Country Code (MCC) of the subscriber's home network.
    */
   readonly attribute DOMString mcc;
@@ -31,15 +38,33 @@ interface nsIDOMMozIccInfo : nsISupports
    * Network name must be a part of displayed carrier name.
    */
   readonly attribute boolean isDisplayNetworkNameRequired;
 
   /**
    * Service provider name must be a part of displayed carrier name.
    */
   readonly attribute boolean isDisplaySpnRequired;
+};
 
+[scriptable, uuid(3c237e39-7af3-4748-baf4-4a3b6c3e0e66)]
+interface nsIDOMMozGsmIccInfo : nsIDOMMozIccInfo
+{
   /**
-   * Mobile Station ISDN Number (MSISDN) of the subscriber's, aka
+   * Mobile Station ISDN Number (MSISDN) of the subscriber, aka
    * his phone number.
    */
   readonly attribute DOMString msisdn;
 };
+
+[scriptable, uuid(013e973e-8b56-4525-b634-d23166b86edb)]
+interface nsIDOMMozCdmaIccInfo : nsIDOMMozIccInfo
+{
+  /**
+   * Mobile Directory Number (MDN) of the subscriber, aka his phone number.
+   */
+  readonly attribute DOMString mdn;
+
+  /**
+   * Mobile Identification Number (MIN) of the subscriber.
+   */
+  readonly attribute DOMString min;
+};
--- a/dom/icc/interfaces/nsIDOMIccManager.idl
+++ b/dom/icc/interfaces/nsIDOMIccManager.idl
@@ -275,16 +275,18 @@ interface nsIDOMMozIccManager : nsIDOMEv
    * ICC.
    */
   [implicit_jscontext] attribute jsval onstksessionend;
 
   // UICC Card Information.
 
   /**
    * Information stored in the device's ICC card.
+   *
+   * Null if the card is not detected.
    */
   readonly attribute nsIDOMMozIccInfo iccInfo;
 
   /**
    * The 'iccinfochange' event is notified whenever the icc info object
    * changes.
    */
   [implicit_jscontext] attribute jsval oniccinfochange;
--- a/dom/icc/tests/marionette/test_icc_info.js
+++ b/dom/icc/tests/marionette/test_icc_info.js
@@ -12,38 +12,32 @@ let icc;
 let iccInfo;
 ifr.onload = function() {
   icc = ifr.contentWindow.navigator.mozIccManager;
   ok(icc instanceof ifr.contentWindow.MozIccManager,
      "icc is instanceof " + icc.constructor);
 
   iccInfo = icc.iccInfo;
 
+  is(iccInfo.iccType, "sim");
+
   // The emulator's hard coded iccid value.
   // See it here {B2G_HOME}/external/qemu/telephony/sim_card.c#L299.
   is(iccInfo.iccid, 89014103211118510720);
 
   // The emulator's hard coded mcc and mnc codes.
   // See it here {B2G_HOME}/external/qemu/telephony/android_modem.c#L2465.
   is(iccInfo.mcc, 310);
   is(iccInfo.mnc, 260);
   is(iccInfo.spn, "Android");
   // Phone number is hardcoded in MSISDN
   // See {B2G_HOME}/external/qemu/telephony/sim_card.c, in asimcard_io()
   is(iccInfo.msisdn, "15555215554");
 
-  testDisplayConditionChange(testSPN, [
-    // [MCC, MNC, isDisplayNetworkNameRequired, isDisplaySpnRequired]
-    [123, 456, false, true], // Not in HPLMN.
-    [234, 136,  true, true], // Not in HPLMN, but in PLMN specified in SPDI.
-    [123, 456, false, true], // Not in HPLMN. Triggering iccinfochange
-    [466,  92,  true, true], // Not in HPLMN, but in another PLMN specified in SPDI.
-    [123, 456, false, true], // Not in HPLMN. Triggering iccinfochange
-    [310, 260,  true, true], // inside HPLMN.
-  ], finalize);
+  runNextTest();
 };
 document.body.appendChild(ifr);
 
 let emulatorCmdPendingCount = 0;
 function sendEmulatorCommand(cmd, callback) {
   emulatorCmdPendingCount++;
   runEmulatorCmd(cmd, function (result) {
     emulatorCmdPendingCount--;
@@ -55,26 +49,51 @@ function sendEmulatorCommand(cmd, callba
 function setEmulatorMccMnc(mcc, mnc) {
   let cmd = "operator set 0 Android,Android," + mcc + mnc;
   sendEmulatorCommand(cmd, function (result) {
     let re = new RegExp("" + mcc + mnc + "$");
     ok(result[0].match(re), "MCC/MNC should be changed.");
   });
 }
 
+function setAirplaneModeEnabled(enabled) {
+  let settings = ifr.contentWindow.navigator.mozSettings;
+  let setLock = settings.createLock();
+  let obj = {
+    "ril.radio.disabled": enabled
+  };
+  let setReq = setLock.set(obj);
+
+  log("set airplane mode to " + enabled);
+
+  setReq.addEventListener("success", function onSetSuccess() {
+    log("set 'ril.radio.disabled' to " + enabled);
+  });
+
+  setReq.addEventListener("error", function onSetError() {
+    ok(false, "cannot set 'ril.radio.disabled' to " + enabled);
+  });
+}
+
 function waitForIccInfoChange(callback) {
   icc.addEventListener("iccinfochange", function handler() {
     icc.removeEventListener("iccinfochange", handler);
     callback();
   });
 }
 
-function finalize() {
-  SpecialPowers.removePermission("mobileconnection", document);
-  finish();
+function waitForCardStateChange(expectedCardState, callback) {
+  icc.addEventListener("cardstatechange", function oncardstatechange() {
+    log("card state changes to " + icc.cardState);
+    if (icc.cardState === expectedCardState) {
+      log("got expected card state: " + icc.cardState);
+      icc.removeEventListener("cardstatechange", oncardstatechange);
+      callback();
+    }
+  });
 }
 
 // Test display condition change.
 function testDisplayConditionChange(func, caseArray, oncomplete) {
   (function do_call(index) {
     let next = index < (caseArray.length - 1) ? do_call.bind(null, index + 1) : oncomplete;
     caseArray[index].push(next);
     func.apply(null, caseArray[index]);
@@ -88,8 +107,50 @@ function testSPN(mcc, mnc, expectedIsDis
        expectedIsDisplayNetworkNameRequired);
     is(iccInfo.isDisplaySpnRequired,
        expectedIsDisplaySpnRequired);
     // operatorchange will be ignored if we send commands too soon.
     window.setTimeout(callback, 100);
   });
   setEmulatorMccMnc(mcc, mnc);
 }
+
+// Test iccInfo when card is not ready
+function testCardIsNotReady() {
+  // Enable airplane mode
+  setAirplaneModeEnabled(true);
+
+  waitForCardStateChange(null, function callback() {
+    is(icc.iccInfo, null);
+
+    // Disable airplane mode
+    setAirplaneModeEnabled(false);
+    waitForCardStateChange("ready", runNextTest);
+  });
+}
+
+let tests = [
+  testDisplayConditionChange.bind(this, testSPN, [
+    // [MCC, MNC, isDisplayNetworkNameRequired, isDisplaySpnRequired]
+    [123, 456, false, true], // Not in HPLMN.
+    [234, 136,  true, true], // Not in HPLMN, but in PLMN specified in SPDI.
+    [123, 456, false, true], // Not in HPLMN. Triggering iccinfochange
+    [466,  92,  true, true], // Not in HPLMN, but in another PLMN specified in SPDI.
+    [123, 456, false, true], // Not in HPLMN. Triggering iccinfochange
+    [310, 260,  true, true], // inside HPLMN.
+  ], runNextTest),
+  testCardIsNotReady
+];
+
+function runNextTest() {
+  let test = tests.shift();
+  if (!test) {
+    finalize();
+    return;
+  }
+
+  test();
+}
+
+function finalize() {
+  SpecialPowers.removePermission("mobileconnection", document);
+  finish();
+}
--- a/dom/locales/en-US/chrome/layout/MediaDocument.properties
+++ b/dom/locales/en-US/chrome/layout/MediaDocument.properties
@@ -1,21 +1,21 @@
 # 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/.
 
-#LOCALIZATION NOTE (ImageTitleWithDimensionsAndFile): first %S is filename, second %S is type, third %S is width and fourth %S is height
+#LOCALIZATION NOTE (ImageTitleWithDimensions2AndFile): first %S is filename, second %S is type, third %S is width and fourth %S is height
 #LOCALIZATION NOTE (ImageTitleWithoutDimensions): first %S is filename, second %S is type
-#LOCALIZATION NOTE (ImageTitleWithDimensions): first %S is type, second %S is width and third %S is height
+#LOCALIZATION NOTE (ImageTitleWithDimensions2): first %S is type, second %S is width and third %S is height
 #LOCALIZATION NOTE (ImageTitleWithNeitherDimensionsNorFile): first %S is type
 #LOCALIZATION NOTE (MediaTitleWithFile): first %S is filename, second %S is type
 #LOCALIZATION NOTE (MediaTitleWithNoInfo): first %S is type
-ImageTitleWithDimensionsAndFile=%S (%S Image, %S\u00A0\u00D7\u00A0%S pixels)
+ImageTitleWithDimensions2AndFile=%S (%S Image, %S\u00A0\u00D7\u00A0%S pixels)
 ImageTitleWithoutDimensions=%S (%S Image)
-ImageTitleWithDimensions=(%S Image, %S\u00A0\u00D7\u00A0%S pixels)
+ImageTitleWithDimensions2=(%S Image, %S\u00A0\u00D7\u00A0%S pixels)
 ImageTitleWithNeitherDimensionsNorFile=(%S Image)
 MediaTitleWithFile=%S (%S Object)
 MediaTitleWithNoInfo=(%S Object)
 
 InvalidImage=The image \u201c%S\u201d cannot be displayed because it contains errors.
 ScaledImage=Scaled (%S%%)
 
 TitleWithStatus=%S - %S
--- a/dom/mobilemessage/interfaces/nsIDOMMobileMessageManager.idl
+++ b/dom/mobilemessage/interfaces/nsIDOMMobileMessageManager.idl
@@ -6,20 +6,20 @@
 
 interface nsIDOMEventListener;
 interface nsIDOMMozSmsFilter;
 interface nsIDOMMozSmsSegmentInfo;
 interface nsIDOMDOMCursor;
 interface nsIDOMDOMRequest;
 interface nsIDOMBlob;
 
-[scriptable, builtinclass, uuid(efff5276-0f3f-4137-9b16-66e894400e01)]
+[scriptable, builtinclass, uuid(3f81dcbc-00cf-11e3-ae66-538115636543)]
 interface nsIDOMMozMobileMessageManager : nsIDOMEventTarget
 {
-  nsIDOMMozSmsSegmentInfo getSegmentInfoForText(in DOMString text);
+  nsIDOMDOMRequest getSegmentInfoForText(in DOMString text);
 
   // The first parameter can be either a DOMString (only one number) or an array
   // of DOMStrings.
   // The method returns a DOMRequest object if one number has been passed.
   // An array of DOMRequest objects otherwise.
   jsval send(in jsval number, in DOMString message);
 
   nsIDOMDOMRequest sendMMS(in jsval parameters /* MmsParameters */);
--- a/dom/mobilemessage/interfaces/nsIMobileMessageCallback.idl
+++ b/dom/mobilemessage/interfaces/nsIMobileMessageCallback.idl
@@ -1,24 +1,25 @@
 /* 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/. */
 
 #include "nsISupports.idl"
+#include "nsIDOMSmsSegmentInfo.idl"
 
 dictionary SmsThreadListItem
 {
   unsigned long long id;
   DOMString senderOrReceiver;
   unsigned long long timestamp;
   DOMString body;
   unsigned long long unreadCount;
 };
 
-[scriptable, uuid(ea5fb581-bee7-40a6-b2dc-c98b99a2dc49)]
+[scriptable, uuid(399125a8-00d2-11e3-8d12-3fba4465c097)]
 interface nsIMobileMessageCallback : nsISupports
 {
   /**
    * All SMS related errors.
    * Make sure to keep this list in sync with the list in:
    * embedding/android/GeckoSmsManager.java
    */
   const unsigned short SUCCESS_NO_ERROR          = 0;
@@ -43,9 +44,12 @@ interface nsIMobileMessageCallback : nsI
   void notifyGetMessageFailed(in long error);
 
   void notifyMessageDeleted([array, size_is(count)] in boolean deleted,
                             in uint32_t count);
   void notifyDeleteMessageFailed(in long error);
 
   void notifyMessageMarkedRead(in boolean read);
   void notifyMarkMessageReadFailed(in long error);
+
+  void notifySegmentInfoForTextGot(in nsIDOMMozSmsSegmentInfo info);
+  void notifyGetSegmentInfoForTextFailed(in long error);
 };
--- a/dom/mobilemessage/interfaces/nsISmsService.idl
+++ b/dom/mobilemessage/interfaces/nsISmsService.idl
@@ -8,22 +8,23 @@ interface nsIDOMMozSmsMessage;
 interface nsIDOMMozSmsSegmentInfo;
 interface nsIMobileMessageCallback;
 
 %{C++
 #define SMS_SERVICE_CID { 0xbada3cb8, 0xa568, 0x4dff, { 0xb5, 0x43, 0x52, 0xbb, 0xb3, 0x14, 0x31, 0x21 } }
 #define SMS_SERVICE_CONTRACTID "@mozilla.org/sms/smsservice;1"
 %}
 
-[scriptable, builtinclass, uuid(f0d5d11b-0326-4cb1-bb76-a3f912212287)]
+[scriptable, builtinclass, uuid(0f3f75ec-00dd-11e3-87ac-0b1d5c79afdf)]
 interface nsISmsService : nsISupports
 {
   boolean hasSupport();
 
-  nsIDOMMozSmsSegmentInfo getSegmentInfoForText(in DOMString text);
+  void getSegmentInfoForText(in DOMString text,
+                             in nsIMobileMessageCallback request);
 
   void send(in DOMString number,
             in DOMString message,
             in boolean silent,
             in nsIMobileMessageCallback request);
 
   boolean isSilentNumber(in DOMString number);
   void addSilentNumber(in DOMString number);
--- a/dom/mobilemessage/src/MobileMessageCallback.cpp
+++ b/dom/mobilemessage/src/MobileMessageCallback.cpp
@@ -3,16 +3,17 @@
  * 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/. */
 
 #include "MobileMessageCallback.h"
 #include "nsContentUtils.h"
 #include "nsCxPusher.h"
 #include "nsIDOMMozSmsMessage.h"
 #include "nsIDOMMozMmsMessage.h"
+#include "nsIDOMSmsSegmentInfo.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsPIDOMWindow.h"
 #include "MmsMessage.h"
 #include "jsapi.h"
 #include "xpcpublic.h"
 #include "nsServiceManagerUtils.h"
 #include "nsTArrayHelpers.h"
 
@@ -34,24 +35,32 @@ MobileMessageCallback::MobileMessageCall
 }
 
 MobileMessageCallback::~MobileMessageCallback()
 {
 }
 
 
 nsresult
-MobileMessageCallback::NotifySuccess(JS::Handle<JS::Value> aResult)
+MobileMessageCallback::NotifySuccess(JS::Handle<JS::Value> aResult, bool aAsync)
 {
+  if (aAsync) {
+    nsCOMPtr<nsIDOMRequestService> rs =
+      do_GetService(DOMREQUEST_SERVICE_CONTRACTID);
+    NS_ENSURE_TRUE(rs, NS_ERROR_FAILURE);
+
+    return rs->FireSuccessAsync(mDOMRequest, aResult);
+  }
+
   mDOMRequest->FireSuccess(aResult);
   return NS_OK;
 }
 
 nsresult
-MobileMessageCallback::NotifySuccess(nsISupports *aMessage)
+MobileMessageCallback::NotifySuccess(nsISupports *aMessage, bool aAsync)
 {
   nsresult rv;
   nsIScriptContext* scriptContext = mDOMRequest->GetContextForEventHandlers(&rv);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(scriptContext, NS_ERROR_FAILURE);
 
   AutoPushJSContext cx(scriptContext->GetNativeContext());
   NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE);
@@ -61,48 +70,58 @@ MobileMessageCallback::NotifySuccess(nsI
 
   JSAutoCompartment ac(cx, global);
 
   JS::Rooted<JS::Value> wrappedMessage(cx);
   rv = nsContentUtils::WrapNative(cx, global, aMessage,
                                   wrappedMessage.address());
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return NotifySuccess(wrappedMessage);
+  return NotifySuccess(wrappedMessage, aAsync);
 }
 
 nsresult
-MobileMessageCallback::NotifyError(int32_t aError)
+MobileMessageCallback::NotifyError(int32_t aError, bool aAsync)
 {
+  nsAutoString errorStr;
   switch (aError) {
     case nsIMobileMessageCallback::NO_SIGNAL_ERROR:
-      mDOMRequest->FireError(NS_LITERAL_STRING("NoSignalError"));
+      errorStr = NS_LITERAL_STRING("NoSignalError");
       break;
     case nsIMobileMessageCallback::NOT_FOUND_ERROR:
-      mDOMRequest->FireError(NS_LITERAL_STRING("NotFoundError"));
+      errorStr = NS_LITERAL_STRING("NotFoundError");
       break;
     case nsIMobileMessageCallback::UNKNOWN_ERROR:
-      mDOMRequest->FireError(NS_LITERAL_STRING("UnknownError"));
+      errorStr = NS_LITERAL_STRING("UnknownError");
       break;
     case nsIMobileMessageCallback::INTERNAL_ERROR:
-      mDOMRequest->FireError(NS_LITERAL_STRING("InternalError"));
+      errorStr = NS_LITERAL_STRING("InternalError");
       break;
     case nsIMobileMessageCallback::NO_SIM_CARD_ERROR:
-      mDOMRequest->FireError(NS_LITERAL_STRING("NoSimCardError"));
+      errorStr = NS_LITERAL_STRING("NoSimCardError");
       break;
     case nsIMobileMessageCallback::RADIO_DISABLED_ERROR:
-      mDOMRequest->FireError(NS_LITERAL_STRING("RadioDisabledError"));
+      errorStr = NS_LITERAL_STRING("RadioDisabledError");
       break;
     case nsIMobileMessageCallback::INVALID_ADDRESS_ERROR:
-      mDOMRequest->FireError(NS_LITERAL_STRING("InvalidAddressError"));
+      errorStr = NS_LITERAL_STRING("InvalidAddressError");
       break;
     default: // SUCCESS_NO_ERROR is handled above.
       MOZ_CRASH("Should never get here!");
   }
 
+  if (aAsync) {
+    nsCOMPtr<nsIDOMRequestService> rs =
+      do_GetService(DOMREQUEST_SERVICE_CONTRACTID);
+    NS_ENSURE_TRUE(rs, NS_ERROR_FAILURE);
+
+    return rs->FireErrorAsync(mDOMRequest, errorStr);
+  }
+
+  mDOMRequest->FireError(errorStr);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MobileMessageCallback::NotifyMessageSent(nsISupports *aMessage)
 {
   return NotifySuccess(aMessage);
 }
@@ -168,11 +187,23 @@ MobileMessageCallback::NotifyMessageMark
 }
 
 NS_IMETHODIMP
 MobileMessageCallback::NotifyMarkMessageReadFailed(int32_t aError)
 {
   return NotifyError(aError);
 }
 
+NS_IMETHODIMP
+MobileMessageCallback::NotifySegmentInfoForTextGot(nsIDOMMozSmsSegmentInfo *aInfo)
+{
+  return NotifySuccess(aInfo, true);
+}
+
+NS_IMETHODIMP
+MobileMessageCallback::NotifyGetSegmentInfoForTextFailed(int32_t aError)
+{
+  return NotifyError(aError, true);
+}
+
 } // namesapce mobilemessage
 } // namespace dom
 } // namespace mozilla
--- a/dom/mobilemessage/src/MobileMessageCallback.h
+++ b/dom/mobilemessage/src/MobileMessageCallback.h
@@ -24,18 +24,18 @@ public:
 
   MobileMessageCallback(DOMRequest* aDOMRequest);
 
 private:
   ~MobileMessageCallback();
 
   nsRefPtr<DOMRequest> mDOMRequest;
 
-  nsresult NotifySuccess(JS::Handle<JS::Value> aResult);
-  nsresult NotifySuccess(nsISupports *aMessage);
-  nsresult NotifyError(int32_t aError);
+  nsresult NotifySuccess(JS::Handle<JS::Value> aResult, bool aAsync = false);
+  nsresult NotifySuccess(nsISupports *aMessage, bool aAsync = false);
+  nsresult NotifyError(int32_t aError, bool aAsync = false);
 };
 
 } // namespace mobilemessage
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_mobilemessage_MobileMessageCallback_h
--- a/dom/mobilemessage/src/MobileMessageManager.cpp
+++ b/dom/mobilemessage/src/MobileMessageManager.cpp
@@ -96,22 +96,29 @@ MobileMessageManager::Shutdown()
   obs->RemoveObserver(this, kSmsSentObserverTopic);
   obs->RemoveObserver(this, kSmsFailedObserverTopic);
   obs->RemoveObserver(this, kSmsDeliverySuccessObserverTopic);
   obs->RemoveObserver(this, kSmsDeliveryErrorObserverTopic);
 }
 
 NS_IMETHODIMP
 MobileMessageManager::GetSegmentInfoForText(const nsAString& aText,
-                                            nsIDOMMozSmsSegmentInfo** aResult)
+                                            nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(smsService, NS_ERROR_FAILURE);
 
-  return smsService->GetSegmentInfoForText(aText, aResult);
+  nsRefPtr<DOMRequest> request = new DOMRequest(GetOwner());
+  nsCOMPtr<nsIMobileMessageCallback> msgCallback =
+    new MobileMessageCallback(request);
+  nsresult rv = smsService->GetSegmentInfoForText(aText, msgCallback);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  request.forget(aRequest);
+  return NS_OK;
 }
 
 nsresult
 MobileMessageManager::Send(JSContext* aCx, JS::Handle<JSObject*> aGlobal,
                            JS::Handle<JSString*> aNumber,
                            const nsAString& aMessage, JS::Value* aRequest)
 {
   nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
--- a/dom/mobilemessage/src/SmsSegmentInfo.cpp
+++ b/dom/mobilemessage/src/SmsSegmentInfo.cpp
@@ -50,10 +50,16 @@ SmsSegmentInfo::GetCharsPerSegment(int32
 
 NS_IMETHODIMP
 SmsSegmentInfo::GetCharsAvailableInLastSegment(int32_t* aCharsAvailableInLastSegment)
 {
   *aCharsAvailableInLastSegment = mData.charsAvailableInLastSegment();
   return NS_OK;
 }
 
+const SmsSegmentInfoData&
+SmsSegmentInfo::GetData() const
+{
+  return mData;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/mobilemessage/src/SmsSegmentInfo.h
+++ b/dom/mobilemessage/src/SmsSegmentInfo.h
@@ -17,18 +17,21 @@ class SmsSegmentInfo MOZ_FINAL : public 
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMMOZSMSSEGMENTINFO
 
   SmsSegmentInfo(int32_t aSegments,
                  int32_t aCharsPerSegment,
                  int32_t aCharsAvailableInLastSegment);
+
   SmsSegmentInfo(const mobilemessage::SmsSegmentInfoData& aData);
 
+  const mobilemessage::SmsSegmentInfoData& GetData() const;
+
 private:
   mobilemessage::SmsSegmentInfoData mData;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_mobilemessage_SmsSegmentInfo_h
--- a/dom/mobilemessage/src/android/SmsService.cpp
+++ b/dom/mobilemessage/src/android/SmsService.cpp
@@ -18,29 +18,26 @@ NS_IMPL_ISUPPORTS1(SmsService, nsISmsSer
 NS_IMETHODIMP
 SmsService::HasSupport(bool* aHasSupport)
 {
   *aHasSupport = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-SmsService::GetSegmentInfoForText(const nsAString & aText,
-                                  nsIDOMMozSmsSegmentInfo** aResult)
+SmsService::GetSegmentInfoForText(const nsAString& aText,
+                                  nsIMobileMessageCallback* aRequest)
 {
   if (!AndroidBridge::Bridge()) {
     return NS_ERROR_FAILURE;
   }
 
-  SmsSegmentInfoData data;
-  nsresult rv = AndroidBridge::Bridge()->GetSegmentInfoForText(aText, &data);
+  nsresult rv = AndroidBridge::Bridge()->GetSegmentInfoForText(aText, aRequest);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCOMPtr<nsIDOMMozSmsSegmentInfo> info = new SmsSegmentInfo(data);
-  info.forget(aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 SmsService::Send(const nsAString& aNumber,
                  const nsAString& aMessage,
                  const bool       aSilent,
                  nsIMobileMessageCallback* aRequest)
--- a/dom/mobilemessage/src/fallback/SmsService.cpp
+++ b/dom/mobilemessage/src/fallback/SmsService.cpp
@@ -17,18 +17,18 @@ NS_IMPL_ISUPPORTS1(SmsService, nsISmsSer
 NS_IMETHODIMP
 SmsService::HasSupport(bool* aHasSupport)
 {
   *aHasSupport = false;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-SmsService::GetSegmentInfoForText(const nsAString & aText,
-                                  nsIDOMMozSmsSegmentInfo** aResult)
+SmsService::GetSegmentInfoForText(const nsAString& aText,
+                                  nsIMobileMessageCallback* aRequest)
 {
   NS_ERROR("We should not be here!");
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 SmsService::Send(const nsAString& aNumber,
                  const nsAString& aMessage,
--- a/dom/mobilemessage/src/gonk/MmsPduHelper.jsm
+++ b/dom/mobilemessage/src/gonk/MmsPduHelper.jsm
@@ -6,16 +6,18 @@
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 let WSP = {};
 Cu.import("resource://gre/modules/WspPduHelper.jsm", WSP);
 
 Cu.import("resource://gre/modules/mms_consts.js");
 
+Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
+
 let DEBUG; // set to true to see debug messages
 
 this.MMS_VERSION = (function () {
   Cu.import("resource://gre/modules/Services.jsm");
 
   try {
     return Services.prefs.getIntPref("dom.mms.version");
   } catch(ex) {}
@@ -173,29 +175,60 @@ this.Address = {
     }
 
     if (!str) {
       throw new WSP.CodeError("Address: invalid value: " + JSON.stringify(value));
     }
 
     EncodedStringValue.encode(data, str);
   },
+
+  /**
+   * @param address
+   *        Address string which want to find the type.
+   *
+   * @return Address type.
+   */
+  resolveType: function resolveType(address) {
+    if (address.match(this.REGEXP_EMAIL)) {
+      return "email";
+    }
+
+    if (address.match(this.REGEXP_IPV4)) {
+      return "IPv4";
+    }
+
+    if (address.match(this.REGEXP_IPV6)) {
+      return "IPv6";
+    }
+
+    let normalizedAddress = PhoneNumberUtils.normalize(address, false);
+    if (PhoneNumberUtils.isPlainPhoneNumber(normalizedAddress)) {
+      return "PLMN";
+    }
+
+    return "Others";
+  },
 };
 
 defineLazyRegExp(Address, "REGEXP_DECODE_PLMN",        "^(\\+?[\\d.-]+)\\/TYPE=(PLMN)$");
 defineLazyRegExp(Address, "REGEXP_DECODE_IPV4",        "^(\\d{1,3}(?:\\.\\d{1,3}){3})\\/TYPE=(IPv4)$");
 defineLazyRegExp(Address, "REGEXP_DECODE_IPV6",        "^([\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7})\\/TYPE=(IPv6)$");
 defineLazyRegExp(Address, "REGEXP_DECODE_CUSTOM",      "^([\\w\\+\\-.%]+)\\/TYPE=(\\w+)$");
 defineLazyRegExp(Address, "REGEXP_ENCODE_PLMN",        "^\\+?[\\d.-]+$");
 defineLazyRegExp(Address, "REGEXP_ENCODE_IPV4",        "^\\d{1,3}(?:\\.\\d{1,3}){3}$");
 defineLazyRegExp(Address, "REGEXP_ENCODE_IPV6",        "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$");
 defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_TYPE", "^\\w+$");
 defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_ADDR", "^[\\w\\+\\-.%]+$");
 defineLazyRegExp(Address, "REGEXP_NUM",                "^[\\+*#]\\d+$");
 defineLazyRegExp(Address, "REGEXP_ALPHANUM",           "^\\w+$");
+defineLazyRegExp(Address, "REGEXP_PLMN",               "^\\?[\\d.-]$");
+defineLazyRegExp(Address, "REGEXP_IPV4",               "^\\d{1,3}(?:\\.\\d{1,3}){3}$");
+defineLazyRegExp(Address, "REGEXP_IPV6",               "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$");
+defineLazyRegExp(Address, "REGEXP_EMAIL",              "@");
 
 /**
  * Header-field = MMS-header | Application-header
  *
  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2
  */
 this.HeaderField = {
   /**
--- a/dom/mobilemessage/src/gonk/MmsService.js
+++ b/dom/mobilemessage/src/gonk/MmsService.js
@@ -1682,28 +1682,32 @@ MmsService.prototype = {
     let smil = aParams.smil;
 
     // |aMessage.headers|
     let headers = aMessage["headers"] = {};
     let receivers = aParams.receivers;
     if (receivers.length != 0) {
       let headersTo = headers["to"] = [];
       for (let i = 0; i < receivers.length; i++) {
-        let normalizedAddress = PhoneNumberUtils.normalize(receivers[i], false);
-        if (DEBUG) debug("createSavableFromParams: normalize phone number " +
-                         "from " + receivers[i] + " to " + normalizedAddress);
-
-        headersTo.push({"address": normalizedAddress, "type": "PLMN"});
-
-        // Check if the address is valid to send MMS.
-        if (!PhoneNumberUtils.isPlainPhoneNumber(normalizedAddress)) {
-          if (DEBUG) debug("Error! Address is invalid to send MMS: " +
-                           normalizedAddress);
+        let receiver = receivers[i];
+        let type = MMS.Address.resolveType(receiver);
+        let address;
+        if (type == "PLMN") {
+          address = PhoneNumberUtils.normalize(receiver, false);
+          if (!PhoneNumberUtils.isPlainPhoneNumber(address)) {
+            isAddrValid = false;
+          }
+          if (DEBUG) debug("createSavableFromParams: normalize phone number " +
+                           "from " + receiver + " to " + address);
+        } else {
+          address = receiver;
           isAddrValid = false;
+          if (DEBUG) debug("Error! Address is invalid to send MMS: " + address);
         }
+        headersTo.push({"address": address, "type": type});
       }
     }
     if (aParams.subject) {
       headers["subject"] = aParams.subject;
     }
 
     // |aMessage.parts|
     let attachments = aParams.attachments;
--- a/dom/mobilemessage/src/gonk/SmsService.cpp
+++ b/dom/mobilemessage/src/gonk/SmsService.cpp
@@ -26,22 +26,22 @@ SmsService::SmsService()
 NS_IMETHODIMP
 SmsService::HasSupport(bool* aHasSupport)
 {
   *aHasSupport = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-SmsService::GetSegmentInfoForText(const nsAString & aText,
-                                  nsIDOMMozSmsSegmentInfo** aResult)
+SmsService::GetSegmentInfoForText(const nsAString& aText,
+                                  nsIMobileMessageCallback* aRequest)
 {
   NS_ENSURE_TRUE(mRadioInterface, NS_ERROR_FAILURE);
 
-  return mRadioInterface->GetSegmentInfoForText(aText, aResult);
+  return mRadioInterface->GetSegmentInfoForText(aText, aRequest);
 }
 
 NS_IMETHODIMP
 SmsService::Send(const nsAString& aNumber,
                  const nsAString& aMessage,
                  const bool       aSilent,
                  nsIMobileMessageCallback* aRequest)
 {
--- a/dom/mobilemessage/src/ipc/PSms.ipdl
+++ b/dom/mobilemessage/src/ipc/PSms.ipdl
@@ -57,27 +57,33 @@ struct CreateMessageCursorRequest
 };
 
 struct MarkMessageReadRequest
 {
   int32_t messageId;
   bool value;
 };
 
+struct GetSegmentInfoForTextRequest
+{
+  nsString text;
+};
+
 struct CreateThreadCursorRequest
 {
 };
 
 union IPCSmsRequest
 {
   SendMessageRequest;
   RetrieveMessageRequest;
   GetMessageRequest;
   DeleteMessageRequest;
   MarkMessageReadRequest;
+  GetSegmentInfoForTextRequest;
 };
 
 union IPCMobileMessageCursor
 {
   CreateMessageCursorRequest;
   CreateThreadCursorRequest;
 };
 
@@ -117,18 +123,15 @@ parent:
   /**
    * Sent when the child makes an asynchronous cursor to the parent.
    */
   PMobileMessageCursor(IPCMobileMessageCursor request);
 
   sync HasSupport()
       returns (bool aHasSupport);
 
-  sync GetSegmentInfoForText(nsString aText)
-      returns (SmsSegmentInfoData aResult);
-
   AddSilentNumber(nsString aNumber);
   RemoveSilentNumber(nsString aNumber);
 };
 
 } // namespace mobilemessage
 } // namespace dom
 } // namespace mozilla
--- a/dom/mobilemessage/src/ipc/PSmsRequest.ipdl
+++ b/dom/mobilemessage/src/ipc/PSmsRequest.ipdl
@@ -61,23 +61,35 @@ struct ReplyMarkeMessageRead
   bool read;
 };
 
 struct ReplyMarkeMessageReadFail
 {
   int32_t error;
 };
 
+struct ReplyGetSegmentInfoForText
+{
+  SmsSegmentInfoData infoData;
+};
+
+struct ReplyGetSegmentInfoForTextFail
+{
+  int32_t error;
+};
+
 union MessageReply
 {
   ReplyMessageSend;
   ReplyMessageSendFail;
   ReplyGetMessage;
   ReplyGetMessageFail;
   ReplyMessageDelete;
   ReplyMessageDeleteFail;
   ReplyMarkeMessageRead;
   ReplyMarkeMessageReadFail;
+  ReplyGetSegmentInfoForText;
+  ReplyGetSegmentInfoForTextFail;
 };
 
 } // namespace mobilemessage
 } // namespace dom
 } // namespace mozilla
--- a/dom/mobilemessage/src/ipc/SmsChild.cpp
+++ b/dom/mobilemessage/src/ipc/SmsChild.cpp
@@ -1,15 +1,16 @@
 /* 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/. */
 
 #include "SmsChild.h"
 #include "SmsMessage.h"
 #include "MmsMessage.h"
+#include "SmsSegmentInfo.h"
 #include "Constants.h"
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "mozilla/dom/ContentChild.h"
 #include "MobileMessageThread.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
@@ -17,17 +18,17 @@ using namespace mozilla::dom::mobilemess
 
 namespace {
 
 already_AddRefed<nsISupports>
 CreateMessageFromMessageData(const MobileMessageData& aData)
 {
   nsCOMPtr<nsISupports> message;
 
-  switch(aData. type()) {
+  switch(aData.type()) {
     case MobileMessageData::TMmsMessageData:
       message = new MmsMessage(aData.get_MmsMessageData());
       break;
     case MobileMessageData::TSmsMessageData:
       message = new SmsMessage(aData.get_SmsMessageData());
       break;
     default:
       MOZ_CRASH("Unexpected type of MobileMessageData");
@@ -198,16 +199,27 @@ SmsRequestChild::Recv__delete__(const Me
       mReplyRequest->NotifyDeleteMessageFailed(aReply.get_ReplyMessageDeleteFail().error());
       break;
     case MessageReply::TReplyMarkeMessageRead:
       mReplyRequest->NotifyMessageMarkedRead(aReply.get_ReplyMarkeMessageRead().read());
       break;
     case MessageReply::TReplyMarkeMessageReadFail:
       mReplyRequest->NotifyMarkMessageReadFailed(aReply.get_ReplyMarkeMessageReadFail().error());
       break;
+    case MessageReply::TReplyGetSegmentInfoForText: {
+        const SmsSegmentInfoData& data =
+          aReply.get_ReplyGetSegmentInfoForText().infoData();
+        nsCOMPtr<nsIDOMMozSmsSegmentInfo> info = new SmsSegmentInfo(data);
+        mReplyRequest->NotifySegmentInfoForTextGot(info);
+      }
+      break;
+    case MessageReply::TReplyGetSegmentInfoForTextFail:
+      mReplyRequest->NotifyGetSegmentInfoForTextFailed(
+        aReply.get_ReplyGetSegmentInfoForTextFail().error());
+      break;
     default:
       MOZ_CRASH("Received invalid response parameters!");
   }
 
   return true;
 }
 
 /*******************************************************************************
--- a/dom/mobilemessage/src/ipc/SmsIPCService.cpp
+++ b/dom/mobilemessage/src/ipc/SmsIPCService.cpp
@@ -91,29 +91,21 @@ SmsIPCService::HasSupport(bool* aHasSupp
   NS_ENSURE_TRUE(smsChild, NS_ERROR_FAILURE);
 
   smsChild->SendHasSupport(aHasSupport);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-SmsIPCService::GetSegmentInfoForText(const nsAString & aText,
-                                     nsIDOMMozSmsSegmentInfo** aResult)
+SmsIPCService::GetSegmentInfoForText(const nsAString& aText,
+                                     nsIMobileMessageCallback* aRequest)
 {
-  PSmsChild* smsChild = GetSmsChild();
-  NS_ENSURE_TRUE(smsChild, NS_ERROR_FAILURE);
-
-  SmsSegmentInfoData data;
-  bool ok = smsChild->SendGetSegmentInfoForText(nsString(aText), &data);
-  NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
-
-  nsCOMPtr<nsIDOMMozSmsSegmentInfo> info = new SmsSegmentInfo(data);
-  info.forget(aResult);
-  return NS_OK;
+  return SendRequest(GetSegmentInfoForTextRequest(nsString(aText)),
+                                                  aRequest);
 }
 
 NS_IMETHODIMP
 SmsIPCService::Send(const nsAString& aNumber,
                     const nsAString& aMessage,
                     const bool aSilent,
                     nsIMobileMessageCallback* aRequest)
 {
--- a/dom/mobilemessage/src/ipc/SmsParent.cpp
+++ b/dom/mobilemessage/src/ipc/SmsParent.cpp
@@ -307,45 +307,16 @@ SmsParent::RecvHasSupport(bool* aHasSupp
   nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(smsService, true);
 
   smsService->HasSupport(aHasSupport);
   return true;
 }
 
 bool
-SmsParent::RecvGetSegmentInfoForText(const nsString& aText,
-                                     SmsSegmentInfoData* aResult)
-{
-  aResult->segments() = 0;
-  aResult->charsPerSegment() = 0;
-  aResult->charsAvailableInLastSegment() = 0;
-
-  nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
-  NS_ENSURE_TRUE(smsService, true);
-
-  nsCOMPtr<nsIDOMMozSmsSegmentInfo> info;
-  nsresult rv = smsService->GetSegmentInfoForText(aText, getter_AddRefs(info));
-  NS_ENSURE_SUCCESS(rv, true);
-
-  int segments, charsPerSegment, charsAvailableInLastSegment;
-  if (NS_FAILED(info->GetSegments(&segments)) ||
-      NS_FAILED(info->GetCharsPerSegment(&charsPerSegment)) ||
-      NS_FAILED(info->GetCharsAvailableInLastSegment(&charsAvailableInLastSegment))) {
-    NS_ERROR("Can't get attribute values from nsIDOMMozSmsSegmentInfo");
-    return true;
-  }
-
-  aResult->segments() = segments;
-  aResult->charsPerSegment() = charsPerSegment;
-  aResult->charsAvailableInLastSegment() = charsAvailableInLastSegment;
-  return true;
-}
-
-bool
 SmsParent::RecvAddSilentNumber(const nsString& aNumber)
 {
   if (mSilentNumbers.Contains(aNumber)) {
     return true;
   }
 
   nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(smsService, true);
@@ -388,16 +359,18 @@ SmsParent::RecvPSmsRequestConstructor(PS
     case IPCSmsRequest::TRetrieveMessageRequest:
       return actor->DoRequest(aRequest.get_RetrieveMessageRequest());
     case IPCSmsRequest::TGetMessageRequest:
       return actor->DoRequest(aRequest.get_GetMessageRequest());
     case IPCSmsRequest::TDeleteMessageRequest:
       return actor->DoRequest(aRequest.get_DeleteMessageRequest());
     case IPCSmsRequest::TMarkMessageReadRequest:
       return actor->DoRequest(aRequest.get_MarkMessageReadRequest());
+    case IPCSmsRequest::TGetSegmentInfoForTextRequest:
+      return actor->DoRequest(aRequest.get_GetSegmentInfoForTextRequest());
     default:
       MOZ_CRASH("Unknown type!");
   }
 
   return false;
 }
 
 PSmsRequestParent*
@@ -572,16 +545,34 @@ SmsRequestParent::DoRequest(const MarkMe
 
   if (NS_FAILED(rv)) {
     return NS_SUCCEEDED(NotifyMarkMessageReadFailed(nsIMobileMessageCallback::INTERNAL_ERROR));
   }
 
   return true;
 }
 
+bool
+SmsRequestParent::DoRequest(const GetSegmentInfoForTextRequest& aRequest)
+{
+  nsresult rv = NS_ERROR_FAILURE;
+
+  nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
+  if (smsService) {
+    rv = smsService->GetSegmentInfoForText(aRequest.text(), this);
+  }
+
+  if (NS_FAILED(rv)) {
+    return NS_SUCCEEDED(NotifyGetSegmentInfoForTextFailed(
+                          nsIMobileMessageCallback::INTERNAL_ERROR));
+  }
+
+  return true;
+}
+
 nsresult
 SmsRequestParent::SendReply(const MessageReply& aReply)
 {
   // The child process could die before this asynchronous notification, in which
   // case ActorDestroy() was called and mActorDestroyed is set to true. Return
   // an error here to avoid sending a message to the dead process.
   NS_ENSURE_TRUE(!mActorDestroyed, NS_ERROR_FAILURE);
 
@@ -669,16 +660,29 @@ SmsRequestParent::NotifyMessageMarkedRea
 }
 
 NS_IMETHODIMP
 SmsRequestParent::NotifyMarkMessageReadFailed(int32_t aError)
 {
   return SendReply(ReplyMarkeMessageReadFail(aError));
 }
 
+NS_IMETHODIMP
+SmsRequestParent::NotifySegmentInfoForTextGot(nsIDOMMozSmsSegmentInfo *aInfo)
+{
+  SmsSegmentInfo* info = static_cast<SmsSegmentInfo*>(aInfo);
+  return SendReply(ReplyGetSegmentInfoForText(info->GetData()));
+}
+
+NS_IMETHODIMP
+SmsRequestParent::NotifyGetSegmentInfoForTextFailed(int32_t aError)
+{
+  return SendReply(ReplyGetSegmentInfoForTextFail(aError));
+}
+
 /*******************************************************************************
  * MobileMessageCursorParent
  ******************************************************************************/
 
 NS_IMPL_ISUPPORTS1(MobileMessageCursorParent, nsIMobileMessageCursorCallback)
 
 void
 MobileMessageCursorParent::ActorDestroy(ActorDestroyReason aWhy)
--- a/dom/mobilemessage/src/ipc/SmsParent.h
+++ b/dom/mobilemessage/src/ipc/SmsParent.h
@@ -30,19 +30,16 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
 protected:
   virtual bool
   RecvHasSupport(bool* aHasSupport) MOZ_OVERRIDE;
 
   virtual bool
-  RecvGetSegmentInfoForText(const nsString& aText, SmsSegmentInfoData* aResult) MOZ_OVERRIDE;
-
-  virtual bool
   RecvAddSilentNumber(const nsString& aNumber) MOZ_OVERRIDE;
 
   virtual bool
   RecvRemoveSilentNumber(const nsString& aNumber) MOZ_OVERRIDE;
 
   SmsParent();
   virtual ~SmsParent()
   {
@@ -115,16 +112,19 @@ protected:
   DoRequest(const GetMessageRequest& aRequest);
 
   bool
   DoRequest(const DeleteMessageRequest& aRequest);
 
   bool
   DoRequest(const MarkMessageReadRequest& aRequest);
 
+  bool
+  DoRequest(const GetSegmentInfoForTextRequest& aRequest);
+
   nsresult
   SendReply(const MessageReply& aReply);
 };
 
 class MobileMessageCursorParent : public PMobileMessageCursorParent
                                 , public nsIMobileMessageCursorCallback
 {
   friend class SmsParent;
--- a/dom/mobilemessage/tests/marionette/test_getsegmentinfofortext.js
+++ b/dom/mobilemessage/tests/marionette/test_getsegmentinfofortext.js
@@ -44,33 +44,51 @@ let tasks = {
   run: function run() {
     this.next();
   }
 };
 
 function addTest(text, segments, charsPerSegment, charsAvailableInLastSegment) {
   tasks.push(function () {
     log("Testing '" + text + "' ...");
-    let info = manager.getSegmentInfoForText(text);
-    is(info.segments, segments, "info.segments");
-    is(info.charsPerSegment, charsPerSegment, "info.charsPerSegment");
-    is(info.charsAvailableInLastSegment, charsAvailableInLastSegment,
-       "info.charsAvailableInLastSegment");
+    let domRequest = manager.getSegmentInfoForText(text);
+    ok(domRequest, "DOMRequest object returned.");
+
+    domRequest.onsuccess = function(e) {
+      log("Received 'onsuccess' DOMRequest event.");
+
+      let result = e.target.result;
+      if (!result) {
+        ok(false, "getSegmentInfoForText() result is not valid.");
+        tasks.finish();
+        return;
+      }
 
-    tasks.next();
+      is(result.segments, segments, "info.segments");
+      is(result.charsPerSegment, charsPerSegment, "info.charsPerSegment");
+      is(result.charsAvailableInLastSegment, charsAvailableInLastSegment,
+         "info.charsAvailableInLastSegment");
+
+      tasks.next();
+    };
+
+    domRequest.onerror = function(e) {
+      ok(false, "Failed to call getSegmentInfoForText().");
+      tasks.finish();
+    };
   });
 }
 
 function addTestThrows(text) {
   tasks.push(function () {
     log("Testing '" + text + "' ...");
     try {
-      let info = manager.getSegmentInfoForText(text);
+      let domRequest = manager.getSegmentInfoForText(text);
 
-      ok(false, "Not thrown");
+      ok(false, "Not thrown.");
       tasks.finish();
     } catch (e) {
       tasks.next();
     }
   });
 }
 
 addTestThrows(null);
--- a/dom/mobilemessage/tests/marionette/test_segment_info.js
+++ b/dom/mobilemessage/tests/marionette/test_segment_info.js
@@ -7,123 +7,182 @@ const LEN_7BIT = 160;
 const LEN_7BIT_WITH_8BIT_REF = 153;
 const LEN_7BIT_WITH_16BIT_REF = 152;
 const LEN_UCS2 = 70;
 const LEN_UCS2_WITH_8BIT_REF = 67;
 const LEN_UCS2_WITH_16BIT_REF = 66;
 
 SpecialPowers.setBoolPref("dom.sms.enabled", true);
 let currentStrict7BitEncoding = false;
-SpecialPowers.setBoolPref("dom.sms.strict7BitEncoding", currentStrict7BitEncoding);
+SpecialPowers.setBoolPref("dom.sms.strict7BitEncoding",
+                          currentStrict7BitEncoding);
 SpecialPowers.addPermission("sms", true, document);
 
 let manager = window.navigator.mozMobileMessage;
 ok(manager instanceof MozMobileMessageManager,
    "manager is instance of " + manager.constructor);
 
 function times(str, n) {
   return (new Array(n + 1)).join(str);
 }
 
-function doTest(text, strict7BitEncoding, expected) {
-  if (strict7BitEncoding != currentStrict7BitEncoding) {
-    currentStrict7BitEncoding = strict7BitEncoding;
-    SpecialPowers.setBoolPref("dom.sms.strict7BitEncoding", currentStrict7BitEncoding);
+let tasks = {
+  // List of test fuctions. Each of them should call |tasks.next()| when
+  // completed or |tasks.finish()| to jump to the last one.
+  _tasks: [],
+  _nextTaskIndex: 0,
+
+  push: function push(func) {
+    this._tasks.push(func);
+  },
+
+  next: function next() {
+    let index = this._nextTaskIndex++;
+    let task = this._tasks[index];
+    try {
+      task();
+    } catch (ex) {
+      ok(false, "test task[" + index + "] throws: " + ex);
+      // Run last task as clean up if possible.
+      if (index != this._tasks.length - 1) {
+        this.finish();
+      }
+    }
+  },
+
+  finish: function finish() {
+    this._tasks[this._tasks.length - 1]();
+  },
+
+  run: function run() {
+    this.next();
   }
+};
 
-  let result = manager.getSegmentInfoForText(text);
-  ok(result, "result of GetSegmentInfoForText is valid");
-  is(result.segments, expected[0], "segments");
-  is(result.charsPerSegment, expected[1], "charsPerSegment");
-  is(result.charsAvailableInLastSegment, expected[2], "charsAvailableInLastSegment");
-}
+function addTest(text, strict7BitEncoding, expected) {
+  tasks.push(function () {
+    if (strict7BitEncoding != currentStrict7BitEncoding) {
+      currentStrict7BitEncoding = strict7BitEncoding;
+      SpecialPowers.setBoolPref("dom.sms.strict7BitEncoding",
+                                currentStrict7BitEncoding);
+    }
+
+    let domRequest = manager.getSegmentInfoForText(text);
+    ok(domRequest, "DOMRequest object returned.");
+
+    domRequest.onsuccess = function(e) {
+      log("Received 'onsuccess' DOMRequest event.");
 
-function cleanUp() {
-  SpecialPowers.removePermission("sms", document);
-  SpecialPowers.clearUserPref("dom.sms.enabled");
-  SpecialPowers.clearUserPref("dom.sms.strict7BitEncoding");
-  finish();
+      let result = e.target.result;
+      if (!result) {
+        ok(false, "getSegmentInfoForText() result is not valid.");
+        tasks.finish();
+        return;
+      }
+
+      is(result.segments, expected[0], "segments");
+      is(result.charsPerSegment, expected[1], "charsPerSegment");
+      is(result.charsAvailableInLastSegment, expected[2],
+         "charsAvailableInLastSegment");
+
+      tasks.next();
+    };
+
+    domRequest.onerror = function(e) {
+      ok(false, "Failed to call getSegmentInfoForText().");
+      tasks.finish();
+    };
+  });
 }
 
 // GSM 7Bit Alphabets:
 //
 // 'a' is in GSM default locking shift table, so it takes 1 septet.
-doTest("a", false, [1, LEN_7BIT, LEN_7BIT - 1]);
+addTest("a", false, [1, LEN_7BIT, LEN_7BIT - 1]);
 // '\u20ac' is in GSM default single shift table, so it takes 2 septets.
-doTest("\u20ac", false, [1, LEN_7BIT, LEN_7BIT - 2]);
+addTest("\u20ac", false, [1, LEN_7BIT, LEN_7BIT - 2]);
 // SP is defined in both locking shift and single shift tables.
-doTest(" ", false, [1, LEN_7BIT, LEN_7BIT - 1]);
+addTest(" ", false, [1, LEN_7BIT, LEN_7BIT - 1]);
 // Some combinations.
-doTest("a\u20ac", false, [1, LEN_7BIT, LEN_7BIT - 3]);
-doTest("a ", false, [1, LEN_7BIT, LEN_7BIT - 2]);
-doTest("\u20aca", false, [1, LEN_7BIT, LEN_7BIT - 3]);
-doTest("\u20ac ", false, [1, LEN_7BIT, LEN_7BIT - 3]);
-doTest(" \u20ac", false, [1, LEN_7BIT, LEN_7BIT - 3]);
-doTest(" a", false, [1, LEN_7BIT, LEN_7BIT - 2]);
+addTest("a\u20ac", false, [1, LEN_7BIT, LEN_7BIT - 3]);
+addTest("a ", false, [1, LEN_7BIT, LEN_7BIT - 2]);
+addTest("\u20aca", false, [1, LEN_7BIT, LEN_7BIT - 3]);
+addTest("\u20ac ", false, [1, LEN_7BIT, LEN_7BIT - 3]);
+addTest(" \u20ac", false, [1, LEN_7BIT, LEN_7BIT - 3]);
+addTest(" a", false, [1, LEN_7BIT, LEN_7BIT - 2]);
 
 // GSM 7Bit Alphabets (multipart):
 //
 // Exactly 160 locking shift table chararacters.
-doTest(times("a", LEN_7BIT), false, [1, LEN_7BIT, 0]);
+addTest(times("a", LEN_7BIT), false, [1, LEN_7BIT, 0]);
 // 161 locking shift table chararacters. We'll have |161 - 153 = 8| septets in
 // the 2nd segment.
-doTest(times("a", LEN_7BIT + 1), false,
-       [2, LEN_7BIT_WITH_8BIT_REF, LEN_7BIT_WITH_8BIT_REF - 8]);
+addTest(times("a", LEN_7BIT + 1), false,
+        [2, LEN_7BIT_WITH_8BIT_REF, LEN_7BIT_WITH_8BIT_REF - 8]);
 // |LEN_7BIT_WITH_8BIT_REF * 2| locking shift table chararacters.
-doTest(times("a", LEN_7BIT_WITH_8BIT_REF * 2), false,
-       [2, LEN_7BIT_WITH_8BIT_REF, 0]);
+addTest(times("a", LEN_7BIT_WITH_8BIT_REF * 2), false,
+        [2, LEN_7BIT_WITH_8BIT_REF, 0]);
 // |LEN_7BIT_WITH_8BIT_REF * 2 + 1| locking shift table chararacters.
-doTest(times("a", LEN_7BIT_WITH_8BIT_REF * 2 + 1), false,
-       [3, LEN_7BIT_WITH_8BIT_REF, LEN_7BIT_WITH_8BIT_REF - 1]);
+addTest(times("a", LEN_7BIT_WITH_8BIT_REF * 2 + 1), false,
+        [3, LEN_7BIT_WITH_8BIT_REF, LEN_7BIT_WITH_8BIT_REF - 1]);
 // Exactly 80 single shift table chararacters.
-doTest(times("\u20ac", LEN_7BIT / 2), false, [1, LEN_7BIT, 0]);
+addTest(times("\u20ac", LEN_7BIT / 2), false, [1, LEN_7BIT, 0]);
 // 81 single shift table chararacters. Because |Math.floor(153 / 2) = 76|, it
 // should left 5 septets in the 2nd segment.
-doTest(times("\u20ac", LEN_7BIT / 2 + 1), false,
-       [2, LEN_7BIT_WITH_8BIT_REF, LEN_7BIT_WITH_8BIT_REF - 10]);
+addTest(times("\u20ac", LEN_7BIT / 2 + 1), false,
+        [2, LEN_7BIT_WITH_8BIT_REF, LEN_7BIT_WITH_8BIT_REF - 10]);
 // |1 + 2 * 76| single shift table chararacters. We have only |153 - 76 * 2 = 1|
 // space left, but each single shift table character takes 2, so it will be
 // filled in the 3rd segment.
-doTest(times("\u20ac", 1 + 2 * Math.floor(LEN_7BIT_WITH_8BIT_REF / 2)), false,
-       [3, LEN_7BIT_WITH_8BIT_REF, LEN_7BIT_WITH_8BIT_REF - 2]);
+addTest(times("\u20ac", 1 + 2 * Math.floor(LEN_7BIT_WITH_8BIT_REF / 2)), false,
+        [3, LEN_7BIT_WITH_8BIT_REF, LEN_7BIT_WITH_8BIT_REF - 2]);
 // |2 * 76| single shift table chararacters + 1 locking shift table chararacter.
-doTest("a" + times("\u20ac", 2 * Math.floor(LEN_7BIT_WITH_8BIT_REF / 2)), false,
-       [2, LEN_7BIT_WITH_8BIT_REF, 1]);
-doTest(times("\u20ac", 2 * Math.floor(LEN_7BIT_WITH_8BIT_REF / 2)) + "a", false,
-       [2, LEN_7BIT_WITH_8BIT_REF, 0]);
+addTest("a" + times("\u20ac", 2 * Math.floor(LEN_7BIT_WITH_8BIT_REF / 2)), false,
+        [2, LEN_7BIT_WITH_8BIT_REF, 1]);
+addTest(times("\u20ac", 2 * Math.floor(LEN_7BIT_WITH_8BIT_REF / 2)) + "a", false,
+        [2, LEN_7BIT_WITH_8BIT_REF, 0]);
 
 // UCS2:
 //
 // '\u6afb' should be encoded as UCS2.
-doTest("\u6afb", false, [1, LEN_UCS2, LEN_UCS2 - 1]);
+addTest("\u6afb", false, [1, LEN_UCS2, LEN_UCS2 - 1]);
 // Combination of GSM 7bit alphabets.
-doTest("\u6afba", false, [1, LEN_UCS2, LEN_UCS2 - 2]);
-doTest("\u6afb\u20ac", false, [1, LEN_UCS2, LEN_UCS2 - 2]);
-doTest("\u6afb ", false, [1, LEN_UCS2, LEN_UCS2 - 2]);
+addTest("\u6afba", false, [1, LEN_UCS2, LEN_UCS2 - 2]);
+addTest("\u6afb\u20ac", false, [1, LEN_UCS2, LEN_UCS2 - 2]);
+addTest("\u6afb ", false, [1, LEN_UCS2, LEN_UCS2 - 2]);
 
 // UCS2 (multipart):
 //
 // Exactly 70 UCS2 chararacters.
-doTest(times("\u6afb", LEN_UCS2), false, [1, LEN_UCS2, 0]);
+addTest(times("\u6afb", LEN_UCS2), false, [1, LEN_UCS2, 0]);
 // 71 UCS2 chararacters. We'll have |71 - 67 = 4| chararacters in the 2nd
 // segment.
-doTest(times("\u6afb", LEN_UCS2 + 1), false,
-       [2, LEN_UCS2_WITH_8BIT_REF, LEN_UCS2_WITH_8BIT_REF - 4]);
+addTest(times("\u6afb", LEN_UCS2 + 1), false,
+        [2, LEN_UCS2_WITH_8BIT_REF, LEN_UCS2_WITH_8BIT_REF - 4]);
 // |LEN_UCS2_WITH_8BIT_REF * 2| ucs2 chararacters.
-doTest(times("\u6afb", LEN_UCS2_WITH_8BIT_REF * 2), false,
-       [2, LEN_UCS2_WITH_8BIT_REF, 0]);
+addTest(times("\u6afb", LEN_UCS2_WITH_8BIT_REF * 2), false,
+        [2, LEN_UCS2_WITH_8BIT_REF, 0]);
 // |LEN_7BIT_WITH_8BIT_REF * 2 + 1| ucs2 chararacters.
-doTest(times("\u6afb", LEN_UCS2_WITH_8BIT_REF * 2 + 1), false,
-       [3, LEN_UCS2_WITH_8BIT_REF, LEN_UCS2_WITH_8BIT_REF - 1]);
+addTest(times("\u6afb", LEN_UCS2_WITH_8BIT_REF * 2 + 1), false,
+        [3, LEN_UCS2_WITH_8BIT_REF, LEN_UCS2_WITH_8BIT_REF - 1]);
 
 // Strict 7-Bit Encoding:
 //
 // Should have no effect on GSM default alphabet characters.
-doTest("\u0041", true, [1, LEN_7BIT, LEN_7BIT - 1]);
+addTest("\u0041", true, [1, LEN_7BIT, LEN_7BIT - 1]);
 // "\u00c0"(À) should be mapped to "\u0041"(A).
-doTest("\u00c0", true, [1, LEN_7BIT, LEN_7BIT - 1]);
+addTest("\u00c0", true, [1, LEN_7BIT, LEN_7BIT - 1]);
 // Mixing mapped characters with unmapped ones.
-doTest("\u00c0\u0041", true, [1, LEN_7BIT, LEN_7BIT - 2]);
-doTest("\u0041\u00c0", true, [1, LEN_7BIT, LEN_7BIT - 2]);
+addTest("\u00c0\u0041", true, [1, LEN_7BIT, LEN_7BIT - 2]);
+addTest("\u0041\u00c0", true, [1, LEN_7BIT, LEN_7BIT - 2]);
 // UCS2 characters should be mapped to '*'.
-doTest("\u1234", true, [1, LEN_7BIT, LEN_7BIT - 1]);
+addTest("\u1234", true, [1, LEN_7BIT, LEN_7BIT - 1]);
+
 
-cleanUp();
+// WARNING: All tasks should be pushed before this!!!
+tasks.push(function cleanUp() {
+  SpecialPowers.removePermission("sms", document);
+  SpecialPowers.clearUserPref("dom.sms.enabled");
+  SpecialPowers.clearUserPref("dom.sms.strict7BitEncoding");
+  finish();
+});
+
+tasks.run();
--- a/dom/network/interfaces/nsIDOMMobileConnection.idl
+++ b/dom/network/interfaces/nsIDOMMobileConnection.idl
@@ -204,49 +204,49 @@ interface nsIDOMMozMobileConnection : ns
    *
    * @param CFInfo
    *        An object containing the call forward rule to set.
    *
    * If successful, the request's onsuccess will be called.
    *
    * Otherwise, the request's onerror will be called, and the request's error
    * will be either 'RadioNotAvailable', 'RequestNotSupported',
-   * 'IllegalSIMorME', or 'GenericFailure'
+   * 'IllegalSIMorME', 'InvalidParameter', or 'GenericFailure'
    */
   nsIDOMDOMRequest setCallForwardingOption(in nsIDOMMozMobileCFInfo CFInfo);
 
   /**
    * Queries current call forward options.
    *
    * @param reason
    *        Indicates the reason the call is being forwarded. It will be either
    *        unconditional (0), mobile busy (1), no reply (2), not reachable (3),
    *        all call forwarding (4), or all conditional call forwarding (5).
    *
    * If successful, the request's onsuccess will be called, and the request's
    * result will be an array of nsIDOMMozMobileCFInfo.
    *
    * Otherwise, the request's onerror will be called, and the request's error
    * will be either 'RadioNotAvailable', 'RequestNotSupported',
-   * or 'GenericFailure'.
+   * 'InvalidParameter', or 'GenericFailure'.
    */
   nsIDOMDOMRequest getCallForwardingOption(in unsigned short reason);
 
   /**
    * Configures call barring option.
    *
    * @param option
    *        An object containing the call barring rule to set.
    * @see MozCallBarringOption for the detail of info.
    *
    * If successful, the request's onsuccess will be called.
    *
    * Otherwise, the request's onerror will be called, and the request's error
    * will be either 'RadioNotAvailable', 'RequestNotSupported',
-   * 'IllegalSIMorME', 'InvalidCallBarringOption' or 'GenericFailure'
+   * 'IllegalSIMorME', 'InvalidParameter', or 'GenericFailure'
    */
   nsIDOMDOMRequest setCallBarringOption(in jsval option);
 
   /**
    * Queries current call barring status.
    *
    * @param info
    *        An object containing the call barring rule to query. No need to
@@ -254,17 +254,17 @@ interface nsIDOMMozMobileConnection : ns
    * @see MozCallBarringOption for the detail of info.
    *
    * If successful, the request's onsuccess will be called, and the request's
    * result will be an object of MozCallBarringOption with correct 'enabled'
    * property indicating the status of this rule.
    *
    * Otherwise, the request's onerror will be called, and the request's error
    * will be either 'RadioNotAvailable', 'RequestNotSupported',
-   * 'InvalidCallBarringOption' or 'GenericFailure'.
+   * 'InvalidParameter', or 'GenericFailure'.
    */
   nsIDOMDOMRequest getCallBarringOption(in jsval option);
 
   /**
    * Change call barring facility password.
    *
    * @param info
    *        An object containing information about pin and newPin, and,
--- a/dom/network/tests/marionette/test_call_barring_set_error.js
+++ b/dom/network/tests/marionette/test_call_barring_set_error.js
@@ -48,17 +48,17 @@ function buildOption(program, enabled, p
 function testSetCallBarringOptionError(option) {
   let request = connection.setCallBarringOption(option);
   request.onsuccess = function() {
     ok(false,
        'should not fire onsuccess for invaild call barring option: '
        + JSON.stringify(option));
   };
   request.onerror = function(event) {
-    is(event.target.error.name, 'InvalidCallBarringOption', JSON.stringify(option));
+    is(event.target.error.name, 'InvalidParameter', JSON.stringify(option));
     nextTest();
   };
 }
 
 function nextTest() {
   if (caseId >= options.length) {
     cleanUp();
   } else {
--- a/dom/phonenumberutils/PhoneNumberUtils.jsm
+++ b/dom/phonenumberutils/PhoneNumberUtils.jsm
@@ -43,17 +43,17 @@ this.PhoneNumberUtils = {
     // Get network mcc
     let voice = mobileConnection.voiceConnectionInfo;
     if (voice && voice.network && voice.network.mcc) {
       mcc = voice.network.mcc;
     }
 
     // Get SIM mcc
     let iccInfo = mobileConnection.iccInfo;
-    if (!mcc && iccInfo.mcc) {
+    if (!mcc && iccInfo && iccInfo.mcc) {
       mcc = iccInfo.mcc;
     }
 
     // Attempt to grab last known sim mcc from prefs
     if (!mcc) {
       try {
         mcc = Services.prefs.getCharPref("ril.lastKnownSimMcc");
       } catch (e) {}
--- a/dom/system/gonk/GonkGPSGeolocationProvider.cpp
+++ b/dom/system/gonk/GonkGPSGeolocationProvider.cpp
@@ -421,18 +421,21 @@ GonkGPSGeolocationProvider::RequestSetID
       type = AGPS_SETID_TYPE_IMSI;
       rilCtx->GetImsi(id);
     }
 
     if (flags & AGPS_RIL_REQUEST_SETID_MSISDN) {
       nsCOMPtr<nsIDOMMozIccInfo> iccInfo;
       rilCtx->GetIccInfo(getter_AddRefs(iccInfo));
       if (iccInfo) {
-        type = AGPS_SETID_TYPE_MSISDN;
-        iccInfo->GetMsisdn(id);
+        nsCOMPtr<nsIDOMMozGsmIccInfo> gsmIccInfo = do_QueryInterface(iccInfo);
+        if (gsmIccInfo) {
+          type = AGPS_SETID_TYPE_MSISDN;
+          gsmIccInfo->GetMsisdn(id);
+        }
       }
     }
 
     NS_ConvertUTF16toUTF8 idBytes(id);
     mAGpsRilInterface->set_set_id(type, idBytes.get());
   }
 }
 
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -40,18 +40,20 @@ if (DEBUG) {
     dump("-*- RILContentHelper: " + s + "\n");
   };
 } else {
   debug = function (s) {};
 }
 
 const RILCONTENTHELPER_CID =
   Components.ID("{472816e1-1fd6-4405-996c-806f9ea68174}");
-const ICCINFO_CID =
-  Components.ID("{fab2c0f0-d73a-11e2-8b8b-0800200c9a66}");
+const GSMICCINFO_CID =
+  Components.ID("{e0fa785b-ad3f-46ed-bc56-fcb0d6fe4fa8}");
+const CDMAICCINFO_CID =
+  Components.ID("{3d1f844f-9ec5-48fb-8907-aed2e5421709}");
 const MOBILECONNECTIONINFO_CID =
   Components.ID("{a35cfd39-2d93-4489-ac7d-396475dacb27}");
 const MOBILENETWORKINFO_CID =
   Components.ID("{a6c8416c-09b4-46d1-bf29-6520d677d085}");
 const MOBILECELLINFO_CID =
   Components.ID("{ae724dd4-ccaf-4006-98f1-6ce66a092464}");
 const VOICEMAILSTATUS_CID=
   Components.ID("{5467f2eb-e214-43ea-9b89-67711241ec8e}");
@@ -143,34 +145,60 @@ function MobileIccCardLockRetryCount(opt
 MobileIccCardLockRetryCount.prototype = {
   __exposedProps__ : {lockType: 'r',
                       retryCount: 'r',
                       success: 'r'}
 };
 
 function IccInfo() {}
 IccInfo.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMMozIccInfo]),
-  classID:        ICCINFO_CID,
-  classInfo:      XPCOMUtils.generateCI({
-    classID:          ICCINFO_CID,
-    classDescription: "IccInfo",
-    flags:            Ci.nsIClassInfo.DOM_OBJECT,
-    interfaces:       [Ci.nsIDOMMozIccInfo]
-  }),
-
-  // nsIDOMMozIccInfo
-
+  iccType: null,
   iccid: null,
   mcc: null,
   mnc: null,
   spn: null,
+  isDisplayNetworkNameRequired: null,
+  isDisplaySpnRequired: null
+};
+
+function GsmIccInfo() {}
+GsmIccInfo.prototype = {
+  __proto__: IccInfo.prototype,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMMozGsmIccInfo]),
+  classID: GSMICCINFO_CID,
+  classInfo: XPCOMUtils.generateCI({
+    classID:          GSMICCINFO_CID,
+    classDescription: "MozGsmIccInfo",
+    flags:            Ci.nsIClassInfo.DOM_OBJECT,
+    interfaces:       [Ci.nsIDOMMozGsmIccInfo]
+  }),
+
+  // nsIDOMMozGsmIccInfo
+
   msisdn: null
 };
 
+function CdmaIccInfo() {}
+CdmaIccInfo.prototype = {
+  __proto__: IccInfo.prototype,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMMozCdmaIccInfo]),
+  classID: CDMAICCINFO_CID,
+  classInfo: XPCOMUtils.generateCI({
+    classID:          CDMAICCINFO_CID,
+    classDescription: "MozCdmaIccInfo",
+    flags:            Ci.nsIClassInfo.DOM_OBJECT,
+    interfaces:       [Ci.nsIDOMMozCdmaIccInfo]
+  }),
+
+  // nsIDOMMozCdmaIccInfo
+
+  mdn: null,
+  min: null
+};
+
 function VoicemailInfo() {}
 VoicemailInfo.prototype = {
   number: null,
   displayName: null
 };
 
 function MobileConnectionInfo() {}
 MobileConnectionInfo.prototype = {
@@ -399,17 +427,17 @@ DOMMMIError.prototype = {
     this.additionalInformation = additionalInformation;
   },
 };
 
 function RILContentHelper() {
   this.rilContext = {
     cardState:            RIL.GECKO_CARDSTATE_UNKNOWN,
     networkSelectionMode: RIL.GECKO_NETWORK_SELECTION_UNKNOWN,
-    iccInfo:              new IccInfo(),
+    iccInfo:              null,
     voiceConnectionInfo:  new MobileConnectionInfo(),
     dataConnectionInfo:   new MobileConnectionInfo()
   };
   this.voicemailInfo = new VoicemailInfo();
 
   this.initDOMRequestHelper(/* aWindow */ null, RIL_IPC_MSG_NAMES);
   this._windowsMap = [];
   Services.obs.addObserver(this, "xpcom-shutdown", false);
@@ -467,17 +495,41 @@ RILContentHelper.prototype = {
     }
 
     let network = destInfo.network;
     if (!network) {
       network = destInfo.network = new MobileNetworkInfo();
     }
 
     this.updateInfo(srcNetwork, network);
- },
+  },
+
+  /**
+   * We need to consider below cases when update iccInfo:
+   * 1. Should clear iccInfo to null if there is no card detected.
+   * 2. Need to create corresponding object based on iccType.
+   */
+  updateIccInfo: function updateIccInfo(newInfo) {
+    // Card is not detected, clear iccInfo to null.
+    if (!newInfo || !newInfo.iccType) {
+      this.rilContext.iccInfo = null;
+      return;
+    }
+
+    // If iccInfo is null, new corresponding object based on iccType.
+    if (!this.rilContext.iccInfo) {
+      if (newInfo.iccType === "ruim" || newInfo.iccType === "csim") {
+        this.rilContext.iccInfo = new CdmaIccInfo();
+      } else {
+        this.rilContext.iccInfo = new GsmIccInfo();
+      }
+    }
+
+    this.updateInfo(newInfo, this.rilContext.iccInfo);
+  },
 
   _windowsMap: null,
 
   rilContext: null,
 
   getRilContext: function getRilContext() {
     // Update ril context by sending IPC message to chrome only when the first
     // time we require it. The information will be updated by following info
@@ -489,17 +541,17 @@ RILContentHelper.prototype = {
     let rilContext =
       cpmm.sendSyncMessage("RIL:GetRilContext", {clientId: 0})[0];
     if (!rilContext) {
       debug("Received null rilContext from chrome process.");
       return;
     }
     this.rilContext.cardState = rilContext.cardState;
     this.rilContext.networkSelectionMode = rilContext.networkSelectionMode;
-    this.updateInfo(rilContext.iccInfo, this.rilContext.iccInfo);
+    this.updateIccInfo(rilContext.iccInfo);
     this.updateConnectionInfo(rilContext.voice, this.rilContext.voiceConnectionInfo);
     this.updateConnectionInfo(rilContext.data, this.rilContext.dataConnectionInfo);
 
     return this.rilContext;
   },
 
   /**
    * nsIMobileConnectionProvider
@@ -638,17 +690,18 @@ RILContentHelper.prototype = {
       throw Components.Exception("Can't get window object",
                                   Cr.NS_ERROR_UNEXPECTED);
     }
 
     let request = Services.DOMRequest.createRequest(window);
     let requestId = this.getRequestId(request);
 
     if (!mode) {
-      this.dispatchFireRequestError(requestId, "InvalidParameter");
+      this.dispatchFireRequestError(requestId,
+                                    RIL.GECKO_ERROR_INVALID_PARAMETER);
       return request;
     }
 
     cpmm.sendAsyncMessage("RIL:SetRoamingPreference", {
       clientId: 0,
       data: {
         requestId: requestId,
         mode: mode
@@ -1001,17 +1054,18 @@ RILContentHelper.prototype = {
     if (window == null) {
       throw Components.Exception("Can't get window object",
                                   Cr.NS_ERROR_UNEXPECTED);
     }
     let request = Services.DOMRequest.createRequest(window);
     let requestId = this.getRequestId(request);
 
     if (!this._isValidCFReason(reason)){
-      this.dispatchFireRequestError(requestId, "Invalid call forwarding reason.");
+      this.dispatchFireRequestError(requestId,
+                                    RIL.GECKO_ERROR_INVALID_PARAMETER);
       return request;
     }
 
     cpmm.sendAsyncMessage("RIL:GetCallForwardingOption", {
       clientId: 0,
       data: {
         requestId: requestId,
         reason: reason
@@ -1027,17 +1081,18 @@ RILContentHelper.prototype = {
                                   Cr.NS_ERROR_UNEXPECTED);
     }
     let request = Services.DOMRequest.createRequest(window);
     let requestId = this.getRequestId(request);
 
     if (!cfInfo ||
         !this._isValidCFReason(cfInfo.reason) ||
         !this._isValidCFAction(cfInfo.action)){
-      this.dispatchFireRequestError(requestId, "Invalid call forwarding rule definition.");
+      this.dispatchFireRequestError(requestId,
+                                    RIL.GECKO_ERROR_INVALID_PARAMETER);
       return request;
     }
 
     cpmm.sendAsyncMessage("RIL:SetCallForwardingOption", {
       clientId: 0,
       data: {
         requestId: requestId,
         active: cfInfo.active,
@@ -1056,17 +1111,18 @@ RILContentHelper.prototype = {
       throw Components.Exception("Can't get window object",
                                   Cr.NS_ERROR_UNEXPECTED);
     }
     let request = Services.DOMRequest.createRequest(window);
     let requestId = this.getRequestId(request);
 
     if (DEBUG) debug("getCallBarringOption: " + JSON.stringify(option));
     if (!this._isValidCallBarringOption(option)) {
-      this.dispatchFireRequestError(requestId, "InvalidCallBarringOption");
+      this.dispatchFireRequestError(requestId,
+                                    RIL.GECKO_ERROR_INVALID_PARAMETER);
       return request;
     }
 
     cpmm.sendAsyncMessage("RIL:GetCallBarringOption", {
       clientId: 0,
       data: {
         requestId: requestId,
         program: option.program,
@@ -1082,17 +1138,18 @@ RILContentHelper.prototype = {
       throw Components.Exception("Can't get window object",
                                   Cr.NS_ERROR_UNEXPECTED);
     }
     let request = Services.DOMRequest.createRequest(window);
     let requestId = this.getRequestId(request);
 
     if (DEBUG) debug("setCallBarringOption: " + JSON.stringify(option));
     if (!this._isValidCallBarringOption(option, true)) {
-      this.dispatchFireRequestError(requestId, "InvalidCallBarringOption");
+      this.dispatchFireRequestError(requestId,
+                                    RIL.GECKO_ERROR_INVALID_PARAMETER);
       return request;
     }
 
     cpmm.sendAsyncMessage("RIL:SetCallBarringOption", {
       clientId: 0,
       data: {
         requestId: requestId,
         program: option.program,
@@ -1536,17 +1593,17 @@ RILContentHelper.prototype = {
           this.rilContext.cardState = data.cardState;
           this._deliverEvent("_iccListeners",
                              "notifyCardStateChanged",
                              null);
         }
         break;
       }
       case "RIL:IccInfoChanged":
-        this.updateInfo(msg.json.data, this.rilContext.iccInfo);
+        this.updateIccInfo(msg.json.data);
         this._deliverEvent("_iccListeners", "notifyIccInfoChanged", null);
         break;
       case "RIL:VoiceInfoChanged":
         this.updateConnectionInfo(msg.json.data,
                                   this.rilContext.voiceConnectionInfo);
         this._deliverEvent("_mobileConnectionListeners",
                            "notifyVoiceChanged",
                            null);
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -2217,31 +2217,24 @@ RadioInterface.prototype = {
     gMessageManager.sendVoicemailMessage("RIL:VoicemailInfoChanged",
                                          this.clientId, voicemailInfo);
   },
 
   handleIccInfoChange: function handleIccInfoChange(message) {
     let oldIccInfo = this.rilContext.iccInfo;
     this.rilContext.iccInfo = message;
 
-    let iccInfoChanged = !oldIccInfo ||
-                          oldIccInfo.iccid != message.iccid ||
-                          oldIccInfo.mcc != message.mcc ||
-                          oldIccInfo.mnc != message.mnc ||
-                          oldIccInfo.spn != message.spn ||
-                          oldIccInfo.isDisplayNetworkNameRequired != message.isDisplayNetworkNameRequired ||
-                          oldIccInfo.isDisplaySpnRequired != message.isDisplaySpnRequired ||
-                          oldIccInfo.msisdn != message.msisdn;
-    if (!iccInfoChanged) {
+    if (!this.isInfoChanged(message, oldIccInfo)) {
       return;
     }
     // RIL:IccInfoChanged corresponds to a DOM event that gets fired only
-    // when the MCC or MNC codes have changed.
+    // when iccInfo has changed.
     gMessageManager.sendIccMessage("RIL:IccInfoChanged",
-                                   this.clientId, message);
+                                   this.clientId,
+                                   message.iccType ? message : null);
 
     // Update lastKnownSimMcc.
     if (message.mcc) {
       try {
         Services.prefs.setCharPref("ril.lastKnownSimMcc",
                                    message.mcc.toString());
       } catch (e) {}
     }
@@ -2989,17 +2982,17 @@ RadioInterface.prototype = {
     }
 
     // Re-sync options.segmentMaxSeq with actual length of returning array.
     options.segmentMaxSeq = options.segments.length;
 
     return options;
   },
 
-  getSegmentInfoForText: function getSegmentInfoForText(text) {
+  getSegmentInfoForText: function getSegmentInfoForText(text, request) {
     let strict7BitEncoding;
     try {
       strict7BitEncoding = Services.prefs.getBoolPref("dom.sms.strict7BitEncoding");
     } catch (e) {
       strict7BitEncoding = false;
     }
 
     let options = this._fragmentText(text, null, strict7BitEncoding);
@@ -3010,20 +3003,21 @@ RadioInterface.prototype = {
       if (options.dcs == RIL.PDU_DCS_MSG_CODING_16BITS_ALPHABET) {
         // In UCS2 encoding, encodedBodyLength is in octets.
         charsInLastSegment /= 2;
       }
     } else {
       charsInLastSegment = 0;
     }
 
-    let result = gMobileMessageService.createSmsSegmentInfo(options.segmentMaxSeq,
-                                                            options.segmentChars,
-                                                            options.segmentChars - charsInLastSegment);
-    return result;
+    let result = gMobileMessageService
+                 .createSmsSegmentInfo(options.segmentMaxSeq,
+                                       options.segmentChars,
+                                       options.segmentChars - charsInLastSegment);
+    request.notifySegmentInfoForTextGot(result);
   },
 
   sendSMS: function sendSMS(number, message, silent, request) {
     let strict7BitEncoding;
     try {
       strict7BitEncoding = Services.prefs.getBoolPref("dom.sms.strict7BitEncoding");
     } catch (e) {
       strict7BitEncoding = false;
--- a/dom/system/gonk/nsIRadioInterfaceLayer.idl
+++ b/dom/system/gonk/nsIRadioInterfaceLayer.idl
@@ -1,17 +1,16 @@
 /* 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/. */
 
 #include "nsISupports.idl"
 
 interface nsIDOMMozIccInfo;
 interface nsIDOMMozMobileConnectionInfo;
-interface nsIDOMMozSmsSegmentInfo;
 interface nsIMobileMessageCallback;
 
 [scriptable, uuid(1e602d20-d066-4399-8997-daf36b3158ef)]
 interface nsIRILDataCallInfo : nsISupports
 {
   /**
    * Current data call state, one of the
    * nsINetworkInterface::NETWORK_STATE_* constants.
@@ -74,17 +73,17 @@ interface nsIRilContext : nsISupports
 
   readonly attribute nsIDOMMozIccInfo iccInfo;
 
   readonly attribute nsIDOMMozMobileConnectionInfo voice;
 
   readonly attribute nsIDOMMozMobileConnectionInfo data;
 };
 
-[scriptable, uuid(5efcd358-080e-46d6-a7f7-4f36c204eec3)]
+[scriptable, uuid(a50d65aa-00da-11e3-b954-7bfb233d98fc)]
 interface nsIRadioInterface : nsISupports
 {
   readonly attribute nsIRilContext rilContext;
 
   /**
    * PDP APIs
    */
   void setupDataCallByType(in DOMString apntype);
@@ -94,17 +93,18 @@ interface nsIRadioInterface : nsISupport
   void registerDataCallCallback(in nsIRILDataCallback callback);
   void unregisterDataCallCallback(in nsIRILDataCallback callback);
 
   void updateRILNetworkInterface();
 
   /**
    * SMS-related functionality.
    */
-  nsIDOMMozSmsSegmentInfo getSegmentInfoForText(in DOMString text);
+  void getSegmentInfoForText(in DOMString text,
+                             in nsIMobileMessageCallback request);
 
   void sendSMS(in DOMString number,
                in DOMString message,
                in boolean silent,
                in nsIMobileMessageCallback request);
 };
 
 [scriptable, uuid(44b03951-1444-4c03-bd37-0bcb3a01b56f)]
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -365,16 +365,25 @@ this.CARD_APPTYPE_UNKNOWN = 0;
 this.CARD_APPTYPE_SIM = 1;
 this.CARD_APPTYPE_USIM = 2;
 this.CARD_APPTYPE_RUIM = 3;
 this.CARD_APPTYPE_CSIM = 4;
 this.CARD_APPTYPE_ISIM = 5;
 
 this.CARD_MAX_APPS = 8;
 
+this.GECKO_CARD_TYPE = [
+  null,
+  "sim",
+  "usim",
+  "ruim",
+  "csim",
+  "isim"
+];
+
 this.NETWORK_STATE_UNKNOWN = "unknown";
 this.NETWORK_STATE_AVAILABLE = "available";
 this.NETWORK_STATE_CONNECTED = "connected";
 this.NETWORK_STATE_FORBIDDEN = "forbidden";
 
 this.NETWORK_SELECTION_MODE_AUTOMATIC = 0;
 this.NETWORK_SELECTION_MODE_MANUAL = 1;
 
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -91,17 +91,109 @@ let RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS
 // Needed for call-waiting on Peak device
 let RILQUIRKS_EXTRA_UINT32_2ND_CALL = libcutils.property_get("ro.moz.ril.extra_int_2nd_call", "false") == "true";
 // On the emulator we support querying the number of lock retries
 let RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT = libcutils.property_get("ro.moz.ril.query_icc_count", "false") == "true";
 
 // Marker object.
 let PENDING_NETWORK_TYPE = {};
 
-let Buf = require("resource://gre/modules/workers/worker_buf.js");
+let Buf = {
+  __proto__: (function(){
+    return require("resource://gre/modules/workers/worker_buf.js").Buf;
+  })(),
+
+  mToken: 0,
+  mTokenRequestMap: null,
+
+  init: function init() {
+    this._init();
+
+    // This gets incremented each time we send out a parcel.
+    this.mToken = 1;
+
+    // Maps tokens we send out with requests to the request type, so that
+    // when we get a response parcel back, we know what request it was for.
+    this.mTokenRequestMap = {};
+  },
+
+  /**
+   * Process one parcel.
+   */
+  processParcel: function processParcel() {
+    let response_type = this.readUint32();
+
+    let request_type, options;
+    if (response_type == RESPONSE_TYPE_SOLICITED) {
+      let token = this.readUint32();
+      let error = this.readUint32();
+
+      options = this.mTokenRequestMap[token];
+      if (!options) {
+        if (DEBUG) {
+          debug("Suspicious uninvited request found: " + token + ". Ignored!");
+        }
+        return;
+      }
+
+      delete this.mTokenRequestMap[token];
+      request_type = options.rilRequestType;
+
+      options.rilRequestError = error;
+      if (DEBUG) {
+        debug("Solicited response for request type " + request_type +
+              ", token " + token + ", error " + error);
+      }
+    } else if (response_type == RESPONSE_TYPE_UNSOLICITED) {
+      request_type = this.readUint32();
+      if (DEBUG) debug("Unsolicited response for request type " + request_type);
+    } else {
+      if (DEBUG) debug("Unknown response type: " + response_type);
+      return;
+    }
+
+    RIL.handleParcel(request_type, this.mReadAvailable, options);
+  },
+
+  /**
+   * Start a new outgoing parcel.
+   *
+   * @param type
+   *        Integer specifying the request type.
+   * @param options [optional]
+   *        Object containing information about the request, e.g. the
+   *        original main thread message object that led to the RIL request.
+   */
+  newParcel: function newParcel(type, options) {
+    if (DEBUG) debug("New outgoing parcel of type " + type);
+
+    // We're going to leave room for the parcel size at the beginning.
+    this.mOutgoingIndex = this.PARCEL_SIZE_SIZE;
+    this.writeUint32(type);
+    this.writeUint32(this.mToken);
+
+    if (!options) {
+      options = {};
+    }
+    options.rilRequestType = type;
+    options.rilRequestError = null;
+    this.mTokenRequestMap[this.mToken] = options;
+    this.mToken++;
+    return this.mToken;
+  },
+
+  simpleRequest: function simpleRequest(type, options) {
+    this.newParcel(type, options);
+    this.sendParcel();
+  },
+
+  onSendParcel: function onSendParcel(parcel) {
+    postRILMessage(CLIENT_ID, parcel);
+  }
+};
 
 /**
  * The RIL state machine.
  *
  * This object communicates with rild via parcels and with the main thread
  * via post messages. It maintains state about the radio, ICC, calls, etc.
  * and acts upon state changes accordingly.
  */
@@ -1217,16 +1309,20 @@ let RIL = {
   getBasebandVersion: function getBasebandVersion() {
     Buf.simpleRequest(REQUEST_BASEBAND_VERSION);
   },
 
   sendExitEmergencyCbModeRequest: function sendExitEmergencyCbModeRequest(options) {
     Buf.simpleRequest(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, options);
   },
 
+  getCdmaSubscription: function getCdmaSubscription() {
+    Buf.simpleRequest(REQUEST_CDMA_SUBSCRIPTION);
+  },
+
   exitEmergencyCbMode: function exitEmergencyCbMode(options) {
     // The function could be called by an API from RadioInterfaceLayer or by
     // ril_worker itself. From ril_worker, we won't pass the parameter
     // 'options'. In this case, it is marked as internal.
     if (!options) {
       options = {internal: true};
     }
     this._cancelEmergencyCbModeTimeout();
@@ -2832,16 +2928,19 @@ let RIL = {
             debug("ICC absent");
           }
           newCardState = GECKO_CARDSTATE_ABSENT;
           break;
       }
       if (newCardState == this.cardState) {
         return;
       }
+      this.iccInfo = {iccType: null};
+      ICCUtilsHelper.handleICCInfoChange();
+
       this.cardState = newCardState;
       this.sendChromeMessage({rilMessageType: "cardstatechange",
                               cardState: this.cardState});
       return;
     }
 
     let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex :
                                iccStatus.gsmUmtsSubscriptionAppIndex;
@@ -2883,16 +2982,18 @@ let RIL = {
 
     if (this.cardState == newCardState) {
       return;
     }
 
     // This was moved down from CARD_APPSTATE_READY
     this.requestNetworkInfo();
     if (newCardState == GECKO_CARDSTATE_READY) {
+      this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType];
+
       // For type SIM, we need to check EF_phase first.
       // Other types of ICC we can send Terminal_Profile immediately.
       if (this.appType == CARD_APPTYPE_SIM) {
         ICCRecordHelper.readICCPhase();
         ICCRecordHelper.fetchICCRecords();
       } else if (this.appType == CARD_APPTYPE_USIM) {
         this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
         ICCRecordHelper.fetchICCRecords();
@@ -5666,17 +5767,31 @@ RIL[REQUEST_GSM_SET_BROADCAST_SMS_CONFIG
   if (options.rilRequestError == ERROR_SUCCESS) {
     this.setSmsBroadcastActivation(true);
   }
 };
 RIL[REQUEST_GSM_SMS_BROADCAST_ACTIVATION] = null;
 RIL[REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG] = null;
 RIL[REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG] = null;
 RIL[REQUEST_CDMA_SMS_BROADCAST_ACTIVATION] = null;
-RIL[REQUEST_CDMA_SUBSCRIPTION] = null;
+RIL[REQUEST_CDMA_SUBSCRIPTION] = function REQUEST_CDMA_SUBSCRIPTION(length, options) {
+  if (options.rilRequestError) {
+    return;
+  }
+
+  let result = Buf.readStringList();
+
+  this.iccInfo.mdn = result[0];
+  // The result[1] is Home SID. (Already be handled in readCDMAHome())
+  // The result[2] is Home NID. (Already be handled in readCDMAHome())
+  this.iccInfo.min = result[3];
+  // The result[4] is PRL version.
+
+  ICCUtilsHelper.handleICCInfoChange();
+};
 RIL[REQUEST_CDMA_WRITE_SMS_TO_RUIM] = null;
 RIL[REQUEST_CDMA_DELETE_SMS_ON_RUIM] = null;
 RIL[REQUEST_DEVICE_IDENTITY] = function REQUEST_DEVICE_IDENTITY(length, options) {
   if (options.rilRequestError) {
     return;
   }
 
   let result = Buf.readStringList();
@@ -10980,21 +11095,21 @@ let ICCRecordHelper = {
    * Read ICC MBDN. (Mailbox Dialling Number)
    *
    * @see TS 131.102, clause 4.2.60
    */
   readMBDN: function readMBDN() {
     function callback(options) {
       let contact = GsmPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
       if (!contact ||
-          (RIL.iccInfo.mbdn !== undefined &&
-           RIL.iccInfo.mbdn === contact.number)) {
+          (RIL.iccInfoPrivate.mbdn !== undefined &&
+           RIL.iccInfoPrivate.mbdn === contact.number)) {
         return;
       }
-      RIL.iccInfo.mbdn = contact.number;
+      RIL.iccInfoPrivate.mbdn = contact.number;
       if (DEBUG) {
         debug("MBDN, alphaId="+contact.alphaId+" number="+contact.number);
       }
       contact.rilMessageType = "iccmbdn";
       RIL.sendChromeMessage(contact);
     }
 
     ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_MBDN,
@@ -12663,16 +12778,17 @@ let ICCContactHelper = {
 };
 
 let RuimRecordHelper = {
   fetchRuimRecords: function fetchRuimRecords() {
     ICCRecordHelper.readICCID();
     RIL.getIMSI();
     this.readCST();
     this.readCDMAHome();
+    RIL.getCdmaSubscription();
   },
 
   /**
    * Read CDMAHOME for CSIM.
    * See 3GPP2 C.S0023 Sec. 3.4.8.
    */
   readCDMAHome: function readCDMAHome() {
     function callback(options) {
@@ -12787,19 +12903,16 @@ let RuimRecordHelper = {
 
 /**
  * Global stuff.
  */
 
 // Initialize buffers. This is a separate function so that unit tests can
 // re-initialize the buffers at will.
 Buf.init();
-Buf.setOutputStream(function (parcel) {
-  postRILMessage(CLIENT_ID, parcel);
-});
 
 function onRILMessage(data) {
   Buf.processIncoming(data);
 }
 
 onmessage = function onmessage(event) {
   RIL.handleChromeMessage(event.data);
 };
--- a/dom/system/gonk/worker_buf.js
+++ b/dom/system/gonk/worker_buf.js
@@ -1,676 +1,609 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const INT32_MAX   = 2147483647;
 const UINT8_SIZE  = 1;
 const UINT16_SIZE = 2;
 const UINT32_SIZE = 4;
-const PARCEL_SIZE_SIZE = UINT32_SIZE;
 
 /**
  * This object contains helpers buffering incoming data & deconstructing it
  * into parcels as well as buffering outgoing data & constructing parcels.
  * For that it maintains two buffers and corresponding uint8 views, indexes.
  *
  * The incoming buffer is a circular buffer where we store incoming data.
  * As soon as a complete parcel is received, it is processed right away, so
  * the buffer only needs to be large enough to hold one parcel.
  *
  * The outgoing buffer is to prepare outgoing parcels. The index is reset
  * every time a parcel is sent.
  */
 
-let mIncomingBufferLength = 1024;
-let mOutgoingBufferLength = 1024;
-let mIncomingBuffer, mOutgoingBuffer, mIncomingBytes, mOutgoingBytes,
-    mIncomingWriteIndex, mIncomingReadIndex, mOutgoingIndex, mReadIncoming,
-    mReadAvailable, mCurrentParcelSize, mToken, mTokenRequestMap,
-    mLasSolicitedToken, mOutgoingBufferCalSizeQueue, mOutputStream;
-
-function init() {
-  mIncomingBuffer = new ArrayBuffer(mIncomingBufferLength);
-  mOutgoingBuffer = new ArrayBuffer(mOutgoingBufferLength);
-
-  mIncomingBytes = new Uint8Array(mIncomingBuffer);
-  mOutgoingBytes = new Uint8Array(mOutgoingBuffer);
-
-  // Track where incoming data is read from and written to.
-  mIncomingWriteIndex = 0;
-  mIncomingReadIndex = 0;
-
-  // Leave room for the parcel size for outgoing parcels.
-  mOutgoingIndex = PARCEL_SIZE_SIZE;
+let Buf = {
+  PARCEL_SIZE_SIZE: UINT32_SIZE,
 
-  // How many bytes we've read for this parcel so far.
-  mReadIncoming = 0;
-
-  // How many bytes available as parcel data.
-  mReadAvailable = 0;
-
-  // Size of the incoming parcel. If this is zero, we're expecting a new
-  // parcel.
-  mCurrentParcelSize = 0;
+  mIncomingBufferLength: 1024,
+  mIncomingBuffer: null,
+  mIncomingBytes: null,
+  mIncomingWriteIndex: 0,
+  mIncomingReadIndex: 0,
+  mReadIncoming: 0,
+  mReadAvailable: 0,
+  mCurrentParcelSize: 0,
 
-  // This gets incremented each time we send out a parcel.
-  mToken = 1;
-
-  // Maps tokens we send out with requests to the request type, so that
-  // when we get a response parcel back, we know what request it was for.
-  mTokenRequestMap = {};
+  mOutgoingBufferLength: 1024,
+  mOutgoingBuffer: null,
+  mOutgoingBytes: null,
+  mOutgoingIndex: 0,
+  mOutgoingBufferCalSizeQueue: null,
 
-  // This is the token of last solicited response.
-  mLasSolicitedToken = 0;
-
-  // Queue for storing outgoing override points
-  mOutgoingBufferCalSizeQueue = [];
-}
+  _init: function _init() {
+    this.mIncomingBuffer = new ArrayBuffer(this.mIncomingBufferLength);
+    this.mOutgoingBuffer = new ArrayBuffer(this.mOutgoingBufferLength);
 
-/**
- * Mark current mOutgoingIndex as start point for calculation length of data
- * written to mOutgoingBuffer.
- * Mark can be nested for here uses queue to remember marks.
- *
- * @param writeFunction
- *        Function to write data length into mOutgoingBuffer, this function is
- *        also used to allocate buffer for data length.
- *        Raw data size(in Uint8) is provided as parameter calling writeFunction.
- *        If raw data size is not in proper unit for writing, user can adjust
- *        the length value in writeFunction before writing.
- **/
-function startCalOutgoingSize(writeFunction) {
-  let sizeInfo = {index: mOutgoingIndex,
-                  write: writeFunction};
+    this.mIncomingBytes = new Uint8Array(this.mIncomingBuffer);
+    this.mOutgoingBytes = new Uint8Array(this.mOutgoingBuffer);
 
-  // Allocate buffer for data lemgtj.
-  writeFunction.call(0);
+    // Track where incoming data is read from and written to.
+    this.mIncomingWriteIndex = 0;
+    this.mIncomingReadIndex = 0;
 
-  // Get size of data length buffer for it is not counted into data size.
-  sizeInfo.size = mOutgoingIndex - sizeInfo.index;
+    // Leave room for the parcel size for outgoing parcels.
+    this.mOutgoingIndex = this.PARCEL_SIZE_SIZE;
 
-  // Enqueue size calculation information.
-  mOutgoingBufferCalSizeQueue.push(sizeInfo);
-}
+    // How many bytes we've read for this parcel so far.
+    this.mReadIncoming = 0;
 
-/**
- * Calculate data length since last mark, and write it into mark position.
- **/
-function stopCalOutgoingSize() {
-  let sizeInfo = mOutgoingBufferCalSizeQueue.pop();
+    // How many bytes available as parcel data.
+    this.mReadAvailable = 0;
 
-  // Remember current mOutgoingIndex.
-  let currentOutgoingIndex = mOutgoingIndex;
-  // Calculate data length, in uint8.
-  let writeSize = mOutgoingIndex - sizeInfo.index - sizeInfo.size;
+    // Size of the incoming parcel. If this is zero, we're expecting a new
+    // parcel.
+    this.mCurrentParcelSize = 0;
 
-  // Write data length to mark, use same function for allocating buffer to make
-  // sure there is no buffer overloading.
-  mOutgoingIndex = sizeInfo.index;
-  sizeInfo.write(writeSize);
-
-  // Restore mOutgoingIndex.
-  mOutgoingIndex = currentOutgoingIndex;
-}
+    // Queue for storing outgoing override points
+    this.mOutgoingBufferCalSizeQueue = [];
+  },
 
-/**
- * Grow the incoming buffer.
- *
- * @param min_size
- *        Minimum new size. The actual new size will be the the smallest
- *        power of 2 that's larger than this number.
- */
-function growIncomingBuffer(min_size) {
-  if (DEBUG) {
-    debug("Current buffer of " + mIncomingBufferLength +
-          " can't handle incoming " + min_size + " bytes.");
-  }
-  let oldBytes = mIncomingBytes;
-  mIncomingBufferLength =
-    2 << Math.floor(Math.log(min_size)/Math.log(2));
-  if (DEBUG) debug("New incoming buffer size: " + mIncomingBufferLength);
-  mIncomingBuffer = new ArrayBuffer(mIncomingBufferLength);
-  mIncomingBytes = new Uint8Array(mIncomingBuffer);
-  if (mIncomingReadIndex <= mIncomingWriteIndex) {
-    // Read and write index are in natural order, so we can just copy
-    // the old buffer over to the bigger one without having to worry
-    // about the indexes.
-    mIncomingBytes.set(oldBytes, 0);
-  } else {
-    // The write index has wrapped around but the read index hasn't yet.
-    // Write whatever the read index has left to read until it would
-    // circle around to the beginning of the new buffer, and the rest
-    // behind that.
-    let head = oldBytes.subarray(mIncomingReadIndex);
-    let tail = oldBytes.subarray(0, mIncomingReadIndex);
-    mIncomingBytes.set(head, 0);
-    mIncomingBytes.set(tail, head.length);
-    mIncomingReadIndex = 0;
-    mIncomingWriteIndex += head.length;
-  }
-  if (DEBUG) {
-    debug("New incoming buffer size is " + mIncomingBufferLength);
-  }
-}
+  /**
+   * Mark current mOutgoingIndex as start point for calculation length of data
+   * written to mOutgoingBuffer.
+   * Mark can be nested for here uses queue to remember marks.
+   *
+   * @param writeFunction
+   *        Function to write data length into mOutgoingBuffer, this function is
+   *        also used to allocate buffer for data length.
+   *        Raw data size(in Uint8) is provided as parameter calling writeFunction.
+   *        If raw data size is not in proper unit for writing, user can adjust
+   *        the length value in writeFunction before writing.
+   **/
+  startCalOutgoingSize: function startCalOutgoingSize(writeFunction) {
+    let sizeInfo = {index: this.mOutgoingIndex,
+                    write: writeFunction};
 
-/**
- * Grow the outgoing buffer.
- *
- * @param min_size
- *        Minimum new size. The actual new size will be the the smallest
- *        power of 2 that's larger than this number.
- */
-function growOutgoingBuffer(min_size) {
-  if (DEBUG) {
-    debug("Current buffer of " + mOutgoingBufferLength +
-          " is too small.");
-  }
-  let oldBytes = mOutgoingBytes;
-  mOutgoingBufferLength =
-    2 << Math.floor(Math.log(min_size)/Math.log(2));
-  mOutgoingBuffer = new ArrayBuffer(mOutgoingBufferLength);
-  mOutgoingBytes = new Uint8Array(mOutgoingBuffer);
-  mOutgoingBytes.set(oldBytes, 0);
-  if (DEBUG) {
-    debug("New outgoing buffer size is " + mOutgoingBufferLength);
-  }
-}
+    // Allocate buffer for data lemgtj.
+    writeFunction.call(0);
 
-/**
- * Functions for reading data from the incoming buffer.
- *
- * These are all little endian, apart from readParcelSize();
- */
+    // Get size of data length buffer for it is not counted into data size.
+    sizeInfo.size = this.mOutgoingIndex - sizeInfo.index;
 
-/**
- * Ensure position specified is readable.
- *
- * @param index
- *        Data position in incoming parcel, valid from 0 to
- *        mCurrentParcelSize.
- */
-function ensureIncomingAvailable(index) {
-  if (index >= mCurrentParcelSize) {
-    throw new Error("Trying to read data beyond the parcel end!");
-  } else if (index < 0) {
-    throw new Error("Trying to read data before the parcel begin!");
-  }
-}
+    // Enqueue size calculation information.
+    this.mOutgoingBufferCalSizeQueue.push(sizeInfo);
+  },
 
-/**
- * Seek in current incoming parcel.
- *
- * @param offset
- *        Seek offset in relative to current position.
- */
-function seekIncoming(offset) {
-  // Translate to 0..mCurrentParcelSize
-  let cur = mCurrentParcelSize - mReadAvailable;
-
-  let newIndex = cur + offset;
-  ensureIncomingAvailable(newIndex);
-
-  // ... mIncomingReadIndex -->|
-  // 0               new     cur           mCurrentParcelSize
-  // |================|=======|===================|
-  // |<--        cur       -->|<- mReadAvailable ->|
-  // |<-- newIndex -->|<--  new mReadAvailable  -->|
-  mReadAvailable = mCurrentParcelSize - newIndex;
+  /**
+   * Calculate data length since last mark, and write it into mark position.
+   **/
+  stopCalOutgoingSize: function stopCalOutgoingSize() {
+    let sizeInfo = this.mOutgoingBufferCalSizeQueue.pop();
 
-  // Translate back:
-  if (mIncomingReadIndex < cur) {
-    // The mIncomingReadIndex is wrapped.
-    newIndex += mIncomingBufferLength;
-  }
-  newIndex += (mIncomingReadIndex - cur);
-  newIndex %= mIncomingBufferLength;
-  mIncomingReadIndex = newIndex;
-}
-
-function readUint8Unchecked() {
-  let value = mIncomingBytes[mIncomingReadIndex];
-  mIncomingReadIndex = (mIncomingReadIndex + 1) %
-                           mIncomingBufferLength;
-  return value;
-}
+    // Remember current mOutgoingIndex.
+    let currentOutgoingIndex = this.mOutgoingIndex;
+    // Calculate data length, in uint8.
+    let writeSize = this.mOutgoingIndex - sizeInfo.index - sizeInfo.size;
 
-function readUint8() {
-  // Translate to 0..mCurrentParcelSize
-  let cur = mCurrentParcelSize - mReadAvailable;
-  ensureIncomingAvailable(cur);
-
-  mReadAvailable--;
-  return readUint8Unchecked();
-}
+    // Write data length to mark, use same function for allocating buffer to make
+    // sure there is no buffer overloading.
+    this.mOutgoingIndex = sizeInfo.index;
+    sizeInfo.write(writeSize);
 
-function readUint8Array(length) {
-  // Translate to 0..mCurrentParcelSize
-  let last = mCurrentParcelSize - mReadAvailable;
-  last += (length - 1);
-  ensureIncomingAvailable(last);
-
-  let array = new Uint8Array(length);
-  for (let i = 0; i < length; i++) {
-    array[i] = readUint8Unchecked();
-  }
-
-  mReadAvailable -= length;
-  return array;
-}
+    // Restore mOutgoingIndex.
+    this.mOutgoingIndex = currentOutgoingIndex;
+  },
 
-function readUint16() {
-  return readUint8() | readUint8() << 8;
-}
-
-function readUint32() {
-  return readUint8()       | readUint8() <<  8 |
-         readUint8() << 16 | readUint8() << 24;
-}
-
-function readUint32List() {
-  let length = readUint32();
-  let ints = [];
-  for (let i = 0; i < length; i++) {
-    ints.push(readUint32());
-  }
-  return ints;
-}
-
-function readString() {
-  let string_len = readUint32();
-  if (string_len < 0 || string_len >= INT32_MAX) {
-    return null;
-  }
-  let s = "";
-  for (let i = 0; i < string_len; i++) {
-    s += String.fromCharCode(readUint16());
-  }
-  // Strings are \0\0 delimited, but that isn't part of the length. And
-  // if the string length is even, the delimiter is two characters wide.
-  // It's insane, I know.
-  readStringDelimiter(string_len);
-  return s;
-}
-
-function readStringList() {
-  let num_strings = readUint32();
-  let strings = [];
-  for (let i = 0; i < num_strings; i++) {
-    strings.push(readString());
-  }
-  return strings;
-}
-
-function readStringDelimiter(length) {
-  let delimiter = readUint16();
-  if (!(length & 1)) {
-    delimiter |= readUint16();
-  }
-  if (DEBUG) {
-    if (delimiter !== 0) {
-      debug("Something's wrong, found string delimiter: " + delimiter);
+  /**
+   * Grow the incoming buffer.
+   *
+   * @param min_size
+   *        Minimum new size. The actual new size will be the the smallest
+   *        power of 2 that's larger than this number.
+   */
+  growIncomingBuffer: function growIncomingBuffer(min_size) {
+    if (DEBUG) {
+      debug("Current buffer of " + this.mIncomingBufferLength +
+            " can't handle incoming " + min_size + " bytes.");
     }
-  }
-}
+    let oldBytes = this.mIncomingBytes;
+    this.mIncomingBufferLength =
+      2 << Math.floor(Math.log(min_size)/Math.log(2));
+    if (DEBUG) debug("New incoming buffer size: " + this.mIncomingBufferLength);
+    this.mIncomingBuffer = new ArrayBuffer(this.mIncomingBufferLength);
+    this.mIncomingBytes = new Uint8Array(this.mIncomingBuffer);
+    if (this.mIncomingReadIndex <= this.mIncomingWriteIndex) {
+      // Read and write index are in natural order, so we can just copy
+      // the old buffer over to the bigger one without having to worry
+      // about the indexes.
+      this.mIncomingBytes.set(oldBytes, 0);
+    } else {
+      // The write index has wrapped around but the read index hasn't yet.
+      // Write whatever the read index has left to read until it would
+      // circle around to the beginning of the new buffer, and the rest
+      // behind that.
+      let head = oldBytes.subarray(this.mIncomingReadIndex);
+      let tail = oldBytes.subarray(0, this.mIncomingReadIndex);
+      this.mIncomingBytes.set(head, 0);
+      this.mIncomingBytes.set(tail, head.length);
+      this.mIncomingReadIndex = 0;
+      this.mIncomingWriteIndex += head.length;
+    }
+    if (DEBUG) {
+      debug("New incoming buffer size is " + this.mIncomingBufferLength);
+    }
+  },
 
-function readParcelSize() {
-  return readUint8Unchecked() << 24 |
-         readUint8Unchecked() << 16 |
-         readUint8Unchecked() <<  8 |
-         readUint8Unchecked();
-}
-
-/**
- * Functions for writing data to the outgoing buffer.
- */
-
-/**
- * Ensure position specified is writable.
- *
- * @param index
- *        Data position in outgoing parcel, valid from 0 to
- *        mOutgoingBufferLength.
- */
-function ensureOutgoingAvailable(index) {
-  if (index >= mOutgoingBufferLength) {
-    growOutgoingBuffer(index + 1);
-  }
-}
-
-function writeUint8(value) {
-  ensureOutgoingAvailable(mOutgoingIndex);
-
-  mOutgoingBytes[mOutgoingIndex] = value;
-  mOutgoingIndex++;
-}
+  /**
+   * Grow the outgoing buffer.
+   *
+   * @param min_size
+   *        Minimum new size. The actual new size will be the the smallest
+   *        power of 2 that's larger than this number.
+   */
+  growOutgoingBuffer: function growOutgoingBuffer(min_size) {
+    if (DEBUG) {
+      debug("Current buffer of " + this.mOutgoingBufferLength +
+            " is too small.");
+    }
+    let oldBytes = this.mOutgoingBytes;
+    this.mOutgoingBufferLength =
+      2 << Math.floor(Math.log(min_size)/Math.log(2));
+    this.mOutgoingBuffer = new ArrayBuffer(this.mOutgoingBufferLength);
+    this.mOutgoingBytes = new Uint8Array(this.mOutgoingBuffer);
+    this.mOutgoingBytes.set(oldBytes, 0);
+    if (DEBUG) {
+      debug("New outgoing buffer size is " + this.mOutgoingBufferLength);
+    }
+  },
 
-function writeUint16(value) {
-  writeUint8(value & 0xff);
-  writeUint8((value >> 8) & 0xff);
-}
-
-function writeUint32(value) {
-  writeUint8(value & 0xff);
-  writeUint8((value >> 8) & 0xff);
-  writeUint8((value >> 16) & 0xff);
-  writeUint8((value >> 24) & 0xff);
-}
-
-function writeString(value) {
-  if (value == null) {
-    writeUint32(-1);
-    return;
-  }
-  writeUint32(value.length);
-  for (let i = 0; i < value.length; i++) {
-    writeUint16(value.charCodeAt(i));
-  }
-  // Strings are \0\0 delimited, but that isn't part of the length. And
-  // if the string length is even, the delimiter is two characters wide.
-  // It's insane, I know.
-  writeStringDelimiter(value.length);
-}
-
-function writeStringList(strings) {
-  writeUint32(strings.length);
-  for (let i = 0; i < strings.length; i++) {
-    writeString(strings[i]);
-  }
-}
+  /**
+   * Functions for reading data from the incoming buffer.
+   *
+   * These are all little endian, apart from readParcelSize();
+   */
 
-function writeStringDelimiter(length) {
-  writeUint16(0);
-  if (!(length & 1)) {
-    writeUint16(0);
-  }
-}
-
-function writeParcelSize(value) {
   /**
-   *  Parcel size will always be the first thing in the parcel byte
-   *  array, but the last thing written. Store the current index off
-   *  to a temporary to be reset after we write the size.
+   * Ensure position specified is readable.
+   *
+   * @param index
+   *        Data position in incoming parcel, valid from 0 to
+   *        mCurrentParcelSize.
    */
-  let currentIndex = mOutgoingIndex;
-  mOutgoingIndex = 0;
-  writeUint8((value >> 24) & 0xff);
-  writeUint8((value >> 16) & 0xff);
-  writeUint8((value >> 8) & 0xff);
-  writeUint8(value & 0xff);
-  mOutgoingIndex = currentIndex;
-}
-
-function copyIncomingToOutgoing(length) {
-  if (!length || (length < 0)) {
-    return;
-  }
+  ensureIncomingAvailable: function ensureIncomingAvailable(index) {
+    if (index >= this.mCurrentParcelSize) {
+      throw new Error("Trying to read data beyond the parcel end!");
+    } else if (index < 0) {
+      throw new Error("Trying to read data before the parcel begin!");
+    }
+  },
 
-  let translatedReadIndexEnd = mCurrentParcelSize - mReadAvailable + length - 1;
-  ensureIncomingAvailable(translatedReadIndexEnd);
-
-  let translatedWriteIndexEnd = mOutgoingIndex + length - 1;
-  ensureOutgoingAvailable(translatedWriteIndexEnd);
+  /**
+   * Seek in current incoming parcel.
+   *
+   * @param offset
+   *        Seek offset in relative to current position.
+   */
+  seekIncoming: function seekIncoming(offset) {
+    // Translate to 0..mCurrentParcelSize
+    let cur = this.mCurrentParcelSize - this.mReadAvailable;
 
-  let newIncomingReadIndex = mIncomingReadIndex + length;
-  if (newIncomingReadIndex < mIncomingBufferLength) {
-    // Reading won't cause wrapping, go ahead with builtin copy.
-    mOutgoingBytes.set(mIncomingBytes.subarray(mIncomingReadIndex, newIncomingReadIndex),
-                           mOutgoingIndex);
-  } else {
-    // Not so lucky.
-    newIncomingReadIndex %= mIncomingBufferLength;
-    mOutgoingBytes.set(mIncomingBytes.subarray(mIncomingReadIndex, mIncomingBufferLength),
-                           mOutgoingIndex);
-    if (newIncomingReadIndex) {
-      let firstPartLength = mIncomingBufferLength - mIncomingReadIndex;
-      mOutgoingBytes.set(mIncomingBytes.subarray(0, newIncomingReadIndex),
-                             mOutgoingIndex + firstPartLength);
-    }
-  }
+    let newIndex = cur + offset;
+    this.ensureIncomingAvailable(newIndex);
 
-  mIncomingReadIndex = newIncomingReadIndex;
-  mReadAvailable -= length;
-  mOutgoingIndex += length;
-}
-
-/**
- * Parcel management
- */
+    // ... mIncomingReadIndex -->|
+    // 0               new     cur           mCurrentParcelSize
+    // |================|=======|====================|
+    // |<--        cur       -->|<- mReadAvailable ->|
+    // |<-- newIndex -->|<--  new mReadAvailable  -->|
+    this.mReadAvailable = this.mCurrentParcelSize - newIndex;
 
-/**
- * Write incoming data to the circular buffer.
- *
- * @param incoming
- *        Uint8Array containing the incoming data.
- */
-function writeToIncoming(incoming) {
-  // We don't have to worry about the head catching the tail since
-  // we process any backlog in parcels immediately, before writing
-  // new data to the buffer. So the only edge case we need to handle
-  // is when the incoming data is larger than the buffer size.
-  let minMustAvailableSize = incoming.length + mReadIncoming;
-  if (minMustAvailableSize > mIncomingBufferLength) {
-    growIncomingBuffer(minMustAvailableSize);
-  }
+    // Translate back:
+    if (this.mIncomingReadIndex < cur) {
+      // The mIncomingReadIndex is wrapped.
+      newIndex += this.mIncomingBufferLength;
+    }
+    newIndex += (this.mIncomingReadIndex - cur);
+    newIndex %= this.mIncomingBufferLength;
+    this.mIncomingReadIndex = newIndex;
+  },
+
+  readUint8Unchecked: function readUint8Unchecked() {
+    let value = this.mIncomingBytes[this.mIncomingReadIndex];
+    this.mIncomingReadIndex = (this.mIncomingReadIndex + 1) %
+                             this.mIncomingBufferLength;
+    return value;
+  },
 
-  // We can let the typed arrays do the copying if the incoming data won't
-  // wrap around the edges of the circular buffer.
-  let remaining = mIncomingBufferLength - mIncomingWriteIndex;
-  if (remaining >= incoming.length) {
-    mIncomingBytes.set(incoming, mIncomingWriteIndex);
-  } else {
-    // The incoming data would wrap around it.
-    let head = incoming.subarray(0, remaining);
-    let tail = incoming.subarray(remaining);
-    mIncomingBytes.set(head, mIncomingWriteIndex);
-    mIncomingBytes.set(tail, 0);
-  }
-  mIncomingWriteIndex = (mIncomingWriteIndex + incoming.length) %
-                            mIncomingBufferLength;
-}
+  readUint8: function readUint8() {
+    // Translate to 0..mCurrentParcelSize
+    let cur = this.mCurrentParcelSize - this.mReadAvailable;
+    this.ensureIncomingAvailable(cur);
+
+    this.mReadAvailable--;
+    return this.readUint8Unchecked();
+  },
 
-/**
- * Process incoming data.
- *
- * @param incoming
- *        Uint8Array containing the incoming data.
- */
-function processIncoming(incoming) {
-  if (DEBUG) {
-    debug("Received " + incoming.length + " bytes.");
-    debug("Already read " + mReadIncoming);
-  }
+  readUint8Array: function readUint8Array(length) {
+    // Translate to 0..mCurrentParcelSize
+    let last = this.mCurrentParcelSize - this.mReadAvailable;
+    last += (length - 1);
+    this.ensureIncomingAvailable(last);
 
-  writeToIncoming(incoming);
-  mReadIncoming += incoming.length;
-  while (true) {
-    if (!mCurrentParcelSize) {
-      // We're expecting a new parcel.
-      if (mReadIncoming < PARCEL_SIZE_SIZE) {
-        // We don't know how big the next parcel is going to be, need more
-        // data.
-        if (DEBUG) debug("Next parcel size unknown, going to sleep.");
-        return;
-      }
-      mCurrentParcelSize = readParcelSize();
-      if (DEBUG) debug("New incoming parcel of size " +
-                       mCurrentParcelSize);
-      // The size itself is not included in the size.
-      mReadIncoming -= PARCEL_SIZE_SIZE;
+    let array = new Uint8Array(length);
+    for (let i = 0; i < length; i++) {
+      array[i] = this.readUint8Unchecked();
     }
 
-    if (mReadIncoming < mCurrentParcelSize) {
-      // We haven't read enough yet in order to be able to process a parcel.
-      if (DEBUG) debug("Read " + mReadIncoming + ", but parcel size is "
-                       + mCurrentParcelSize + ". Going to sleep.");
+    this.mReadAvailable -= length;
+    return array;
+  },
+
+  readUint16: function readUint16() {
+    return this.readUint8() | this.readUint8() << 8;
+  },
+
+  readUint32: function readUint32() {
+    return this.readUint8()       | this.readUint8() <<  8 |
+           this.readUint8() << 16 | this.readUint8() << 24;
+  },
+
+  readUint32List: function readUint32List() {
+    let length = this.readUint32();
+    let ints = [];
+    for (let i = 0; i < length; i++) {
+      ints.push(this.readUint32());
+    }
+    return ints;
+  },
+
+  readString: function readString() {
+    let string_len = this.readUint32();
+    if (string_len < 0 || string_len >= INT32_MAX) {
+      return null;
+    }
+    let s = "";
+    for (let i = 0; i < string_len; i++) {
+      s += String.fromCharCode(this.readUint16());
+    }
+    // Strings are \0\0 delimited, but that isn't part of the length. And
+    // if the string length is even, the delimiter is two characters wide.
+    // It's insane, I know.
+    this.readStringDelimiter(string_len);
+    return s;
+  },
+
+  readStringList: function readStringList() {
+    let num_strings = this.readUint32();
+    let strings = [];
+    for (let i = 0; i < num_strings; i++) {
+      strings.push(this.readString());
+    }
+    return strings;
+  },
+
+  readStringDelimiter: function readStringDelimiter(length) {
+    let delimiter = this.readUint16();
+    if (!(length & 1)) {
+      delimiter |= this.readUint16();
+    }
+    if (DEBUG) {
+      if (delimiter !== 0) {
+        debug("Something's wrong, found string delimiter: " + delimiter);
+      }
+    }
+  },
+
+  readParcelSize: function readParcelSize() {
+    return this.readUint8Unchecked() << 24 |
+           this.readUint8Unchecked() << 16 |
+           this.readUint8Unchecked() <<  8 |
+           this.readUint8Unchecked();
+  },
+
+  /**
+   * Functions for writing data to the outgoing buffer.
+   */
+
+  /**
+   * Ensure position specified is writable.
+   *
+   * @param index
+   *        Data position in outgoing parcel, valid from 0 to
+   *        mOutgoingBufferLength.
+   */
+  ensureOutgoingAvailable: function ensureOutgoingAvailable(index) {
+    if (index >= this.mOutgoingBufferLength) {
+      this.growOutgoingBuffer(index + 1);
+    }
+  },
+
+  writeUint8: function writeUint8(value) {
+    this.ensureOutgoingAvailable(this.mOutgoingIndex);
+
+    this.mOutgoingBytes[this.mOutgoingIndex] = value;
+    this.mOutgoingIndex++;
+  },
+
+  writeUint16: function writeUint16(value) {
+    this.writeUint8(value & 0xff);
+    this.writeUint8((value >> 8) & 0xff);
+  },
+
+  writeUint32: function writeUint32(value) {
+    this.writeUint8(value & 0xff);
+    this.writeUint8((value >> 8) & 0xff);
+    this.writeUint8((value >> 16) & 0xff);
+    this.writeUint8((value >> 24) & 0xff);
+  },
+
+  writeString: function writeString(value) {
+    if (value == null) {
+      this.writeUint32(-1);
+      return;
+    }
+    this.writeUint32(value.length);
+    for (let i = 0; i < value.length; i++) {
+      this.writeUint16(value.charCodeAt(i));
+    }
+    // Strings are \0\0 delimited, but that isn't part of the length. And
+    // if the string length is even, the delimiter is two characters wide.
+    // It's insane, I know.
+    this.writeStringDelimiter(value.length);
+  },
+
+  writeStringList: function writeStringList(strings) {
+    this.writeUint32(strings.length);
+    for (let i = 0; i < strings.length; i++) {
+      this.writeString(strings[i]);
+    }
+  },
+
+  writeStringDelimiter: function writeStringDelimiter(length) {
+    this.writeUint16(0);
+    if (!(length & 1)) {
+      this.writeUint16(0);
+    }
+  },
+
+  writeParcelSize: function writeParcelSize(value) {
+    /**
+     *  Parcel size will always be the first thing in the parcel byte
+     *  array, but the last thing written. Store the current index off
+     *  to a temporary to be reset after we write the size.
+     */
+    let currentIndex = this.mOutgoingIndex;
+    this.mOutgoingIndex = 0;
+    this.writeUint8((value >> 24) & 0xff);
+    this.writeUint8((value >> 16) & 0xff);
+    this.writeUint8((value >> 8) & 0xff);
+    this.writeUint8(value & 0xff);
+    this.mOutgoingIndex = currentIndex;
+  },
+
+  copyIncomingToOutgoing: function copyIncomingToOutgoing(length) {
+    if (!length || (length < 0)) {
       return;
     }
 
-    // Alright, we have enough data to process at least one whole parcel.
-    // Let's do that.
-    let expectedAfterIndex = (mIncomingReadIndex + mCurrentParcelSize)
-                             % mIncomingBufferLength;
+    let translatedReadIndexEnd =
+      this.mCurrentParcelSize - this.mReadAvailable + length - 1;
+    this.ensureIncomingAvailable(translatedReadIndexEnd);
+
+    let translatedWriteIndexEnd = this.mOutgoingIndex + length - 1;
+    this.ensureOutgoingAvailable(translatedWriteIndexEnd);
 
-    if (DEBUG) {
-      let parcel;
-      if (expectedAfterIndex < mIncomingReadIndex) {
-        let head = mIncomingBytes.subarray(mIncomingReadIndex);
-        let tail = mIncomingBytes.subarray(0, expectedAfterIndex);
-        parcel = Array.slice(head).concat(Array.slice(tail));
-      } else {
-        parcel = Array.slice(mIncomingBytes.subarray(
-          mIncomingReadIndex, expectedAfterIndex));
+    let newIncomingReadIndex = this.mIncomingReadIndex + length;
+    if (newIncomingReadIndex < this.mIncomingBufferLength) {
+      // Reading won't cause wrapping, go ahead with builtin copy.
+      this.mOutgoingBytes
+          .set(this.mIncomingBytes.subarray(this.mIncomingReadIndex,
+                                            newIncomingReadIndex),
+               this.mOutgoingIndex);
+    } else {
+      // Not so lucky.
+      newIncomingReadIndex %= this.mIncomingBufferLength;
+      this.mOutgoingBytes
+          .set(this.mIncomingBytes.subarray(this.mIncomingReadIndex,
+                                            this.mIncomingBufferLength),
+               this.mOutgoingIndex);
+      if (newIncomingReadIndex) {
+        let firstPartLength = this.mIncomingBufferLength - this.mIncomingReadIndex;
+        this.mOutgoingBytes.set(this.mIncomingBytes.subarray(0, newIncomingReadIndex),
+                               this.mOutgoingIndex + firstPartLength);
       }
-      debug("Parcel (size " + mCurrentParcelSize + "): " + parcel);
     }
 
-    if (DEBUG) debug("We have at least one complete parcel.");
-    try {
-      mReadAvailable = mCurrentParcelSize;
-      processParcel();
-    } catch (ex) {
-      if (DEBUG) debug("Parcel handling threw " + ex + "\n" + ex.stack);
+    this.mIncomingReadIndex = newIncomingReadIndex;
+    this.mReadAvailable -= length;
+    this.mOutgoingIndex += length;
+  },
+
+  /**
+   * Parcel management
+   */
+
+  /**
+   * Write incoming data to the circular buffer.
+   *
+   * @param incoming
+   *        Uint8Array containing the incoming data.
+   */
+  writeToIncoming: function writeToIncoming(incoming) {
+    // We don't have to worry about the head catching the tail since
+    // we process any backlog in parcels immediately, before writing
+    // new data to the buffer. So the only edge case we need to handle
+    // is when the incoming data is larger than the buffer size.
+    let minMustAvailableSize = incoming.length + this.mReadIncoming;
+    if (minMustAvailableSize > this.mIncomingBufferLength) {
+      this.growIncomingBuffer(minMustAvailableSize);
+    }
+
+    // We can let the typed arrays do the copying if the incoming data won't
+    // wrap around the edges of the circular buffer.
+    let remaining = this.mIncomingBufferLength - this.mIncomingWriteIndex;
+    if (remaining >= incoming.length) {
+      this.mIncomingBytes.set(incoming, this.mIncomingWriteIndex);
+    } else {
+      // The incoming data would wrap around it.
+      let head = incoming.subarray(0, remaining);
+      let tail = incoming.subarray(remaining);
+      this.mIncomingBytes.set(head, this.mIncomingWriteIndex);
+      this.mIncomingBytes.set(tail, 0);
+    }
+    this.mIncomingWriteIndex = (this.mIncomingWriteIndex + incoming.length) %
+                               this.mIncomingBufferLength;
+  },
+
+  /**
+   * Process incoming data.
+   *
+   * @param incoming
+   *        Uint8Array containing the incoming data.
+   */
+  processIncoming: function processIncoming(incoming) {
+    if (DEBUG) {
+      debug("Received " + incoming.length + " bytes.");
+      debug("Already read " + this.mReadIncoming);
     }
 
-    // Ensure that the whole parcel was consumed.
-    if (mIncomingReadIndex != expectedAfterIndex) {
-      if (DEBUG) {
-        debug("Parcel handler didn't consume whole parcel, " +
-              Math.abs(expectedAfterIndex - mIncomingReadIndex) +
-              " bytes left over");
+    this.writeToIncoming(incoming);
+    this.mReadIncoming += incoming.length;
+    while (true) {
+      if (!this.mCurrentParcelSize) {
+        // We're expecting a new parcel.
+        if (this.mReadIncoming < this.PARCEL_SIZE_SIZE) {
+          // We don't know how big the next parcel is going to be, need more
+          // data.
+          if (DEBUG) debug("Next parcel size unknown, going to sleep.");
+          return;
+        }
+        this.mCurrentParcelSize = this.readParcelSize();
+        if (DEBUG) {
+          debug("New incoming parcel of size " + this.mCurrentParcelSize);
+        }
+        // The size itself is not included in the size.
+        this.mReadIncoming -= this.PARCEL_SIZE_SIZE;
       }
-      mIncomingReadIndex = expectedAfterIndex;
-    }
-    mReadIncoming -= mCurrentParcelSize;
-    mReadAvailable = 0;
-    mCurrentParcelSize = 0;
-  }
-}
+
+      if (this.mReadIncoming < this.mCurrentParcelSize) {
+        // We haven't read enough yet in order to be able to process a parcel.
+        if (DEBUG) debug("Read " + this.mReadIncoming + ", but parcel size is "
+                         + this.mCurrentParcelSize + ". Going to sleep.");
+        return;
+      }
+
+      // Alright, we have enough data to process at least one whole parcel.
+      // Let's do that.
+      let expectedAfterIndex = (this.mIncomingReadIndex + this.mCurrentParcelSize)
+                               % this.mIncomingBufferLength;
 
-/**
- * Process one parcel.
- */
-function processParcel() {
-  let response_type = readUint32();
+      if (DEBUG) {
+        let parcel;
+        if (expectedAfterIndex < this.mIncomingReadIndex) {
+          let head = this.mIncomingBytes.subarray(this.mIncomingReadIndex);
+          let tail = this.mIncomingBytes.subarray(0, expectedAfterIndex);
+          parcel = Array.slice(head).concat(Array.slice(tail));
+        } else {
+          parcel = Array.slice(this.mIncomingBytes.subarray(
+            this.mIncomingReadIndex, expectedAfterIndex));
+        }
+        debug("Parcel (size " + this.mCurrentParcelSize + "): " + parcel);
+      }
 
-  let request_type, options;
-  if (response_type == RESPONSE_TYPE_SOLICITED) {
-    let token = readUint32();
-    let error = readUint32();
+      if (DEBUG) debug("We have at least one complete parcel.");
+      try {
+        this.mReadAvailable = this.mCurrentParcelSize;
+        this.processParcel();
+      } catch (ex) {
+        if (DEBUG) debug("Parcel handling threw " + ex + "\n" + ex.stack);
+      }
 
-    options = mTokenRequestMap[token];
-    if (!options) {
-      if (DEBUG) {
-        debug("Suspicious uninvited request found: " + token + ". Ignored!");
+      // Ensure that the whole parcel was consumed.
+      if (this.mIncomingReadIndex != expectedAfterIndex) {
+        if (DEBUG) {
+          debug("Parcel handler didn't consume whole parcel, " +
+                Math.abs(expectedAfterIndex - this.mIncomingReadIndex) +
+                " bytes left over");
+        }
+        this.mIncomingReadIndex = expectedAfterIndex;
       }
-      return;
+      this.mReadIncoming -= this.mCurrentParcelSize;
+      this.mReadAvailable = 0;
+      this.mCurrentParcelSize = 0;
     }
+  },
 
-    delete mTokenRequestMap[token];
-    request_type = options.rilRequestType;
+  /**
+   * Communicate with the IPC thread.
+   */
+  sendParcel: function sendParcel() {
+    // Compute the size of the parcel and write it to the front of the parcel
+    // where we left room for it. Note that he parcel size does not include
+    // the size itself.
+    let parcelSize = this.mOutgoingIndex - this.PARCEL_SIZE_SIZE;
+    this.writeParcelSize(parcelSize);
 
-    options.rilRequestError = error;
-    if (DEBUG) {
-      debug("Solicited response for request type " + request_type +
-            ", token " + token + ", error " + error);
-    }
-  } else if (response_type == RESPONSE_TYPE_UNSOLICITED) {
-    request_type = readUint32();
-    if (DEBUG) debug("Unsolicited response for request type " + request_type);
-  } else {
-    if (DEBUG) debug("Unknown response type: " + response_type);
-    return;
+    // This assumes that postRILMessage will make a copy of the ArrayBufferView
+    // right away!
+    let parcel = this.mOutgoingBytes.subarray(0, this.mOutgoingIndex);
+    if (DEBUG) debug("Outgoing parcel: " + Array.slice(parcel));
+    this.onSendParcel(parcel);
+    this.mOutgoingIndex = this.PARCEL_SIZE_SIZE;
+  },
+
+  getCurrentParcelSize: function getCurrentParcelSize() {
+    return this.mCurrentParcelSize;
+  },
+
+  getReadAvailable: function getReadAvailable() {
+    return this.mReadAvailable;
   }
 
-  RIL.handleParcel(request_type, mReadAvailable, options);
-}
-
-/**
- * Start a new outgoing parcel.
- *
- * @param type
- *        Integer specifying the request type.
- * @param options [optional]
- *        Object containing information about the request, e.g. the
- *        original main thread message object that led to the RIL request.
- */
-function newParcel(type, options) {
-  if (DEBUG) debug("New outgoing parcel of type " + type);
-
-  // We're going to leave room for the parcel size at the beginning.
-  mOutgoingIndex = PARCEL_SIZE_SIZE;
-  writeUint32(type);
-  writeUint32(mToken);
-
-  if (!options) {
-    options = {};
-  }
-  options.rilRequestType = type;
-  options.rilRequestError = null;
-  mTokenRequestMap[mToken] = options;
-  mToken++;
-  return mToken;
-}
-
-/**
- * Communicate with the RIL IPC thread.
- */
-function sendParcel() {
-  // Compute the size of the parcel and write it to the front of the parcel
-  // where we left room for it. Note that he parcel size does not include
-  // the size itself.
-  let parcelSize = mOutgoingIndex - PARCEL_SIZE_SIZE;
-  writeParcelSize(parcelSize);
+  /**
+   * Process one parcel.
+   *
+   * |processParcel| is an implementation provided incoming parcel processing
+   * function invoked when we have received a complete parcel.  Implementation
+   * may call multiple read functions to extract data from the incoming buffer.
+   */
+  //processParcel: function processParcel() {
+  //  let something = this.readUint32();
+  //  ...
+  //},
 
-  // This assumes that postRILMessage will make a copy of the ArrayBufferView
-  // right away!
-  let parcel = mOutgoingBytes.subarray(0, mOutgoingIndex);
-  if (DEBUG) debug("Outgoing parcel: " + Array.slice(parcel));
-  mOutputStream(parcel);
-  mOutgoingIndex = PARCEL_SIZE_SIZE;
-}
-
-function setOutputStream(func) {
-  mOutputStream = func;
-}
-
-function simpleRequest(type, options) {
-  newParcel(type, options);
-  sendParcel();
-}
-
-function getCurrentParcelSize() {
-  return mCurrentParcelSize;
-}
-
-function getReadAvailable() {
-  return mReadAvailable;
-}
+  /**
+   * Write raw data out to underlying channel.
+   *
+   * |onSendParcel| is an implementation provided stream output function
+   * invoked when we're really going to write something out.  We assume the
+   * data are completely copied to some output buffer in this call and may
+   * be destroyed when it's done.
+   *
+   * @param parcel
+   *        An array of numeric octet data.
+   */
+  //onSendParcel: function onSendParcel(parcel) {
+  //  ...
+  //}
+};
 
-module.exports = {
-  init: init,
-  startCalOutgoingSize: startCalOutgoingSize,
-  stopCalOutgoingSize: stopCalOutgoingSize,
-  seekIncoming: seekIncoming,
-  readUint8: readUint8,
-  readUint8Array: readUint8Array,
-  readUint16: readUint16,
-  readUint32: readUint32,
-  readUint32List: readUint32List,
-  readString: readString,
-  readStringList: readStringList,
-  readStringDelimiter: readStringDelimiter,
-  writeUint8: writeUint8,
-  writeUint16: writeUint16,
-  writeUint32: writeUint32,
-  writeString: writeString,
-  writeStringList: writeStringList,
-  writeStringDelimiter: writeStringDelimiter,
-  copyIncomingToOutgoing: copyIncomingToOutgoing,
-  processIncoming: processIncoming,
-  newParcel: newParcel,
-  sendParcel: sendParcel,
-  simpleRequest: simpleRequest,
-  setOutputStream: setOutputStream,
-  getCurrentParcelSize: getCurrentParcelSize,
-  getReadAvailable: getReadAvailable,
-};
+module.exports = { Buf: Buf };
--- a/ipc/dbus/DBusThread.cpp
+++ b/ipc/dbus/DBusThread.cpp
@@ -474,21 +474,23 @@ public:
           while (dbus_connection_dispatch(mConnection->GetConnection()) ==
                  DBUS_DISPATCH_DATA_REMAINS)
           {}
         }
         ++i;
       }
     }
 
+    mConnection->CleanUp();
+
     return NS_OK;
   }
 
 private:
-  DBusThread* mConnection;
+  nsRefPtr<DBusThread> mConnection;
 };
 
 static StaticRefPtr<DBusThread> gDBusThread;
 static StaticRefPtr<nsIThread>  gDBusServiceThread;
 
 // Startup/Shutdown utility functions
 
 bool
@@ -527,52 +529,48 @@ StartDBus()
 }
 
 bool
 StopDBus()
 {
   MOZ_ASSERT(!NS_IsMainThread());
   NS_ENSURE_TRUE(gDBusServiceThread, true);
 
-  if (gDBusThread) {
+  nsRefPtr<DBusThread> dbusThread(gDBusThread);
+  gDBusThread = nullptr;
+
+  if (dbusThread) {
     static const char data = DBUS_EVENT_LOOP_EXIT;
-    ssize_t wret = TEMP_FAILURE_RETRY(write(gDBusThread->mControlFdW.get(),
+    ssize_t wret = TEMP_FAILURE_RETRY(write(dbusThread->mControlFdW.get(),
                                             &data, sizeof(data)));
     NS_ENSURE_TRUE(wret == 1, false);
   }
 
-#ifdef DEBUG
-  LOG("DBus Thread Joining\n");
-#endif
-
-  if (NS_FAILED(gDBusServiceThread->Shutdown())) {
-    NS_WARNING("DBus thread shutdown failed!");
-  }
+  nsRefPtr<nsIThread> dbusServiceThread(gDBusServiceThread);
   gDBusServiceThread = nullptr;
 
-#ifdef DEBUG
-  LOG("DBus Thread Joined\n");
-#endif
-
-  if (gDBusThread) {
-    gDBusThread->CleanUp();
-    gDBusThread = nullptr;
-  }
+  nsRefPtr<nsIRunnable> runnable =
+    NS_NewRunnableMethod(dbusServiceThread, &nsIThread::Shutdown);
+  nsresult rv = NS_DispatchToMainThread(runnable);
+  NS_ENSURE_SUCCESS(rv, false);
 
   return true;
 }
 
 nsresult
 DispatchToDBusThread(nsIRunnable* event)
 {
-  MOZ_ASSERT(gDBusServiceThread);
-  MOZ_ASSERT(gDBusThread);
+  nsRefPtr<nsIThread> dbusServiceThread(gDBusServiceThread);
+  nsRefPtr<DBusThread> dbusThread(gDBusThread);
 
-  nsresult rv = gDBusServiceThread->Dispatch(event, NS_DISPATCH_NORMAL);
+  NS_ENSURE_TRUE(dbusServiceThread.get() && dbusThread.get(),
+                 NS_ERROR_NOT_INITIALIZED);
+
+  nsresult rv = dbusServiceThread->Dispatch(event, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  gDBusThread->WakeUp();
+  dbusThread->WakeUp();
 
   return NS_OK;
 }
 
 }
 }
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -192,16 +192,17 @@ FENNEC_JAVA_FILES = \
   gfx/NinePatchTileLayer.java \
   gfx/PanningPerfAPI.java \
   gfx/PanZoomController.java \
   gfx/PanZoomTarget.java \
   gfx/PluginLayer.java \
   gfx/PointUtils.java \
   gfx/ProgressiveUpdateData.java \
   gfx/RectUtils.java \
+  gfx/RenderTask.java \
   gfx/ScrollbarLayer.java \
   gfx/SimpleScaleGestureDetector.java \
   gfx/SingleTileLayer.java \
   gfx/SubdocumentScrollHelper.java \
   gfx/TextLayer.java \
   gfx/TextureGenerator.java \
   gfx/TextureReaper.java \
   gfx/TileLayer.java \
--- a/mobile/android/base/gfx/Axis.java
+++ b/mobile/android/base/gfx/Axis.java
@@ -81,32 +81,32 @@ abstract class Axis {
 
             @Override public void finish() {
                 setPrefs(mPrefs);
             }
         });
     }
 
     static final float MS_PER_FRAME = 1000.0f / 60.0f;
+    static final long NS_PER_FRAME = Math.round(1000000000f / 60f);
     private static final float FRAMERATE_MULTIPLIER = (1000f/60f) / MS_PER_FRAME;
     private static final int FLING_VELOCITY_POINTS = 8;
 
-    //  The values we use for friction are based on a 16.6ms frame, adjust them to MS_PER_FRAME:
-    //  FRICTION^1 = FRICTION_ADJUSTED^(16/MS_PER_FRAME)
-    //  FRICTION_ADJUSTED = e ^ ((ln(FRICTION))/FRAMERATE_MULTIPLIER)
-    static float getFrameAdjustedFriction(float baseFriction) {
-        return (float)Math.pow(Math.E, (Math.log(baseFriction) / FRAMERATE_MULTIPLIER));
+    //  The values we use for friction are based on a 16.6ms frame, adjust them to currentNsPerFrame:
+    static float getFrameAdjustedFriction(float baseFriction, long currentNsPerFrame) {
+        float framerateMultiplier = currentNsPerFrame / NS_PER_FRAME;
+        return (float)Math.pow(Math.E, (Math.log(baseFriction) / framerateMultiplier));
     }
 
     static void setPrefs(Map<String, Integer> prefs) {
-        FRICTION_SLOW = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850));
-        FRICTION_FAST = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970));
+        FRICTION_SLOW = getFloatPref(prefs, PREF_SCROLLING_FRICTION_SLOW, 850);
+        FRICTION_FAST = getFloatPref(prefs, PREF_SCROLLING_FRICTION_FAST, 970);
         VELOCITY_THRESHOLD = 10 / FRAMERATE_MULTIPLIER;
         MAX_EVENT_ACCELERATION = getFloatPref(prefs, PREF_SCROLLING_MAX_EVENT_ACCELERATION, GeckoAppShell.getDpi() > 300 ? 100 : 40);
-        OVERSCROLL_DECEL_RATE = getFrameAdjustedFriction(getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_DECEL_RATE, 40));
+        OVERSCROLL_DECEL_RATE = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_DECEL_RATE, 40);
         SNAP_LIMIT = getFloatPref(prefs, PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT, 300);
         MIN_SCROLLABLE_DISTANCE = getFloatPref(prefs, PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE, 500);
         Log.i(LOGTAG, "Prefs: " + FRICTION_SLOW + "," + FRICTION_FAST + "," + VELOCITY_THRESHOLD + ","
                 + MAX_EVENT_ACCELERATION + "," + OVERSCROLL_DECEL_RATE + "," + SNAP_LIMIT + "," + MIN_SCROLLABLE_DISTANCE);
     }
 
     static {
         // set the scrolling parameters to default values on startup
@@ -309,17 +309,17 @@ abstract class Axis {
             mFlingState = FlingStates.STOPPED;
         } else {
             mVelocity = calculateFlingVelocity();
             mFlingState = FlingStates.FLINGING;
         }
     }
 
     /* Advances a fling animation by one step. */
-    boolean advanceFling() {
+    boolean advanceFling(long realNsPerFrame) {
         if (mFlingState != FlingStates.FLINGING) {
             return false;
         }
         if (mSubscroller.scrolling() && !mSubscroller.lastScrollSucceeded()) {
             // if the subdocument stopped scrolling, it's because it reached the end
             // of the subdocument. we don't do overscroll on subdocuments, so there's
             // no point in continuing this fling.
             return false;
@@ -332,28 +332,30 @@ abstract class Axis {
             (overscroll == Overscroll.PLUS && mVelocity < 0))
         {
             decreasingOverscroll = true;
         }
 
         if (mDisableSnap || FloatUtils.fuzzyEquals(excess, 0.0f) || decreasingOverscroll) {
             // If we aren't overscrolled, just apply friction.
             if (Math.abs(mVelocity) >= VELOCITY_THRESHOLD) {
-                mVelocity *= FRICTION_FAST;
+                mVelocity *= getFrameAdjustedFriction(FRICTION_FAST, realNsPerFrame);
             } else {
                 float t = mVelocity / VELOCITY_THRESHOLD;
-                mVelocity *= FloatUtils.interpolate(FRICTION_SLOW, FRICTION_FAST, t);
+                mVelocity *= FloatUtils.interpolate(getFrameAdjustedFriction(FRICTION_SLOW, realNsPerFrame),
+                                                    getFrameAdjustedFriction(FRICTION_FAST, realNsPerFrame), t);
             }
         } else {
             // Otherwise, decrease the velocity linearly.
             float elasticity = 1.0f - excess / (getViewportLength() * SNAP_LIMIT);
+            float overscrollDecelRate = getFrameAdjustedFriction(OVERSCROLL_DECEL_RATE, realNsPerFrame);
             if (overscroll == Overscroll.MINUS) {
-                mVelocity = Math.min((mVelocity + OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
+                mVelocity = Math.min((mVelocity + overscrollDecelRate) * elasticity, 0.0f);
             } else { // must be Overscroll.PLUS
-                mVelocity = Math.max((mVelocity - OVERSCROLL_DECEL_RATE) * elasticity, 0.0f);
+                mVelocity = Math.max((mVelocity - overscrollDecelRate) * elasticity, 0.0f);
             }
         }
 
         return true;
     }
 
     void stopFling() {
         mVelocity = 0.0f;
--- a/mobile/android/base/gfx/GLController.java
+++ b/mobile/android/base/gfx/GLController.java
@@ -80,44 +80,48 @@ public class GLController {
             sInstance = new GLController();
         }
         sInstance.mView = view;
         return sInstance;
     }
 
     synchronized void surfaceDestroyed() {
         ThreadUtils.assertOnUiThread();
+        Log.w(LOGTAG, "GLController::surfaceDestroyed() with mCompositorCreated=" + mCompositorCreated);
 
         mSurfaceValid = false;
         mEGLSurface = null;
 
         // We need to coordinate with Gecko when pausing composition, to ensure
         // that Gecko never executes a draw event while the compositor is paused.
         // This is sent synchronously to make sure that we don't attempt to use
         // any outstanding Surfaces after we call this (such as from a
         // surfaceDestroyed notification), and to make sure that any in-flight
         // Gecko draw events have been processed.  When this returns, composition is
         // definitely paused -- it'll synchronize with the Gecko event loop, which
         // in turn will synchronize with the compositor thread.
         if (mCompositorCreated) {
             GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createCompositorPauseEvent());
         }
+        Log.w(LOGTAG, "done GLController::surfaceDestroyed()");
     }
 
     synchronized void surfaceChanged(int newWidth, int newHeight) {
         ThreadUtils.assertOnUiThread();
+        Log.w(LOGTAG, "GLController::surfaceChanged(" + newWidth + ", " + newHeight + ") with mSurfaceValid=" + mSurfaceValid);
 
         mWidth = newWidth;
         mHeight = newHeight;
 
         if (mSurfaceValid) {
             // We need to make this call even when the compositor isn't currently
             // paused (e.g. during an orientation change), to make the compositor
             // aware of the changed surface.
             resumeCompositor(mWidth, mHeight);
+            Log.w(LOGTAG, "done GLController::surfaceChanged with compositor resume");
             return;
         }
         mSurfaceValid = true;
 
         // If we get here, we supposedly have a valid surface where previously we
         // did not. So we're going to create the window surface and hold on to it
         // until the compositor comes asking for it. However, we can't call
         // eglCreateWindowSurface right away because the UI thread isn't *actually*
@@ -125,16 +129,17 @@ public class GLController {
         // notification before the surface is actually ready. So, we need to do the
         // call to eglCreateWindowSurface in a runnable posted back to the UI thread
         // that will run once this call unwinds all the way out and Android finishes
         // doing its thing.
 
         mView.post(new Runnable() {
             @Override
             public void run() {
+                Log.w(LOGTAG, "GLController::surfaceChanged, creating compositor; mCompositorCreated=" + mCompositorCreated + ", mSurfaceValid=" + mSurfaceValid);
                 // If we haven't yet created the compositor, and the GfxInfoThread
                 // isn't done it's data gathering activities, then postpone creating
                 // the compositor a little bit more. Don't block though, since this is
                 // the UI thread we're running on.
                 if (!mCompositorCreated && !GfxInfoThread.hasData()) {
                     mView.postDelayed(this, 1);
                     return;
                 }
@@ -166,35 +171,39 @@ public class GLController {
                 // to create the compositor if it hasn't been created already.
                 createCompositor();
             }
         });
     }
 
     void createCompositor() {
         ThreadUtils.assertOnUiThread();
+        Log.w(LOGTAG, "GLController::createCompositor with mCompositorCreated=" + mCompositorCreated);
 
         if (mCompositorCreated) {
             // If the compositor has already been created, just resume it instead. We don't need
             // to block here because if the surface is destroyed before the compositor grabs it,
             // we can handle that gracefully (i.e. the compositor will remain paused).
             resumeCompositor(mWidth, mHeight);
+            Log.w(LOGTAG, "done GLController::createCompositor with compositor resume");
             return;
         }
 
         // Only try to create the compositor if we have a valid surface and gecko is up. When these
         // two conditions are satisfied, we can be relatively sure that the compositor creation will
         // happen without needing to block anyhwere. Do it with a sync gecko event so that the
         // android doesn't have a chance to destroy our surface in between.
         if (mEGLSurface != null && GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
             GeckoAppShell.sendEventToGeckoSync(GeckoEvent.createCompositorCreateEvent(mWidth, mHeight));
         }
+        Log.w(LOGTAG, "done GLController::createCompositor");
     }
 
     void compositorCreated() {
+        Log.w(LOGTAG, "GLController::compositorCreated");
         // This is invoked on the compositor thread, while the java UI thread
         // is blocked on the gecko sync event in createCompositor() above
         mCompositorCreated = true;
     }
 
     public boolean hasValidSurface() {
         return mSurfaceValid;
     }
@@ -258,26 +267,28 @@ public class GLController {
         return mEGLSurface;
     }
 
     private String getEGLError() {
         return "Error " + (mEGL == null ? "(no mEGL)" : mEGL.eglGetError());
     }
 
     void resumeCompositor(int width, int height) {
+        Log.w(LOGTAG, "GLController::resumeCompositor(" + width + ", " + height + ") and mCompositorCreated=" + mCompositorCreated);
         // Asking Gecko to resume the compositor takes too long (see
         // https://bugzilla.mozilla.org/show_bug.cgi?id=735230#c23), so we
         // resume the compositor directly. We still need to inform Gecko about
         // the compositor resuming, so that Gecko knows that it can now draw.
         // It is important to not notify Gecko until after the compositor has
         // been resumed, otherwise Gecko may send updates that get dropped.
         if (mCompositorCreated) {
             GeckoAppShell.scheduleResumeComposition(width, height);
             GeckoAppShell.sendEventToGecko(GeckoEvent.createCompositorResumeEvent());
         }
+        Log.w(LOGTAG, "done GLController::resumeCompositor");
     }
 
     public static class GLControllerException extends RuntimeException {
         public static final long serialVersionUID = 1L;
 
         GLControllerException(String e) {
             super(e);
         }
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -916,16 +916,29 @@ public class GeckoLayerClient implements
     /** Implementation of PanZoomTarget */
     @Override
     public boolean post(Runnable action) {
         return mView.post(action);
     }
 
     /** Implementation of PanZoomTarget */
     @Override
+    public void postRenderTask(RenderTask task) {
+        mView.postRenderTask(task);
+    }
+
+    /** Implementation of PanZoomTarget */
+    @Override
+    public void removeRenderTask(RenderTask task) {
+        mView.removeRenderTask(task);
+    }
+
+
+    /** Implementation of PanZoomTarget */
+    @Override
     public boolean postDelayed(Runnable action, long delayMillis) {
         return mView.postDelayed(action, delayMillis);
     }
 
     /** Implementation of PanZoomTarget */
     @Override
     public Object getLock() {
         return this;
--- a/mobile/android/base/gfx/JavaPanZoomController.java
+++ b/mobile/android/base/gfx/JavaPanZoomController.java
@@ -25,19 +25,16 @@ import android.os.Build;
 import android.util.FloatMath;
 import android.util.Log;
 import android.view.GestureDetector;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 
-import java.util.Timer;
-import java.util.TimerTask;
-
 /*
  * Handles the kinetic scrolling and zooming physics for a layer controller.
  *
  * Many ideas are from Joe Hewitt's Scrollability:
  *   https://github.com/joehewitt/scrollability/
  */
 class JavaPanZoomController
     extends GestureDetector.SimpleOnGestureListener
@@ -72,18 +69,18 @@ class JavaPanZoomController
     private static final float MAX_ZOOM = 4.0f;
 
     // The maximum amount we would like to scroll with the mouse
     private static final float MAX_SCROLL = 0.075f * GeckoAppShell.getDpi();
 
     // The maximum zoom factor adjustment per frame of the AUTONAV animation
     private static final float MAX_ZOOM_DELTA = 0.125f;
 
-    // Length of the bounce animation in ms
-    private static final int BOUNCE_ANIMATION_DURATION = 250;
+    // The duration of the bounce animation in ns
+    private static final int BOUNCE_ANIMATION_DURATION = 250000000;
 
     private enum PanZoomState {
         NOTHING,                /* no touch-start events received */
         FLING,                  /* all touches removed, but we're still scrolling page */
         TOUCHING,               /* one touch-start event received */
         PANNING_LOCKED_X,       /* touch-start followed by move (i.e. panning with axis lock) X axis */
         PANNING_LOCKED_Y,       /* as above for Y axis */
         PANNING,                /* panning without axis lock */
@@ -110,20 +107,18 @@ class JavaPanZoomController
 
     private final PanZoomTarget mTarget;
     private final SubdocumentScrollHelper mSubscroller;
     private final Axis mX;
     private final Axis mY;
     private final TouchEventHandler mTouchEventHandler;
     private final EventDispatcher mEventDispatcher;
 
-    /* The timer that handles flings or bounces. */
-    private Timer mAnimationTimer;
-    /* The runnable being scheduled by the animation timer. */
-    private AnimationRunnable mAnimationRunnable;
+    /* The task that handles flings, autonav or bounces. */
+    private PanZoomRenderTask mAnimationRenderTask;
     /* The zoom focus at the first zoom event (in page coordinates). */
     private PointF mLastZoomFocus;
     /* The time the last motion event took place. */
     private long mLastEventTime;
     /* Current state the pan/zoom UI is in. */
     private PanZoomState mState;
     /* The per-frame zoom delta for the currently-running AUTONAV animation. */
     private float mAutonavZoomDelta;
@@ -414,17 +409,17 @@ class JavaPanZoomController
 
     /*
      * Panning/scrolling
      */
 
     private boolean handleTouchStart(MotionEvent event) {
         // user is taking control of movement, so stop
         // any auto-movement we have going
-        stopAnimationTimer();
+        stopAnimationTask();
 
         switch (mState) {
         case ANIMATED_ZOOM:
             // We just interrupted a double-tap animation, so force a redraw in
             // case this touchstart is just a tap that doesn't end up triggering
             // a redraw
             mTarget.forceRedraw(null);
             // fall through
@@ -601,17 +596,17 @@ class JavaPanZoomController
                 bounce(); // if not needed, this will automatically go to state NOTHING
                 return true;
             }
             return false;
         }
 
         if (mState == PanZoomState.NOTHING) {
             setState(PanZoomState.AUTONAV);
-            startAnimationTimer(new AutonavRunnable());
+            startAnimationRenderTask(new AutonavRenderTask());
         }
         if (mState == PanZoomState.AUTONAV) {
             mX.setAutoscrollVelocity(velocityX);
             mY.setAutoscrollVelocity(velocityY);
             mAutonavZoomDelta = zoomDelta;
             return true;
         }
         return false;
@@ -727,74 +722,67 @@ class JavaPanZoomController
 
     private void scrollBy(float dx, float dy) {
         mTarget.scrollBy(dx, dy);
     }
 
     private void fling() {
         updatePosition();
 
-        stopAnimationTimer();
+        stopAnimationTask();
 
         boolean stopped = stopped();
         mX.startFling(stopped);
         mY.startFling(stopped);
 
-        startAnimationTimer(new FlingRunnable());
+        startAnimationRenderTask(new FlingRenderTask());
     }
 
     /* Performs a bounce-back animation to the given viewport metrics. */
     private void bounce(ImmutableViewportMetrics metrics, PanZoomState state) {
-        stopAnimationTimer();
+        stopAnimationTask();
 
         ImmutableViewportMetrics bounceStartMetrics = getMetrics();
         if (bounceStartMetrics.fuzzyEquals(metrics)) {
             setState(PanZoomState.NOTHING);
             return;
         }
 
         setState(state);
 
         // At this point we have already set mState to BOUNCE or ANIMATED_ZOOM, so
         // getRedrawHint() is returning false. This means we can safely call
         // setAnimationTarget to set the new final display port and not have it get
         // clobbered by display ports from intermediate animation frames.
         mTarget.setAnimationTarget(metrics);
-        startAnimationTimer(new BounceRunnable(bounceStartMetrics, metrics));
+        startAnimationRenderTask(new BounceRenderTask(bounceStartMetrics, metrics));
     }
 
     /* Performs a bounce-back animation to the nearest valid viewport metrics. */
     private void bounce() {
         bounce(getValidViewportMetrics(), PanZoomState.BOUNCE);
     }
 
     /* Starts the fling or bounce animation. */
-    private void startAnimationTimer(final AnimationRunnable runnable) {
-        if (mAnimationTimer != null) {
-            Log.e(LOGTAG, "Attempted to start a new timer without canceling the old one!");
-            stopAnimationTimer();
+    private void startAnimationRenderTask(final PanZoomRenderTask task) {
+        if (mAnimationRenderTask != null) {
+            Log.e(LOGTAG, "Attempted to start a new task without canceling the old one!");
+            stopAnimationTask();
         }
 
-        mAnimationTimer = new Timer("Animation Timer");
-        mAnimationRunnable = runnable;
-        mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
-            @Override
-            public void run() { mTarget.post(runnable); }
-        }, 0, (int)Axis.MS_PER_FRAME);
+        mAnimationRenderTask = task;
+        mTarget.postRenderTask(mAnimationRenderTask);
     }
 
     /* Stops the fling or bounce animation. */
-    private void stopAnimationTimer() {
-        if (mAnimationTimer != null) {
-            mAnimationTimer.cancel();
-            mAnimationTimer = null;
-        }
-        if (mAnimationRunnable != null) {
-            mAnimationRunnable.terminate();
-            mAnimationRunnable = null;
+    private void stopAnimationTask() {
+        if (mAnimationRenderTask != null) {
+            mAnimationRenderTask.terminate();
+            mTarget.removeRenderTask(mAnimationRenderTask);
+            mAnimationRenderTask = null;
         }
     }
 
     private float getVelocity() {
         float xvel = mX.getRealVelocity();
         float yvel = mY.getRealVelocity();
         return FloatMath.sqrt(xvel * xvel + yvel * yvel);
     }
@@ -825,69 +813,102 @@ class JavaPanZoomController
             }
         } else {
             synchronized (mTarget.getLock()) {
                 scrollBy(displacement.x, displacement.y);
             }
         }
     }
 
-    private abstract class AnimationRunnable implements Runnable {
-        private boolean mAnimationTerminated;
+    /**
+     * This class is an implementation of RenderTask which enforces its implementor to run in the UI thread.
+     *
+     */
+    private abstract class PanZoomRenderTask extends RenderTask {
 
-        /* This should always run on the UI thread */
-        @Override
-        public final void run() {
-            /*
-             * Since the animation timer queues this runnable on the UI thread, it
-             * is possible that even when the animation timer is cancelled, there
-             * are multiple instances of this queued, so we need to have another
-             * mechanism to abort. This is done by using the mAnimationTerminated flag.
-             */
-            if (mAnimationTerminated) {
-                return;
+        /**
+         * the time when the current frame was started in ns.
+         */
+        protected long mCurrentFrameStartTime;
+        /**
+         * The current frame duration in ns.
+         */
+        protected long mLastFrameTimeDelta;
+
+        private final Runnable mRunnable = new Runnable() {
+            @Override
+            public final void run() {
+                if (mContinueAnimation) {
+                    animateFrame();
+                }
             }
-            animateFrame();
+        };
+
+        private boolean mContinueAnimation = true;
+
+        public PanZoomRenderTask() {
+            super(false);
         }
 
+        @Override
+        protected final boolean internalRun(long timeDelta, long currentFrameStartTime) {
+
+            mCurrentFrameStartTime = currentFrameStartTime;
+            mLastFrameTimeDelta = timeDelta;
+
+            mTarget.post(mRunnable);
+            return mContinueAnimation;
+        }
+
+        /**
+         * The method subclasses must override. This method is run on the UI thread thanks to internalRun
+         */
         protected abstract void animateFrame();
 
-        /* This should always run on the UI thread */
-        protected final void terminate() {
-            mAnimationTerminated = true;
+        /**
+         * Terminate the animation.
+         */
+        public void terminate() {
+            mContinueAnimation = false;
         }
     }
 
-    private class AutonavRunnable extends AnimationRunnable {
+    private class AutonavRenderTask extends PanZoomRenderTask {
+        public AutonavRenderTask() {
+            super();
+        }
+
         @Override
         protected void animateFrame() {
             if (mState != PanZoomState.AUTONAV) {
                 finishAnimation();
                 return;
             }
 
             updatePosition();
             synchronized (mTarget.getLock()) {
                 mTarget.setViewportMetrics(applyZoomDelta(getMetrics(), mAutonavZoomDelta));
             }
         }
     }
 
-    /* The callback that performs the bounce animation. */
-    private class BounceRunnable extends AnimationRunnable {
-        /* The current frame of the bounce-back animation */
-        private int mBounceFrame;
+    /* The task that performs the bounce animation. */
+    private class BounceRenderTask extends PanZoomRenderTask {
+
         /*
          * The viewport metrics that represent the start and end of the bounce-back animation,
          * respectively.
          */
         private ImmutableViewportMetrics mBounceStartMetrics;
         private ImmutableViewportMetrics mBounceEndMetrics;
+        // How long ago this bounce was started in ns.
+        private long mBounceDuration;
 
-        BounceRunnable(ImmutableViewportMetrics startMetrics, ImmutableViewportMetrics endMetrics) {
+        BounceRenderTask(ImmutableViewportMetrics startMetrics, ImmutableViewportMetrics endMetrics) {
+            super();
             mBounceStartMetrics = startMetrics;
             mBounceEndMetrics = endMetrics;
         }
 
         @Override
         protected void animateFrame() {
             /*
              * The pan/zoom controller might have signaled to us that it wants to abort the
@@ -895,63 +916,67 @@ class JavaPanZoomController
              * out.
              */
             if (!(mState == PanZoomState.BOUNCE || mState == PanZoomState.ANIMATED_ZOOM)) {
                 finishAnimation();
                 return;
             }
 
             /* Perform the next frame of the bounce-back animation. */
-            if (mBounceFrame < (int)(BOUNCE_ANIMATION_DURATION / Axis.MS_PER_FRAME)) {
+            mBounceDuration = mCurrentFrameStartTime - getStartTime();
+            if (mBounceDuration < BOUNCE_ANIMATION_DURATION) {
                 advanceBounce();
                 return;
             }
 
             /* Finally, if there's nothing else to do, complete the animation and go to sleep. */
             finishBounce();
             finishAnimation();
             setState(PanZoomState.NOTHING);
         }
 
         /* Performs one frame of a bounce animation. */
         private void advanceBounce() {
             synchronized (mTarget.getLock()) {
-                float t = easeOut(mBounceFrame * Axis.MS_PER_FRAME / BOUNCE_ANIMATION_DURATION);
+                float t = easeOut((float)mBounceDuration / BOUNCE_ANIMATION_DURATION);
                 ImmutableViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t);
                 mTarget.setViewportMetrics(newMetrics);
-                mBounceFrame++;
             }
         }
 
         /* Concludes a bounce animation and snaps the viewport into place. */
         private void finishBounce() {
             synchronized (mTarget.getLock()) {
                 mTarget.setViewportMetrics(mBounceEndMetrics);
-                mBounceFrame = -1;
             }
         }
     }
 
     // The callback that performs the fling animation.
-    private class FlingRunnable extends AnimationRunnable {
+    private class FlingRenderTask extends PanZoomRenderTask {
+
+        public FlingRenderTask() {
+            super();
+        }
+
         @Override
         protected void animateFrame() {
             /*
              * The pan/zoom controller might have signaled to us that it wants to abort the
              * animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
              * out.
              */
             if (mState != PanZoomState.FLING) {
                 finishAnimation();
                 return;
             }
 
             /* Advance flings, if necessary. */
-            boolean flingingX = mX.advanceFling();
-            boolean flingingY = mY.advanceFling();
+            boolean flingingX = mX.advanceFling(mLastFrameTimeDelta);
+            boolean flingingY = mY.advanceFling(mLastFrameTimeDelta);
 
             boolean overscrolled = (mX.overscrolled() || mY.overscrolled());
 
             /* If we're still flinging in any direction, update the origin. */
             if (flingingX || flingingY) {
                 updatePosition();
 
                 /*
@@ -978,17 +1003,17 @@ class JavaPanZoomController
                 setState(PanZoomState.NOTHING);
             }
         }
     }
 
     private void finishAnimation() {
         checkMainThread();
 
-        stopAnimationTimer();
+        stopAnimationTask();
 
         // Force a viewport synchronisation
         mTarget.forceRedraw(null);
     }
 
     /* Returns the nearest viewport metrics with no overscroll visible. */
     private ImmutableViewportMetrics getValidViewportMetrics() {
         return getValidViewportMetrics(getMetrics());
--- a/mobile/android/base/gfx/LayerMarginsAnimator.java
+++ b/mobile/android/base/gfx/LayerMarginsAnimator.java
@@ -15,38 +15,35 @@ import org.mozilla.gecko.util.ThreadUtil
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.animation.DecelerateInterpolator;
 import android.view.MotionEvent;
 import android.view.View;
 
-import java.util.Timer;
-import java.util.TimerTask;
-
 public class LayerMarginsAnimator implements TouchEventInterceptor {
     private static final String LOGTAG = "GeckoLayerMarginsAnimator";
-    private static final float MS_PER_FRAME = 1000.0f / 60.0f;
-    private static final long MARGIN_ANIMATION_DURATION = 250;
+    // The duration of the animation in ns
+    private static final long MARGIN_ANIMATION_DURATION = 250000000;
     private static final String PREF_SHOW_MARGINS_THRESHOLD = "browser.ui.show-margins-threshold";
 
     /* This is the proportion of the viewport rect, minus maximum margins,
      * that needs to be travelled before margins will be exposed.
      */
     private float SHOW_MARGINS_THRESHOLD = 0.20f;
 
     /* This rect stores the maximum value margins can grow to when scrolling. When writing
      * to this member variable, or when reading from this member variable on a non-UI thread,
      * you must synchronize on the LayerMarginsAnimator instance. */
     private final RectF mMaxMargins;
     /* If this boolean is true, scroll changes will not affect margins */
     private boolean mMarginsPinned;
-    /* The timer that handles showing/hiding margins */
-    private Timer mAnimationTimer;
+    /* The task that handles showing/hiding margins */
+    private LayerMarginsAnimationTask mAnimationTask;
     /* This interpolator is used for the above mentioned animation */
     private final DecelerateInterpolator mInterpolator;
     /* The GeckoLayerClient whose margins will be animated */
     private final GeckoLayerClient mTarget;
     /* The distance that has been scrolled since either the first touch event,
      * or since the margins were last fully hidden */
     private final PointF mTouchTravelDistance;
     /* The ID of the prefs listener for the show-marginss threshold */
@@ -100,70 +97,31 @@ public class LayerMarginsAnimator implem
                 + ", \"bottom\" : " + bottom + ", \"left\" : " + left + " }"));
     }
 
     RectF getMaxMargins() {
         return mMaxMargins;
     }
 
     private void animateMargins(final float left, final float top, final float right, final float bottom, boolean immediately) {
-        if (mAnimationTimer != null) {
-            mAnimationTimer.cancel();
-            mAnimationTimer = null;
+        if (mAnimationTask != null) {
+            mTarget.getView().removeRenderTask(mAnimationTask);
+            mAnimationTask = null;
         }
 
         if (immediately) {
             ImmutableViewportMetrics newMetrics = mTarget.getViewportMetrics().setMargins(left, top, right, bottom);
             mTarget.forceViewportMetrics(newMetrics, true, true);
             return;
         }
 
         ImmutableViewportMetrics metrics = mTarget.getViewportMetrics();
 
-        final long startTime = SystemClock.uptimeMillis();
-        final float startLeft = metrics.marginLeft;
-        final float startTop = metrics.marginTop;
-        final float startRight = metrics.marginRight;
-        final float startBottom = metrics.marginBottom;
-
-        mAnimationTimer = new Timer("Margin Animation Timer");
-        mAnimationTimer.scheduleAtFixedRate(new TimerTask() {
-            @Override
-            public void run() {
-                float progress = mInterpolator.getInterpolation(
-                    Math.min(1.0f, (SystemClock.uptimeMillis() - startTime)
-                                     / (float)MARGIN_ANIMATION_DURATION));
-
-                synchronized(mTarget.getLock()) {
-                    ImmutableViewportMetrics oldMetrics = mTarget.getViewportMetrics();
-                    ImmutableViewportMetrics newMetrics = oldMetrics.setMargins(
-                        FloatUtils.interpolate(startLeft, left, progress),
-                        FloatUtils.interpolate(startTop, top, progress),
-                        FloatUtils.interpolate(startRight, right, progress),
-                        FloatUtils.interpolate(startBottom, bottom, progress));
-                    PointF oldOffset = oldMetrics.getMarginOffset();
-                    PointF newOffset = newMetrics.getMarginOffset();
-                    newMetrics =
-                        newMetrics.offsetViewportByAndClamp(newOffset.x - oldOffset.x,
-                                                            newOffset.y - oldOffset.y);
-
-                    if (progress >= 1.0f) {
-                        if (mAnimationTimer != null) {
-                            mAnimationTimer.cancel();
-                            mAnimationTimer = null;
-                        }
-
-                        // Force a redraw and update Gecko
-                        mTarget.forceViewportMetrics(newMetrics, true, true);
-                    } else {
-                        mTarget.forceViewportMetrics(newMetrics, false, false);
-                    }
-                }
-            }
-        }, 0, (int)MS_PER_FRAME);
+        mAnimationTask = new LayerMarginsAnimationTask(false, metrics, left, top, right, bottom);
+        mTarget.getView().postRenderTask(mAnimationTask);
     }
 
     /**
      * Exposes the margin area by growing the margin components of the current
      * metrics to the values set in setMaxMargins.
      */
     public synchronized void showMargins(boolean immediately) {
         animateMargins(mMaxMargins.left, mMaxMargins.top, mMaxMargins.right, mMaxMargins.bottom, immediately);
@@ -236,19 +194,19 @@ public class LayerMarginsAnimator implem
      */
     ImmutableViewportMetrics scrollBy(ImmutableViewportMetrics aMetrics, float aDx, float aDy) {
         float[] newMarginsX = { aMetrics.marginLeft, aMetrics.marginRight };
         float[] newMarginsY = { aMetrics.marginTop, aMetrics.marginBottom };
 
         // Only alter margins if the toolbar isn't pinned
         if (!mMarginsPinned) {
             // Make sure to cancel any margin animations when margin-scrolling begins
-            if (mAnimationTimer != null) {
-                mAnimationTimer.cancel();
-                mAnimationTimer = null;
+            if (mAnimationTask != null) {
+                mTarget.getView().removeRenderTask(mAnimationTask);
+                mAnimationTask = null;
             }
 
             // Reset the touch travel when changing direction
             if ((aDx >= 0) != (mTouchTravelDistance.x >= 0)) {
                 mTouchTravelDistance.x = 0;
             }
             if ((aDy >= 0) != (mTouchTravelDistance.y >= 0)) {
                 mTouchTravelDistance.y = 0;
@@ -292,9 +250,67 @@ public class LayerMarginsAnimator implem
     public boolean onInterceptTouchEvent(View view, MotionEvent event) {
         int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN && event.getPointerCount() == 1) {
             mTouchTravelDistance.set(0.0f, 0.0f);
         }
 
         return false;
     }
+
+    class LayerMarginsAnimationTask extends RenderTask {
+        private float mStartLeft, mStartTop, mStartRight, mStartBottom;
+        private float mTop, mBottom, mLeft, mRight;
+        private boolean mContinueAnimation;
+
+        public LayerMarginsAnimationTask(boolean runAfter, ImmutableViewportMetrics metrics,
+                float left, float top, float right, float bottom) {
+            super(runAfter);
+            mContinueAnimation = true;
+            this.mStartLeft = metrics.marginLeft;
+            this.mStartTop = metrics.marginTop;
+            this.mStartRight = metrics.marginRight;
+            this.mStartBottom = metrics.marginBottom;
+            this.mLeft = left;
+            this.mRight = right;
+            this.mTop = top;
+            this.mBottom = bottom;
+        }
+
+        @Override
+        public boolean internalRun(long timeDelta, long currentFrameStartTime) {
+            if (!mContinueAnimation) {
+                return false;
+            }
+
+            // Calculate the progress (between 0 and 1)
+            float progress = mInterpolator.getInterpolation(
+                    Math.min(1.0f, (System.nanoTime() - getStartTime())
+                                    / (float)MARGIN_ANIMATION_DURATION));
+
+            // Calculate the new metrics accordingly
+            synchronized (mTarget.getLock()) {
+                ImmutableViewportMetrics oldMetrics = mTarget.getViewportMetrics();
+                ImmutableViewportMetrics newMetrics = oldMetrics.setMargins(
+                        FloatUtils.interpolate(mStartLeft, mLeft, progress),
+                        FloatUtils.interpolate(mStartTop, mTop, progress),
+                        FloatUtils.interpolate(mStartRight, mRight, progress),
+                        FloatUtils.interpolate(mStartBottom, mBottom, progress));
+                PointF oldOffset = oldMetrics.getMarginOffset();
+                PointF newOffset = newMetrics.getMarginOffset();
+                newMetrics =
+                        newMetrics.offsetViewportByAndClamp(newOffset.x - oldOffset.x,
+                                                            newOffset.y - oldOffset.y);
+
+                if (progress >= 1.0f) {
+                    mContinueAnimation = false;
+
+                    // Force a redraw and update Gecko
+                    mTarget.forceViewportMetrics(newMetrics, true, true);
+                } else {
+                    mTarget.forceViewportMetrics(newMetrics, false, false);
+                }
+            }
+            return mContinueAnimation;
+        }
+    }
+
 }
--- a/mobile/android/base/gfx/LayerRenderer.java
+++ b/mobile/android/base/gfx/LayerRenderer.java
@@ -5,16 +5,17 @@
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.gfx.Layer.RenderContext;
+import org.mozilla.gecko.gfx.RenderTask;
 import org.mozilla.gecko.mozglue.DirectBufferAllocator;
 
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
@@ -45,29 +46,35 @@ public class LayerRenderer implements Ta
      * The amount of time a frame is allowed to take to render before we declare it a dropped
      * frame.
      */
     private static final int MAX_FRAME_TIME = 16;   /* 1000 ms / 60 FPS */
 
     private static final int FRAME_RATE_METER_WIDTH = 128;
     private static final int FRAME_RATE_METER_HEIGHT = 32;
 
+    private static final long NANOS_PER_MS = 1000000;
+    private static final int NANOS_PER_SECOND = 1000000000;
+
     private final LayerView mView;
     private final NinePatchTileLayer mShadowLayer;
     private TextLayer mFrameRateLayer;
     private final ScrollbarLayer mHorizScrollLayer;
     private final ScrollbarLayer mVertScrollLayer;
     private final FadeRunnable mFadeRunnable;
     private ByteBuffer mCoordByteBuffer;
     private FloatBuffer mCoordBuffer;
     private RenderContext mLastPageContext;
     private int mMaxTextureSize;
     private int mBackgroundColor;
     private int mOverscrollColor;
 
+    private long mLastFrameTime;
+    private final CopyOnWriteArrayList<RenderTask> mTasks;
+
     private CopyOnWriteArrayList<Layer> mExtraLayers = new CopyOnWriteArrayList<Layer>();
 
     // Dropped frames display
     private int[] mFrameTimings;
     private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames;
 
     // Render profiling output
     private int mFramesRendered;
@@ -133,16 +140,20 @@ public class LayerRenderer implements Ta
         mOverscrollColor = view.getContext().getResources().getColor(R.color.background_normal);
 
         CairoImage shadowImage = new BufferedCairoImage(view.getShadowPattern());
         mShadowLayer = new NinePatchTileLayer(shadowImage);
 
         Bitmap scrollbarImage = view.getScrollbarImage();
         IntSize size = new IntSize(scrollbarImage.getWidth(), scrollbarImage.getHeight());
         scrollbarImage = expandCanvasToPowerOfTwo(scrollbarImage, size);
+
+        mTasks = new CopyOnWriteArrayList<RenderTask>();
+        mLastFrameTime = System.nanoTime();
+
         mVertScrollLayer = new ScrollbarLayer(this, scrollbarImage, size, true);
         mHorizScrollLayer = new ScrollbarLayer(this, diagonalFlip(scrollbarImage), new IntSize(size.height, size.width), false);
         mFadeRunnable = new FadeRunnable();
 
         mFrameTimings = new int[60];
         mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0;
 
         // Initialize the FloatBuffer that will be used to store all vertices and texture
@@ -236,16 +247,40 @@ public class LayerRenderer implements Ta
         GLES20.glDisableVertexAttribArray(mPositionHandle);
         GLES20.glUseProgram(0);
     }
 
     public int getMaxTextureSize() {
         return mMaxTextureSize;
     }
 
+    public void postRenderTask(RenderTask aTask) {
+        mTasks.add(aTask);
+        mView.requestRender();
+    }
+
+    public void removeRenderTask(RenderTask aTask) {
+        mTasks.remove(aTask);
+    }
+
+    private void runRenderTasks(CopyOnWriteArrayList<RenderTask> tasks, boolean after, long frameStartTime) {
+        for (RenderTask task : tasks) {
+            if (task.runAfter != after) {
+                continue;
+            }
+
+            boolean stillRunning = task.run(frameStartTime - mLastFrameTime, frameStartTime);
+
+            // Remove the task from the list if its finished
+            if (!stillRunning) {
+                tasks.remove(task);
+            }
+        }
+    }
+
     public void addLayer(Layer layer) {
         synchronized (mExtraLayers) {
             if (mExtraLayers.contains(layer)) {
                 mExtraLayers.remove(layer);
             }
 
             mExtraLayers.add(layer);
         }
@@ -294,17 +329,17 @@ public class LayerRenderer implements Ta
     }
 
     private RenderContext createContext(RectF viewport, RectF pageRect, float zoomFactor, PointF offset) {
         return new RenderContext(viewport, pageRect, zoomFactor, offset, mPositionHandle, mTextureHandle,
                                  mCoordBuffer);
     }
 
     private void updateDroppedFrames(long frameStartTime) {
-        int frameElapsedTime = (int)(SystemClock.uptimeMillis() - frameStartTime);
+        int frameElapsedTime = (int)((System.nanoTime() - frameStartTime) / NANOS_PER_MS);
 
         /* Update the running statistics. */
         mFrameTimingsSum -= mFrameTimings[mCurrentFrame];
         mFrameTimingsSum += frameElapsedTime;
         mDroppedFrames -= (mFrameTimings[mCurrentFrame] + 1) / MAX_FRAME_TIME;
         mDroppedFrames += (frameElapsedTime + 1) / MAX_FRAME_TIME;
 
         mFrameTimings[mCurrentFrame] = frameElapsedTime;
@@ -451,25 +486,28 @@ public class LayerRenderer implements Ta
                                         (screenSize.height - bottom) + (bottom - top));
             scissorRect.offset(Math.round(-mRenderOffset.x), Math.round(-mRenderOffset.y));
 
             return scissorRect;
         }
 
         /** This function is invoked via JNI; be careful when modifying signature. */
         public void beginDrawing() {
-            mFrameStartTime = SystemClock.uptimeMillis();
+            mFrameStartTime = System.nanoTime();
 
             TextureReaper.get().reap();
             TextureGenerator.get().fill();
 
             mUpdated = true;
 
             Layer rootLayer = mView.getLayerClient().getRoot();
 
+            // Run through pre-render tasks
+            runRenderTasks(mTasks, false, mFrameStartTime);
+
             if (!mPageContext.fuzzyEquals(mLastPageContext) && !mView.isFullScreen()) {
                 // The viewport or page changed, so show the scrollbars again
                 // as per UX decision. Don't do this if we're in full-screen mode though.
                 mVertScrollLayer.unfade();
                 mHorizScrollLayer.unfade();
                 mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);
             } else if (mFadeRunnable.timeToFade()) {
                 boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade();
@@ -599,22 +637,24 @@ public class LayerRenderer implements Ta
                 PanningPerfAPI.recordCheckerboard(checkerboard);
                 if (checkerboard < 0.0f || checkerboard > 1.0f) {
                     Log.e(LOGTAG, "Checkerboard value out of bounds: " + checkerboard);
                 }
 
                 mCompleteFramesRendered += 1.0f - checkerboard;
                 mFramesRendered ++;
 
-                if (mFrameStartTime - mProfileOutputTime > 1000) {
+                if (mFrameStartTime - mProfileOutputTime > NANOS_PER_SECOND) {
                     mProfileOutputTime = mFrameStartTime;
                     printCheckerboardStats();
                 }
             }
 
+            runRenderTasks(mTasks, true, mFrameStartTime);
+
             /* Draw the FPS. */
             if (mFrameRateLayer != null) {
                 updateDroppedFrames(mFrameStartTime);
 
                 GLES20.glEnable(GLES20.GL_BLEND);
                 GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
                 mFrameRateLayer.draw(mScreenContext);
             }
@@ -647,16 +687,17 @@ public class LayerRenderer implements Ta
                 mView.post(new Runnable() {
                     @Override
                     public void run() {
                         mView.getChildAt(0).setBackgroundColor(Color.TRANSPARENT);
                     }
                 });
                 mView.setPaintState(LayerView.PAINT_AFTER_FIRST);
             }
+            mLastFrameTime = mFrameStartTime;
         }
     }
 
     @Override
     public void onTabChanged(final Tab tab, Tabs.TabEvents msg, Object data) {
         // Sets the background of the newly selected tab. This background color
         // gets cleared in endDrawing(). This function runs on the UI thread,
         // but other code that touches the paint state is run on the compositor
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -396,16 +396,24 @@ public class LayerView extends FrameLayo
     public void addLayer(Layer layer) {
         mRenderer.addLayer(layer);
     }
 
     public void removeLayer(Layer layer) {
         mRenderer.removeLayer(layer);
     }
 
+    public void postRenderTask(RenderTask task) {
+        mRenderer.postRenderTask(task);
+    }
+
+    public void removeRenderTask(RenderTask task) {
+        mRenderer.removeRenderTask(task);
+    }
+
     public int getMaxTextureSize() {
         return mRenderer.getMaxTextureSize();
     }
 
     /** Used by robocop for testing purposes. Not for production use! */
     public IntBuffer getPixels() {
         return mRenderer.getPixels();
     }
--- a/mobile/android/base/gfx/PanZoomTarget.java
+++ b/mobile/android/base/gfx/PanZoomTarget.java
@@ -21,11 +21,13 @@ public interface PanZoomTarget {
     public void scrollBy(float dx, float dy);
     public void onSubdocumentScrollBy(float dx, float dy);
     public void panZoomStopped();
     /** This triggers an (asynchronous) viewport update/redraw. */
     public void forceRedraw(DisplayPortMetrics displayPort);
 
     public boolean post(Runnable action);
     public boolean postDelayed(Runnable action, long delayMillis);
+    public void postRenderTask(RenderTask task);
+    public void removeRenderTask(RenderTask task);
     public Object getLock();
     public PointF convertViewPointToLayerPoint(PointF viewPoint);
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/RenderTask.java
@@ -0,0 +1,80 @@
+/* -*- 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;
+
+/**
+ * A class used to schedule a callback to occur when the next frame is drawn.
+ * Subclasses must redefine the internalRun method, not the run method.
+ */
+public abstract class RenderTask {
+    /**
+     * Whether to run the task after the render, or before.
+     */
+    public final boolean runAfter;
+
+    /**
+     * Time when this task has first run, in ns. Useful for tasks which run for a specific duration.
+     */
+    private long mStartTime;
+
+    /**
+     * Whether we should initialise mStartTime on the next frame run.
+     */
+    private boolean mResetStartTime = true;
+
+    /**
+     * The callback to run on each frame. timeDelta is the time elapsed since
+     * the last call, in nanoseconds. Returns true if it should continue
+     * running, or false if it should be removed from the task queue. Returning
+     * true implicitly schedules a redraw.
+     *
+     * This method first initializes the start time if resetStartTime has been invoked,
+     * then calls internalRun.
+     *
+     * Note : subclasses should override internalRun.
+     *
+     * @param timeDelta the time between the beginning of last frame and the beginning of this frame, in ns.
+     * @param currentFrameStartTime the startTime of the current frame, in ns.
+     * @return true if animation should be run at the next frame, false otherwise
+     * @see RenderTask#internalRun(long, long)
+     */
+    public final boolean run(long timeDelta, long currentFrameStartTime) {
+        if (mResetStartTime) {
+            mStartTime = currentFrameStartTime;
+            mResetStartTime = false;
+        }
+        return internalRun(timeDelta, currentFrameStartTime);
+    }
+
+    /**
+     * Abstract method to be overridden by subclasses.
+     * @param timeDelta the time between the beginning of last frame and the beginning of this frame, in ns
+     * @param currentFrameStartTime the startTime of the current frame, in ns.
+     * @return true if animation should be run at the next frame, false otherwise
+     */
+    protected abstract boolean internalRun(long timeDelta, long currentFrameStartTime);
+
+    public RenderTask(boolean aRunAfter) {
+        runAfter = aRunAfter;
+    }
+
+    /**
+     * Get the start time of this task.
+     * It is the start time of the first frame this task was run on.
+     * @return the start time in ns
+     */
+    public long getStartTime() {
+        return mStartTime;
+    }
+
+    /**
+     * Schedule a reset of the recorded start time next time {@link RenderTask#run(long, long)} is run.
+     * @see RenderTask#getStartTime()
+     */
+    public void resetStartTime() {
+        mResetStartTime = true;
+    }
+}
--- a/mobile/android/base/preferences/SearchEnginePreference.java
+++ b/mobile/android/base/preferences/SearchEnginePreference.java
@@ -100,16 +100,20 @@ public class SearchEnginePreference exte
         }
         setTitle(titleSpannable);
 
         // setIcon is only available on Honeycomb and up.
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
             // Create a drawable from the iconURI and assign it to this Preference for display.
             String iconURI = geckoEngineJSON.getString("iconURI");
             Bitmap iconBitmap = BitmapUtils.getBitmapFromDataURI(iconURI);
+            // The favicon provided may be null or corrupt, if there was a network error or similar.
+            if (iconBitmap == null) {
+                return;
+            }
             Bitmap scaledIconBitmap = Bitmap.createScaledBitmap(iconBitmap, sIconSize, sIconSize, false);
             BitmapDrawable drawable = new BitmapDrawable(scaledIconBitmap);
             setIcon(drawable);
         }
     }
 
     /**
      * Set if this object's UI should show that this is the default engine.
--- a/mobile/android/base/resources/values-large-v11/styles.xml
+++ b/mobile/android/base/resources/values-large-v11/styles.xml
@@ -35,16 +35,37 @@
     <style name="TabsItem">
          <item name="android:nextFocusDown">@+id/close</item>
     </style>
 
     <style name="TabsItemClose">
          <item name="android:nextFocusUp">@+id/info</item>
     </style>
 
+    <style name="Toast">
+        <item name="android:layout_width">300dp</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_centerHorizontal">true</item>
+
+        <!-- Copied from root Toast style. Margin-left/right were removed since they
+             large tablets are never going to be 300dp wide. -->
+        <item name="android:layout_alignParentBottom">true</item>
+        <item name="android:layout_marginBottom">64dp</item>
+        <item name="android:orientation">horizontal</item>
+        <item name="android:background">@drawable/toast</item>
+        <item name="android:clickable">true</item>
+        <item name="android:showDividers">middle</item>
+        <item name="android:dividerPadding">16dp</item>
+
+        <item name="android:paddingTop">0dp</item>
+        <item name="android:paddingBottom">0dp</item>
+        <item name="android:paddingLeft">0dp</item>
+        <item name="android:paddingRight">0dp</item>
+    </style>
+
     <style name="Widget.BookmarksListView" parent="Widget.HomeListView">
         <item name="android:paddingLeft">32dp</item>
         <item name="android:paddingRight">32dp</item>
         <item name="android:scrollbarStyle">outsideOverlay</item>
     </style>
 
     <style name="Widget.TopBookmarksView" parent="Widget.GridView">
         <item name="android:paddingLeft">5dp</item>
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -1,12 +1,12 @@
 [testAwesomebar]
 # [testAwesomebarSwipes] # disabled on fig - bug 880060
 # [testBookmark] # disabled on fig - bug 880060
-# [testBookmarklets] # disabled on fig - bug 880060
+[testBookmarklets]
 [testBookmarkKeyword]
 [testBrowserSearchVisibility]
 [testJNI]
 # [testLoad] # see bug 851861
 [testNewTab]
 [testOrderedBroadcast]
 [testPrefsObserver]
 # [testPanCorrectness] # see bug 851861
--- a/mobile/android/base/tests/testBookmarklets.java.in
+++ b/mobile/android/base/tests/testBookmarklets.java.in
@@ -18,54 +18,60 @@ public class testBookmarklets extends Ab
         final String title = "alertBookmarklet";
         final String js = "javascript:alert(12 + .34)";
         boolean alerted;
 
         blockForGeckoReady();
 
         // load a standard page so bookmarklets work
         inputAndLoadUrl(url);
+        verifyPageTitle("Browser Blank Page 01"); // Waiting for page title to ensure the page is loaded
 
         // verify that user-entered bookmarklets do *not* work
         enterUrl(js);
         mActions.sendSpecialKey(Actions.SpecialKey.ENTER);
         alerted = waitForTest(new BooleanTest() {
             @Override
             public boolean test() {
                 return mSolo.searchButton("OK", true) || mSolo.searchText("12.34", true);
             }
         }, 3000);
         mAsserter.is(alerted, false, "Alert was not shown for user-entered bookmarklet");
 
         // add the bookmarklet to the database. there's currently no way to
         // add this using the UI, so we go through the content provider.
         addOrUpdateMobileBookmark(title, js);
 
-        // verify that bookmarklets clicked in awesomescreen work
-        /*  Removed by Bug 896576 - [fig] Remove [getBookmarksList] from BaseTest
-        ListView bookmarks = getBookmarksList(title);
+        // Open about:home in the Bookmarks page
+        openAboutHomeTab(AboutHomeTabs.BOOKMARKS);
+
+        ListView bookmarks = findListViewWithTag("bookmarks");
+        mAsserter.is(waitForListToLoad(bookmarks), true, "list is properly loaded");
+
+        int width = mDriver.getGeckoWidth();
+        int height = mDriver.getGeckoHeight();
 
-        Boolean found = false;
-        if (bookmarks == null) {
-            mAsserter.is(true, true, "Did not find the bookmarks section in the awesomebar");
-        } else {
-            for (int i = 0; i < bookmarks.getAdapter().getCount(); i++) {
-                Cursor c = (Cursor)bookmarks.getItemAtPosition(i);
-                String turl = c.getString(c.getColumnIndexOrThrow("url"));
-                if (turl.equals(js)) {
-                    found = true;
-                    mAsserter.is(1, 1, "Found bookmarklet added to bookmarks: " + js);
-                    mSolo.clickOnView(bookmarks.getChildAt(i));
-                }
+        // Scroll down so that the bookmarks list has more items on screen.
+        mActions.drag(width / 2, width / 2, height - 10, height / 2);
+
+        // Verify that bookmarklets clicked in awesomescreen work
+        boolean found = false;
+        for (int i = bookmarks.getHeaderViewsCount(); i < bookmarks.getAdapter().getCount(); i++) {
+            Cursor c = (Cursor)bookmarks.getItemAtPosition(i);
+            String aUrl = c.getString(c.getColumnIndexOrThrow("url"));
+            if (aUrl.equals(js)) {
+                found = true;
+                mAsserter.is(1, 1, "Found bookmarklet added to bookmarks: " + js);
+                mSolo.clickOnView(bookmarks.getChildAt(i));
             }
         }
+
         if (!found) {
             mAsserter.is(found, true, "Found the bookmark: " + js + " and clicked on it");
         }
-        */
 
         alerted = waitForTest(new BooleanTest() {
             @Override
             public boolean test() {
                 return mSolo.searchButton("OK", true) && mSolo.searchText("12.34", true);
             }
         }, 3000);
         mAsserter.is(alerted, true, "Alert was shown for clicked bookmarklet");
--- a/mobile/android/locales/jar.mn
+++ b/mobile/android/locales/jar.mn
@@ -27,24 +27,24 @@
   locale/@AB_CD@/browser/notification.dtd         (%chrome/notification.dtd)
   locale/@AB_CD@/browser/pippki.properties        (%chrome/pippki.properties)
   locale/@AB_CD@/browser/sync.dtd                 (%chrome/sync.dtd)
   locale/@AB_CD@/browser/sync.properties          (%chrome/sync.properties)
   locale/@AB_CD@/browser/prompt.dtd               (%chrome/prompt.dtd)
   locale/@AB_CD@/browser/feedback.dtd             (%chrome/feedback.dtd)
   locale/@AB_CD@/browser/phishing.dtd             (%chrome/phishing.dtd)
   locale/@AB_CD@/browser/payments.properties      (%chrome/payments.properties)
+  locale/@AB_CD@/browser/handling.properties      (%chrome/handling.properties)
 
 # overrides for toolkit l10n, also for en-US
 relativesrcdir toolkit/locales:
   locale/@AB_CD@/browser/overrides/about.dtd                       (%chrome/global/about.dtd)
   locale/@AB_CD@/browser/overrides/aboutAbout.dtd                  (%chrome/global/aboutAbout.dtd)
   locale/@AB_CD@/browser/overrides/aboutRights.dtd                 (%chrome/global/aboutRights.dtd)
   locale/@AB_CD@/browser/overrides/commonDialogs.properties        (%chrome/global/commonDialogs.properties)
-  locale/@AB_CD@/browser/overrides/handling/handling.properties    (%chrome/mozapps/handling/handling.properties)
   locale/@AB_CD@/browser/overrides/intl.properties                 (%chrome/global/intl.properties)
   locale/@AB_CD@/browser/overrides/intl.css                        (%chrome/global/intl.css)
   locale/@AB_CD@/browser/overrides/passwordmgr.properties          (%chrome/passwordmgr/passwordmgr.properties)
   locale/@AB_CD@/browser/overrides/search/search.properties        (%chrome/search/search.properties)
   locale/@AB_CD@/browser/overrides/update/updates.properties       (%chrome/mozapps/update/updates.properties)
 # about:support
   locale/@AB_CD@/browser/overrides/global/aboutSupport.dtd         (%chrome/global/aboutSupport.dtd)
   locale/@AB_CD@/browser/overrides/global/aboutSupport.properties  (%chrome/global/aboutSupport.properties)
@@ -56,17 +56,17 @@ relativesrcdir toolkit/locales:
 #about:telemetry
   locale/@AB_CD@/browser/overrides/global/aboutTelemetry.dtd         (%chrome/global/aboutTelemetry.dtd)
   locale/@AB_CD@/browser/overrides/global/aboutTelemetry.properties  (%chrome/global/aboutTelemetry.properties)
 
 % override chrome://global/locale/about.dtd chrome://browser/locale/overrides/about.dtd
 % override chrome://global/locale/aboutAbout.dtd chrome://browser/locale/overrides/aboutAbout.dtd
 % override chrome://global/locale/aboutRights.dtd chrome://browser/locale/overrides/aboutRights.dtd
 % override chrome://global/locale/commonDialogs.properties chrome://browser/locale/overrides/commonDialogs.properties
-% override chrome://mozapps/locale/handling/handling.properties chrome://browser/locale/overrides/handling/handling.properties
+% override chrome://mozapps/locale/handling/handling.properties chrome://browser/locale/handling.properties
 % override chrome://global/locale/intl.properties chrome://browser/locale/overrides/intl.properties
 % override chrome://global/locale/intl.css chrome://browser/locale/overrides/intl.css
 % override chrome://passwordmgr/locale/passwordmgr.properties chrome://browser/locale/overrides/passwordmgr/passwordmgr.properties
 % override chrome://global/locale/search/search.properties chrome://browser/locale/overrides/search/search.properties
 % override chrome://mozapps/locale/update/updates.properties chrome://browser/locale/overrides/update/updates.properties
 % override chrome://global/locale/aboutSupport.dtd chrome://browser/locale/overrides/global/aboutSupport.dtd
 % override chrome://global/locale/aboutSupport.properties chrome://browser/locale/overrides/global/aboutSupport.properties
 % override chrome://global/locale/crashes.dtd chrome://browser/locale/overrides/crashreporter/crashes.dtd
--- a/netwerk/base/src/Dashboard.cpp
+++ b/netwerk/base/src/Dashboard.cpp
@@ -20,16 +20,18 @@ using mozilla::dom::Sequence;
 
 Dashboard::Dashboard()
 {
     mEnableLogging = false;
 }
 
 Dashboard::~Dashboard()
 {
+    if (mDnsup.cancel)
+        mDnsup.cancel->Cancel(NS_ERROR_ABORT);
 }
 
 NS_IMETHODIMP
 Dashboard::RequestSockets(NetDashboardCallback* cb)
 {
     if (mSock.cb)
         return NS_ERROR_FAILURE;
     mSock.cb = cb;
@@ -398,76 +400,16 @@ Dashboard::RequestDNSInfo(NetDashboardCa
             return rv;
     }
 
     nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this, &Dashboard::GetDnsInfoDispatch);
     gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
     return NS_OK;
 }
 
-NS_IMETHODIMP
-Dashboard::RequestDNSLookup(const nsACString &aHost, NetDashboardCallback* cb)
-{
-    if (mDnsup.cb)
-        return NS_ERROR_FAILURE;
-    mDnsup.cb = cb;
-    nsresult rv;
-    mDnsup.thread = NS_GetCurrentThread();
-
-    if (!mDnsup.serv) {
-        mDnsup.serv = do_GetService("@mozilla.org/network/dns-service;1", &rv);
-        if (NS_FAILED(rv)) {
-            mDnsup.cb = nullptr;
-            return rv;
-        }
-    }
-    mDnsup.serv->AsyncResolve(aHost, 0, this, mDnsup.thread, getter_AddRefs(mDnsup.mCancel));
-    return NS_OK;
-}
-
-NS_IMETHODIMP
-Dashboard::OnLookupComplete(nsICancelable *aRequest, nsIDNSRecord *aRecord, nsresult aStatus)
-{
-    AutoSafeJSContext cx;
-
-    mozilla::dom::DNSLookupDict dict;
-    dict.mAddress.Construct();
-    dict.mError.Construct();
-    dict.mAnswer.Construct();
-
-    Sequence<nsString> &addresses = dict.mAddress.Value();
-    nsString &error = dict.mError.Value();
-    bool &answer = dict.mAnswer.Value();
-
-    if (!NS_FAILED(aStatus)) {
-        answer = true;
-        bool hasMore;
-        aRecord->HasMore(&hasMore);
-        while(hasMore) {
-           nsCString nextAddress;
-           aRecord->GetNextAddrAsString(nextAddress);
-           CopyASCIItoUTF16(nextAddress, *addresses.AppendElement());
-           aRecord->HasMore(&hasMore);
-        }
-    } else {
-        answer = false;
-        CopyASCIItoUTF16(GetErrorString(aStatus), error);
-    }
-
-    JS::RootedValue val(cx);
-    if (!dict.ToObject(cx, JS::NullPtr(), &val)) {
-        mDnsup.cb = nullptr;
-        return NS_ERROR_FAILURE;
-    }
-    mDnsup.cb->OnDashboardDataAvailable(val);
-    mDnsup.cb = nullptr;
-
-    return NS_OK;
-}
-
 void
 Dashboard::GetDnsInfoDispatch()
 {
     if (mDns.serv)
         mDns.serv->GetDNSCacheEntries(&mDns.data);
     nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this, &Dashboard::GetDNSCacheEntries);
     mDns.thread->Dispatch(event, NS_DISPATCH_NORMAL);
 }
@@ -525,16 +467,82 @@ Dashboard::GetDNSCacheEntries()
         return NS_ERROR_FAILURE;
     }
     mDns.cb->OnDashboardDataAvailable(val);
     mDns.cb = nullptr;
 
     return NS_OK;
 }
 
+NS_IMETHODIMP
+Dashboard::RequestDNSLookup(const nsACString &aHost, NetDashboardCallback *cb)
+{
+    if (mDnsup.cb)
+        return NS_ERROR_FAILURE;
+    nsresult rv;
+
+    if (!mDnsup.serv) {
+        mDnsup.serv = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+        if (NS_FAILED(rv))
+            return rv;
+    }
+
+    mDnsup.cb = cb;
+    rv = mDnsup.serv->AsyncResolve(aHost, 0, this, NS_GetCurrentThread(), getter_AddRefs(mDnsup.cancel));
+    if (NS_FAILED(rv)) {
+        mDnsup.cb = nullptr;
+        return rv;
+    }
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::OnLookupComplete(nsICancelable *aRequest, nsIDNSRecord *aRecord, nsresult aStatus)
+{
+    MOZ_ASSERT(aRequest == mDnsup.cancel);
+    mDnsup.cancel = nullptr;
+
+    AutoSafeJSContext cx;
+
+    mozilla::dom::DNSLookupDict dict;
+    dict.mAddress.Construct();
+    dict.mError.Construct();
+    dict.mAnswer.Construct();
+
+    Sequence<nsString> &addresses = dict.mAddress.Value();
+    nsString &error = dict.mError.Value();
+    bool &answer = dict.mAnswer.Value();
+
+    if (NS_SUCCEEDED(aStatus)) {
+        answer = true;
+        bool hasMore;
+        aRecord->HasMore(&hasMore);
+        while(hasMore) {
+           nsCString nextAddress;
+           aRecord->GetNextAddrAsString(nextAddress);
+           CopyASCIItoUTF16(nextAddress, *addresses.AppendElement());
+           aRecord->HasMore(&hasMore);
+        }
+    } else {
+        answer = false;
+        CopyASCIItoUTF16(GetErrorString(aStatus), error);
+    }
+
+    JS::RootedValue val(cx);
+    if (!dict.ToObject(cx, JS::NullPtr(), &val)) {
+        mDnsup.cb = nullptr;
+        return NS_ERROR_FAILURE;
+    }
+    mDnsup.cb->OnDashboardDataAvailable(val);
+    mDnsup.cb = nullptr;
+
+    return NS_OK;
+}
+
 void
 HttpConnInfo::SetHTTP1ProtocolVersion(uint8_t pv)
 {
     switch (pv) {
     case NS_HTTP_VERSION_0_9:
         protocolVersion.Assign(NS_LITERAL_STRING("http/0.9"));
         break;
     case NS_HTTP_VERSION_1_0:
--- a/netwerk/base/src/Dashboard.h
+++ b/netwerk/base/src/Dashboard.h
@@ -120,19 +120,18 @@ private:
         nsTArray<DNSCacheEntries> data;
         nsCOMPtr<NetDashboardCallback> cb;
         nsIThread* thread;
     };
 
     struct DnsLookup
     {
         nsCOMPtr<nsIDNSService> serv;
-        nsCOMPtr<nsICancelable> mCancel;
+        nsCOMPtr<nsICancelable> cancel;
         nsCOMPtr<NetDashboardCallback> cb;
-        nsIThread* thread;
     };
 
     struct ConnectionData
     {
         nsCOMPtr<nsISocketTransport> socket;
         nsCOMPtr<nsIInputStream> streamIn;
         nsCOMPtr<nsITimer> timer;
         nsCOMPtr<NetDashboardCallback> cb;
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -787,19 +787,20 @@ Connection::setClosedState()
     NS_ENSURE_FALSE(mAsyncExecutionThreadShuttingDown, NS_ERROR_UNEXPECTED);
     mAsyncExecutionThreadShuttingDown = true;
   }
 
   return NS_OK;
 }
 
 bool
-Connection::isAsyncClosing() {
+Connection::isClosing(bool aResultOnClosed) {
   MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
-  return mAsyncExecutionThreadShuttingDown && ConnectionReady();
+  return mAsyncExecutionThreadShuttingDown &&
+    (aResultOnClosed || ConnectionReady());
 }
 
 nsresult
 Connection::internalClose()
 {
 #ifdef DEBUG
   // Sanity checks to make sure we are in the proper state before calling this.
   NS_ASSERTION(mDBConn, "Database connection is already null!");
@@ -837,17 +838,17 @@ Connection::internalClose()
     sqlite3_stmt *stmt = NULL;
     while ((stmt = ::sqlite3_next_stmt(mDBConn, stmt))) {
       PR_LOG(gStorageLog, PR_LOG_NOTICE,
              ("Auto-finalizing SQL statement '%s' (%x)",
               ::sqlite3_sql(stmt),
               stmt));
 
 #ifdef DEBUG
-      char *msg = ::PR_smprintf("SQL statement '%s' (%x) should have been finalized",
+      char *msg = ::PR_smprintf("SQL statement '%s' (%x) should have been finalized before closing the connection",
                                 ::sqlite3_sql(stmt),
                                 stmt);
       NS_WARNING(msg);
       ::PR_smprintf_free(msg);
 #endif // DEBUG
 
       srv = ::sqlite3_finalize(stmt);
 
--- a/storage/src/mozStorageConnection.h
+++ b/storage/src/mozStorageConnection.h
@@ -162,21 +162,26 @@ public:
    */
   int stepStatement(sqlite3_stmt* aStatement);
 
   bool ConnectionReady() {
     return mDBConn != nullptr;
   }
 
   /**
-   * True if this is an async connection, it is shutting down and it is not
-   * closed yet.
+   * True if this connection is currently shutting down.
+   *
+   * In particular, if |isClosing(true)| returns |true|, any sqlite3 statement
+   * belonging to this connection must be discarded as its memory has already
+   * been released to sqlite3.
+   *
+   * @param aResultOnceClosed
+   *        The value to return if closing has completed.
    */
-  bool isAsyncClosing();
-
+  bool isClosing(bool aResultOnceClosed = false);
 
   nsresult initializeClone(Connection *aClone, bool aReadOnly);
 
 private:
   ~Connection();
   nsresult initializeInternal(nsIFile *aDatabaseFile);
 
   /**
@@ -234,21 +239,29 @@ private:
   nsCOMPtr<nsIFile> mDatabaseFile;
 
   /**
    * Lazily created thread for asynchronous statement execution.  Consumers
    * should use getAsyncExecutionTarget rather than directly accessing this
    * field.
    */
   nsCOMPtr<nsIThread> mAsyncExecutionThread;
+
   /**
-   * Set to true by Close() prior to actually shutting down the thread.  This
-   * lets getAsyncExecutionTarget() know not to hand out any more thread
-   * references (or to create the thread in the first place).  This variable
-   * should be accessed while holding the mAsyncExecutionMutex.
+   * Set to true by Close() or AsyncClose() prior to shutdown.
+   *
+   * If false, we guarantee both that the underlying sqlite3 database
+   * connection is still open and that getAsyncExecutionTarget() can
+   * return a thread. Once true, either the sqlite3 database
+   * connection is being shutdown or it has been
+   * shutdown. Additionally, once true, getAsyncExecutionTarget()
+   * returns null.
+   *
+   * This variable should be accessed while holding the
+   * mAsyncExecutionMutex.
    */
   bool mAsyncExecutionThreadShuttingDown;
 
   /**
    * Tracks if we have a transaction in progress or not.  Access protected by
    * mDBMutex.
    */
   bool mTransactionInProgress;
--- a/storage/src/mozStorageService.cpp
+++ b/storage/src/mozStorageService.cpp
@@ -897,17 +897,17 @@ Service::Observe(nsISupports *, const ch
       nsTArray<nsRefPtr<Connection> > connections;
       getConnections(connections);
       anyOpen = false;
       for (uint32_t i = 0; i < connections.Length(); i++) {
         nsRefPtr<Connection> &conn = connections[i];
 
         // While it would be nice to close all connections, we only
         // check async ones for now.
-        if (conn->isAsyncClosing()) {
+        if (conn->isClosing()) {
           anyOpen = true;
           break;
         }
       }
       if (anyOpen) {
         nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
         NS_ProcessNextEvent(thread);
       }
--- a/storage/src/mozStorageStatement.cpp
+++ b/storage/src/mozStorageStatement.cpp
@@ -352,22 +352,73 @@ Statement::Finalize()
 }
 
 nsresult
 Statement::internalFinalize(bool aDestructing)
 {
   if (!mDBStatement)
     return NS_OK;
 
+  int srv = SQLITE_OK;
+
+  if (!mDBConnection->isClosing(true)) {
+    //
+    // The connection is still open. While statement finalization and
+    // closing may, in some cases, take place in two distinct threads,
+    // we have a guarantee that the connection will remain open until
+    // this method terminates:
+    //
+    // a. The connection will be closed synchronously. In this case,
+    // there is no race condition, as everything takes place on the
+    // same thread.
+    //
+    // b. The connection is closed asynchronously and this code is
+    // executed on the opener thread. In this case, asyncClose() has
+    // not been called yet and will not be called before we return
+    // from this function.
+    //
+    // c. The connection is closed asynchronously and this code is
+    // executed on the async execution thread. In this case,
+    // AsyncCloseConnection::Run() has not been called yet and will
+    // not be called before we return from this function.
+    //
+    // In either case, the connection is still valid, hence closing
+    // here is safe.
+    //
 #ifdef PR_LOGGING
-  PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Finalizing statement '%s'",
-                                      ::sqlite3_sql(mDBStatement)));
+    PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Finalizing statement '%s' during garbage-collection",
+                                        ::sqlite3_sql(mDBStatement)));
 #endif
+    srv = ::sqlite3_finalize(mDBStatement);
+  }
+#ifdef DEBUG
+  else {
+    //
+    // The database connection is either closed or closing. The sqlite
+    // statement has either been finalized already by the connection
+    // or is about to be finalized by the connection.
+    //
+    // Finalizing it here would be useless and segfaultish.
+    //
 
-  int srv = ::sqlite3_finalize(mDBStatement);
+    char *msg = ::PR_smprintf("SQL statement (%x) should have been finalized"
+      "before garbage-collection. For more details on this statement, set"
+      "NSPR_LOG_MESSAGES=mozStorage:5 .",
+      mDBStatement);
+    //
+    // Note that we can't display the statement itself, as the data structure
+    // is not valid anymore. However, the address shown here should help
+    // developers correlate with the more complete debug message triggered
+    // by AsyncClose().
+    //
+    NS_WARNING(msg);
+    ::PR_smprintf_free(msg);
+  }
+#endif // DEBUG
+
   mDBStatement = nullptr;
 
   if (mAsyncStatement) {
     // If the destructor called us, there are no pending async statements (they
     // hold a reference to us) and we can/must just kill the statement directly.
     if (aDestructing)
       destructorAsyncFinalize();
     else
--- a/storage/test/unit/test_storage_connection.js
+++ b/storage/test/unit/test_storage_connection.js
@@ -289,16 +289,42 @@ add_task(function test_asyncClose_succee
   stmt.executeAsync();
   stmt.finalize();
 
   yield asyncClose(getOpenedDatabase());
   // Reset gDBConn so that later tests will get a new connection object.
   gDBConn = null;
 });
 
+add_task(function test_close_then_release_statement() {
+  // Testing the behavior in presence of a bad client that finalizes
+  // statements after the database has been closed (typically by
+  // letting the gc finalize the statement).
+  let db = getOpenedDatabase();
+  let stmt = createStatement("SELECT * FROM test -- test_close_then_release_statement");
+  db.close();
+  stmt.finalize(); // Finalize too late - this should not crash
+
+  // Reset gDBConn so that later tests will get a new connection object.
+  gDBConn = null;
+});
+
+add_task(function test_asyncClose_then_release_statement() {
+  // Testing the behavior in presence of a bad client that finalizes
+  // statements after the database has been async closed (typically by
+  // letting the gc finalize the statement).
+  let db = getOpenedDatabase();
+  let stmt = createStatement("SELECT * FROM test -- test_asyncClose_then_release_statement");
+  yield asyncClose(db);
+  stmt.finalize(); // Finalize too late - this should not crash
+
+  // Reset gDBConn so that later tests will get a new connection object.
+  gDBConn = null;
+});
+
 add_task(function test_close_fails_with_async_statement_ran()
 {
   let deferred = Promise.defer();
   let stmt = createStatement("SELECT * FROM test");
   stmt.executeAsync();
   stmt.finalize();
 
   let db = getOpenedDatabase();
--- a/toolkit/components/social/SocialService.jsm
+++ b/toolkit/components/social/SocialService.jsm
@@ -57,24 +57,16 @@ let SocialServiceInternal = {
         if (manifest && typeof(manifest) == "object" && manifest.origin)
           yield manifest;
       } catch (err) {
         Cu.reportError("SocialService: failed to load manifest: " + pref +
                        ", exception: " + err);
       }
     }
   },
-  getManifestByOrigin: function(origin) {
-    for (let manifest of SocialServiceInternal.manifests) {
-      if (origin == manifest.origin) {
-        return manifest;
-      }
-    }
-    return null;
-  },
   getManifestPrefname: function(origin) {
     // Retrieve the prefname for a given origin/manifest.
     // If no existing pref, return a generated prefname.
     let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
     let prefs = MANIFEST_PREFS.getChildList("", []);
     for (let pref of prefs) {
       try {
         var manifest = JSON.parse(MANIFEST_PREFS.getComplexValue(pref, Ci.nsISupportsString).data);
@@ -386,17 +378,17 @@ this.SocialService = {
   // provider exists, or the activated provider on success.
   addBuiltinProvider: function addBuiltinProvider(origin, onDone) {
     if (SocialServiceInternal.providers[origin]) {
       schedule(function() {
         onDone(SocialServiceInternal.providers[origin]);
       });
       return;
     }
-    let manifest = SocialServiceInternal.getManifestByOrigin(origin);
+    let manifest = SocialService.getManifestByOrigin(origin);
     if (manifest) {
       let addon = new AddonWrapper(manifest);
       AddonManagerPrivate.callAddonListeners("onEnabling", addon, false);
       addon.pendingOperations |= AddonManager.PENDING_ENABLE;
       this.addProvider(manifest, onDone);
       addon.pendingOperations -= AddonManager.PENDING_ENABLE;
       AddonManagerPrivate.callAddonListeners("onEnabled", addon);
       return;
@@ -411,30 +403,30 @@ this.SocialService = {
     if (SocialServiceInternal.providers[manifest.origin])
       throw new Error("SocialService.addProvider: provider with this origin already exists");
 
     let provider = new SocialProvider(manifest);
     SocialServiceInternal.providers[provider.origin] = provider;
     ActiveProviders.add(provider.origin);
 
     this.getOrderedProviderList(function (providers) {
-      this._notifyProviderListeners("provider-added", providers);
+      this._notifyProviderListeners("provider-enabled", provider.origin, providers);
       if (onDone)
         onDone(provider);
     }.bind(this));
   },
 
   // Removes a provider with the given origin, and notifies when the removal is
   // complete.
   removeProvider: function removeProvider(origin, onDone) {
     if (!(origin in SocialServiceInternal.providers))
       throw new Error("SocialService.removeProvider: no provider with origin " + origin + " exists!");
 
     let provider = SocialServiceInternal.providers[origin];
-    let manifest = SocialServiceInternal.getManifestByOrigin(origin);
+    let manifest = SocialService.getManifestByOrigin(origin);
     let addon = manifest && new AddonWrapper(manifest);
     if (addon) {
       AddonManagerPrivate.callAddonListeners("onDisabling", addon, false);
       addon.pendingOperations |= AddonManager.PENDING_DISABLE;
     }
     provider.enabled = false;
 
     ActiveProviders.delete(provider.origin);
@@ -445,17 +437,17 @@ this.SocialService = {
       // we have to do this now so the addon manager ui will update an uninstall
       // correctly.
       addon.pendingOperations -= AddonManager.PENDING_DISABLE;
       AddonManagerPrivate.callAddonListeners("onDisabled", addon);
       AddonManagerPrivate.notifyAddonChanged(addon.id, ADDON_TYPE_SERVICE, false);
     }
 
     this.getOrderedProviderList(function (providers) {
-      this._notifyProviderListeners("provider-removed", providers);
+      this._notifyProviderListeners("provider-disabled", origin, providers);
       if (onDone)
         onDone();
     }.bind(this));
   },
 
   // Returns a single provider object with the specified origin.  The provider
   // must be "installed" (ie, in ActiveProviders)
   getProvider: function getProvider(origin, onDone) {
@@ -466,16 +458,25 @@ this.SocialService = {
 
   // Returns an unordered array of installed providers
   getProviderList: function(onDone) {
     schedule(function () {
       onDone(SocialServiceInternal.providerArray);
     });
   },
 
+  getManifestByOrigin: function(origin) {
+    for (let manifest of SocialServiceInternal.manifests) {
+      if (origin == manifest.origin) {
+        return manifest;
+      }
+    }
+    return null;
+  },
+
   // Returns an array of installed providers, sorted by frecency
   getOrderedProviderList: function(onDone) {
     SocialServiceInternal.orderedProviders(onDone);
   },
 
   getOriginActivationType: function (origin) {
     return getOriginActivationType(origin);
   },
@@ -483,28 +484,28 @@ this.SocialService = {
   _providerListeners: new Map(),
   registerProviderListener: function registerProviderListener(listener) {
     this._providerListeners.set(listener, 1);
   },
   unregisterProviderListener: function unregisterProviderListener(listener) {
     this._providerListeners.delete(listener);
   },
 
-  _notifyProviderListeners: function (topic, data) {
+  _notifyProviderListeners: function (topic, origin, providers) {
     for (let [listener, ] of this._providerListeners) {
       try {
-        listener(topic, data);
+        listener(topic, origin, providers);
       } catch (ex) {
         Components.utils.reportError("SocialService: provider listener threw an exception: " + ex);
       }
     }
   },
 
   _manifestFromData: function(type, data, principal) {
-    let sameOriginRequired = ['workerURL', 'sidebarURL', 'shareURL'];
+    let sameOriginRequired = ['workerURL', 'sidebarURL', 'shareURL', 'statusURL'];
 
     if (type == 'directory') {
       // directory provided manifests must have origin in manifest, use that
       if (!data['origin']) {
         Cu.reportError("SocialService.manifestFromData directory service provided manifest without origin.");
         return null;
       }
       let URI = Services.io.newURI(data.origin, null, null);
@@ -512,18 +513,19 @@ this.SocialService = {
     }
     // force/fixup origin
     data.origin = principal.origin;
 
     // workerURL, sidebarURL is required and must be same-origin
     // iconURL and name are required
     // iconURL may be a different origin (CDN or data url support) if this is
     // a whitelisted or directory listed provider
-    if (!data['workerURL'] && !data['sidebarURL'] && !data['shareURL']) {
-      Cu.reportError("SocialService.manifestFromData manifest missing required workerURL or sidebarURL.");
+    let providerHasFeatures = [url for (url of sameOriginRequired) if (data[url])].length > 0;
+    if (!providerHasFeatures) {
+      Cu.reportError("SocialService.manifestFromData manifest missing required urls.");
       return null;
     }
     if (!data['name'] || !data['iconURL']) {
       Cu.reportError("SocialService.manifestFromData manifest missing name or iconURL.");
       return null;
     }
     for (let url of sameOriginRequired) {
       if (data[url]) {
@@ -591,17 +593,20 @@ this.SocialService = {
                       installOrigin + "] is blocklisted");
 
     AddonManager.getAddonByID(id, function(aAddon) {
       if (aAddon && aAddon.userDisabled) {
         aAddon.cancelUninstall();
         aAddon.userDisabled = false;
       }
       schedule(function () {
-        this._installProvider(aDOMDocument, data, installCallback);
+        this._installProvider(aDOMDocument, data, aManifest => {
+          this._notifyProviderListeners("provider-installed", aManifest.origin);
+          installCallback(aManifest);
+        });
       }.bind(this));
     }.bind(this));
   },
 
   _installProvider: function(aDOMDocument, data, installCallback) {
     let sourceURI = aDOMDocument.location.href;
     let installOrigin = aDOMDocument.nodePrincipal.origin;
 
@@ -656,17 +661,17 @@ this.SocialService = {
     }
   },
 
   /**
    * updateProvider is used from the worker to self-update.  Since we do not
    * have knowledge of the currently selected provider here, we will notify
    * the front end to deal with any reload.
    */
-  updateProvider: function(aUpdateOrigin, aManifest, aCallback) {
+  updateProvider: function(aUpdateOrigin, aManifest) {
     let originUri = Services.io.newURI(aUpdateOrigin, null, null);
     let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri);
     let installType = this.getOriginActivationType(aUpdateOrigin);
     // if we get data, we MUST have a valid manifest generated from the data
     let manifest = this._manifestFromData(installType, aManifest, principal);
     if (!manifest)
       throw new Error("SocialService.installProvider: service configuration is invalid from " + aUpdateOrigin);
 
@@ -677,23 +682,25 @@ this.SocialService = {
     Services.prefs.setComplexValue(getPrefnameFromOrigin(manifest.origin), Ci.nsISupportsString, string);
 
     // overwrite the existing provider then notify the front end so it can
     // handle any reload that might be necessary.
     if (ActiveProviders.has(manifest.origin)) {
       let provider = new SocialProvider(manifest);
       SocialServiceInternal.providers[provider.origin] = provider;
       // update the cache and ui, reload provider if necessary
-      this._notifyProviderListeners("provider-update", provider);
+      this.getOrderedProviderList(providers => {
+        this._notifyProviderListeners("provider-update", provider.origin, providers);
+      });
     }
 
   },
 
   uninstallProvider: function(origin, aCallback) {
-    let manifest = SocialServiceInternal.getManifestByOrigin(origin);
+    let manifest = SocialService.getManifestByOrigin(origin);
     let addon = new AddonWrapper(manifest);
     addon.uninstall(aCallback);
   }
 };
 
 /**
  * The SocialProvider object represents a social provider, and allows
  * access to its FrameWorker (if it has one).
@@ -715,16 +722,17 @@ function SocialProvider(input) {
 
   this.name = input.name;
   this.iconURL = input.iconURL;
   this.icon32URL = input.icon32URL;
   this.icon64URL = input.icon64URL;
   this.workerURL = input.workerURL;
   this.sidebarURL = input.sidebarURL;
   this.shareURL = input.shareURL;
+  this.statusURL = input.statusURL;
   this.origin = input.origin;
   let originUri = Services.io.newURI(input.origin, null, null);
   this.principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri);
   this.ambientNotificationIcons = {};
   this.errorState = null;
   this.frecency = 0;
 
   let activationType = getOriginActivationType(input.origin);
@@ -761,17 +769,17 @@ SocialProvider.prototype = {
     if (enable) {
       this._activate();
     } else {
       this._terminate();
     }
   },
 
   get manifest() {
-    return SocialServiceInternal.getManifestByOrigin(this.origin);
+    return SocialService.getManifestByOrigin(this.origin);
   },
 
   // Reference to a workerAPI object for this provider. Null if the provider has
   // no FrameWorker, or is disabled.
   workerAPI: null,
 
   // Contains information related to the user's profile. Populated by the
   // workerAPI via updateUserProfile.
@@ -1007,17 +1015,17 @@ function getAddonIDFromOrigin(origin) {
 
 function getPrefnameFromOrigin(origin) {
   return "social.manifest." + SocialServiceInternal.getManifestPrefname(origin);
 }
 
 function AddonInstaller(sourceURI, aManifest, installCallback) {
   aManifest.updateDate = Date.now();
   // get the existing manifest for installDate
-  let manifest = SocialServiceInternal.getManifestByOrigin(aManifest.origin);
+  let manifest = SocialService.getManifestByOrigin(aManifest.origin);
   let isNewInstall = !manifest;
   if (manifest && manifest.installDate)
     aManifest.installDate = manifest.installDate;
   else
     aManifest.installDate = aManifest.updateDate;
 
   this.sourceURI = sourceURI;
   this.install = function() {
@@ -1083,16 +1091,17 @@ var SocialAddonProvider = {
   },
 
   removeAddon: function(aAddon, aCallback) {
     AddonManagerPrivate.callAddonListeners("onUninstalling", aAddon, false);
     aAddon.pendingOperations |= AddonManager.PENDING_UNINSTALL;
     Services.prefs.clearUserPref(getPrefnameFromOrigin(aAddon.manifest.origin));
     aAddon.pendingOperations -= AddonManager.PENDING_UNINSTALL;
     AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon);
+    SocialService._notifyProviderListeners("provider-uninstalled", aAddon.manifest.origin);
     if (aCallback)
       schedule(aCallback);
   }
 }
 
 
 function AddonWrapper(aManifest) {
   this.manifest = aManifest;
--- a/toolkit/devtools/server/actors/styles.js
+++ b/toolkit/devtools/server/actors/styles.js
@@ -15,16 +15,19 @@ loader.lazyGetter(this, "CssLogic", () =
 loader.lazyGetter(this, "DOMUtils", () => Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils));
 
 // The PageStyle actor flattens the DOM CSS objects a little bit, merging
 // Rules and their Styles into one actor.  For elements (which have a style
 // but no associated rule) we fake a rule with the following style id.
 const ELEMENT_STYLE = 100;
 exports.ELEMENT_STYLE = ELEMENT_STYLE;
 
+const PSEUDO_ELEMENTS = [":first-line", ":first-letter", ":before", ":after", ":-moz-selection"];
+exports.PSEUDO_ELEMENTS = PSEUDO_ELEMENTS;
+
 // Predeclare the domnode actor type for use in requests.
 types.addActorType("domnode");
 
 /**
  * DOM Nodes returned by the style actor will be owned by the DOM walker
  * for the connection.
   */
 types.addLifetime("walker", "walker");
@@ -355,46 +358,56 @@ var PageStyleActor = protocol.ActorClass
 
     if (!inherited || this._hasInheritedProps(element.style)) {
       rules.push({
         rule: elementStyle,
         inherited: inherited,
       });
     }
 
-    // Get the styles that apply to the element.
-    let domRules = DOMUtils.getCSSStyleRules(element);
+    let pseudoElements = inherited ? [null] : [null, ...PSEUDO_ELEMENTS];
+    for (let pseudo of pseudoElements) {
 
-    // getCSSStyleRules returns ordered from least-specific to
-    // most-specific.
-    for (let i = domRules.Count() - 1; i >= 0; i--) {
-      let domRule = domRules.GetElementAt(i);
+      // Get the styles that apply to the element.
+      let domRules = DOMUtils.getCSSStyleRules(element, pseudo);
 
-      let isSystem = !CssLogic.isContentStylesheet(domRule.parentStyleSheet);
-
-      if (isSystem && options.filter != CssLogic.FILTER.UA) {
+      if (!domRules) {
         continue;
       }
 
-      if (inherited) {
-        // Don't include inherited rules if none of its properties
-        // are inheritable.
-        let hasInherited = Array.prototype.some.call(domRule.style, prop => {
-          return DOMUtils.isInheritedProperty(prop);
-        });
-        if (!hasInherited) {
+      // getCSSStyleRules returns ordered from least-specific to
+      // most-specific.
+      for (let i = domRules.Count() - 1; i >= 0; i--) {
+        let domRule = domRules.GetElementAt(i);
+
+        let isSystem = !CssLogic.isContentStylesheet(domRule.parentStyleSheet);
+
+        if (isSystem && options.filter != CssLogic.FILTER.UA) {
           continue;
         }
+
+        if (inherited) {
+          // Don't include inherited rules if none of its properties
+          // are inheritable.
+          let hasInherited = Array.prototype.some.call(domRule.style, prop => {
+            return DOMUtils.isInheritedProperty(prop);
+          });
+          if (!hasInherited) {
+            continue;
+          }
+        }
+
+        let ruleActor = this._styleRef(domRule);
+        rules.push({
+          rule: ruleActor,
+          inherited: inherited,
+          pseudoElement: pseudo
+        });
       }
 
-      let ruleActor = this._styleRef(domRule);
-      rules.push({
-        rule: ruleActor,
-        inherited: inherited,
-      });
     }
   },
 
   /**
    * Expand Sets of rules and sheets to include all parent rules and sheets.
    */
   expandSets: function(ruleSet, sheetSet) {
     // Sets include new items in their iteration
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -1653,26 +1653,28 @@ AndroidBridge::SetURITitle(const nsAStri
     AutoLocalJNIFrame jniFrame(env);
     jstring jstrURI = NewJavaString(&jniFrame, aURI);
     jstring jstrTitle = NewJavaString(&jniFrame, aTitle);
     env->CallStaticVoidMethod(mGeckoAppShellClass, jSetUriTitle, jstrURI, jstrTitle);
 }
 
 nsresult
 AndroidBridge::GetSegmentInfoForText(const nsAString& aText,
-                                     dom::mobilemessage::SmsSegmentInfoData* aData)
+                                     nsIMobileMessageCallback* aRequest)
 {
 #ifndef MOZ_WEBSMS_BACKEND
     return NS_ERROR_FAILURE;
 #else
     ALOG_BRIDGE("AndroidBridge::GetSegmentInfoForText");
 
-    aData->segments() = 0;
-    aData->charsPerSegment() = 0;
-    aData->charsAvailableInLastSegment() = 0;
+    dom::mobilemessage::SmsSegmentInfoData data;
+
+    data.segments() = 0;
+    data.charsPerSegment() = 0;
+    data.charsAvailableInLastSegment() = 0;
 
     JNIEnv *env = GetJNIEnv();
     if (!env)
         return NS_ERROR_FAILURE;
 
     AutoLocalJNIFrame jniFrame(env);
     jstring jText = NewJavaString(&jniFrame, aText);
     jobject obj = env->CallStaticObjectMethod(mAndroidSmsMessageClass,
@@ -1681,23 +1683,27 @@ AndroidBridge::GetSegmentInfoForText(con
         return NS_ERROR_FAILURE;
 
     jintArray arr = static_cast<jintArray>(obj);
     if (!arr || env->GetArrayLength(arr) != 4)
         return NS_ERROR_FAILURE;
 
     jint* info = env->GetIntArrayElements(arr, JNI_FALSE);
 
-    aData->segments() = info[0]; // msgCount
-    aData->charsPerSegment() = info[2]; // codeUnitsRemaining
+    data.segments() = info[0]; // msgCount
+    data.charsPerSegment() = info[2]; // codeUnitsRemaining
     // segmentChars = (codeUnitCount + codeUnitsRemaining) / msgCount
-    aData->charsAvailableInLastSegment() = (info[1] + info[2]) / info[0];
+    data.charsAvailableInLastSegment() = (info[1] + info[2]) / info[0];
 
     env->ReleaseIntArrayElements(arr, info, JNI_ABORT);
-    return NS_OK;
+
+    // TODO Bug 908598 - Should properly use |QueueSmsRequest(...)| to queue up
+    // the nsIMobileMessageCallback just like other functions.
+    nsCOMPtr<nsIDOMMozSmsSegmentInfo> info = new SmsSegmentInfo(data);
+    return aRequest->NotifySegmentInfoForTextGot(info);
 #endif
 }
 
 void
 AndroidBridge::SendMessage(const nsAString& aNumber,
                            const nsAString& aMessage,
                            nsIMobileMessageCallback* aRequest)
 {
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -351,17 +351,17 @@ public:
 
     void CloseCamera();
 
     void EnableBatteryNotifications();
     void DisableBatteryNotifications();
     void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo);
 
     nsresult GetSegmentInfoForText(const nsAString& aText,
-                                   dom::mobilemessage::SmsSegmentInfoData* aData);
+                                   nsIMobileMessageCallback* aRequest);
     void SendMessage(const nsAString& aNumber, const nsAString& aText,
                      nsIMobileMessageCallback* aRequest);
     void GetMessage(int32_t aMessageId, nsIMobileMessageCallback* aRequest);
     void DeleteMessage(int32_t aMessageId, nsIMobileMessageCallback* aRequest);
     void CreateMessageList(const dom::mobilemessage::SmsFilterData& aFilter,
                            bool aReverse, nsIMobileMessageCallback* aRequest);
     void GetNextMessageInList(int32_t aListId, nsIMobileMessageCallback* aRequest);
     void ClearMessageList(int32_t aListId);