Merge m-c to elm
authorNick Alexander <nalexander@mozilla.com>
Thu, 14 Nov 2013 22:41:14 -0800
changeset 161490 1e18bf475edb08f1ef365caacdd84b063981cc5f
parent 161489 1c895f764624e3033fd6fafe698846bd619745ec (current diff)
parent 154845 b2fab608772f4d8cc243be416638f54e38e4eb00 (diff)
child 161491 73a447af8c4aab01a18496f7c867627af9ef9a54
push id25884
push userttaubert@mozilla.com
push dateSat, 21 Dec 2013 00:37:32 +0000
treeherdermozilla-central@b3d4af4ec2df [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to elm
browser/app/profile/firefox.js
caps/tests/mochitest/Makefile.in
netwerk/test/TestUDPServerSocket.cpp
--- a/CLOBBER
+++ b/CLOBBER
@@ -13,9 +13,9 @@
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
-Another Windows WebIDL clobber needed due to bug 928195
+Another Windows WebIDL clobber needed due to bug 674741
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "d9e07db2d0512169f304985a070eade8e81e6ba7", 
+    "revision": "88cc9854d6daff8c577e3867b95a1e523e429112", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -184,16 +184,19 @@
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_file.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_media.xpt
 @BINPATH@/components/dom_network.xpt
+#ifdef MOZ_NFC
+@BINPATH@/components/dom_nfc.xpt
+#endif
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_indexeddb.xpt
 @BINPATH@/components/dom_inputmethod.xpt
 @BINPATH@/components/dom_offline.xpt
 @BINPATH@/components/dom_payment.xpt
 @BINPATH@/components/dom_json.xpt
 @BINPATH@/components/dom_messages.xpt
@@ -489,16 +492,23 @@
 @BINPATH@/components/messageWakeupService.manifest
 @BINPATH@/components/SettingsManager.js
 @BINPATH@/components/SettingsManager.manifest
 @BINPATH@/components/SettingsService.js
 @BINPATH@/components/SettingsService.manifest
 @BINPATH@/components/webvtt.xpt
 @BINPATH@/components/WebVTT.manifest
 @BINPATH@/components/WebVTTParserWrapper.js
+#ifdef MOZ_NFC
+@BINPATH@/components/nsNfc.manifest
+@BINPATH@/components/nsNfc.js
+@BINPATH@/components/Nfc.manifest
+@BINPATH@/components/Nfc.js
+@BINPATH@/components/NfcContentHelper.js
+#endif
 #ifdef MOZ_ENABLE_DBUS
 @BINPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@
 #endif
 @BINPATH@/components/nsINIProcessor.manifest
 @BINPATH@/components/nsINIProcessor.js
 @BINPATH@/components/nsPrompter.manifest
 @BINPATH@/components/nsPrompter.js
 #ifdef MOZ_SERVICES_SYNC
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1205,16 +1205,21 @@ pref("devtools.browserconsole.filter.sec
 // Text size in the Web Console. Use 0 for the system default size.
 pref("devtools.webconsole.fontSize", 0);
 
 // Persistent logging: |true| if you want the Web Console to keep all of the
 // logged messages after reloading the page, |false| if you want the output to
 // be cleared each time page navigation happens.
 pref("devtools.webconsole.persistlog", false);
 
+// Web Console timestamp: |true| if you want the logs and instructions
+// in the Web Console to display a timestamp, or |false| to not display
+// any timestamps.
+pref("devtools.webconsole.timestampMessages", false);
+
 // The number of lines that are displayed in the web console for the Net,
 // CSS, JS and Web Developer categories.
 pref("devtools.hud.loglimit.network", 200);
 pref("devtools.hud.loglimit.cssparser", 200);
 pref("devtools.hud.loglimit.exception", 200);
 pref("devtools.hud.loglimit.console", 200);
 
 // The developer tools editor configuration:
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -726,20 +726,16 @@ var gPluginHandler = {
     }
 
     let browser = aNotification.browser;
     let contentWindow = browser.contentWindow;
     if (aNewState != "continue") {
       let principal = contentWindow.document.nodePrincipal;
       Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString,
                                       permission, expireType, expireTime);
-
-      if (aNewState == "block") {
-        return;
-      }
     }
 
     // Manually activate the plugins that would have been automatically
     // activated.
     let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
     let plugins = cwu.plugins;
     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
@@ -747,29 +743,33 @@ var gPluginHandler = {
     let pluginFound = false;
     for (let plugin of plugins) {
       plugin.QueryInterface(Ci.nsIObjectLoadingContent);
       if (!gPluginHandler.isKnownPlugin(plugin)) {
         continue;
       }
       if (aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
         pluginFound = true;
-        if (gPluginHandler.canActivatePlugin(plugin)) {
-          let overlay = this.getPluginUI(plugin, "main");
-          if (overlay) {
-            overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
+        if (aNewState == "block") {
+          plugin.reload(true);
+        } else {
+          if (gPluginHandler.canActivatePlugin(plugin)) {
+            let overlay = this.getPluginUI(plugin, "main");
+            if (overlay) {
+              overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
+            }
+            plugin.playPlugin();
           }
-          plugin.playPlugin();
         }
       }
     }
 
     // If there are no instances of the plugin on the page any more, what the
     // user probably needs is for us to allow and then refresh.
-    if (!pluginFound) {
+    if (aNewState != "block" && !pluginFound) {
       browser.reload();
     }
   },
 
   _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPlugin, aShowNow) {
     let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
     let plugins = [];
 
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -1509,16 +1509,17 @@ SocialStatus = {
         notificationFrameId, /* frame name */
         panel, /* parent */
         {
           "type": "content",
           "mozbrowser": "true",
           "class": "social-panel-frame",
           "id": notificationFrameId,
           "tooltip": "aHTMLTooltip",
+          "context": "contentAreaContextMenu",
 
           // 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
         }
--- a/browser/base/content/test/general/browser_CTP_data_urls.js
+++ b/browser/base/content/test/general/browser_CTP_data_urls.js
@@ -3,62 +3,16 @@ const gTestRoot = rootDir;
 const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 
 var gTestBrowser = null;
 var gNextTest = null;
 var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
-// This listens for the next opened tab and checks it is of the right url.
-// opencallback is called when the new tab is fully loaded
-// closecallback is called when the tab is closed
-function TabOpenListener(url, opencallback, closecallback) {
-  this.url = url;
-  this.opencallback = opencallback;
-  this.closecallback = closecallback;
-
-  gBrowser.tabContainer.addEventListener("TabOpen", this, false);
-}
-
-TabOpenListener.prototype = {
-  url: null,
-  opencallback: null,
-  closecallback: null,
-  tab: null,
-  browser: null,
-
-  handleEvent: function(event) {
-    if (event.type == "TabOpen") {
-      gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
-      this.tab = event.originalTarget;
-      this.browser = this.tab.linkedBrowser;
-      gBrowser.addEventListener("pageshow", this, false);
-    } else if (event.type == "pageshow") {
-      if (event.target.location.href != this.url)
-        return;
-      gBrowser.removeEventListener("pageshow", this, false);
-      this.tab.addEventListener("TabClose", this, false);
-      var url = this.browser.contentDocument.location.href;
-      is(url, this.url, "Should have opened the correct tab");
-      this.opencallback(this.tab, this.browser.contentWindow);
-    } else if (event.type == "TabClose") {
-      if (event.originalTarget != this.tab)
-        return;
-      this.tab.removeEventListener("TabClose", this, false);
-      this.opencallback = null;
-      this.tab = null;
-      this.browser = null;
-      // Let the window close complete
-      executeSoon(this.closecallback);
-      this.closecallback = null;
-    }
-  }
-};
-
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     clearAllPluginPermissions();
     Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
   });
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
--- a/browser/base/content/test/general/browser_CTP_nonplugins.js
+++ b/browser/base/content/test/general/browser_CTP_nonplugins.js
@@ -4,62 +4,16 @@ const gHttpTestRoot = rootDir.replace("c
 
 var gTestBrowser = null;
 var gNextTest = null;
 var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 var gRunNextTestAfterPluginRemoved = false;
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
-// This listens for the next opened tab and checks it is of the right url.
-// opencallback is called when the new tab is fully loaded
-// closecallback is called when the tab is closed
-function TabOpenListener(url, opencallback, closecallback) {
-  this.url = url;
-  this.opencallback = opencallback;
-  this.closecallback = closecallback;
-
-  gBrowser.tabContainer.addEventListener("TabOpen", this, false);
-}
-
-TabOpenListener.prototype = {
-  url: null,
-  opencallback: null,
-  closecallback: null,
-  tab: null,
-  browser: null,
-
-  handleEvent: function(event) {
-    if (event.type == "TabOpen") {
-      gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
-      this.tab = event.originalTarget;
-      this.browser = this.tab.linkedBrowser;
-      gBrowser.addEventListener("pageshow", this, false);
-    } else if (event.type == "pageshow") {
-      if (event.target.location.href != this.url)
-        return;
-      gBrowser.removeEventListener("pageshow", this, false);
-      this.tab.addEventListener("TabClose", this, false);
-      var url = this.browser.contentDocument.location.href;
-      is(url, this.url, "Should have opened the correct tab");
-      this.opencallback(this.tab, this.browser.contentWindow);
-    } else if (event.type == "TabClose") {
-      if (event.originalTarget != this.tab)
-        return;
-      this.tab.removeEventListener("TabClose", this, false);
-      this.opencallback = null;
-      this.tab = null;
-      this.browser = null;
-      // Let the window close complete
-      executeSoon(this.closecallback);
-      this.closecallback = null;
-    }
-  }
-};
-
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     clearAllPluginPermissions();
     Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
     gTestBrowser.removeEventListener("PluginRemoved", handlePluginRemoved, true, true);
   });
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
--- a/browser/base/content/test/general/browser_CTP_resize.js
+++ b/browser/base/content/test/general/browser_CTP_resize.js
@@ -3,62 +3,16 @@ const gTestRoot = rootDir;
 const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 
 var gTestBrowser = null;
 var gNextTest = null;
 var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
-// This listens for the next opened tab and checks it is of the right url.
-// opencallback is called when the new tab is fully loaded
-// closecallback is called when the tab is closed
-function TabOpenListener(url, opencallback, closecallback) {
-  this.url = url;
-  this.opencallback = opencallback;
-  this.closecallback = closecallback;
-
-  gBrowser.tabContainer.addEventListener("TabOpen", this, false);
-}
-
-TabOpenListener.prototype = {
-  url: null,
-  opencallback: null,
-  closecallback: null,
-  tab: null,
-  browser: null,
-
-  handleEvent: function(event) {
-    if (event.type == "TabOpen") {
-      gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
-      this.tab = event.originalTarget;
-      this.browser = this.tab.linkedBrowser;
-      gBrowser.addEventListener("pageshow", this, false);
-    } else if (event.type == "pageshow") {
-      if (event.target.location.href != this.url)
-        return;
-      gBrowser.removeEventListener("pageshow", this, false);
-      this.tab.addEventListener("TabClose", this, false);
-      var url = this.browser.contentDocument.location.href;
-      is(url, this.url, "Should have opened the correct tab");
-      this.opencallback(this.tab, this.browser.contentWindow);
-    } else if (event.type == "TabClose") {
-      if (event.originalTarget != this.tab)
-        return;
-      this.tab.removeEventListener("TabClose", this, false);
-      this.opencallback = null;
-      this.tab = null;
-      this.browser = null;
-      // Let the window close complete
-      executeSoon(this.closecallback);
-      this.closecallback = null;
-    }
-  }
-};
-
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(function() {
     clearAllPluginPermissions();
     Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
   });
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
--- a/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
+++ b/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
@@ -85,18 +85,22 @@ function test() {
     saveDir.append("testsavedir");
     if (!saveDir.exists())
       saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
     return saveDir;
   }
 
   function doTest(aIsPrivateMode, aWindow, aCallback) {
     aWindow.gBrowser.addEventListener("pageshow", function pageShown(event) {
-      if (event.target.location == "about:blank")
+      // If data: -url PAC file isn't loaded soon enough, we may get about:privatebrowsing loaded
+      if (event.target.location == "about:blank" ||
+          event.target.location == "about:privatebrowsing") {
+        aWindow.gBrowser.selectedBrowser.loadURI(testURI);
         return;
+      }
       aWindow.gBrowser.removeEventListener("pageshow", pageShown);
 
       executeSoon(function () {
         aWindow.document.addEventListener("popupshown",
                                           function(e) contextMenuOpened(aWindow, e), false);
         var img = aWindow.gBrowser.selectedBrowser.contentDocument.getElementById("img");
         EventUtils.synthesizeMouseAtCenter(img,
                                            { type: "contextmenu", button: 2 },
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -88,17 +88,17 @@ let MessageListener = {
 
   init: function () {
     this.MESSAGES.forEach(m => addMessageListener(m, this));
   },
 
   receiveMessage: function ({name, data: {id}}) {
     switch (name) {
       case "SessionStore:collectSessionHistory":
-        let history = SessionHistory.read(docShell);
+        let history = SessionHistory.collect(docShell);
         if ("index" in history) {
           let tabIndex = history.index - 1;
           // Don't include private data. It's only needed when duplicating
           // tabs, which collects data synchronously.
           TextAndScrollData.updateFrame(history.entries[tabIndex],
                                         content,
                                         docShell.isAppTab);
         }
@@ -136,17 +136,17 @@ let SyncHandler = {
     // Send this object as a CPOW to chrome. In single-process mode,
     // the synchronous send ensures that the handler object is
     // available in SessionStore.jsm immediately upon loading
     // content-sessionStore.js.
     sendSyncMessage("SessionStore:setupSyncHandler", {}, {handler: this});
   },
 
   collectSessionHistory: function (includePrivateData) {
-    let history = SessionHistory.read(docShell);
+    let history = SessionHistory.collect(docShell);
     if ("index" in history) {
       let tabIndex = history.index - 1;
       TextAndScrollData.updateFrame(history.entries[tabIndex],
                                     content,
                                     docShell.isAppTab,
                                     {includePrivateData: includePrivateData});
     }
     return history;
--- a/browser/components/sessionstore/src/SessionHistory.jsm
+++ b/browser/components/sessionstore/src/SessionHistory.jsm
@@ -10,16 +10,18 @@ const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
   "resource:///modules/sessionstore/PrivacyLevel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Utils",
+  "resource:///modules/sessionstore/Utils.jsm");
 
 function debug(msg) {
   Services.console.logStringMessage("SessionHistory: " + msg);
 }
 
 // The preference value that determines how much post data to save.
 XPCOMUtils.defineLazyGetter(this, "gPostData", function () {
   const PREF = "browser.sessionstore.postdata";
@@ -31,44 +33,48 @@ XPCOMUtils.defineLazyGetter(this, "gPost
 
   return Services.prefs.getIntPref(PREF);
 });
 
 /**
  * The external API exported by this module.
  */
 this.SessionHistory = Object.freeze({
-  read: function (docShell, includePrivateData) {
-    return SessionHistoryInternal.read(docShell, includePrivateData);
+  collect: function (docShell, includePrivateData) {
+    return SessionHistoryInternal.collect(docShell, includePrivateData);
+  },
+
+  restore: function (docShell, tabData) {
+    SessionHistoryInternal.restore(docShell, tabData);
   }
 });
 
 /**
  * The internal API for the SessionHistory module.
  */
 let SessionHistoryInternal = {
   /**
    * Collects session history data for a given docShell.
    *
    * @param docShell
    *        The docShell that owns the session history.
    * @param includePrivateData (optional)
    *        True to always include private data and skip any privacy checks.
    */
-  read: function (docShell, includePrivateData = false) {
+  collect: function (docShell, includePrivateData = false) {
     let data = {entries: []};
     let isPinned = docShell.isAppTab;
     let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
     let history = webNavigation.sessionHistory;
 
     if (history && history.count > 0) {
       try {
         for (let i = 0; i < history.count; i++) {
           let shEntry = history.getEntryAtIndex(i, false);
-          let entry = this._serializeEntry(shEntry, includePrivateData, isPinned);
+          let entry = this.serializeEntry(shEntry, includePrivateData, isPinned);
           data.entries.push(entry);
         }
       } catch (ex) {
         // In some cases, getEntryAtIndex will throw. This seems to be due to
         // history.count being higher than it should be. By doing this in a
         // try-catch, we'll update history to where it breaks, print an error
         // message, and still save sessionstore.js.
         debug("SessionStore failed gathering complete history " +
@@ -104,17 +110,17 @@ let SessionHistoryInternal = {
    * @param shEntry
    *        nsISHEntry instance
    * @param includePrivateData
    *        Always return privacy sensitive data (use with care).
    * @param isPinned
    *        The tab is pinned and should be treated differently for privacy.
    * @return object
    */
-  _serializeEntry: function (shEntry, includePrivateData, isPinned) {
+  serializeEntry: function (shEntry, includePrivateData, isPinned) {
     let entry = { url: shEntry.URI.spec };
 
     // Save some bytes and don't include the title property
     // if that's identical to the current entry's URL.
     if (shEntry.title && shEntry.title != entry.url) {
       entry.title = shEntry.title;
     }
     if (shEntry.isSubFrame) {
@@ -147,28 +153,28 @@ let SessionHistoryInternal = {
 
     let x = {}, y = {};
     shEntry.getScrollPosition(x, y);
     if (x.value != 0 || y.value != 0)
       entry.scroll = x.value + "," + y.value;
 
     // Collect post data for the current history entry.
     try {
-      let postdata = this._serializePostData(shEntry, isPinned);
+      let postdata = this.serializePostData(shEntry, isPinned);
       if (postdata) {
         entry.postdata_b64 = postdata;
       }
     } catch (ex) {
       // POSTDATA is tricky - especially since some extensions don't get it right
       debug("Failed serializing post data: " + ex);
     }
 
     // Collect owner data for the current history entry.
     try {
-      let owner = this._serializeOwner(shEntry);
+      let owner = this.serializeOwner(shEntry);
       if (owner) {
         entry.owner_b64 = owner;
       }
     } catch (ex) {
       // Not catching anything specific here, just possible errors
       // from writeCompoundObject() and the like.
       debug("Failed serializing owner data: " + ex);
     }
@@ -192,17 +198,17 @@ let SessionHistoryInternal = {
         if (child) {
           // Don't try to restore framesets containing wyciwyg URLs.
           // (cf. bug 424689 and bug 450595)
           if (child.URI.schemeIs("wyciwyg")) {
             children.length = 0;
             break;
           }
 
-          children.push(this._serializeEntry(child, includePrivateData, isPinned));
+          children.push(this.serializeEntry(child, includePrivateData, isPinned));
         }
       }
 
       if (children.length) {
         entry.children = children;
       }
     }
 
@@ -213,17 +219,17 @@ let SessionHistoryInternal = {
    * Serialize post data contained in the given session history entry.
    *
    * @param shEntry
    *        The session history entry.
    * @param isPinned
    *        Whether the docShell is owned by a pinned tab.
    * @return The base64 encoded post data.
    */
-  _serializePostData: function (shEntry, isPinned) {
+  serializePostData: function (shEntry, isPinned) {
     let isHttps = shEntry.URI.schemeIs("https");
     if (!shEntry.postData || !gPostData ||
         !PrivacyLevel.canSave({isHttps: isHttps, isPinned: isPinned})) {
       return null;
     }
 
     shEntry.postData.QueryInterface(Ci.nsISeekableStream)
                     .seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
@@ -245,17 +251,17 @@ let SessionHistoryInternal = {
 
   /**
    * Serialize owner data contained in the given session history entry.
    *
    * @param shEntry
    *        The session history entry.
    * @return The base64 encoded owner data.
    */
-  _serializeOwner: function (shEntry) {
+  serializeOwner: function (shEntry) {
     if (!shEntry.owner) {
       return null;
     }
 
     let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
                        createInstance(Ci.nsIObjectOutputStream);
     let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
     pipe.init(false, false, 0, 0xffffffff, null);
@@ -269,10 +275,171 @@ let SessionHistoryInternal = {
     scriptableStream.setInputStream(pipe.inputStream);
     let ownerBytes =
       scriptableStream.readByteArray(scriptableStream.available());
 
     // We can stop doing base64 encoding once our serialization into JSON
     // is guaranteed to handle all chars in strings, including embedded
     // nulls.
     return btoa(String.fromCharCode.apply(null, ownerBytes));
-  }
+  },
+
+  /**
+   * Restores session history data for a given docShell.
+   *
+   * @param docShell
+   *        The docShell that owns the session history.
+   * @param tabData
+   *        The tabdata including all history entries.
+   */
+  restore: function (docShell, tabData) {
+    let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
+    let history = webNavigation.sessionHistory;
+
+    if (history.count > 0) {
+      history.PurgeHistory(history.count);
+    }
+    history.QueryInterface(Ci.nsISHistoryInternal);
+
+    let idMap = { used: {} };
+    let docIdentMap = {};
+    for (let i = 0; i < tabData.entries.length; i++) {
+      //XXXzpao Wallpaper patch for bug 514751
+      if (!tabData.entries[i].url)
+        continue;
+      history.addEntry(this.deserializeEntry(tabData.entries[i],
+                                             idMap, docIdentMap), true);
+    }
+  },
+
+  /**
+   * Expands serialized history data into a session-history-entry instance.
+   *
+   * @param entry
+   *        Object containing serialized history data for a URL
+   * @param idMap
+   *        Hash for ensuring unique frame IDs
+   * @param docIdentMap
+   *        Hash to ensure reuse of BFCache entries
+   * @returns nsISHEntry
+   */
+  deserializeEntry: function (entry, idMap, docIdentMap) {
+
+    var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
+                  createInstance(Ci.nsISHEntry);
+
+    shEntry.setURI(Utils.makeURI(entry.url));
+    shEntry.setTitle(entry.title || entry.url);
+    if (entry.subframe)
+      shEntry.setIsSubFrame(entry.subframe || false);
+    shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
+    if (entry.contentType)
+      shEntry.contentType = entry.contentType;
+    if (entry.referrer)
+      shEntry.referrerURI = Utils.makeURI(entry.referrer);
+    if (entry.isSrcdocEntry)
+      shEntry.srcdocData = entry.srcdocData;
+
+    if (entry.cacheKey) {
+      var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
+                     createInstance(Ci.nsISupportsPRUint32);
+      cacheKey.data = entry.cacheKey;
+      shEntry.cacheKey = cacheKey;
+    }
+
+    if (entry.ID) {
+      // get a new unique ID for this frame (since the one from the last
+      // start might already be in use)
+      var id = idMap[entry.ID] || 0;
+      if (!id) {
+        for (id = Date.now(); id in idMap.used; id++);
+        idMap[entry.ID] = id;
+        idMap.used[id] = true;
+      }
+      shEntry.ID = id;
+    }
+
+    if (entry.docshellID)
+      shEntry.docshellID = entry.docshellID;
+
+    if (entry.structuredCloneState && entry.structuredCloneVersion) {
+      shEntry.stateData =
+        Cc["@mozilla.org/docshell/structured-clone-container;1"].
+        createInstance(Ci.nsIStructuredCloneContainer);
+
+      shEntry.stateData.initFromBase64(entry.structuredCloneState,
+                                       entry.structuredCloneVersion);
+    }
+
+    if (entry.scroll) {
+      var scrollPos = (entry.scroll || "0,0").split(",");
+      scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
+      shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
+    }
+
+    if (entry.postdata_b64) {
+      var postdata = atob(entry.postdata_b64);
+      var stream = Cc["@mozilla.org/io/string-input-stream;1"].
+                   createInstance(Ci.nsIStringInputStream);
+      stream.setData(postdata, postdata.length);
+      shEntry.postData = stream;
+    }
+
+    let childDocIdents = {};
+    if (entry.docIdentifier) {
+      // If we have a serialized document identifier, try to find an SHEntry
+      // which matches that doc identifier and adopt that SHEntry's
+      // BFCacheEntry.  If we don't find a match, insert shEntry as the match
+      // for the document identifier.
+      let matchingEntry = docIdentMap[entry.docIdentifier];
+      if (!matchingEntry) {
+        matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents};
+        docIdentMap[entry.docIdentifier] = matchingEntry;
+      }
+      else {
+        shEntry.adoptBFCacheEntry(matchingEntry.shEntry);
+        childDocIdents = matchingEntry.childDocIdents;
+      }
+    }
+
+    if (entry.owner_b64) {
+      var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
+                       createInstance(Ci.nsIStringInputStream);
+      var binaryData = atob(entry.owner_b64);
+      ownerInput.setData(binaryData, binaryData.length);
+      var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
+                         createInstance(Ci.nsIObjectInputStream);
+      binaryStream.setInputStream(ownerInput);
+      try { // Catch possible deserialization exceptions
+        shEntry.owner = binaryStream.readObject(true);
+      } catch (ex) { debug(ex); }
+    }
+
+    if (entry.children && shEntry instanceof Ci.nsISHContainer) {
+      for (var i = 0; i < entry.children.length; i++) {
+        //XXXzpao Wallpaper patch for bug 514751
+        if (!entry.children[i].url)
+          continue;
+
+        // We're getting sessionrestore.js files with a cycle in the
+        // doc-identifier graph, likely due to bug 698656.  (That is, we have
+        // an entry where doc identifier A is an ancestor of doc identifier B,
+        // and another entry where doc identifier B is an ancestor of A.)
+        //
+        // If we were to respect these doc identifiers, we'd create a cycle in
+        // the SHEntries themselves, which causes the docshell to loop forever
+        // when it looks for the root SHEntry.
+        //
+        // So as a hack to fix this, we restrict the scope of a doc identifier
+        // to be a node's siblings and cousins, and pass childDocIdents, not
+        // aDocIdents, to _deserializeHistoryEntry.  That is, we say that two
+        // SHEntries with the same doc identifier have the same document iff
+        // they have the same parent or their parents have the same document.
+
+        shEntry.AddChild(this.deserializeEntry(entry.children[i], idMap,
+                                               childDocIdents), i);
+      }
+    }
+
+    return shEntry;
+  },
+
 };
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -112,16 +112,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
   "resource:///modules/sessionstore/SessionStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
   "resource:///modules/sessionstore/SessionCookies.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TextAndScrollData",
   "resource:///modules/sessionstore/TextAndScrollData.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
   "resource:///modules/sessionstore/SessionFile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
+  "resource:///modules/sessionstore/SessionHistory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
   "resource:///modules/sessionstore/TabAttributes.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabState",
   "resource:///modules/sessionstore/TabState.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
   "resource:///modules/sessionstore/TabStateCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
   "resource:///modules/sessionstore/Utils.jsm");
@@ -1341,20 +1343,20 @@ let SessionStoreInternal = {
    */
   onTabSelect: function ssi_onTabSelect(aWindow) {
     if (this._loadState == STATE_RUNNING) {
       this._windows[aWindow.__SSi].selected = aWindow.gBrowser.tabContainer.selectedIndex;
 
       let tab = aWindow.gBrowser.selectedTab;
       // If __SS_restoreState is still on the browser and it is
       // TAB_STATE_NEEDS_RESTORE, then then we haven't restored
-      // this tab yet. Explicitly call restoreTab to kick off the restore.
+      // this tab yet. Explicitly call restoreTabContent to kick off the restore.
       if (tab.linkedBrowser.__SS_restoreState &&
           tab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
-        this.restoreTab(tab);
+        this.restoreTabContent(tab);
 
       // attempt to update the current URL we send in a crash report
       this._updateCrashReportURL(aWindow);
     }
   },
 
   onTabShow: function ssi_onTabShow(aWindow, aTab) {
     // If the tab hasn't been restored yet, move it into the right bucket
@@ -1472,17 +1474,17 @@ let SessionStoreInternal = {
 
     return this._toJSONString(tabState);
   },
 
   setTabState: function ssi_setTabState(aTab, aState) {
     // Remove the tab state from the cache.
     // Note that we cannot simply replace the contents of the cache
     // as |aState| can be an incomplete state that will be completed
-    // by |restoreHistoryPrecursor|.
+    // by |restoreTabs|.
     let tabState = JSON.parse(aState);
     if (!tabState) {
       debug("Empty state argument");
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
     }
     if (typeof tabState != "object") {
       debug("State argument does not represent an object");
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
@@ -1503,17 +1505,17 @@ let SessionStoreInternal = {
     }
 
     if (aTab.linkedBrowser.__SS_restoreState) {
       this._resetTabRestoringState(aTab);
     }
 
     TabStateCache.delete(aTab);
     this._setWindowStateBusy(window);
-    this.restoreHistoryPrecursor(window, [aTab], [tabState], 0);
+    this.restoreTabs(window, [aTab], [tabState], 0);
   },
 
   duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0) {
     if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi ||
         !aWindow.getBrowser)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
     // Duplicate the tab state
@@ -1523,18 +1525,18 @@ let SessionStoreInternal = {
     tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
     tabState.pinned = false;
 
     this._setWindowStateBusy(aWindow);
     let newTab = aTab == aWindow.gBrowser.selectedTab ?
       aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab}) :
       aWindow.gBrowser.addTab();
 
-    this.restoreHistoryPrecursor(aWindow, [newTab], [tabState], 0,
-                                 true /* Load this tab right away. */);
+    this.restoreTabs(aWindow, [newTab], [tabState], 0,
+                     true /* Load this tab right away. */);
 
     return newTab;
   },
 
   setNumberOfTabsClosedLast: function ssi_setNumberOfTabsClosedLast(aWindow, aNumber) {
     if (this._disabledForMultiProcess)
       return;
 
@@ -1603,17 +1605,17 @@ let SessionStoreInternal = {
     let closedTabState = closedTab.state;
 
     this._setWindowStateBusy(aWindow);
     // create a new tab
     let tabbrowser = aWindow.gBrowser;
     let tab = tabbrowser.addTab();
 
     // restore tab content
-    this.restoreHistoryPrecursor(aWindow, [tab], [closedTabState], 1);
+    this.restoreTabs(aWindow, [tab], [closedTabState], 1);
 
     // restore the tab's position
     tabbrowser.moveTabTo(tab, closedTab.pos);
 
     // focus the tab's content area (bug 342432)
     tab.linkedBrowser.focus();
 
     return tab;
@@ -2222,17 +2224,17 @@ let SessionStoreInternal = {
     catch (ex) { // invalid state object - don't restore anything
       debug(ex);
       this._sendRestoreCompletedNotifications();
       return;
     }
 
     TelemetryStopwatch.start("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
 
-    // We're not returning from this before we end up calling restoreHistoryPrecursor
+    // We're not returning from this before we end up calling restoreTabs
     // for this window, so make sure we send the SSWindowStateBusy event.
     this._setWindowStateBusy(aWindow);
 
     if (root._closedWindows)
       this._closedWindows = root._closedWindows;
 
     var winData;
     if (!root.selectedWindow || root.selectedWindow > root.windows.length) {
@@ -2320,17 +2322,17 @@ let SessionStoreInternal = {
     if (!numVisibleTabs && winData.tabs.length) {
       winData.tabs[0].hidden = false;
       tabbrowser.showTab(tabs[0]);
     }
 
     // If overwriting tabs, we want to reset each tab's "restoring" state. Since
     // we're overwriting those tabs, they should no longer be restoring. The
     // tabs will be rebuilt and marked if they need to be restored after loading
-    // state (in restoreHistoryPrecursor).
+    // state (in restoreTabs).
     // We also want to invalidate any cached information on the tab state.
     if (overwriteTabs) {
       for (let i = 0; i < tabbrowser.tabs.length; i++) {
         let tab = tabbrowser.tabs[i];
         TabStateCache.delete(tab);
         if (tabbrowser.browsers[i].__SS_restoreState)
           this._resetTabRestoringState(tab);
       }
@@ -2375,17 +2377,17 @@ let SessionStoreInternal = {
       for (var key in winData.extData) {
         this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
       }
     }
     if (overwriteTabs || firstWindow) {
       this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs || [];
     }
 
-    this.restoreHistoryPrecursor(aWindow, tabs, winData.tabs,
+    this.restoreTabs(aWindow, tabs, winData.tabs,
       (overwriteTabs ? (parseInt(winData.selected) || 1) : 0));
 
     if (aState.scratchpads) {
       ScratchpadManager.restoreSession(aState.scratchpads);
     }
 
     // set smoothScroll back to the original value
     tabstrip.smoothScroll = smoothScroll;
@@ -2469,34 +2471,33 @@ let SessionStoreInternal = {
         aTabs = aTabs.splice(selectedTabIndex, 1).concat(aTabs);
         aTabData = aTabData.splice(selectedTabIndex, 1).concat(aTabData);
       }
       aTabBrowser.selectedTab = selectedTab;
     }
 
     return [aTabs, aTabData];
   },
-  
+
   /**
    * Manage history restoration for a window
    * @param aWindow
    *        Window to restore the tabs into
    * @param aTabs
    *        Array of tab references
    * @param aTabData
    *        Array of tab data
    * @param aSelectTab
    *        Index of selected tab
    * @param aRestoreImmediately
    *        Flag to indicate whether the given set of tabs aTabs should be
    *        restored/loaded immediately even if restore_on_demand = true
    */
-  restoreHistoryPrecursor:
-    function ssi_restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab,
-                                         aRestoreImmediately = false)
+  restoreTabs: function (aWindow, aTabs, aTabData, aSelectTab,
+                         aRestoreImmediately = false)
   {
 
     var tabbrowser = aWindow.gBrowser;
 
     if (!this._isWindowLoaded(aWindow)) {
       // from now on, the data will come from the actual window
       delete this._statesToRestore[aWindow.__SS_restoreID];
       delete aWindow.__SS_restoreID;
@@ -2548,30 +2549,37 @@ let SessionStoreInternal = {
         // Ensure that we persist tab attributes restored from previous sessions.
         Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
       }
 
       // Any data that's in the process of being collected for this tab will be
       // out of date now that we're restoring it.
       TabState.dropPendingCollections(tab);
 
+      if (!tabData.entries) {
+        tabData.entries = [];
+      }
+      if (tabData.extData) {
+        tab.__SS_extdata = {};
+        for (let key in tabData.extData)
+         tab.__SS_extdata[key] = tabData.extData[key];
+      } else {
+        delete tab.__SS_extdata;
+      }
+
       browser.__SS_tabStillLoading = true;
 
       // keep the data around to prevent dataloss in case
       // a tab gets closed before it's been properly restored
       browser.__SS_data = tabData;
       browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
       browser.setAttribute("pending", "true");
       tab.setAttribute("pending", "true");
 
-      // Make sure that set/getTabValue will set/read the correct data by
-      // wiping out any current value in tab.__SS_extdata.
-      delete tab.__SS_extdata;
-
-      if (!tabData.entries || tabData.entries.length == 0) {
+      if (tabData.entries.length == 0) {
         // make sure to blank out this tab's content
         // (just purging the tab's history won't be enough)
         browser.loadURIWithFlags("about:blank",
                                  Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY,
                                  null, null, null);
         continue;
       }
 
@@ -2596,124 +2604,94 @@ let SessionStoreInternal = {
         if (activePageData.title) {
           tab.label = activePageData.title;
           tab.crop = "end";
         } else if (activePageData.url != "about:blank") {
           tab.label = activePageData.url;
           tab.crop = "center";
         }
       }
+
+      // Restore tab attributes.
+      if ("attributes" in tabData) {
+        TabAttributes.set(tab, tabData.attributes);
+      }
+
+      // Restore the tab icon.
+      if ("image" in tabData) {
+        tabbrowser.setIcon(tab, tabData.image);
+      }
     }
 
-    // helper hashes for ensuring unique frame IDs and unique document
-    // identifiers.
-    let idMap = { used: {} };
-    let docIdentMap = {};
-    this.restoreHistory(aWindow, aTabs, aTabData, idMap, docIdentMap,
-                        aRestoreImmediately);
+    function restoreNextHistory() {
+      if (aWindow.closed) {
+        return;
+      }
+
+      // if the tab got removed before being completely restored, then skip it
+      while (aTabs.length > 0 && !this._canRestoreTabHistory(aTabs[0])) {
+        aTabs.shift();
+        aTabData.shift();
+      }
+      if (aTabs.length == 0) {
+        // At this point we're essentially ready for consumers to read/write data
+        // via the sessionstore API so we'll send the SSWindowStateReady event.
+        this._setWindowStateReady(aWindow);
+        return; // no more tabs to restore
+      }
+
+      let tab = aTabs.shift();
+      let tabData = aTabData.shift();
+      this.restoreHistory(aWindow, tab, tabData, aRestoreImmediately);
+
+      // Restore the history in the next tab
+      aWindow.setTimeout(restoreNextHistory.bind(this), 0);
+    }
+
+    restoreNextHistory.call(this);
   },
 
   /**
    * Restore history for a list of tabs.
-   * @param aWindow
+   * @param window
    *        Window reference
-   * @param aTabs
-   *        Array of tab references
-   * @param aTabData
-   *        Array of tab data
-   * @param aIdMap
-   *        Hash for ensuring unique frame IDs
-   * @param aRestoreImmediately
+   * @param tab
+   *        Tab to be restored
+   * @param tabData
+   *        Tab data to restore
+   * @param restoreImmediately
    *        Flag to indicate whether the given set of tabs aTabs should be
    *        restored/loaded immediately even if restore_on_demand = true
    */
-  restoreHistory:
-    function ssi_restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap,
-                                aRestoreImmediately)
-  {
-    // if the tab got removed before being completely restored, then skip it
-    while (aTabs.length > 0 && !(this._canRestoreTabHistory(aTabs[0]))) {
-      aTabs.shift();
-      aTabData.shift();
-    }
-    if (aTabs.length == 0) {
-      // At this point we're essentially ready for consumers to read/write data
-      // via the sessionstore API so we'll send the SSWindowStateReady event.
-      this._setWindowStateReady(aWindow);
-      return; // no more tabs to restore
-    }
-
-    var tab = aTabs.shift();
-    var tabData = aTabData.shift();
-    var browser = aWindow.gBrowser.getBrowserForTab(tab);
-    var history = browser.webNavigation.sessionHistory;
-
-    if (history.count > 0) {
-      history.PurgeHistory(history.count);
-    }
-    history.QueryInterface(Ci.nsISHistoryInternal);
+  restoreHistory: function (window, tab, tabData, restoreImmediately) {
+    let browser = tab.linkedBrowser;
+    let history = browser.webNavigation.sessionHistory;
 
     browser.__SS_shistoryListener = new SessionStoreSHistoryListener(tab);
     history.addSHistoryListener(browser.__SS_shistoryListener);
 
-    if (!tabData.entries) {
-      tabData.entries = [];
-    }
-    if (tabData.extData) {
-      tab.__SS_extdata = {};
-      for (let key in tabData.extData)
-        tab.__SS_extdata[key] = tabData.extData[key];
-    }
-    else
-      delete tab.__SS_extdata;
-
-    for (var i = 0; i < tabData.entries.length; i++) {
-      //XXXzpao Wallpaper patch for bug 514751
-      if (!tabData.entries[i].url)
-        continue;
-      history.addEntry(this._deserializeHistoryEntry(tabData.entries[i],
-                                                     aIdMap, aDocIdentMap), true);
-    }
+    SessionHistory.restore(browser.docShell, tabData);
 
     // make sure to reset the capabilities and attributes, in case this tab gets reused
     let disallow = new Set(tabData.disallow && tabData.disallow.split(","));
     DocShellCapabilities.restore(browser.docShell, disallow);
 
-    // Restore tab attributes.
-    if ("attributes" in tabData) {
-      TabAttributes.set(tab, tabData.attributes);
-    }
-
-    // Restore the tab icon.
-    if ("image" in tabData) {
-      aWindow.gBrowser.setIcon(tab, tabData.image);
-    }
-
     if (tabData.storage && browser.docShell instanceof Ci.nsIDocShell)
       SessionStorage.deserialize(browser.docShell, tabData.storage);
 
     // notify the tabbrowser that the tab chrome has been restored
-    var event = aWindow.document.createEvent("Events");
+    var event = window.document.createEvent("Events");
     event.initEvent("SSTabRestoring", true, false);
     tab.dispatchEvent(event);
 
-    // Restore the history in the next tab
-    aWindow.setTimeout(() => {
-      if (!aWindow.closed) {
-        this.restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap,
-                            aRestoreImmediately);
-      }
-    }, 0);
-
     // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
     // it ensures each window will have its selected tab loaded.
-    if (aRestoreImmediately || aWindow.gBrowser.selectedBrowser == browser) {
-      this.restoreTab(tab);
-    }
-    else {
+    if (restoreImmediately || window.gBrowser.selectedBrowser == browser) {
+      this.restoreTabContent(tab);
+    } else {
       TabRestoreQueue.add(tab);
       this.restoreNextTab();
     }
   },
 
   /**
    * Restores the specified tab. If the tab can't be restored (eg, no history or
    * calling gotoIndex fails), then state changes will be rolled back.
@@ -2726,17 +2704,17 @@ let SessionStoreInternal = {
    * restore the next tab. In the other case (selecting the tab, reloading the
    * tab), the caller doesn't actually want to do anything if no page is loaded.
    *
    * @param aTab
    *        the tab to restore
    *
    * @returns true/false indicating whether or not a load actually happened
    */
-  restoreTab: function ssi_restoreTab(aTab) {
+  restoreTabContent: function (aTab) {
     let window = aTab.ownerDocument.defaultView;
     let browser = aTab.linkedBrowser;
     let tabData = browser.__SS_data;
 
     // There are cases within where we haven't actually started a load. In that
     // that case we'll reset state changes we made and return false to the caller
     // can handle appropriately.
     let didStartLoad = false;
@@ -2829,155 +2807,25 @@ let SessionStoreInternal = {
       return;
 
     // Don't exceed the maximum number of concurrent tab restores.
     if (this._tabsRestoringCount >= MAX_CONCURRENT_TAB_RESTORES)
       return;
 
     let tab = TabRestoreQueue.shift();
     if (tab) {
-      let didStartLoad = this.restoreTab(tab);
+      let didStartLoad = this.restoreTabContent(tab);
       // If we don't start a load in the restored tab (eg, no entries) then we
       // want to attempt to restore the next tab.
       if (!didStartLoad)
         this.restoreNextTab();
     }
   },
 
   /**
-   * expands serialized history data into a session-history-entry instance
-   * @param aEntry
-   *        Object containing serialized history data for a URL
-   * @param aIdMap
-   *        Hash for ensuring unique frame IDs
-   * @returns nsISHEntry
-   */
-  _deserializeHistoryEntry:
-    function ssi_deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) {
-
-    var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
-                  createInstance(Ci.nsISHEntry);
-
-    shEntry.setURI(Utils.makeURI(aEntry.url));
-    shEntry.setTitle(aEntry.title || aEntry.url);
-    if (aEntry.subframe)
-      shEntry.setIsSubFrame(aEntry.subframe || false);
-    shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
-    if (aEntry.contentType)
-      shEntry.contentType = aEntry.contentType;
-    if (aEntry.referrer)
-      shEntry.referrerURI = Utils.makeURI(aEntry.referrer);
-    if (aEntry.isSrcdocEntry)
-      shEntry.srcdocData = aEntry.srcdocData;
-
-    if (aEntry.cacheKey) {
-      var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
-                     createInstance(Ci.nsISupportsPRUint32);
-      cacheKey.data = aEntry.cacheKey;
-      shEntry.cacheKey = cacheKey;
-    }
-
-    if (aEntry.ID) {
-      // get a new unique ID for this frame (since the one from the last
-      // start might already be in use)
-      var id = aIdMap[aEntry.ID] || 0;
-      if (!id) {
-        for (id = Date.now(); id in aIdMap.used; id++);
-        aIdMap[aEntry.ID] = id;
-        aIdMap.used[id] = true;
-      }
-      shEntry.ID = id;
-    }
-
-    if (aEntry.docshellID)
-      shEntry.docshellID = aEntry.docshellID;
-
-    if (aEntry.structuredCloneState && aEntry.structuredCloneVersion) {
-      shEntry.stateData =
-        Cc["@mozilla.org/docshell/structured-clone-container;1"].
-        createInstance(Ci.nsIStructuredCloneContainer);
-
-      shEntry.stateData.initFromBase64(aEntry.structuredCloneState,
-                                       aEntry.structuredCloneVersion);
-    }
-
-    if (aEntry.scroll) {
-      var scrollPos = (aEntry.scroll || "0,0").split(",");
-      scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
-      shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
-    }
-
-    if (aEntry.postdata_b64) {
-      var postdata = atob(aEntry.postdata_b64);
-      var stream = Cc["@mozilla.org/io/string-input-stream;1"].
-                   createInstance(Ci.nsIStringInputStream);
-      stream.setData(postdata, postdata.length);
-      shEntry.postData = stream;
-    }
-
-    let childDocIdents = {};
-    if (aEntry.docIdentifier) {
-      // If we have a serialized document identifier, try to find an SHEntry
-      // which matches that doc identifier and adopt that SHEntry's
-      // BFCacheEntry.  If we don't find a match, insert shEntry as the match
-      // for the document identifier.
-      let matchingEntry = aDocIdentMap[aEntry.docIdentifier];
-      if (!matchingEntry) {
-        matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents};
-        aDocIdentMap[aEntry.docIdentifier] = matchingEntry;
-      }
-      else {
-        shEntry.adoptBFCacheEntry(matchingEntry.shEntry);
-        childDocIdents = matchingEntry.childDocIdents;
-      }
-    }
-
-    if (aEntry.owner_b64) {
-      var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
-                       createInstance(Ci.nsIStringInputStream);
-      var binaryData = atob(aEntry.owner_b64);
-      ownerInput.setData(binaryData, binaryData.length);
-      var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
-                         createInstance(Ci.nsIObjectInputStream);
-      binaryStream.setInputStream(ownerInput);
-      try { // Catch possible deserialization exceptions
-        shEntry.owner = binaryStream.readObject(true);
-      } catch (ex) { debug(ex); }
-    }
-
-    if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
-      for (var i = 0; i < aEntry.children.length; i++) {
-        //XXXzpao Wallpaper patch for bug 514751
-        if (!aEntry.children[i].url)
-          continue;
-
-        // We're getting sessionrestore.js files with a cycle in the
-        // doc-identifier graph, likely due to bug 698656.  (That is, we have
-        // an entry where doc identifier A is an ancestor of doc identifier B,
-        // and another entry where doc identifier B is an ancestor of A.)
-        //
-        // If we were to respect these doc identifiers, we'd create a cycle in
-        // the SHEntries themselves, which causes the docshell to loop forever
-        // when it looks for the root SHEntry.
-        //
-        // So as a hack to fix this, we restrict the scope of a doc identifier
-        // to be a node's siblings and cousins, and pass childDocIdents, not
-        // aDocIdents, to _deserializeHistoryEntry.  That is, we say that two
-        // SHEntries with the same doc identifier have the same document iff
-        // they have the same parent or their parents have the same document.
-
-        shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap,
-                                                       childDocIdents), i);
-      }
-    }
-
-    return shEntry;
-  },
-
-  /**
    * Accumulates a list of frames that need to be restored for the
    * given browser element. A frame is only restored if its current
    * URL matches the one saved in the session data. Each frame to be
    * restored is returned along with its associated session data.
    *
    * @param browser the browser being restored
    * @return an array of [frame, data] pairs
    */
@@ -3503,17 +3351,17 @@ let SessionStoreInternal = {
                 (aTabState.entries[0].url == "about:blank" ||
                  aTabState.entries[0].url == "about:newtab") &&
                  !aTabState.userTypedValue);
   },
 
   /**
    * Determine if we can restore history into this tab.
    * This will be false when a tab has been removed (usually between
-   * restoreHistoryPrecursor && restoreHistory) or if the tab is still marked
+   * restoreTabs && restoreHistory) or if the tab is still marked
    * as loading.
    *
    * @param aTab
    * @returns boolean
    */
   _canRestoreTabHistory: function ssi_canRestoreTabHistory(aTab) {
     return aTab.parentNode && aTab.linkedBrowser &&
            aTab.linkedBrowser.__SS_tabStillLoading;
@@ -3830,21 +3678,21 @@ let SessionStoreInternal = {
     this._removeTabsProgressListener(window);
 
     if (previousState == TAB_STATE_RESTORING) {
       if (this._tabsRestoringCount)
         this._tabsRestoringCount--;
     }
     else if (previousState == TAB_STATE_NEEDS_RESTORE) {
       // Make sure the session history listener is removed. This is normally
-      // done in restoreTab, but this tab is being removed before that gets called.
+      // done in restoreTabContent, but this tab is being removed before that gets called.
       this._removeSHistoryListener(aTab);
 
       // Make sure that the tab is removed from the list of tabs to restore.
-      // Again, this is normally done in restoreTab, but that isn't being called
+      // Again, this is normally done in restoreTabContent, but that isn't being called
       // for this tab.
       TabRestoreQueue.remove(aTab);
     }
   },
 
   /**
    * Add the tabs progress listener to the window if it isn't already
    *
@@ -4124,19 +3972,19 @@ SessionStoreSHistoryListener.prototype =
     return true;
   },
   OnHistoryPurge: function(aNumEntries) {
     TabStateCache.delete(this.tab);
     return true;
   },
   OnHistoryReload: function(aReloadURI, aReloadFlags) {
     // On reload, we want to make sure that session history loads the right
-    // URI. In order to do that, we will juet call restoreTab. That will remove
+    // URI. In order to do that, we will juet call restoreTabContent. That will remove
     // the history listener and load the right URI.
-    SessionStoreInternal.restoreTab(this.tab);
+    SessionStoreInternal.restoreTabContent(this.tab);
     // Returning false will stop the load that docshell is attempting.
     return false;
   }
 }
 
 // The state from the previous session (after restoring pinned tabs). This
 // state is persisted and passed through to the next session during an app
 // restart to make the third party add-on warning not trash the deferred
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -399,17 +399,18 @@ SourcesView.prototype = Heritage.extend(
     if (gThreadClient.source(source).isPrettyPrinted) {
       this._prettyPrintButton.removeAttribute("checked");
     } else {
       this._prettyPrintButton.setAttribute("checked", true);
     }
 
     DebuggerController.SourceScripts.togglePrettyPrint(source)
       .then(resetEditor, printError)
-      .then(DebuggerView.showEditor);
+      .then(DebuggerView.showEditor)
+      .then(this.updateToolbarButtonsState);
   },
 
   /**
    * Marks a breakpoint as selected in this sources container.
    *
    * @param object aItem
    *        The breakpoint item to select.
    */
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -123,16 +123,17 @@ skip-if = true
 [browser_dbg_pretty-print-04.js]
 [browser_dbg_pretty-print-05.js]
 [browser_dbg_pretty-print-06.js]
 [browser_dbg_pretty-print-07.js]
 [browser_dbg_pretty-print-08.js]
 [browser_dbg_pretty-print-09.js]
 [browser_dbg_pretty-print-10.js]
 [browser_dbg_pretty-print-11.js]
+[browser_dbg_pretty-print-12.js]
 [browser_dbg_progress-listener-bug.js]
 [browser_dbg_reload-preferred-script-01.js]
 [browser_dbg_reload-preferred-script-02.js]
 [browser_dbg_reload-preferred-script-03.js]
 [browser_dbg_reload-same-script.js]
 [browser_dbg_scripts-switching-01.js]
 [browser_dbg_scripts-switching-02.js]
 [browser_dbg_scripts-switching-03.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_pretty-print-12.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that we don't leave the pretty print button checked when we fail to
+ * pretty print a source (because it isn't a JS file, for example).
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
+
+let gTab, gDebuggee, gPanel, gDebugger;
+let gEditor, gSources;
+
+function test() {
+  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPanel = aPanel;
+    gDebugger = gPanel.panelWin;
+    gEditor = gDebugger.DebuggerView.editor;
+    gSources = gDebugger.DebuggerView.Sources;
+
+    waitForSourceShown(gPanel, "")
+      .then(() => {
+        let shown = ensureSourceIs(gPanel, TAB_URL, true)
+        gSources.selectedValue = TAB_URL;
+        return shown;
+      })
+      .then(clickPrettyPrintButton)
+      .then(testButtonIsntChecked)
+      .then(() => closeDebuggerAndFinish(gPanel))
+      .then(null, aError => {
+        ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
+      });
+  });
+}
+
+function clickPrettyPrintButton() {
+  gDebugger.document.getElementById("pretty-print").click();
+}
+
+function testButtonIsntChecked() {
+  is(gDebugger.document.getElementById("pretty-print").checked, false,
+     "The button shouldn't be checked after trying to pretty print a non-js file.");
+}
+
+registerCleanupFunction(function() {
+  gTab = null;
+  gDebuggee = null;
+  gPanel = null;
+  gDebugger = null;
+  gEditor = null;
+  gSources = null;
+});
--- a/browser/devtools/framework/toolbox-options.xul
+++ b/browser/devtools/framework/toolbox-options.xul
@@ -49,16 +49,20 @@
             </menulist>
           </hbox>
         </vbox>
         <label value="&options.webconsole.label;"/>
         <vbox id="webconsole-options" class="options-groupbox">
           <checkbox label="&options.enablePersistentLogging.label;"
                     tooltiptext="&options.enablePersistentLogging.tooltip;"
                     data-pref="devtools.webconsole.persistlog"/>
+          <checkbox id="webconsole-timestamp-messages"
+                    label="&options.timestampMessages.label;"
+                    tooltiptext="&options.timestampMessages.tooltip;"
+                    data-pref="devtools.webconsole.timestampMessages"/>
         </vbox>
         <label value="&options.profiler.label;"/>
         <vbox id="profiler-options" class="options-groupbox">
           <checkbox label="&options.showPlatformData.label;"
                     tooltiptext="&options.showPlatformData.tooltip;"
                     data-pref="devtools.profiler.ui.show-platform-data"/>
         </vbox>
         <label value="&options.context.advancedSettings;"/>
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -234,8 +234,9 @@ skip-if = os == "linux"
 [browser_webconsole_notifications.js]
 [browser_webconsole_output_copy_newlines.js]
 [browser_webconsole_output_order.js]
 [browser_webconsole_property_provider.js]
 [browser_webconsole_scratchpad_panel_link.js]
 [browser_webconsole_view_source.js]
 [browser_webconsole_reflow.js]
 [browser_webconsole_log_file_filter.js]
+[browser_webconsole_expandable_timestamps.js]
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js
@@ -17,16 +17,23 @@ function testFilterButtons(aHud) {
   hud = aHud;
   hudId = hud.hudId;
   hudBox = hud.ui.rootElement;
 
   testMenuFilterButton("net");
   testMenuFilterButton("css");
   testMenuFilterButton("js");
   testMenuFilterButton("logging");
+  testMenuFilterButton("security");
+
+  testIsolateFilterButton("net");
+  testIsolateFilterButton("css");
+  testIsolateFilterButton("js");
+  testIsolateFilterButton("logging");
+  testIsolateFilterButton("security");
 
   finishTest();
 }
 
 function testMenuFilterButton(aCategory) {
   let selector = ".webconsole-filter-button[category=\"" + aCategory + "\"]";
   let button = hudBox.querySelector(selector);
   ok(button, "we have the \"" + aCategory + "\" button");
@@ -67,25 +74,17 @@ function testMenuFilterButton(aCategory)
   ok(!isChecked(firstMenuItem), "the first menu item for category " +
      aCategory + " is no longer checked after clicking it");
   ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
      "turned off after clicking the appropriate menu item");
   ok(isChecked(button), "the button for category " + aCategory + " is still " +
      "checked after turning off its first menu item");
 
   // Turn all the filters off by clicking the main part of the button.
-  let anonymousNodes = hud.ui.document.getAnonymousNodes(button);
-  let subbutton;
-  for (let i = 0; i < anonymousNodes.length; i++) {
-    let node = anonymousNodes[i];
-    if (node.classList.contains("toolbarbutton-menubutton-button")) {
-      subbutton = node;
-      break;
-    }
-  }
+  let subbutton = getMainButton(button);
   ok(subbutton, "we have the subbutton for category " + aCategory);
 
   clickButton(subbutton);
   ok(!isChecked(button), "the button for category " + aCategory + " is " +
      "no longer checked after clicking its main part");
 
   menuItem = firstMenuItem;
   while (menuItem) {
@@ -124,20 +123,91 @@ function testMenuFilterButton(aCategory)
 
   ok(!isChecked(button), "the button for category " + aCategory + " is " +
      "unchecked after unchecking all its filters");
 
   // Turn all the filters on again by clicking the button.
   clickButton(subbutton);
 }
 
+function testIsolateFilterButton(aCategory) {
+  let selector = ".webconsole-filter-button[category=\"" + aCategory + "\"]";
+  let targetButton = hudBox.querySelector(selector);
+  ok(targetButton, "we have the \"" + aCategory + "\" button");
+
+  // Get the main part of the filter button.
+  let subbutton = getMainButton(targetButton);
+  ok(subbutton, "we have the subbutton for category " + aCategory);
+
+  // Turn on all the filters by alt clicking the main part of the button.
+  altClickButton(subbutton);
+  ok(isChecked(targetButton), "the button for category " + aCategory +
+     " is checked after isolating for filter");
+
+  // Check if all the filters for the target button are on.
+  let menuItems = targetButton.querySelectorAll("menuitem");
+  Array.forEach(menuItems, (item) => {
+    let prefKey = item.getAttribute("prefKey");
+    ok(isChecked(item), "menu item " + prefKey + " for category " +
+      aCategory + " is checked after isolating for " + aCategory);
+    ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+      "turned on after isolating for " + aCategory);
+  });
+
+  // Ensure all other filter buttons are toggled off and their
+  // associated filters are turned off
+  let buttons = hudBox.querySelectorAll(".webconsole-filter-button[category]");
+  Array.forEach(buttons, (filterButton) => {
+    if (filterButton !== targetButton) {
+      let category = filterButton.getAttribute("category");
+      ok(!isChecked(filterButton), "the button for category " +
+        category + " is unchecked after isolating for " + aCategory);
+
+      menuItems = filterButton.querySelectorAll("menuitem");
+      Array.forEach(menuItems, (item) => {
+        let prefKey = item.getAttribute("prefKey");
+        ok(!isChecked(item), "menu item " + prefKey + " for category " +
+          aCategory + " is unchecked after isolating for " + aCategory);
+        ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+          "turned off after isolating for " + aCategory);
+      });
+
+      // Turn all the filters on again by clicking the button.
+      let mainButton = getMainButton(filterButton);
+      clickButton(mainButton);
+    }
+  });
+}
+
+/**
+ * Return the main part of the target filter button.
+ */
+function getMainButton(aTargetButton) {
+  let anonymousNodes = hud.ui.document.getAnonymousNodes(aTargetButton);
+  let subbutton;
+
+  for (let i = 0; i < anonymousNodes.length; i++) {
+    let node = anonymousNodes[i];
+    if (node.classList.contains("toolbarbutton-menubutton-button")) {
+      subbutton = node;
+      break;
+    }
+  }
+
+  return subbutton;
+}
+
 function clickButton(aNode) {
   EventUtils.sendMouseEvent({ type: "click" }, aNode);
 }
 
+function altClickButton(aNode) {
+  EventUtils.sendMouseEvent({ type: "click", altKey: true }, aNode);
+}
+
 function chooseMenuItem(aNode) {
   let event = document.createEvent("XULCommandEvent");
   event.initCommandEvent("command", true, true, window, 0, false, false, false,
                          false, null);
   aNode.dispatchEvent(event);
 }
 
 function isChecked(aNode) {
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_expandable_timestamps.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test for the message timestamps option: check if the preference toggles the
+// display of messages in the console output. See bug 722267.
+
+function test()
+{
+  const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
+  let hud;
+
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref(PREF_MESSAGE_TIMESTAMP);
+  });
+
+  addTab("data:text/html;charset=utf-8,Web Console test for bug 722267 - " +
+         "preference for toggling timestamps in messages");
+
+  browser.addEventListener("load", function tabLoad() {
+    browser.removeEventListener("load", tabLoad, true);
+    openConsole(null, consoleOpened);
+  }, true);
+
+  function consoleOpened(aHud)
+  {
+    hud = aHud;
+
+    info("console opened");
+    let prefValue = Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP);
+    ok(!prefValue, "messages have no timestamp by default (pref check)");
+    ok(hud.outputNode.classList.contains("hideTimestamps"),
+       "messages have no timestamp (class name check)");
+
+    let toolbox = gDevTools.getToolbox(hud.target);
+    toolbox.selectTool("options").then(onOptionsPanelSelected);
+  }
+
+  function onOptionsPanelSelected(panel)
+  {
+    info("options panel opened");
+
+    gDevTools.once("pref-changed", onPrefChanged);
+
+    let checkbox = panel.panelDoc.getElementById("webconsole-timestamp-messages");
+    checkbox.click();
+  }
+
+  function onPrefChanged()
+  {
+    info("pref changed");
+    let prefValue = Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP);
+    ok(prefValue, "messages have timestamps (pref check)");
+    ok(!hud.outputNode.classList.contains("hideTimestamps"),
+       "messages have timestamps (class name check)");
+    finishTest();
+  }
+}
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -166,16 +166,17 @@ const FILTER_PREFS_PREFIX = "devtools.we
 // The minimum font size.
 const MIN_FONT_SIZE = 10;
 
 // The maximum length of strings to be displayed by the Web Console.
 const MAX_LONG_STRING_LENGTH = 200000;
 
 const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
 const PREF_PERSISTLOG = "devtools.webconsole.persistlog";
+const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
 
 /**
  * A WebConsoleFrame instance is an interactive console initialized *per target*
  * that displays console log data as well as provides an interactive terminal to
  * manipulate the target's document content.
  *
  * The WebConsoleFrame is responsible for the actual Web Console UI
  * implementation.
@@ -196,16 +197,17 @@ function WebConsoleFrame(aWebConsoleOwne
   this._networkRequests = {};
   this.filterPrefs = {};
 
   this.output = new ConsoleOutput(this);
 
   this._toggleFilter = this._toggleFilter.bind(this);
   this._onPanelSelected = this._onPanelSelected.bind(this);
   this._flushMessageQueue = this._flushMessageQueue.bind(this);
+  this._onToolboxPrefChanged = this._onToolboxPrefChanged.bind(this);
 
   this._outputTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   this._outputTimerInitialized = false;
 
   EventEmitter.decorate(this);
 }
 exports.WebConsoleFrame = WebConsoleFrame;
 
@@ -566,16 +568,23 @@ WebConsoleFrame.prototype = {
     this.jsterm = new JSTerm(this);
     this.jsterm.init();
     this.jsterm.inputNode.focus();
 
     let toolbox = gDevTools.getToolbox(this.owner.target);
     if (toolbox) {
       toolbox.on("webconsole-selected", this._onPanelSelected);
     }
+
+    // Toggle the timestamp on preference change
+    gDevTools.on("pref-changed", this._onToolboxPrefChanged);
+    this._onToolboxPrefChanged("pref-changed", {
+      pref: PREF_MESSAGE_TIMESTAMP,
+      newValue: Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP),
+    });
   },
 
   /**
    * Sets the focus to JavaScript input field when the web console tab is
    * selected.
    * @private
    */
   _onPanelSelected: function WCF__onPanelSelected()
@@ -796,28 +805,36 @@ WebConsoleFrame.prototype = {
         if (!classes.contains("toolbarbutton-menubutton-button") &&
             originalTarget.getAttribute("type") === "menu-button") {
           // This is a filter button with a drop-down. The user clicked the
           // drop-down, so do nothing. (The menu will automatically appear
           // without our intervention.)
           break;
         }
 
+        // Toggle on the targeted filter button, and if the user alt clicked,
+        // toggle off all other filter buttons and their associated filters.
         let state = target.getAttribute("checked") !== "true";
+        if (aEvent.getModifierState("Alt")) {
+          let buttons = this.document
+                        .querySelectorAll(".webconsole-filter-button");
+          Array.forEach(buttons, (button) => {
+            if (button !== target) {
+              button.setAttribute("checked", false);
+              this._setMenuState(button, false);
+            }
+          });
+          state = true;
+        }
         target.setAttribute("checked", state);
 
         // This is a filter button with a drop-down, and the user clicked the
         // main part of the button. Go through all the severities and toggle
         // their associated filters.
-        let menuItems = target.querySelectorAll("menuitem");
-        for (let i = 0; i < menuItems.length; i++) {
-          menuItems[i].setAttribute("checked", state);
-          let prefKey = menuItems[i].getAttribute("prefKey");
-          this.setFilterState(prefKey, state);
-        }
+        this._setMenuState(target, state);
         break;
       }
 
       case "menuitem": {
         let state = target.getAttribute("checked") !== "true";
         target.setAttribute("checked", state);
 
         let prefKey = target.getAttribute("prefKey");
@@ -847,16 +864,35 @@ WebConsoleFrame.prototype = {
         let toolbarButton = menuPopup.parentNode;
         toolbarButton.setAttribute("checked", someChecked);
         break;
       }
     }
   },
 
   /**
+   * Set the menu attributes for a specific toggle button.
+   *
+   * @private
+   * @param XULElement aTarget
+   *        Button with drop down items to be toggled.
+   * @param boolean aState
+   *        True if the menu item is being toggled on, and false otherwise.
+   */
+  _setMenuState: function WCF__setMenuState(aTarget, aState)
+  {
+    let menuItems = aTarget.querySelectorAll("menuitem");
+    Array.forEach(menuItems, (item) => {
+      item.setAttribute("checked", aState);
+      let prefKey = item.getAttribute("prefKey");
+      this.setFilterState(prefKey, aState);
+    });
+  },
+
+  /**
    * Set the filter state for a specific toggle button.
    *
    * @param string aToggleType
    * @param boolean aState
    * @returns void
    */
   setFilterState: function WCF_setFilterState(aToggleType, aState)
   {
@@ -2771,16 +2807,39 @@ WebConsoleFrame.prototype = {
         return;
       }
 
       aCallback(this, aEvent);
     }, false);
   },
 
   /**
+   * Handler for the pref-changed event coming from the toolbox.
+   * Currently this function only handles the timestamps preferences.
+   *
+   * @private
+   * @param object aEvent
+   *        This parameter is a string that holds the event name
+   *        pref-changed in this case.
+   * @param object aData
+   *        This is the pref-changed data object.
+  */
+  _onToolboxPrefChanged: function WCF__onToolboxPrefChanged(aEvent, aData)
+  {
+    if (aData.pref == PREF_MESSAGE_TIMESTAMP) {
+      if (aData.newValue) {
+        this.outputNode.classList.remove("hideTimestamps");
+      }
+      else {
+        this.outputNode.classList.add("hideTimestamps");
+      }
+    }
+  },
+
+  /**
    * Copies the selected items to the system clipboard.
    *
    * @param object aOptions
    *        - linkOnly:
    *        An optional flag to copy only URL without timestamp and
    *        other meta-information. Default is false.
    */
   copySelectedItems: function WCF_copySelectedItems(aOptions)
@@ -2880,16 +2939,18 @@ WebConsoleFrame.prototype = {
 
     this._destroyer = promise.defer();
 
     let toolbox = gDevTools.getToolbox(this.owner.target);
     if (toolbox) {
       toolbox.off("webconsole-selected", this._onPanelSelected);
     }
 
+    gDevTools.off("pref-changed", this._onToolboxPrefChanged);
+
     this._repeatNodes = {};
     this._outputQueue = [];
     this._pruneCategoriesQueue = {};
     this._networkRequests = {};
 
     if (this._outputTimerInitialized) {
       this._outputTimerInitialized = false;
       this._outputTimer.cancel();
--- a/browser/devtools/webconsole/webconsole.xul
+++ b/browser/devtools/webconsole/webconsole.xul
@@ -43,18 +43,18 @@ function goUpdateConsoleCommands() {
              oncommand="goDoCommand('consoleCmd_clearOutput');"/>
     <command id="cmd_find" oncommand="goDoCommand('cmd_find');"/>
     <command id="cmd_fullZoomEnlarge" oncommand="goDoCommand('cmd_fontSizeEnlarge');" disabled="true"/>
     <command id="cmd_fullZoomReduce" oncommand="goDoCommand('cmd_fontSizeReduce');" disabled="true"/>
     <command id="cmd_fullZoomReset" oncommand="goDoCommand('cmd_fontSizeReset');" disabled="true"/>
     <command id="cmd_close" oncommand="goDoCommand('cmd_close');" disabled="true"/>
   </commandset>
   <keyset id="consoleKeys">
-    <key id="key_fullZoomReduce"  key="&fullZoomReduceCmd.commandkey;" command="cmd_fullZoomReduce"  modifiers="accel"/>
-    <key key="&fullZoomReduceCmd.commandkey2;"  command="cmd_fullZoomReduce" modifiers="accel"/>
+    <key id="key_fullZoomReduce" key="&fullZoomReduceCmd.commandkey;" command="cmd_fullZoomReduce" modifiers="accel"/>
+    <key key="&fullZoomReduceCmd.commandkey2;" command="cmd_fullZoomReduce" modifiers="accel"/>
     <key id="key_fullZoomEnlarge" key="&fullZoomEnlargeCmd.commandkey;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key key="&fullZoomEnlargeCmd.commandkey2;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key id="key_fullZoomReset" key="&fullZoomResetCmd.commandkey;" command="cmd_fullZoomReset" modifiers="accel"/>
     <key key="&fullZoomResetCmd.commandkey2;" command="cmd_fullZoomReset" modifiers="accel"/>
     <key key="&findCmd.key;" command="cmd_find" modifiers="accel"/>
     <key key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
     <key key="&clearOutputCtrl.key;" command="consoleCmd_clearOutput" modifiers="control"/>
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
@@ -104,16 +104,21 @@
 
 <!-- LOCALIZATION NOTE (options.enablePersistentLogging.label): This is the
   -  label for the checkbox that toggles persistent logs in the Web Console,
   -  i.e. devtools.webconsole.persistlog a boolean preference in about:config,
   -  in the options panel. -->
 <!ENTITY options.enablePersistentLogging.label    "Enable persistent logs">
 <!ENTITY options.enablePersistentLogging.tooltip  "If you enable this option the Web Console will not clear the output each time you navigate to a new page">
 
+<!-- LOCALIZATION NOTE (options.timestampMessages.label): This is the
+   - label for the checkbox that toggles timestamps in the Web Console -->
+<!ENTITY options.timestampMessages.label      "Enable timestamps">
+<!ENTITY options.timestampMessages.tooltip    "If you enable this option commands and output in the Web Console will display a timestamp">
+
 <!-- LOCALIZATION NOTE (options.profiler.label): This is the label for the
   -  heading of the group of JavaScript Profiler preferences in the options
   -  panel. -->
 <!ENTITY options.profiler.label            "JavaScript Profiler">
 
 <!-- LOCALIZATION NOTE (options.showPlatformData.label): This is the
   -  label for the checkbox that toggles the display of the platform data in the,
   -  Profiler i.e. devtools.profiler.ui.show-platform-data a boolean preference
--- a/browser/metro/base/content/ContextCommands.js
+++ b/browser/metro/base/content/ContextCommands.js
@@ -165,19 +165,18 @@ var ContextCommands = {
     aRichListItem.childNodes[0].setAttribute("value", "");
     aRichListItem.setAttribute("searchString", "");
     BrowserUI.addAndShowTab(defaultURI, Browser.selectedTab);
   },
 
   // Link specific
 
   openLinkInNewTab: function cc_openLinkInNewTab() {
-    let tab = Browser.addTab(ContextMenuUI.popupState.linkURL, false, Browser.selectedTab);
-    ContextUI.peekTabs(kOpenInNewTabAnimationDelayMsec);
-    Elements.tabList.strip.ensureElementIsVisible(tab.chromeTab);
+    let url = ContextMenuUI.popupState.linkURL;
+    BrowserUI.openLinkInNewTab(url, false, Browser.selectedTab);
   },
 
   copyLink: function cc_copyLink() {
     this.clipboard.copyString(ContextMenuUI.popupState.linkURL,
                               this.docRef);
   },
 
   bookmarkLink: function cc_bookmarkLink() {
--- a/browser/metro/base/content/ContextUI.js
+++ b/browser/metro/base/content/ContextUI.js
@@ -156,17 +156,17 @@ var ContextUI = {
 
     ContextUI.dismissTabsWithDelay(aDelay);
   },
 
   /*
    * Dismiss tab bar after a delay. Fires context ui events.
    */
   dismissTabsWithDelay: function (aDelay) {
-    aDelay = aDelay || kNewTabAnimationDelayMsec;
+    aDelay = aDelay || kForegroundTabAnimationDelay;
     this._clearDelayedTimeout();
     this._hidingId = setTimeout(function () {
         ContextUI.dismissTabs();
       }, aDelay);
   },
 
   // Display the nav bar
   displayNavbar: function () {
--- a/browser/metro/base/content/apzc.js
+++ b/browser/metro/base/content/apzc.js
@@ -34,16 +34,19 @@ var APZCObserver = {
     Elements.tabList.addEventListener("TabSelect", this, true);
     Elements.tabList.addEventListener("TabOpen", this, true);
     Elements.tabList.addEventListener("TabClose", this, true);
   },
 
   handleEvent: function APZC_handleEvent(aEvent) {
     switch (aEvent.type) {
       case 'pageshow':
+        if (aEvent.target != Browser.selectedBrowser.contentDocument)
+          break;
+        // fall through to TabSelect:
       case 'TabSelect':
         // ROOT_ID doesn't really identify the view we want. When we call
         // this on a content document (tab),  findElementWithViewId will
         // always return the root content document associated with the
         // scrollable frame.
         const ROOT_ID = 1;
         let windowUtils = Browser.selectedBrowser.contentWindow.
                           QueryInterface(Ci.nsIInterfaceRequestor).
--- a/browser/metro/base/content/browser-scripts.js
+++ b/browser/metro/base/content/browser-scripts.js
@@ -61,16 +61,19 @@ XPCOMUtils.defineLazyServiceGetter(windo
                                    "@mozilla.org/docshell/urifixup;1",
                                    "nsIURIFixup");
 XPCOMUtils.defineLazyServiceGetter(window, "gFaviconService",
                                    "@mozilla.org/browser/favicon-service;1",
                                    "nsIFaviconService");
 XPCOMUtils.defineLazyServiceGetter(window, "gFocusManager",
                                    "@mozilla.org/focus-manager;1",
                                    "nsIFocusManager");
+XPCOMUtils.defineLazyServiceGetter(window, "gEventListenerService",
+                                   "@mozilla.org/eventlistenerservice;1",
+                                   "nsIEventListenerService");
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
                                    "@mozilla.org/xre/app-info;1",
                                    "nsICrashReporter");
 #endif
 
 /*
  * window.Rect is used by
--- a/browser/metro/base/content/browser-ui.js
+++ b/browser/metro/base/content/browser-ui.js
@@ -9,22 +9,22 @@ Cu.import("resource://gre/modules/devtoo
 /**
  * Constants
  */
 
 // Devtools Messages
 const debugServerStateChanged = "devtools.debugger.remote-enabled";
 const debugServerPortChanged = "devtools.debugger.remote-port";
 
-// delay when showing the tab bar briefly after a new (empty) tab opens
-const kNewTabAnimationDelayMsec = 1000;
-// delay when showing the tab bar after opening a link on a new tab
-const kOpenInNewTabAnimationDelayMsec = 3000;
-// delay before closing tab bar after selecting another tab
-const kSelectTabAnimationDelayMsec = 500;
+// delay when showing the tab bar briefly after a new foreground tab opens
+const kForegroundTabAnimationDelay = 1000;
+// delay when showing the tab bar after opening a new background tab opens
+const kBackgroundTabAnimationDelay = 3000;
+// delay before closing tab bar after closing or selecting a tab
+const kChangeTabAnimationDelay = 500;
 
 /**
  * Cache of commonly used elements.
  */
 
 let Elements = {};
 [
   ["contentShowing",     "bcast_contentShowing"],
@@ -168,24 +168,31 @@ var BrowserUI = {
         if (name != "process")
           Services.console.logStringMessage("[timing] " + name + ": " + (startup[name] - startup.process) + "ms");
       }
     }, 3000);
 #endif
   },
 
   uninit: function() {
+    messageManager.removeMessageListener("DOMTitleChanged", this);
+    messageManager.removeMessageListener("DOMWillOpenModalDialog", this);
+    messageManager.removeMessageListener("DOMWindowClose", this);
+
+    messageManager.removeMessageListener("Browser:OpenURI", this);
+    messageManager.removeMessageListener("Browser:SaveAs:Return", this);
+    messageManager.removeMessageListener("Content:StateChange", this);
+
     messageManager.removeMessageListener("Browser:MozApplicationManifest", OfflineApps);
     Services.obs.removeObserver(this, "handle-xul-text-link");
 
     PanelUI.uninit();
     FlyoutPanelsUI.uninit();
     MetroDownloadsView.uninit();
     SettingsCharm.uninit();
-    messageManager.removeMessageListener("Content:StateChange", this);
     PageThumbs.uninit();
     this.stopDebugServer();
   },
 
   /************************************
    * Devtools Debugger
    */
   runDebugServer: function runDebugServer(aPort) {
@@ -431,20 +438,35 @@ var BrowserUI = {
    * Tab management
    */
 
   /**
    * Open a new tab in the foreground in response to a user action.
    * See Browser.addTab for more documentation.
    */
   addAndShowTab: function (aURI, aOwner) {
-    ContextUI.peekTabs(kNewTabAnimationDelayMsec);
+    ContextUI.peekTabs(kForegroundTabAnimationDelay);
     return Browser.addTab(aURI || kStartURI, true, aOwner);
   },
 
+  /**
+   * Open a new tab in response to clicking a link in an existing tab.
+   * See Browser.addTab for more documentation.
+   */
+  openLinkInNewTab: function (aURI, aBringFront, aOwner) {
+    ContextUI.peekTabs(aBringFront ? kForegroundTabAnimationDelay
+                                   : kBackgroundTabAnimationDelay);
+    let tab = Browser.addTab(aURI, aBringFront, aOwner, {
+      referrerURI: aOwner.browser.documentURI,
+      charset: aOwner.browser.characterSet,
+    });
+    Elements.tabList.strip.ensureElementIsVisible(tab.chromeTab);
+    return tab;
+  },
+
   setOnTabAnimationEnd: function setOnTabAnimationEnd(aCallback) {
     Elements.tabs.addEventListener("animationend", function onAnimationEnd() {
       Elements.tabs.removeEventListener("animationend", onAnimationEnd);
       aCallback();
     });
   },
 
   closeTab: function closeTab(aTab) {
@@ -459,17 +481,17 @@ var BrowserUI = {
     let wasCollapsed = !ContextUI.tabbarVisible;
     if (wasCollapsed) {
       ContextUI.displayTabs();
     }
 
     this.setOnTabAnimationEnd(function() {
       Browser.closeTab(tabToClose, { forceClose: true } );
       if (wasCollapsed)
-        ContextUI.dismissTabsWithDelay(kNewTabAnimationDelayMsec);
+        ContextUI.dismissTabsWithDelay(kChangeTabAnimationDelay);
     });
   },
 
   /**
     * Re-open a closed tab.
     * @param aIndex
     *        The index of the tab (via nsSessionStore.getClosedTabData)
     * @returns a reference to the reopened tab.
@@ -501,17 +523,17 @@ var BrowserUI = {
   },
 
   selectTab: function selectTab(aTab) {
     Browser.selectedTab = aTab;
   },
 
   selectTabAndDismiss: function selectTabAndDismiss(aTab) {
     this.selectTab(aTab);
-    ContextUI.dismissTabsWithDelay(kSelectTabAnimationDelayMsec);
+    ContextUI.dismissTabsWithDelay(kChangeTabAnimationDelay);
   },
 
   selectTabAtIndex: function selectTabAtIndex(aIndex) {
     // count backwards for aIndex < 0
     if (aIndex < 0)
       aIndex += Browser._tabs.length;
 
     if (aIndex >= 0 && aIndex < Browser._tabs.length)
--- a/browser/metro/base/content/browser.js
+++ b/browser/metro/base/content/browser.js
@@ -74,16 +74,17 @@ var Browser = {
     Elements.browsers.customDragger = new Browser.MainDragger();
 
     /* handles web progress management for open browsers */
     Elements.browsers.webProgress = WebProgress.init();
 
     // Call InputSourceHelper first so global listeners get called before
     // we start processing input in TouchModule.
     InputSourceHelper.init();
+    ClickEventHandler.init();
 
     TouchModule.init();
     GestureModule.init();
     BrowserTouchHandler.init();
     PopupBlockerObserver.init();
     APZCObserver.init();
 
     // Init the touch scrollbox
@@ -208,16 +209,17 @@ var Browser = {
       event.initEvent("UIReady", true, false);
       window.dispatchEvent(event);
     }.bind(this));
   },
 
   shutdown: function shutdown() {
     APZCObserver.shutdown();
     BrowserUI.uninit();
+    ClickEventHandler.uninit();
     ContentAreaObserver.shutdown();
     Appbar.shutdown();
 
     messageManager.removeMessageListener("Browser:FormSubmit", this);
     messageManager.removeMessageListener("scroll", this);
     messageManager.removeMessageListener("Browser:CertException", this);
     messageManager.removeMessageListener("Browser:BlockedSite", this);
     messageManager.removeMessageListener("Browser:TapOnSelection", this);
@@ -480,17 +482,17 @@ var Browser = {
       this._tabs.splice(params.index, 0, newTab);
     } else {
       this._tabs.push(newTab);
     }
 
     if (aBringFront)
       this.selectedTab = newTab;
 
-    this._announceNewTab(newTab, params, aBringFront);
+    this._announceNewTab(newTab);
     return newTab;
   },
 
   closeTab: function closeTab(aTab, aOptions) {
     let tab = aTab instanceof XULElement ? this.getTabFromChrome(aTab) : aTab;
     if (!tab) {
       return;
     }
@@ -506,17 +508,17 @@ var Browser = {
   savePage: function() {
     ContentAreaUtils.saveDocument(this.selectedBrowser.contentWindow.document);
   },
 
   /*
    * helper for addTab related methods. Fires events related to
    * new tab creation.
    */
-  _announceNewTab: function _announceNewTab(aTab, aParams, aBringFront) {
+  _announceNewTab: function (aTab) {
     let event = document.createEvent("UIEvents");
     event.initUIEvent("TabOpen", true, false, window, 0);
     aTab.chromeTab.dispatchEvent(event);
     aTab.browser.messageManager.sendAsyncMessage("Browser:TabOpen");
   },
 
   _doCloseTab: function _doCloseTab(aTab) {
     if (this._tabs.length === 1) {
@@ -1015,97 +1017,88 @@ Browser.MainDragger.prototype = {
     this._horizontalScrollbar.removeAttribute("width");
     this._verticalScrollbar.removeAttribute("height");
     this._horizontalScrollbar.style.MozTransform = "";
     this._verticalScrollbar.style.MozTransform = "";
   }
 };
 
 
-
-const OPEN_APPTAB = 100; // Hack until we get a real API
-
 function nsBrowserAccess() { }
 
 nsBrowserAccess.prototype = {
   QueryInterface: function(aIID) {
     if (aIID.equals(Ci.nsIBrowserDOMWindow) || aIID.equals(Ci.nsISupports))
       return this;
     throw Cr.NS_NOINTERFACE;
   },
 
+  _getOpenAction: function _getOpenAction(aURI, aOpener, aWhere, aContext) {
+    let where = aWhere;
+    /*
+     * aWhere: 
+     * OPEN_DEFAULTWINDOW: default action
+     * OPEN_CURRENTWINDOW: current window/tab
+     * OPEN_NEWWINDOW: not allowed, converted to newtab below
+     * OPEN_NEWTAB: open a new tab
+     * OPEN_SWITCHTAB: open in an existing tab if it matches, otherwise open
+     * a new tab. afaict we always open these in the current tab.
+     */
+    if (where == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
+      // query standard browser prefs indicating what to do for default action
+      switch (aContext) {
+        // indicates this is an open request from a 3rd party app.
+        case Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL :
+          where = Services.prefs.getIntPref("browser.link.open_external");
+          break;
+        // internal request
+        default :
+          where = Services.prefs.getIntPref("browser.link.open_newwindow");
+      }
+    }
+    if (where == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW) {
+      Util.dumpLn("Invalid request - we can't open links in new windows.");
+      where = Ci.nsIBrowserDOMWindow.OPEN_NEWTAB;
+    }
+    return where;
+  },
+
   _getBrowser: function _getBrowser(aURI, aOpener, aWhere, aContext) {
     let isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+    // We don't allow externals apps opening chrome docs
     if (isExternal && aURI && aURI.schemeIs("chrome"))
       return null;
 
+    let location;
+    let browser;
     let loadflags = isExternal ?
                       Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
                       Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
-    let location;
-    if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
-      switch (aContext) {
-        case Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL :
-          aWhere = Services.prefs.getIntPref("browser.link.open_external");
-          break;
-        default : // OPEN_NEW or an illegal value
-          aWhere = Services.prefs.getIntPref("browser.link.open_newwindow");
-      }
-    }
+    let openAction = this._getOpenAction(aURI, aOpener, aWhere, aContext);
 
-    let browser;
-    if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW) {
-      let url = aURI ? aURI.spec : "about:blank";
-      let newWindow = openDialog("chrome://browser/content/browser.xul", "_blank",
-                                 "all,dialog=no", url, null, null, null);
-      // since newWindow.Browser doesn't exist yet, just return null
-      return null;
-    } else if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
+    if (openAction == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
       let owner = isExternal ? null : Browser.selectedTab;
-      let tab = Browser.addTab("about:blank", true, owner);
-      if (isExternal)
-        tab.closeOnExit = true;
+      let tab = BrowserUI.openLinkInNewTab("about:blank", true, owner);
       browser = tab.browser;
-    } else if (aWhere == OPEN_APPTAB) {
-      Browser.tabs.forEach(function(aTab) {
-        if ("appURI" in aTab.browser && aTab.browser.appURI.spec == aURI.spec) {
-          Browser.selectedTab = aTab;
-          browser = aTab.browser;
-        }
-      });
-
-      if (!browser) {
-        // Make a new tab to hold the app
-        let tab = Browser.addTab("about:blank", true);
-        browser = tab.browser;
-        browser.appURI = aURI;
-      } else {
-        // Just use the existing browser, but return null to keep the system from trying to load the URI again
-        browser = null;
-      }
-    } else { // OPEN_CURRENTWINDOW and illegal values
+    } else {
       browser = Browser.selectedBrowser;
     }
 
     try {
       let referrer;
       if (aURI && browser) {
         if (aOpener) {
           location = aOpener.location;
           referrer = Services.io.newURI(location, null, null);
         }
         browser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null);
       }
       browser.focus();
     } catch(e) { }
 
-    // We are loading web content into this window, so make sure content is visible
-    // XXX Can we remove this?  It seems to be reproduced in BrowserUI already.
-    BrowserUI.showContent();
-
     return browser;
   },
 
   openURI: function browser_openURI(aURI, aOpener, aWhere, aContext) {
     let browser = this._getBrowser(aURI, aOpener, aWhere, aContext);
     return browser ? browser.contentWindow : null;
   },
 
@@ -1579,8 +1572,73 @@ function rendererFactory(aBrowser, aCanv
     };
     wrapper.drawContent = function(callback) {
       callback(ctx, draw);
     };
   }
 
   return wrapper;
 };
+
+// Based on ClickEventHandler from /browser/base/content/content.js
+let ClickEventHandler = {
+  init: function () {
+    gEventListenerService.addSystemEventListener(Elements.browsers, "click", this, true);
+  },
+
+  uninit: function () {
+    gEventListenerService.removeSystemEventListener(Elements.browsers, "click", this, true);
+  },
+
+  handleEvent: function (aEvent) {
+    if (!aEvent.isTrusted || aEvent.defaultPrevented) {
+      return;
+    }
+    let [href, node] = this._hrefAndLinkNodeForClickEvent(aEvent);
+    if (href && (aEvent.button == 1 || aEvent.ctrlKey)) {
+      // Open link in a new tab for middle-click or ctrl-click
+      BrowserUI.openLinkInNewTab(href, aEvent.shiftKey, Browser.selectedTab);
+    }
+  },
+
+  /**
+   * Extracts linkNode and href for the current click target.
+   *
+   * @param event
+   *        The click event.
+   * @return [href, linkNode].
+   *
+   * @note linkNode will be null if the click wasn't on an anchor
+   *       element (or XLink).
+   */
+  _hrefAndLinkNodeForClickEvent: function(event) {
+    function isHTMLLink(aNode) {
+      return ((aNode instanceof content.HTMLAnchorElement && aNode.href) ||
+              (aNode instanceof content.HTMLAreaElement && aNode.href) ||
+              aNode instanceof content.HTMLLinkElement);
+    }
+
+    let node = event.target;
+    while (node && !isHTMLLink(node)) {
+      node = node.parentNode;
+    }
+
+    if (node)
+      return [node.href, node];
+
+    // If there is no linkNode, try simple XLink.
+    let href, baseURI;
+    node = event.target;
+    while (node && !href) {
+      if (node.nodeType == content.Node.ELEMENT_NODE) {
+        href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
+        if (href)
+          baseURI = node.ownerDocument.baseURIObject;
+      }
+      node = node.parentNode;
+    }
+
+    // In case of XLink, we don't return the node we got href from since
+    // callers expect <a>-like elements.
+    // Note: makeURI() will throw if aUri is not a valid URI.
+    return [href ? Services.io.newURI(href, null, baseURI).spec : null, null];
+  }
+};
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/tests/mochitest/browser_link_click.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="UTF-8">
+    <title>link click test</title>
+  </head>
+  <body>
+    <a id="link" href="about:blank">link</a>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/tests/mochitest/browser_link_click.js
@@ -0,0 +1,97 @@
+/* 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";
+
+function test() {
+  waitForExplicitFinish();
+  runTests();
+}
+
+gTests.push({
+  desc: "regular link click",
+  run: function () {
+    let tab = yield addTab(chromeRoot + "browser_link_click.html");
+    let tabCount = Browser.tabs.length;
+
+    EventUtils.sendMouseEvent({type: "click"}, "link", tab.browser.contentWindow);
+    yield waitForCondition(() => tab.browser.currentURI.spec == "about:blank");
+    is(Browser.tabs.length, tabCount, "link loaded in the same tab");
+  }
+});
+
+gTests.push({
+  desc: "middle-click opens link in background tab",
+  run: function () {
+    let tab = yield addTab(chromeRoot + "browser_link_click.html");
+
+    let tabOpen = waitForEvent(window, "TabOpen");
+    EventUtils.sendMouseEvent({type: "click", button: 1}, "link", tab.browser.contentWindow);
+    let event = yield tabOpen;
+
+    let newTab = Browser.getTabFromChrome(event.originalTarget);
+    yield waitForEvent(newTab.browser, "pageshow");
+
+    is(newTab.browser.currentURI.spec, "about:blank");
+    ok(newTab != Browser.selectedTab, "new tab is in the background");
+
+    Browser.closeTab(newTab, { forceClose: true });
+  }
+});
+
+gTests.push({
+  desc: "shift-middle-click opens link in background tab",
+  run: function () {
+    let tab = yield addTab(chromeRoot + "browser_link_click.html");
+
+    let tabOpen = waitForEvent(window, "TabOpen");
+    EventUtils.sendMouseEvent({type: "click", button: 1, shiftKey: true}, "link", tab.browser.contentWindow);
+    let event = yield tabOpen;
+
+    let newTab = Browser.getTabFromChrome(event.originalTarget);
+    yield waitForEvent(newTab.browser, "pageshow");
+
+    is(newTab.browser.currentURI.spec, "about:blank");
+    ok(newTab == Browser.selectedTab, "new tab is in the foreground");
+
+    Browser.closeTab(newTab, { forceClose: true });
+  }
+});
+
+gTests.push({
+  desc: "ctrl-click opens link in background tab",
+  run: function () {
+    let tab = yield addTab(chromeRoot + "browser_link_click.html");
+
+    let tabOpen = waitForEvent(window, "TabOpen");
+    EventUtils.sendMouseEvent({type: "click", ctrlKey: true}, "link", tab.browser.contentWindow);
+    let event = yield tabOpen;
+
+    let newTab = Browser.getTabFromChrome(event.originalTarget);
+    yield waitForEvent(newTab.browser, "pageshow");
+
+    is(newTab.browser.currentURI.spec, "about:blank");
+    ok(newTab != Browser.selectedTab, "new tab is in the background");
+
+    Browser.closeTab(newTab, { forceClose: true });
+  }
+});
+
+gTests.push({
+  desc: "shift-ctrl-click opens link in background tab",
+  run: function () {
+    let tab = yield addTab(chromeRoot + "browser_link_click.html");
+
+    let tabOpen = waitForEvent(window, "TabOpen");
+    EventUtils.sendMouseEvent({type: "click", ctrlKey: true, shiftKey: true}, "link", tab.browser.contentWindow);
+    let event = yield tabOpen;
+
+    let newTab = Browser.getTabFromChrome(event.originalTarget);
+    yield waitForEvent(newTab.browser, "pageshow");
+
+    is(newTab.browser.currentURI.spec, "about:blank");
+    ok(newTab == Browser.selectedTab, "new tab is in the foreground");
+
+    Browser.closeTab(newTab, { forceClose: true });
+  }
+});
--- a/browser/metro/base/tests/mochitest/metro.ini
+++ b/browser/metro/base/tests/mochitest/metro.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 support-files =
   browser_context_menu_tests_01.html
   browser_context_menu_tests_02.html
   browser_context_menu_tests_03.html
   browser_context_menu_tests_04.html
   browser_findbar.html
   browser_form_auto_complete.html
+  browser_link_click.html
   browser_onscreen_keyboard.html
   browser_progress_indicator.xul
   browser_selection_basic.html
   browser_selection_caretfocus.html
   browser_selection_contenteditable.html
   browser_selection_frame_content.html
   browser_selection_frame_inputs.html
   browser_selection_frame_textarea.html
@@ -37,16 +38,17 @@ support-files =
 [browser_crashprompt.js]
 [browser_context_menu_tests.js]
 [browser_context_ui.js]
 [browser_downloads.js]
 [browser_findbar.js]
 [browser_form_auto_complete.js]
 [browser_history.js]
 [browser_inputsource.js]
+[browser_link_click.js]
 [browser_onscreen_keyboard.js]
 [browser_prefs_ui.js]
 [browser_remotetabs.js]
 [browser_snappedState.js]
 [browser_tabs.js]
 [browser_test.js]
 [browser_tiles.js]
 [browser_topsites.js]
--- a/browser/themes/shared/devtools/webconsole.inc.css
+++ b/browser/themes/shared/devtools/webconsole.inc.css
@@ -107,16 +107,20 @@ a {
 #output-container {
   -moz-user-select: text;
   -moz-box-flex: 1;
   display: flex;
   flex-direction: column;
   align-items: flex-start;
 }
 
+#output-container.hideTimestamps > .message > .timestamp {
+  display: none;
+}
+
 .filtered-by-type,
 .filtered-by-string {
   display: none;
 }
 
 .hidden-message {
   display: block;
   visibility: hidden;
--- a/caps/idl/moz.build
+++ b/caps/idl/moz.build
@@ -1,14 +1,15 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 XPIDL_SOURCES += [
+    'nsIDomainPolicy.idl',
     'nsIPrincipal.idl',
     'nsIScriptSecurityManager.idl',
     'nsISecurityCheckedComponent.idl',
 ]
 
 MODULE = 'caps'
 
new file mode 100644
--- /dev/null
+++ b/caps/idl/nsIDomainPolicy.idl
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 nsIURI;
+interface nsIDomainSet;
+
+/*
+ * When a domain policy is instantiated by invoking activateDomainPolicy() on
+ * nsIScriptSecurityManager, these domain sets are consulted when each new
+ * global is created (they have no effect on already-created globals).
+ * If javascript is globally enabled with |javascript.enabled|, the blacklists
+ * are consulted. If globally disabled, the whitelists are consulted. Lookups
+ * on blacklist and whitelist happen with contains(), and lookups on
+ * superBlacklist and superWhitelist happen with containsSuperDomain().
+ *
+ * When deactivate() is invoked, the domain sets are emptied, and the
+ * nsIDomainPolicy ceases to have any effect on the system.
+ */
+[scriptable, builtinclass, uuid(27b10f54-f34b-42b7-8594-4348d3ad7953)]
+interface nsIDomainPolicy : nsISupports
+{
+    readonly attribute nsIDomainSet blacklist;
+    readonly attribute nsIDomainSet superBlacklist;
+    readonly attribute nsIDomainSet whitelist;
+    readonly attribute nsIDomainSet superWhitelist;
+
+    void deactivate();
+};
+
+[scriptable, builtinclass, uuid(946a01ff-6525-4007-a2c2-447ebe1875d3)]
+interface nsIDomainSet : nsISupports
+{
+    /*
+     * Add a domain to the set. No-op if it already exists.
+     */
+    void add(in nsIURI aDomain);
+
+    /*
+     * Remove a domain from the set. No-op if it doesn't exist.
+     */
+    void remove(in nsIURI aDomain);
+
+    /*
+     * Remove all entries from the set.
+     */
+    void clear();
+
+    /*
+     * Returns true if a given domain is in the set.
+     */
+    bool contains(in nsIURI aDomain);
+
+    /*
+     * Returns true if a given domain is a subdomain of one of the entries in
+     * the set.
+     */
+    bool containsSuperDomain(in nsIURI aDomain);
+};
--- a/caps/idl/nsIScriptSecurityManager.idl
+++ b/caps/idl/nsIScriptSecurityManager.idl
@@ -4,18 +4,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 #include "nsIPrincipal.idl"
 #include "nsIXPCSecurityManager.idl"
 interface nsIURI;
 interface nsIChannel;
 interface nsIDocShell;
+interface nsIDomainPolicy;
 
-[scriptable, uuid(d6475e53-9ece-4dc0-940c-095ac3d85363)]
+[scriptable, uuid(2911ae60-1b5f-47e6-941e-1bb7b53a167d)]
 interface nsIScriptSecurityManager : nsIXPCSecurityManager
 {
     ///////////////// Security Checks //////////////////
     /**
      * Checks whether the running script is allowed to access aProperty.
      */
     [noscript] void checkPropertyAccess(in JSContextPtr aJSContext,
                                         in JSObjectPtr aJSObject,
@@ -98,34 +99,19 @@ interface nsIScriptSecurityManager : nsI
      * load as well); if any of the versions of this URI is not allowed, this
      * function will return error code NS_ERROR_DOM_BAD_URI.
      */
     void checkLoadURIStrWithPrincipal(in nsIPrincipal aPrincipal,
                                       in AUTF8String uri,
                                       in unsigned long flags);
 
     /**
-     * Check that the function 'funObj' is allowed to run on 'targetObj'
-     *
-     * Will return error code NS_ERROR_DOM_SECURITY_ERR if the function
-     * should not run
-     *
-     * @param cx The current active JavaScript context.
-     * @param funObj The function trying to run..
-     * @param targetObj The object the function will run on.
+     * Return true if scripts may be executed in the scope of the given global.
      */
-    [noscript] void checkFunctionAccess(in JSContextPtr cx, in voidPtr funObj,
-                                        in voidPtr targetObj);
-
-    /**
-     * Return true if content from the given principal is allowed to
-     * execute scripts.
-     */
-    [noscript] boolean canExecuteScripts(in JSContextPtr cx,
-                                         in nsIPrincipal principal);
+    [noscript,notxpcom] boolean scriptAllowed(in JSObjectPtr aGlobal);
 
     ///////////////// Principals ///////////////////////
     /**
      * Return the principal of the innermost frame of the currently
      * executing script. Will return null if there is no script
      * currently executing.
      */
     [noscript] nsIPrincipal getSubjectPrincipal();
@@ -211,16 +197,23 @@ interface nsIScriptSecurityManager : nsI
     nsIPrincipal getChannelPrincipal(in nsIChannel aChannel);
 
     /**
      * Check whether a given principal is a system principal.  This allows us
      * to avoid handing back the system principal to script while allowing
      * script to check whether a given principal is system.
      */
     boolean isSystemPrincipal(in nsIPrincipal aPrincipal);
+%{C++
+    bool IsSystemPrincipal(nsIPrincipal* aPrincipal) {
+      bool isSystem = false;
+      IsSystemPrincipal(aPrincipal, &isSystem);
+      return isSystem;
+    }
+%}
 
     /**
      * Same as getSubjectPrincipal(), only faster. cx must *never* be
      * passed null, and it must be the context on the top of the
      * context stack. Does *not* reference count the returned
      * principal.
      */
     [noscript,notxpcom] nsIPrincipal getCxSubjectPrincipal(in JSContextPtr cx);
@@ -231,13 +224,36 @@ interface nsIScriptSecurityManager : nsI
 
     /**
      * Returns the jar prefix for the app.
      * appId can be NO_APP_ID or a valid app id. appId should not be
      * UNKNOWN_APP_ID.
      * inMozBrowser has to be true if the app is inside a mozbrowser iframe.
      */
     AUTF8String getJarPrefix(in unsigned long appId, in boolean inMozBrowser);
+
+    /**
+     * Per-domain controls to enable and disable script. This system is designed
+     * to be used by at most one consumer, and enforces this with its semantics.
+     *
+     * Initially, domainPolicyActive is false. When activateDomainPolicy() is
+     * invoked, domainPolicyActive becomes true, and subsequent calls to
+     * activateDomainPolicy() will fail until deactivate() is invoked on the
+     * nsIDomainPolicy returned from activateDomainPolicy(). At this point,
+     * domainPolicyActive becomes false again, and a new consumer may acquire
+     * control of the system by invoking activateDomainPolicy().
+     */
+    nsIDomainPolicy activateDomainPolicy();
+    readonly attribute boolean domainPolicyActive;
+
+    /**
+     * Query mechanism for the above policy.
+     *
+     * If domainPolicyEnabled is false, this simply returns the current value
+     * of javascript.enabled. Otherwise, it returns the same value, but taking
+     * the various blacklist/whitelist exceptions into account.
+     */
+    bool policyAllowsScript(in nsIURI aDomain);
 };
 
 %{C++
 #define NS_SCRIPTSECURITYMANAGER_CONTRACTID "@mozilla.org/scriptsecuritymanager;1"
 %}
new file mode 100644
--- /dev/null
+++ b/caps/include/DomainPolicy.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DomainPolicy_h__
+#define DomainPolicy_h__
+
+#include "nsIDomainPolicy.h"
+#include "nsTHashtable.h"
+#include "nsURIHashKey.h"
+
+namespace mozilla {
+
+// The name "DomainPolicy" conflicts with some of the old-style policy machinery
+// in nsScriptSecurityManager.cpp, which needs to #include this file. So we
+// temporarily use a sub-namespace until that machinery goes away in bug 913734.
+namespace hotness {
+
+class DomainPolicy : public nsIDomainPolicy
+{
+public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIDOMAINPOLICY
+    DomainPolicy();
+    virtual ~DomainPolicy();
+
+private:
+    nsCOMPtr<nsIDomainSet> mBlacklist;
+    nsCOMPtr<nsIDomainSet> mSuperBlacklist;
+    nsCOMPtr<nsIDomainSet> mWhitelist;
+    nsCOMPtr<nsIDomainSet> mSuperWhitelist;
+};
+
+class DomainSet : public nsIDomainSet
+{
+public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIDOMAINSET
+
+    DomainSet() {}
+    virtual ~DomainSet() {}
+
+protected:
+    nsTHashtable<nsURIHashKey> mHashTable;
+};
+
+} /* namespace hotness */
+} /* namespace mozilla */
+
+#endif /* DomainPolicy_h__ */
--- a/caps/include/nsScriptSecurityManager.h
+++ b/caps/include/nsScriptSecurityManager.h
@@ -357,16 +357,18 @@ public:
      * parent are isolated from each other. All those entities do not share the
      * same data (cookies, IndexedDB, localStorage, etc.) so we shouldn't allow
      * violating that principle.
      */
     static bool
     AppAttributesEqual(nsIPrincipal* aFirst,
                        nsIPrincipal* aSecond);
 
+    void DeactivateDomainPolicy();
+
 private:
 
     // GetScriptSecurityManager is the only call that can make one
     nsScriptSecurityManager();
     virtual ~nsScriptSecurityManager();
 
     bool SubjectIsPrivileged();
 
@@ -376,17 +378,17 @@ private:
                       JS::MutableHandle<JS::Value> vp);
     
     // Decides, based on CSP, whether or not eval() and stuff can be executed.
     static bool
     ContentSecurityPolicyPermitsJSAction(JSContext *cx);
 
     // Returns null if a principal cannot be found; generally callers
     // should error out at that point.
-    static nsIPrincipal* doGetObjectPrincipal(JS::Handle<JSObject*> obj);
+    static nsIPrincipal* doGetObjectPrincipal(JSObject* obj);
 
     // Returns null if a principal cannot be found.  Note that rv can be NS_OK
     // when this happens -- this means that there was no JS running.
     nsIPrincipal*
     doGetSubjectPrincipal(nsresult* rv);
     
     nsresult
     CheckPropertyAccessImpl(uint32_t aAction,
@@ -457,25 +459,16 @@ private:
      *                                or equal privileges to the object.
      */
     nsresult
     CheckXPCPermissions(JSContext* cx,
                         nsISupports* aObj, JSObject* aJSObject,
                         nsIPrincipal* aSubjectPrincipal,
                         const char* aObjectSecurityLevel);
 
-    /**
-     * Helper for CanExecuteScripts that allows the caller to specify
-     * whether execution should be allowed if cx has no
-     * nsIScriptContext.
-     */
-    nsresult
-    CanExecuteScripts(JSContext* cx, nsIPrincipal *aPrincipal,
-                      bool aAllowIfNoScriptContext, bool *result);
-
     nsresult
     Init();
 
     nsresult
     InitPrefs();
 
     nsresult
     InitPolicies();
@@ -491,16 +484,20 @@ private:
     DomainPolicy* mDefaultPolicy;
     nsObjectHashtable* mCapabilities;
 
     nsCOMPtr<nsIPrincipal> mSystemPrincipal;
     bool mPrefInitialized;
     bool mIsJavaScriptEnabled;
     bool mPolicyPrefsChanged;
 
+    // This machinery controls new-style domain policies. The old-style
+    // policy machinery will be removed soon.
+    nsCOMPtr<nsIDomainPolicy> mDomainPolicy;
+
     static bool sStrictFileOriginPolicy;
 
     static nsIIOService    *sIOService;
     static nsIStringBundle *sStrBundle;
     static JSRuntime       *sRuntime;
 };
 
 #define NS_SECURITYNAMESET_CID \
new file mode 100644
--- /dev/null
+++ b/caps/src/DomainPolicy.cpp
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DomainPolicy.h"
+#include "nsScriptSecurityManager.h"
+
+namespace mozilla {
+namespace hotness {
+
+NS_IMPL_ISUPPORTS1(DomainPolicy, nsIDomainPolicy)
+
+DomainPolicy::DomainPolicy() : mBlacklist(new DomainSet())
+                             , mSuperBlacklist(new DomainSet())
+                             , mWhitelist(new DomainSet())
+                             , mSuperWhitelist(new DomainSet())
+{}
+
+DomainPolicy::~DomainPolicy()
+{
+    // The SSM holds a strong ref to the DomainPolicy until Deactivate() is
+    // invoked, so we should never hit the destructor until that happens.
+    MOZ_ASSERT(!mBlacklist && !mSuperBlacklist &&
+               !mWhitelist && !mSuperWhitelist);
+}
+
+
+NS_IMETHODIMP
+DomainPolicy::GetBlacklist(nsIDomainSet** aSet)
+{
+    nsCOMPtr<nsIDomainSet> set = mBlacklist;
+    set.forget(aSet);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainPolicy::GetSuperBlacklist(nsIDomainSet** aSet)
+{
+    nsCOMPtr<nsIDomainSet> set = mSuperBlacklist;
+    set.forget(aSet);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainPolicy::GetWhitelist(nsIDomainSet** aSet)
+{
+    nsCOMPtr<nsIDomainSet> set = mWhitelist;
+    set.forget(aSet);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainPolicy::GetSuperWhitelist(nsIDomainSet** aSet)
+{
+    nsCOMPtr<nsIDomainSet> set = mSuperWhitelist;
+    set.forget(aSet);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainPolicy::Deactivate()
+{
+    // Clear the hashtables first to free up memory, since script might
+    // hold the doomed sets alive indefinitely.
+    mBlacklist->Clear();
+    mSuperBlacklist->Clear();
+    mWhitelist->Clear();
+    mSuperWhitelist->Clear();
+
+    // Null them out.
+    mBlacklist = nullptr;
+    mSuperBlacklist = nullptr;
+    mWhitelist = nullptr;
+    mSuperWhitelist = nullptr;
+
+    // Inform the SSM.
+    nsScriptSecurityManager::GetScriptSecurityManager()->DeactivateDomainPolicy();
+    return NS_OK;
+}
+
+static already_AddRefed<nsIURI>
+GetCanonicalClone(nsIURI* aURI)
+{
+    nsCOMPtr<nsIURI> clone;
+    nsresult rv = aURI->Clone(getter_AddRefs(clone));
+    NS_ENSURE_SUCCESS(rv, nullptr);
+    rv = clone->SetUserPass(EmptyCString());
+    NS_ENSURE_SUCCESS(rv, nullptr);
+    rv = clone->SetPath(EmptyCString());
+    NS_ENSURE_SUCCESS(rv, nullptr);
+    return clone.forget();
+}
+
+NS_IMPL_ISUPPORTS1(DomainSet, nsIDomainSet)
+
+NS_IMETHODIMP
+DomainSet::Add(nsIURI* aDomain)
+{
+    nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain);
+    NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE);
+    mHashTable.PutEntry(clone);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainSet::Remove(nsIURI* aDomain)
+{
+    nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain);
+    NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE);
+    mHashTable.RemoveEntry(clone);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainSet::Clear()
+{
+    mHashTable.Clear();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainSet::Contains(nsIURI* aDomain, bool* aContains)
+{
+    *aContains = false;
+    nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain);
+    NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE);
+    *aContains = mHashTable.Contains(clone);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+DomainSet::ContainsSuperDomain(nsIURI* aDomain, bool* aContains)
+{
+    *aContains = false;
+    nsCOMPtr<nsIURI> clone = GetCanonicalClone(aDomain);
+    NS_ENSURE_TRUE(clone, NS_ERROR_FAILURE);
+    nsAutoCString domain;
+    nsresult rv = clone->GetHost(domain);
+    NS_ENSURE_SUCCESS(rv, rv);
+    while (true) {
+        // Check the current domain.
+        if (mHashTable.Contains(clone)) {
+            *aContains = true;
+            return NS_OK;
+        }
+
+        // Chop off everything before the first dot, or break if there are no
+        // dots left.
+        int32_t index = domain.Find(".");
+        if (index == kNotFound)
+            break;
+        domain.Assign(Substring(domain, index + 1));
+        rv = clone->SetHost(domain);
+        NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    // No match.
+    return NS_OK;
+
+}
+
+} /* namespace hotness */
+} /* namespace mozilla */
--- a/caps/src/moz.build
+++ b/caps/src/moz.build
@@ -2,16 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 MODULE = 'caps'
 
 SOURCES += [
+    'DomainPolicy.cpp',
     'nsJSPrincipals.cpp',
     'nsNullPrincipal.cpp',
     'nsNullPrincipalURI.cpp',
     'nsPrincipal.cpp',
     'nsScriptSecurityManager.cpp',
     'nsSecurityManagerFactory.cpp',
     'nsSystemPrincipal.cpp',
 ]
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -16,16 +16,17 @@
 #include "nsIScriptContext.h"
 #include "nsIURL.h"
 #include "nsINestedURI.h"
 #include "nspr.h"
 #include "nsJSPrincipals.h"
 #include "nsSystemPrincipal.h"
 #include "nsPrincipal.h"
 #include "nsNullPrincipal.h"
+#include "DomainPolicy.h"
 #include "nsXPIDLString.h"
 #include "nsCRT.h"
 #include "nsCRTGlue.h"
 #include "nsError.h"
 #include "nsDOMCID.h"
 #include "nsIXPConnect.h"
 #include "nsIXPCSecurityManager.h"
 #include "nsTextFormatter.h"
@@ -58,16 +59,17 @@
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingUtils.h"
 #include <stdint.h>
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StaticPtr.h"
 #include "nsContentUtils.h"
 #include "nsCxPusher.h"
+#include "nsJSUtils.h"
 
 // This should be probably defined on some other place... but I couldn't find it
 #define WEBAPPS_PERM_NAME "webapps-manage"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
@@ -178,22 +180,16 @@ GetPrincipalDomainOrigin(nsIPrincipal* a
   if (!uri) {
     aPrincipal->GetURI(getter_AddRefs(uri));
   }
   NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
 
   return GetOriginFromURI(uri, aOrigin);
 }
 
-static nsIScriptContext *
-GetScriptContext(JSContext *cx)
-{
-    return GetScriptContextFromJSContext(cx);
-}
-
 inline void SetPendingException(JSContext *cx, const char *aMsg)
 {
     JS_ReportError(cx, "%s", aMsg);
 }
 
 inline void SetPendingException(JSContext *cx, const PRUnichar *aMsg)
 {
     JS_ReportError(cx, "%hs", aMsg);
@@ -1596,183 +1592,48 @@ nsScriptSecurityManager::CheckLoadURIStr
             return rv;
         }
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     return rv;
 }
 
-NS_IMETHODIMP
-nsScriptSecurityManager::CheckFunctionAccess(JSContext *aCx, void *aFunObj,
-                                             void *aTargetObj)
+bool
+nsScriptSecurityManager::ScriptAllowed(JSObject *aGlobal)
 {
-    // This check is called for event handlers
-    nsresult rv;
-    JS::Rooted<JSObject*> rootedFunObj(aCx, static_cast<JSObject*>(aFunObj));
-    nsIPrincipal* subject = doGetObjectPrincipal(rootedFunObj);
-    if (!subject)
-        return NS_ERROR_FAILURE;
-
-    if (subject == mSystemPrincipal)
-        // This is the system principal: just allow access
-        return NS_OK;
-
-    // Check if the principal the function was compiled under is
-    // allowed to execute scripts.
-
-    bool result;
-    rv = CanExecuteScripts(aCx, subject, true, &result);
-    if (NS_FAILED(rv))
-      return rv;
-
-    if (!result)
-      return NS_ERROR_DOM_SECURITY_ERR;
-
-    if (!aTargetObj) {
-        // We're done here
-        return NS_OK;
-    }
-
-    /*
-    ** Get origin of subject and object and compare.
-    */
-    JS::Rooted<JSObject*> obj(aCx, (JSObject*)aTargetObj);
-    nsIPrincipal* object = doGetObjectPrincipal(obj);
+    MOZ_ASSERT(aGlobal);
+    MOZ_ASSERT(JS_IsGlobalObject(aGlobal) || js::IsOuterObject(aGlobal));
+    AutoJSContext cx;
+    JS::RootedObject global(cx, js::UncheckedUnwrap(aGlobal, /* stopAtOuter = */ false));
 
-    if (!object)
-        return NS_ERROR_FAILURE;
-
-    bool subsumes;
-    rv = subject->Subsumes(object, &subsumes);
-    if (NS_SUCCEEDED(rv) && !subsumes) {
-        rv = NS_ERROR_DOM_PROP_ACCESS_DENIED;
-    }
-    return rv;
-}
-
-NS_IMETHODIMP
-nsScriptSecurityManager::CanExecuteScripts(JSContext* cx,
-                                           nsIPrincipal *aPrincipal,
-                                           bool *result)
-{
-    return CanExecuteScripts(cx, aPrincipal, false, result);
-}
-
-nsresult
-nsScriptSecurityManager::CanExecuteScripts(JSContext* cx,
-                                           nsIPrincipal *aPrincipal,
-                                           bool aAllowIfNoScriptContext,
-                                           bool *result)
-{
-    *result = false; 
-
-    if (aPrincipal == mSystemPrincipal)
-    {
-        // Even if JavaScript is disabled, we must still execute system scripts
-        *result = true;
-        return NS_OK;
-    }
-
-    // Same thing for nsExpandedPrincipal, which is pseudo-privileged.
-    nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
-    if (ep)
-    {
-        *result = true;
-        return NS_OK;
+    // Check the bits on the compartment private.
+    xpc::Scriptability& scriptability = xpc::Scriptability::Get(aGlobal);
+    if (!scriptability.Allowed()) {
+        return false;
     }
 
-    //-- See if the current window allows JS execution
-    nsIScriptContext *scriptContext = GetScriptContext(cx);
-    if (!scriptContext) {
-        if (aAllowIfNoScriptContext) {
-            *result = true;
-            return NS_OK;
-        }
-        return NS_ERROR_FAILURE;
-    }
-
-    if (!scriptContext->GetScriptsEnabled()) {
-        // No scripting on this context, folks
-        *result = false;
-        return NS_OK;
-    }
-    
-    nsIScriptGlobalObject *sgo = scriptContext->GetGlobalObject();
-
-    if (!sgo) {
-        return NS_ERROR_FAILURE;
-    }
-
-    // window can be null here if we're running with a non-DOM window
-    // as the script global (i.e. a XUL prototype document).
-    nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(sgo);
-    nsCOMPtr<nsIDocShell> docshell;
-    nsresult rv;
-
-    if (window) {
-        docshell = window->GetDocShell();
-    }
-
-    if (docshell) {
-      rv = docshell->GetCanExecuteScripts(result);
-      if (NS_FAILED(rv)) return rv;
-      if (!*result) return NS_OK;
+    // If the compartment is immune to script policy, we're done.
+    if (scriptability.IsImmuneToScriptPolicy()) {
+        return true;
     }
 
-    // OK, the docshell doesn't have script execution explicitly disabled.
-    // Check whether our URI is an "about:" URI that allows scripts.  If it is,
-    // we need to allow JS to run.  In this case, don't apply the JS enabled
-    // pref or policies.  On failures, just press on and don't do this special
-    // case.
-    nsCOMPtr<nsIURI> principalURI;
-    aPrincipal->GetURI(getter_AddRefs(principalURI));
-    if (!principalURI) {
-        // Broken principal of some sort.  Disallow.
-        *result = false;
-        return NS_ERROR_UNEXPECTED;
-    }
-        
-    bool isAbout;
-    rv = principalURI->SchemeIs("about", &isAbout);
-    if (NS_SUCCEEDED(rv) && isAbout) {
-        nsCOMPtr<nsIAboutModule> module;
-        rv = NS_GetAboutModule(principalURI, getter_AddRefs(module));
-        if (NS_SUCCEEDED(rv)) {
-            uint32_t flags;
-            rv = module->GetURIFlags(principalURI, &flags);
-            if (NS_SUCCEEDED(rv) &&
-                (flags & nsIAboutModule::ALLOW_SCRIPT)) {
-                *result = true;
-                return NS_OK;              
-            }
-        }
+    // Check for a per-site policy.
+    static const char jsPrefGroupName[] = "javascript";
+    ClassInfoData nameData(nullptr, jsPrefGroupName);
+    SecurityLevel secLevel;
+    nsresult rv = LookupPolicy(doGetObjectPrincipal(global), nameData,
+                               EnabledID(),
+                               nsIXPCSecurityManager::ACCESS_GET_PROPERTY,
+                               nullptr, &secLevel);
+    if (NS_FAILED(rv) || secLevel.level == SCRIPT_SECURITY_NO_ACCESS) {
+        return false;
     }
 
-    *result = mIsJavaScriptEnabled;
-    if (!*result)
-        return NS_OK; // Do not run scripts
-
-    //-- Check for a per-site policy
-    static const char jsPrefGroupName[] = "javascript";
-    ClassInfoData nameData(nullptr, jsPrefGroupName);
-
-    SecurityLevel secLevel;
-    rv = LookupPolicy(aPrincipal, nameData, EnabledID(),
-                      nsIXPCSecurityManager::ACCESS_GET_PROPERTY,
-                      nullptr, &secLevel);
-    if (NS_FAILED(rv) || secLevel.level == SCRIPT_SECURITY_NO_ACCESS)
-    {
-        *result = false;
-        return rv;
-    }
-
-    //-- Nobody vetoed, so allow the JS to run.
-    *result = true;
-    return NS_OK;
+    return true;
 }
 
 ///////////////// Principals ///////////////////////
 NS_IMETHODIMP
 nsScriptSecurityManager::GetSubjectPrincipal(nsIPrincipal **aSubjectPrincipal)
 {
     nsresult rv;
     *aSubjectPrincipal = doGetSubjectPrincipal(&rv);
@@ -1959,17 +1820,17 @@ nsScriptSecurityManager::GetObjectPrinci
     if (!*result)
         return NS_ERROR_FAILURE;
     NS_ADDREF(*result);
     return NS_OK;
 }
 
 // static
 nsIPrincipal*
-nsScriptSecurityManager::doGetObjectPrincipal(JS::Handle<JSObject*> aObj)
+nsScriptSecurityManager::doGetObjectPrincipal(JSObject *aObj)
 {
     JSCompartment *compartment = js::GetObjectCompartment(aObj);
     JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment);
     return nsJSPrincipals::get(principals);
 }
 
 ////////////////////////////////////////////////
 // Methods implementing nsIXPCSecurityManager //
@@ -2286,16 +2147,19 @@ static StaticRefPtr<nsScriptSecurityMana
 
 nsScriptSecurityManager::~nsScriptSecurityManager(void)
 {
     Preferences::RemoveObservers(this, kObservedPrefs);
     delete mOriginToPolicyMap;
     if(mDefaultPolicy)
         mDefaultPolicy->Drop();
     delete mCapabilities;
+    if (mDomainPolicy)
+        mDomainPolicy->Deactivate();
+    MOZ_ASSERT(!mDomainPolicy);
 }
 
 void
 nsScriptSecurityManager::Shutdown()
 {
     if (sRuntime) {
         JS_SetSecurityCallbacks(sRuntime, nullptr);
         JS_SetTrustedPrincipals(sRuntime, nullptr);
@@ -2711,8 +2575,79 @@ nsScriptSecurityManager::GetJarPrefix(ui
                                       bool aInMozBrowser,
                                       nsACString& aJarPrefix)
 {
   MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
 
   mozilla::GetJarPrefix(aAppId, aInMozBrowser, aJarPrefix);
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsScriptSecurityManager::GetDomainPolicyActive(bool *aRv)
+{
+    *aRv = !!mDomainPolicy;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::ActivateDomainPolicy(nsIDomainPolicy** aRv)
+{
+    // We only allow one domain policy at a time. The holder of the previous
+    // policy must explicitly deactivate it first.
+    if (mDomainPolicy) {
+        return NS_ERROR_SERVICE_NOT_AVAILABLE;
+    }
+
+    mDomainPolicy = new mozilla::hotness::DomainPolicy();
+    nsCOMPtr<nsIDomainPolicy> ptr = mDomainPolicy;
+    ptr.forget(aRv);
+    return NS_OK;
+}
+
+// Intentionally non-scriptable. Script must have a reference to the
+// nsIDomainPolicy to deactivate it.
+void
+nsScriptSecurityManager::DeactivateDomainPolicy()
+{
+    mDomainPolicy = nullptr;
+}
+
+NS_IMETHODIMP
+nsScriptSecurityManager::PolicyAllowsScript(nsIURI* aURI, bool *aRv)
+{
+    nsresult rv;
+
+    // Compute our rule. If we don't have any domain policy set up that might
+    // provide exceptions to this rule, we're done.
+    *aRv = mIsJavaScriptEnabled;
+    if (!mDomainPolicy) {
+        return NS_OK;
+    }
+
+    // We have a domain policy. Grab the appropriate set of exceptions to the
+    // rule (either the blacklist or the whitelist, depending on whether script
+    // is enabled or disabled by default).
+    nsCOMPtr<nsIDomainSet> exceptions;
+    nsCOMPtr<nsIDomainSet> superExceptions;
+    if (*aRv) {
+        mDomainPolicy->GetBlacklist(getter_AddRefs(exceptions));
+        mDomainPolicy->GetSuperBlacklist(getter_AddRefs(superExceptions));
+    } else {
+        mDomainPolicy->GetWhitelist(getter_AddRefs(exceptions));
+        mDomainPolicy->GetSuperWhitelist(getter_AddRefs(superExceptions));
+    }
+
+    bool contains;
+    rv = exceptions->Contains(aURI, &contains);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (contains) {
+        *aRv = !*aRv;
+        return NS_OK;
+    }
+    rv = superExceptions->ContainsSuperDomain(aURI, &contains);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (contains) {
+        *aRv = !*aRv;
+    }
+
+    return NS_OK;
+}
deleted file mode 100644
--- a/caps/tests/mochitest/Makefile.in
+++ /dev/null
@@ -1,10 +0,0 @@
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# jarPrefix test doesn't work on Windows, see bug 776296.
-ifneq ($(OS_ARCH),WINNT)
-MOCHITEST_CHROME_FILES = test_principal_jarprefix_origin_appid_appstatus.html \
-                         $(NULL)
-endif
new file mode 100644
--- /dev/null
+++ b/caps/tests/mochitest/chrome.ini
@@ -0,0 +1,4 @@
+[test_disableScript.xul]
+[test_principal_jarprefix_origin_appid_appstatus.html]
+# jarPrefix test doesn't work on Windows, see bug 776296.
+skip-if = os == "win"
new file mode 100644
--- /dev/null
+++ b/caps/tests/mochitest/file_disableScript.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+var gFiredOnload = false;
+var gFiredOnclick = false;
+</script>
+</head>
+<body onload="gFiredOnload = true;" onclick="gFiredOnclick = true;">
+</body>
+</html>
--- a/caps/tests/mochitest/mochitest.ini
+++ b/caps/tests/mochitest/mochitest.ini
@@ -1,8 +1,10 @@
 [DEFAULT]
+support-files =
+  file_disableScript.html
 
 [test_app_principal_equality.html]
 [test_bug246699.html]
 [test_bug292789.html]
 [test_bug423375.html]
 [test_bug470804.html]
 [test_disallowInheritPrincipal.html]
--- a/caps/tests/mochitest/moz.build
+++ b/caps/tests/mochitest/moz.build
@@ -1,8 +1,9 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 MOCHITEST_MANIFESTS += ['mochitest.ini']
+MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
 
new file mode 100644
--- /dev/null
+++ b/caps/tests/mochitest/test_disableScript.xul
@@ -0,0 +1,321 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=840488
+-->
+<window title="Mozilla Bug 840488"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=840488"
+     target="_blank">Mozilla Bug 840488</a>
+  </body>
+
+  <iframe id="root" name="root" onload="go();" type="content"/>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+
+  /** Test for all the different ways that script can be disabled for a given global. **/
+
+  SimpleTest.waitForExplicitFinish();
+  const Cu = Components.utils;
+  const Ci = Components.interfaces;
+  Cu.import("resource://gre/modules/Promise.jsm");
+  Cu.import("resource://gre/modules/Services.jsm");
+  const ssm = Services.scriptSecurityManager;
+  function makeURI(uri) { return Services.io.newURI(uri, null, null); }
+  const path = "/tests/caps/tests/mochitest/file_disableScript.html";
+  const uri = "http://www.example.com" + path;
+  var rootFrame = document.getElementById('root');
+  rootFrame.setAttribute('src', uri + "?name=rootframe");
+
+  function navigateFrame(ifr, src) {
+    let deferred = Promise.defer();
+    function onload() {
+      ifr.removeEventListener('load', onload);
+      deferred.resolve();
+    }
+    ifr.addEventListener('load', onload, false);
+    ifr.setAttribute('src', src);
+    return deferred.promise;
+  }
+
+  function navigateBack(ifr) {
+    let deferred = Promise.defer();
+
+    // pageshow events don't fire on the iframe element, so we need to use the
+    // chrome event handler for the docshell.
+    var browser = ifr.contentWindow
+                     .QueryInterface(Ci.nsIInterfaceRequestor)
+                     .getInterface(Ci.nsIWebNavigation)
+                     .QueryInterface(Ci.nsIDocShell)
+                     .chromeEventHandler;
+    function onpageshow(evt) {
+      info("Navigated back. Persisted: " + evt.persisted);
+      browser.removeEventListener('pageshow', onpageshow);
+      deferred.resolve();
+    }
+    browser.addEventListener('pageshow', onpageshow, false);
+    ifr.contentWindow.history.back();
+    return deferred.promise;
+  }
+
+  function addFrame(parentWin, name, expectOnload) {
+    let ifr = parentWin.document.createElement('iframe');
+    parentWin.document.body.appendChild(ifr);
+    ifr.setAttribute('name', name);
+    let deferred = Promise.defer();
+    // We need to append 'name' to avoid running afoul of recursive frame detection.
+    let frameURI = uri + "?name=" + name;
+    navigateFrame(ifr, frameURI).then(function() {
+      is(ifr.contentWindow.location, frameURI, "Successful load");
+      is(!!ifr.contentWindow.wrappedJSObject.gFiredOnload, expectOnload,
+         "onload should only fire when scripts are enabled");
+      deferred.resolve();
+    });
+    return deferred.promise;
+  }
+
+  function checkScriptEnabled(win, expectEnabled) {
+    win.wrappedJSObject.gFiredOnclick = false;
+    win.document.body.dispatchEvent(new win.Event('click'));
+    is(win.wrappedJSObject.gFiredOnclick, expectEnabled, "Checking script-enabled for " + win.name + " (" + win.location + ")");
+  }
+
+  function setScriptEnabledForDocShell(win, enabled) {
+    win.QueryInterface(Ci.nsIInterfaceRequestor)
+       .getInterface(Ci.nsIDocShell)
+       .allowJavascript = enabled;
+  }
+
+  function testList(expectEnabled, win, list, idx) {
+    let idx = idx || 0;
+    let deferred = Promise.defer();
+    let target = list[idx] + path;
+    info("Testing scriptability for: " + target + ". expecting " + expectEnabled);
+    navigateFrame(win.frameElement, target).then(function() {
+      checkScriptEnabled(win, expectEnabled);
+      if (idx == list.length - 1)
+        deferred.resolve();
+      else
+      testList(expectEnabled, win, list, idx + 1).then(function() { deferred.resolve(); });
+    });
+    return deferred.promise;
+  }
+
+  function testDomainPolicy(defaultScriptability, exceptions, superExceptions,
+                            exempt, notExempt, set, superSet, win) {
+    // Populate our sets.
+    for (var e of exceptions)
+      set.add(makeURI(e));
+    for (var e of superExceptions)
+      superSet.add(makeURI(e));
+
+    return testList(defaultScriptability, win, notExempt).then(function() {
+      return testList(!defaultScriptability, win, exempt);
+    });
+  }
+
+  function setScriptEnabledForBrowser(enabled) {
+    var prefname = "javascript.enabled";
+    Services.prefs.setBoolPref(prefname, enabled);
+  }
+
+  function reloadFrame(frame) {
+    let deferred = Promise.defer();
+    frame.addEventListener('load', function onload() {
+      deferred.resolve();
+      frame.removeEventListener('load', onload);
+    }, false);
+    frame.contentWindow.location.reload(true);
+    return deferred.promise;
+  }
+
+  function go() {
+    rootFrame.setAttribute('onload', null);
+
+    // Test simple docshell enable/disable.
+    var rootWin = rootFrame.contentWindow;
+    checkScriptEnabled(rootWin, true);
+    setScriptEnabledForDocShell(rootWin, false);
+    checkScriptEnabled(rootWin, false);
+    setScriptEnabledForDocShell(rootWin, true);
+    checkScriptEnabled(rootWin, true);
+
+    // Play around with the docshell tree and make sure everything works as
+    // we expect.
+    addFrame(rootWin, 'parent', true).then(function() {
+      checkScriptEnabled(rootWin[0], true);
+      return addFrame(rootWin[0], 'childA', true);
+    }).then(function() {
+      checkScriptEnabled(rootWin[0][0], true);
+      setScriptEnabledForDocShell(rootWin[0], false);
+      checkScriptEnabled(rootWin, true);
+      checkScriptEnabled(rootWin[0], false);
+      checkScriptEnabled(rootWin[0][0], false);
+      return addFrame(rootWin[0], 'childB', false);
+    }).then(function() {
+      checkScriptEnabled(rootWin[0][1], false);
+      setScriptEnabledForDocShell(rootWin[0][0], false);
+      setScriptEnabledForDocShell(rootWin[0], true);
+      checkScriptEnabled(rootWin[0], true);
+      checkScriptEnabled(rootWin[0][0], false);
+      setScriptEnabledForDocShell(rootWin[0][0], true);
+
+      // Flags are inherited from the parent docshell at attach time. Note that
+      // the flag itself is inherited, regardless of whether or not scripts are
+      // currently allowed on the parent (which could depend on the parent's
+      // parent). Check that.
+      checkScriptEnabled(rootWin[0][1], false);
+      setScriptEnabledForDocShell(rootWin[0], false);
+      setScriptEnabledForDocShell(rootWin[0][1], true);
+      return addFrame(rootWin[0][1], 'grandchild', false);
+    }).then(function() {
+      checkScriptEnabled(rootWin[0], false);
+      checkScriptEnabled(rootWin[0][1], false);
+      checkScriptEnabled(rootWin[0][1][0], false);
+      setScriptEnabledForDocShell(rootWin[0], true);
+      checkScriptEnabled(rootWin[0], true);
+      checkScriptEnabled(rootWin[0][1], true);
+      checkScriptEnabled(rootWin[0][1][0], true);
+
+    // Try navigating two frames, then munging docshell scriptability, then
+    // pulling the frames out of the bfcache to make sure that flags are
+    // properly propagated to inactive inner windows. We do this both for an
+    // 'own' docshell, as well as for an ancestor docshell.
+      return navigateFrame(rootWin[0][0].frameElement, rootWin[0][0].location + '-navigated');
+    }).then(function() { return navigateFrame(rootWin[0][1][0].frameElement, rootWin[0][1][0].location + '-navigated'); })
+      .then(function() {
+      checkScriptEnabled(rootWin[0][0], true);
+      checkScriptEnabled(rootWin[0][1][0], true);
+      setScriptEnabledForDocShell(rootWin[0][0], false);
+      setScriptEnabledForDocShell(rootWin[0][1], false);
+      checkScriptEnabled(rootWin[0][0], false);
+      checkScriptEnabled(rootWin[0][1][0], false);
+      return navigateBack(rootWin[0][0].frameElement);
+    }).then(function() { return navigateBack(rootWin[0][1][0].frameElement); })
+      .then(function() {
+      checkScriptEnabled(rootWin[0][0], false);
+      checkScriptEnabled(rootWin[0][1][0], false);
+
+    // Disable JS via the global pref pref. This is only guaranteed to have an effect
+    // for subsequent loads.
+      setScriptEnabledForBrowser(false);
+      return reloadFrame(rootFrame);
+    }).then(function() {
+      checkScriptEnabled(rootWin, false);
+      setScriptEnabledForBrowser(true);
+      return reloadFrame(rootFrame);
+    }).then(function() {
+      checkScriptEnabled(rootWin, true);
+
+    // Play around with dynamically blocking script for a given global.
+    // This takes effect immediately.
+      Cu.blockScriptForGlobal(rootWin);
+      Cu.blockScriptForGlobal(rootWin);
+      Cu.unblockScriptForGlobal(rootWin);
+      checkScriptEnabled(rootWin, false);
+      Cu.unblockScriptForGlobal(rootWin);
+      checkScriptEnabled(rootWin, true);
+      Cu.blockScriptForGlobal(rootWin);
+      return reloadFrame(rootFrame);
+    }).then(function() {
+      checkScriptEnabled(rootWin, true);
+
+    // Test system-wide domain policy. This only takes effect for subsequently-
+    // loaded globals.
+
+    // Check the basic semantics of the sets.
+    is(ssm.domainPolicyActive, false, "not enabled");
+    window.policy = ssm.activateDomainPolicy();
+    ok(policy instanceof Ci.nsIDomainPolicy, "Got a policy");
+    try {
+      ssm.activateDomainPolicy();
+      ok(false, "Should have thrown");
+    } catch (e) {
+      ok(true, "can't have two live domain policies");
+    }
+    var sbRef = policy.superBlacklist;
+    isnot(sbRef, null, "superBlacklist non-null");
+    ok(!sbRef.contains(makeURI('http://www.example.com')));
+    sbRef.add(makeURI('http://www.example.com/foopy'));
+    ok(sbRef.contains(makeURI('http://www.example.com')));
+    sbRef.remove(makeURI('http://www.example.com'));
+    ok(!sbRef.contains(makeURI('http://www.example.com')));
+    sbRef.add(makeURI('http://www.example.com/foopy/this.that/'));
+    ok(sbRef.contains(makeURI('http://www.example.com/baz')));
+    ok(!sbRef.contains(makeURI('https://www.example.com')));
+    ok(!sbRef.contains(makeURI('https://www.example.com:88')));
+    ok(!sbRef.contains(makeURI('http://foo.www.example.com')));
+    ok(sbRef.containsSuperDomain(makeURI('http://foo.www.example.com')));
+    ok(sbRef.containsSuperDomain(makeURI('http://foo.bar.www.example.com')));
+    ok(!sbRef.containsSuperDomain(makeURI('http://foo.bar.www.exxample.com')));
+    ok(!sbRef.containsSuperDomain(makeURI('http://example.com')));
+    ok(!sbRef.containsSuperDomain(makeURI('http://com/this.that/')));
+    ok(!sbRef.containsSuperDomain(makeURI('https://foo.www.example.com')));
+    ok(sbRef.contains(makeURI('http://www.example.com')));
+    policy.deactivate();
+    is(ssm.domainPolicyActive, false, "back to inactive");
+    ok(!sbRef.contains(makeURI('http://www.example.com')),
+       "Disabling domain policy clears the set");
+    policy = ssm.activateDomainPolicy();
+    ok(policy.superBlacklist);
+    isnot(sbRef, policy.superBlacklist, "Mint new sets each time!");
+    policy.deactivate();
+    is(policy.blacklist, null, "blacklist nulled out");
+    policy = ssm.activateDomainPolicy();
+    isnot(policy.blacklist, null, "non-null again");
+    isnot(policy.blacklist, sbRef, "freshly minted");
+    policy.deactivate();
+
+    //
+    // Now, create and apply a mock-policy. We check the same policy both as
+    // a blacklist and as a whitelist.
+    //
+
+    window.testPolicy = {
+      // The policy.
+      exceptions: ['http://test1.example.com', 'http://example.com'],
+      superExceptions: ['http://test2.example.org', 'https://test1.example.com'],
+
+      // The testcases.
+      exempt: ['http://test1.example.com', 'http://example.com',
+               'http://test2.example.org', 'http://sub1.test2.example.org',
+               'https://sub1.test1.example.com'],
+
+      notExempt: ['http://test2.example.com', 'http://sub1.test1.example.com',
+                  'http://www.example.com', 'https://test2.example.com',
+                  'https://example.com', 'http://test1.example.org'],
+    };
+
+    policy = ssm.activateDomainPolicy();
+    info("Testing Blacklist-style Domain Policy");
+    return testDomainPolicy(true, testPolicy.exceptions,
+                            testPolicy.superExceptions, testPolicy.exempt,
+                            testPolicy.notExempt, policy.blacklist,
+                            policy.superBlacklist, rootWin);
+  }).then(function() {
+    policy.deactivate();
+    policy = ssm.activateDomainPolicy();
+    info("Testing Whitelist-style Domain Policy");
+    setScriptEnabledForBrowser(false);
+    return testDomainPolicy(false, testPolicy.exceptions,
+                            testPolicy.superExceptions, testPolicy.exempt,
+                            testPolicy.notExempt, policy.whitelist,
+                            policy.superWhitelist, rootWin);
+  }).then(function() {
+    setScriptEnabledForBrowser(true);
+    policy.deactivate();
+
+    SimpleTest.finish();
+    });
+  }
+
+  ]]>
+  </script>
+</window>
--- a/configure.in
+++ b/configure.in
@@ -218,32 +218,34 @@ if test -n "$gonkdir" ; then
         ;;
     esac
 
     case "$ANDROID_VERSION" in
     15)
         GONK_INCLUDES="-I$gonkdir/frameworks/base/opengl/include -I$gonkdir/frameworks/base/native/include -I$gonkdir/frameworks/base/include -I$gonkdir/frameworks/base/services/camera -I$gonkdir/frameworks/base/include/media/stagefright -I$gonkdir/frameworks/base/include/media/stagefright/openmax -I$gonkdir/frameworks/base/media/libstagefright/rtsp -I$gonkdir/frameworks/base/media/libstagefright/include -I$gonkdir/external/dbus -I$gonkdir/external/bluetooth/bluez/lib -I$gonkdir/dalvik/libnativehelper/include/nativehelper"
         MOZ_B2G_BT=1
         MOZ_B2G_BT_BLUEZ=1
+        MOZ_NFC=1
         MOZ_B2G_CAMERA=1
         MOZ_OMX_DECODER=1
         AC_SUBST(MOZ_OMX_DECODER)
         MOZ_RTSP=1
         ;;
     17|18)
         GONK_INCLUDES="-I$gonkdir/frameworks/native/include -I$gonkdir/frameworks/av/include -I$gonkdir/frameworks/av/include/media -I$gonkdir/frameworks/av/include/camera -I$gonkdir/frameworks/native/include/media/openmax -I$gonkdir/frameworks/av/media/libstagefright/include"
         if test -d "$gonkdir/external/bluetooth/bluez"; then
           GONK_INCLUDES="$GONK_INCLUDES -I$gonkdir/external/dbus -I$gonkdir/external/bluetooth/bluez/lib"
             MOZ_B2G_BT=1
             MOZ_B2G_BT_BLUEZ=1
         elif test -d "$gonkdir/external/bluetooth/bluedroid"; then
             MOZ_B2G_BT=1
             MOZ_B2G_BT_BLUEDROID=1
         fi
 
+        MOZ_NFC=1
         MOZ_B2G_CAMERA=1
         MOZ_OMX_DECODER=1
         AC_SUBST(MOZ_OMX_DECODER)
         ;;
     *)
         AC_MSG_ERROR([Unsupported platform version: $ANDROID_VERSION])
         ;;
     esac
@@ -7302,16 +7304,28 @@ MOZ_ARG_ENABLE_BOOL(b2g-bt,
 if test -n "$MOZ_B2G_BT"; then
     AC_DEFINE(MOZ_B2G_BT)
 fi
 AC_SUBST(MOZ_B2G_BT)
 AC_SUBST(MOZ_B2G_BT_BLUEZ)
 AC_SUBST(MOZ_B2G_BT_BLUEDROID)
 
 dnl ========================================================
+dnl = Enable NFC Interface for B2G (Gonk usually)
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(b2g-nfc,
+[  --enable-nfc         Set compile flags necessary for compiling NFC API ],
+    MOZ_NFC=1,
+    MOZ_NFC= )
+if test -n "$MOZ_NFC"; then
+   AC_DEFINE(MOZ_NFC)
+fi
+AC_SUBST(MOZ_NFC)
+
+dnl ========================================================
 dnl = Enable Pico Speech Synthesis (Gonk usually)
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(synth-pico,
 [  --enable-synth-pico  Set compile flags necessary for compiling Pico Web Speech API ],
     MOZ_SYNTH_PICO=1,
     MOZ_SYNTH_PICO= )
 if test -n "$MOZ_SYNTH_PICO"; then
     AC_DEFINE(MOZ_SYNTH_PICO)
--- a/content/base/src/nsAttrAndChildArray.cpp
+++ b/content/base/src/nsAttrAndChildArray.cpp
@@ -5,20 +5,20 @@
 
 /*
  * Storage of the children and attributes of a DOM node; storage for
  * the two is unified to minimize footprint.
  */
 
 #include "nsAttrAndChildArray.h"
 
+#include "mozilla/MathAlgorithms.h"
 #include "mozilla/MemoryReporting.h"
 
 #include "nsMappedAttributeElement.h"
-#include "prbit.h"
 #include "nsString.h"
 #include "nsHTMLStyleSheet.h"
 #include "nsRuleWalker.h"
 #include "nsMappedAttributes.h"
 #include "nsUnicharUtils.h"
 #include "nsAutoPtr.h"
 #include "nsContentUtils.h" // nsAutoScriptBlocker
 
@@ -767,17 +767,17 @@ nsAttrAndChildArray::GrowBy(uint32_t aGr
   uint32_t minSize = size + aGrowSize;
 
   if (minSize <= ATTRCHILD_ARRAY_LINEAR_THRESHOLD) {
     do {
       size += ATTRCHILD_ARRAY_GROWSIZE;
     } while (size < minSize);
   }
   else {
-    size = 1u << PR_CeilingLog2(minSize);
+    size = 1u << mozilla::CeilingLog2(minSize);
   }
 
   bool needToInitialize = !mImpl;
   Impl* newImpl = static_cast<Impl*>(moz_realloc(mImpl, size * sizeof(void*)));
   NS_ENSURE_TRUE(newImpl, false);
 
   mImpl = newImpl;
 
--- a/content/base/src/nsContentList.h
+++ b/content/base/src/nsContentList.h
@@ -248,18 +248,24 @@ public:
                 bool aDeep = true,
                 nsIAtom* aMatchAtom = nullptr,
                 int32_t aMatchNameSpaceId = kNameSpaceID_None,
                 bool aFuncMayDependOnAttr = true);
   virtual ~nsContentList();
 
   // nsWrapperCache
   using nsWrapperCache::GetWrapperPreserveColor;
-  virtual JSObject* WrapObject(JSContext *cx,
-                               JS::Handle<JSObject*> scope) MOZ_OVERRIDE;
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+protected:
+  virtual JSObject* GetWrapperPreserveColorInternal() MOZ_OVERRIDE
+  {
+    return nsWrapperCache::GetWrapperPreserveColor();
+  }
+public:
 
   // nsIDOMHTMLCollection
   NS_DECL_NSIDOMHTMLCOLLECTION
 
   // nsBaseContentList overrides
   virtual int32_t IndexOf(nsIContent *aContent, bool aDoFlush) MOZ_OVERRIDE;
   virtual int32_t IndexOf(nsIContent* aContent) MOZ_OVERRIDE;
   virtual nsINode* GetParentObject() MOZ_OVERRIDE
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -7201,29 +7201,20 @@ nsDocument::IsScriptEnabled()
   // script is not enabled
   if (mSandboxFlags & SANDBOXED_SCRIPTS) {
     return false;
   }
 
   nsCOMPtr<nsIScriptSecurityManager> sm(do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID));
   NS_ENSURE_TRUE(sm, false);
 
-  nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(GetWindow());
-  NS_ENSURE_TRUE(globalObject, false);
-
-  nsIScriptContext *scriptContext = globalObject->GetContext();
-  NS_ENSURE_TRUE(scriptContext, false);
-
-  AutoPushJSContext cx(scriptContext->GetNativeContext());
-  NS_ENSURE_TRUE(cx, false);
-
-  bool enabled;
-  nsresult rv = sm->CanExecuteScripts(cx, NodePrincipal(), &enabled);
-  NS_ENSURE_SUCCESS(rv, false);
-  return enabled;
+  nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(GetInnerWindow());
+  NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), false);
+
+  return sm->ScriptAllowed(globalObject->GetGlobalJSObject());
 }
 
 nsRadioGroupStruct*
 nsDocument::GetRadioGroupInternal(const nsAString& aName) const
 {
 #ifdef DEBUG
   if (IsHTML()) {
     nsAutoString lcName;
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -493,38 +493,16 @@ nsScriptLoader::ProcessScriptElement(nsI
 
   nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
 
   // Step 12. Check that the script is not an eventhandler
   if (IsScriptEventHandler(scriptContent)) {
     return false;
   }
 
-  // Script evaluation can also be disabled in the current script
-  // context even though it's enabled in the document.
-  // XXX - still hard-coded for JS here, even though another language
-  // may be specified.  Should this check be made *after* we examine
-  // the attributes to locate the script-type?
-  // For now though, if JS is disabled we assume every language is
-  // disabled.
-  // XXX is this different from the mDocument->IsScriptEnabled() call?
-  nsCOMPtr<nsIScriptGlobalObject> globalObject =
-    do_QueryInterface(mDocument->GetWindow());
-  if (!globalObject) {
-    return false;
-  }
-  
-  nsIScriptContext *context = globalObject->GetScriptContext();
-
-  // If scripts aren't enabled in the current context, there's no
-  // point in going on.
-  if (!context || !context->GetScriptsEnabled()) {
-    return false;
-  }
-
   JSVersion version = JSVERSION_DEFAULT;
 
   // Check the type attribute to determine language and version.
   // If type exists, it trumps the deprecated 'language='
   nsAutoString type;
   aElement->GetScriptType(type);
   if (!type.IsEmpty()) {
     NS_ENSURE_TRUE(ParseTypeAttribute(type, &version), false);
--- a/content/canvas/src/CanvasRenderingContext2D.cpp
+++ b/content/canvas/src/CanvasRenderingContext2D.cpp
@@ -1079,19 +1079,19 @@ CanvasRenderingContext2D::GetImageBuffer
   *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
 }
 
 NS_IMETHODIMP
 CanvasRenderingContext2D::GetInputStream(const char *aMimeType,
                                          const PRUnichar *aEncoderOptions,
                                          nsIInputStream **aStream)
 {
-  uint8_t* imageBuffer = nullptr;
+  nsAutoArrayPtr<uint8_t> imageBuffer;
   int32_t format = 0;
-  GetImageBuffer(&imageBuffer, &format);
+  GetImageBuffer(getter_Transfers(imageBuffer), &format);
   if (!imageBuffer) {
     return NS_ERROR_FAILURE;
   }
 
   nsCString enccid("@mozilla.org/image/encoder;2?type=");
   enccid += aMimeType;
   nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
   if (!encoder) {
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -789,19 +789,19 @@ NS_IMETHODIMP
 WebGLContext::GetInputStream(const char* aMimeType,
                              const PRUnichar* aEncoderOptions,
                              nsIInputStream **aStream)
 {
     NS_ASSERTION(gl, "GetInputStream on invalid context?");
     if (!gl)
         return NS_ERROR_FAILURE;
 
-    uint8_t* imageBuffer = nullptr;
+    nsAutoArrayPtr<uint8_t> imageBuffer;
     int32_t format = 0;
-    GetImageBuffer(&imageBuffer, &format);
+    GetImageBuffer(getter_Transfers(imageBuffer), &format);
     if (!imageBuffer) {
         return NS_ERROR_FAILURE;
     }
 
     nsCString enccid("@mozilla.org/image/encoder;2?type=");
     enccid += aMimeType;
     nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
     if (!encoder) {
--- a/content/html/content/public/nsIHTMLCollection.h
+++ b/content/html/content/public/nsIHTMLCollection.h
@@ -19,18 +19,18 @@ class ErrorResult;
 
 namespace dom {
 class Element;
 } // namespace dom
 } // namespace mozilla
 
 // IID for the nsIHTMLCollection interface
 #define NS_IHTMLCOLLECTION_IID \
-{ 0x5643235d, 0x9a72, 0x4b6a, \
- { 0xa6, 0x0c, 0x64, 0x63, 0x72, 0xb7, 0x53, 0x4a } }
+{ 0x4e169191, 0x5196, 0x4e17, \
+  { 0xa4, 0x79, 0xd5, 0x35, 0x0b, 0x5b, 0x0a, 0xcd } }
 
 /**
  * An internal interface
  */
 class nsIHTMLCollection : public nsIDOMHTMLCollection
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IHTMLCOLLECTION_IID)
@@ -71,18 +71,18 @@ public:
   }
   virtual mozilla::dom::Element*
   GetFirstNamedElement(const nsAString& aName, bool& aFound) = 0;
 
   virtual void GetSupportedNames(nsTArray<nsString>& aNames) = 0;
 
   JSObject* GetWrapperPreserveColor()
   {
-    nsWrapperCache* cache;
-    CallQueryInterface(this, &cache);
-    return cache->GetWrapperPreserveColor();
+    return GetWrapperPreserveColorInternal();
   }
-  virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> scope) = 0;
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) = 0;
+protected:
+  virtual JSObject* GetWrapperPreserveColorInternal() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIHTMLCollection, NS_IHTMLCOLLECTION_IID)
 
 #endif /* nsIHTMLCollection_h___ */
--- a/content/html/content/src/HTMLFormControlsCollection.h
+++ b/content/html/content/src/HTMLFormControlsCollection.h
@@ -74,18 +74,25 @@ public:
    * elements.
    *
    * @param aControls The list of sorted controls[out].
    * @return NS_OK or NS_ERROR_OUT_OF_MEMORY.
    */
   nsresult GetSortedControls(nsTArray<nsGenericHTMLFormElement*>& aControls) const;
 
   // nsWrapperCache
-  virtual JSObject*
-  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+  using nsWrapperCache::GetWrapperPreserveColor;
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+protected:
+  virtual JSObject* GetWrapperPreserveColorInternal() MOZ_OVERRIDE
+  {
+    return nsWrapperCache::GetWrapperPreserveColor();
+  }
+public:
 
   static bool ShouldBeInElements(nsIFormControl* aFormControl);
 
   HTMLFormElement* mForm;  // WEAK - the form owns me
 
   nsTArray<nsGenericHTMLFormElement*> mElements;  // Holds WEAK references - bug 36639
 
   // This array holds on to all form controls that are not contained
--- a/content/html/content/src/HTMLOptionsCollection.h
+++ b/content/html/content/src/HTMLOptionsCollection.h
@@ -37,18 +37,24 @@ class HTMLOptionsCollection : public nsI
 public:
   HTMLOptionsCollection(HTMLSelectElement* aSelect);
   virtual ~HTMLOptionsCollection();
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
   // nsWrapperCache
   using nsWrapperCache::GetWrapperPreserveColor;
-  virtual JSObject* WrapObject(JSContext* cx,
-                               JS::Handle<JSObject*> scope) MOZ_OVERRIDE;
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+protected:
+  virtual JSObject* GetWrapperPreserveColorInternal() MOZ_OVERRIDE
+  {
+    return nsWrapperCache::GetWrapperPreserveColor();
+  }
+public:
 
   // nsIDOMHTMLOptionsCollection interface
   NS_DECL_NSIDOMHTMLOPTIONSCOLLECTION
 
   // nsIDOMHTMLCollection interface, all its methods are defined in
   // nsIDOMHTMLOptionsCollection
 
   virtual Element* GetElementAt(uint32_t aIndex);
--- a/content/html/content/src/HTMLPropertiesCollection.h
+++ b/content/html/content/src/HTMLPropertiesCollection.h
@@ -50,19 +50,26 @@ class HTMLPropertiesCollection : public 
                                  public nsWrapperCache
 {
   friend class PropertyNodeList;
   friend class PropertyStringList;
 public:
   HTMLPropertiesCollection(nsGenericHTMLElement* aRoot);
   virtual ~HTMLPropertiesCollection();
 
+  // nsWrapperCache
   using nsWrapperCache::GetWrapperPreserveColor;
-  virtual JSObject* WrapObject(JSContext *cx,
-                               JS::Handle<JSObject*> scope) MOZ_OVERRIDE;
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+protected:
+  virtual JSObject* GetWrapperPreserveColorInternal() MOZ_OVERRIDE
+  {
+    return nsWrapperCache::GetWrapperPreserveColor();
+  }
+public:
 
   virtual Element* GetElementAt(uint32_t aIndex);
 
   void SetDocument(nsIDocument* aDocument);
   nsINode* GetParentObject() MOZ_OVERRIDE;
 
   virtual Element*
   GetFirstNamedElement(const nsAString& aName, bool& aFound) MOZ_OVERRIDE
--- a/content/html/content/src/HTMLTableElement.cpp
+++ b/content/html/content/src/HTMLTableElement.cpp
@@ -45,23 +45,25 @@ public:
   GetFirstNamedElement(const nsAString& aName, bool& aFound) MOZ_OVERRIDE;
   virtual void GetSupportedNames(nsTArray<nsString>& aNames);
 
   NS_IMETHOD    ParentDestroyed();
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TableRowsCollection)
 
   // nsWrapperCache
-  virtual JSObject* WrapObject(JSContext *cx,
-                               JS::Handle<JSObject*> scope) MOZ_OVERRIDE
+  using nsWrapperCache::GetWrapperPreserveColor;
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+protected:
+  virtual JSObject* GetWrapperPreserveColorInternal() MOZ_OVERRIDE
   {
-    return mozilla::dom::HTMLCollectionBinding::Wrap(cx, scope, this);
+    return nsWrapperCache::GetWrapperPreserveColor();
   }
 
-protected:
   // Those rows that are not in table sections
   HTMLTableElement* mParent;
   nsRefPtr<nsContentList> mOrphanRows;  
 };
 
 
 TableRowsCollection::TableRowsCollection(HTMLTableElement *aParent)
   : mParent(aParent)
@@ -77,16 +79,23 @@ TableRowsCollection::TableRowsCollection
 TableRowsCollection::~TableRowsCollection()
 {
   // we do NOT have a ref-counted reference to mParent, so do NOT
   // release it!  this is to avoid circular references.  The
   // instantiator who provided mParent is responsible for managing our
   // reference for us.
 }
 
+JSObject*
+TableRowsCollection::WrapObject(JSContext* aCx,
+                                JS::Handle<JSObject*> aScope)
+{
+  return HTMLCollectionBinding::Wrap(aCx, aScope, this);
+}
+
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(TableRowsCollection, mOrphanRows)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(TableRowsCollection)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(TableRowsCollection)
 
 NS_INTERFACE_TABLE_HEAD(TableRowsCollection)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_TABLE2(TableRowsCollection, nsIHTMLCollection,
                       nsIDOMHTMLCollection)
--- a/content/html/document/src/nsHTMLContentSink.cpp
+++ b/content/html/document/src/nsHTMLContentSink.cpp
@@ -701,34 +701,27 @@ NS_IMPL_ADDREF_INHERITED(HTMLContentSink
 NS_IMPL_RELEASE_INHERITED(HTMLContentSink, nsContentSink)
 
 static bool
 IsScriptEnabled(nsIDocument *aDoc, nsIDocShell *aContainer)
 {
   NS_ENSURE_TRUE(aDoc && aContainer, true);
 
   nsCOMPtr<nsIScriptGlobalObject> globalObject =
-    do_QueryInterface(aDoc->GetWindow());
+    do_QueryInterface(aDoc->GetInnerWindow());
 
   // Getting context is tricky if the document hasn't had its
   // GlobalObject set yet
   if (!globalObject) {
     globalObject = aContainer->GetScriptGlobalObject();
-    NS_ENSURE_TRUE(globalObject, true);
   }
 
-  nsIScriptContext *scriptContext = globalObject->GetContext();
-  NS_ENSURE_TRUE(scriptContext, true);
-  JSContext *cx = scriptContext->GetNativeContext();
-  NS_ENSURE_TRUE(cx, true);
-
-  bool enabled = true;
-  nsContentUtils::GetSecurityManager()->
-    CanExecuteScripts(cx, aDoc->NodePrincipal(), &enabled);
-  return enabled;
+  NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), true);
+  return nsContentUtils::GetSecurityManager()->
+           ScriptAllowed(globalObject->GetGlobalJSObject());
 }
 
 nsresult
 HTMLContentSink::Init(nsIDocument* aDoc,
                       nsIURI* aURI,
                       nsISupports* aContainer,
                       nsIChannel* aChannel)
 {
--- a/content/xbl/src/nsXBLBinding.cpp
+++ b/content/xbl/src/nsXBLBinding.cpp
@@ -1075,34 +1075,22 @@ nsXBLBinding::AllowScripts()
     return false;
   }
 
   nsIDocument* doc = mBoundElement ? mBoundElement->OwnerDoc() : nullptr;
   if (!doc) {
     return false;
   }
 
-  nsCOMPtr<nsIScriptGlobalObject> global = do_QueryInterface(doc->GetWindow());
-  if (!global) {
+  nsCOMPtr<nsIScriptGlobalObject> global = do_QueryInterface(doc->GetInnerWindow());
+  if (!global || !global->GetGlobalJSObject()) {
     return false;
   }
 
-  nsCOMPtr<nsIScriptContext> context = global->GetContext();
-  if (!context) {
-    return false;
-  }
-  
-  AutoPushJSContext cx(context->GetNativeContext());
-
-  nsCOMPtr<nsIDocument> ourDocument =
-    mPrototypeBinding->XBLDocumentInfo()->GetDocument();
-  bool canExecute;
-  nsresult rv =
-    mgr->CanExecuteScripts(cx, ourDocument->NodePrincipal(), &canExecute);
-  return NS_SUCCEEDED(rv) && canExecute;
+  return mgr->ScriptAllowed(global->GetGlobalJSObject());
 }
 
 nsXBLBinding*
 nsXBLBinding::RootBinding()
 {
   if (mNextBinding)
     return mNextBinding->RootBinding();
 
--- a/content/xbl/src/nsXBLProtoImplMethod.cpp
+++ b/content/xbl/src/nsXBLProtoImplMethod.cpp
@@ -323,22 +323,22 @@ nsXBLProtoImplAnonymousMethod::Execute(n
   // the scope chain of the resulting function (for backwards compat to the
   // days when this was an event handler).
   JS::Rooted<JSObject*> method(cx, ::JS_CloneFunctionObject(cx, GetCompiledMethod(), thisObject));
   if (!method)
     return NS_ERROR_OUT_OF_MEMORY;
 
   // Now call the method
 
-  // Check whether it's OK to call the method.
-  rv = nsContentUtils::GetSecurityManager()->CheckFunctionAccess(cx, method,
-                                                                 thisObject);
+  // Check whether script is enabled.
+  bool scriptAllowed = nsContentUtils::GetSecurityManager()->
+                         ScriptAllowed(js::GetGlobalForObjectCrossCompartment(method));
 
   bool ok = true;
-  if (NS_SUCCEEDED(rv)) {
+  if (scriptAllowed) {
     JS::Rooted<JS::Value> retval(cx);
     ok = ::JS_CallFunctionValue(cx, thisObject, OBJECT_TO_JSVAL(method),
                                 0 /* argc */, nullptr /* argv */, retval.address());
   }
 
   if (!ok) {
     // If a constructor or destructor threw an exception, it doesn't stop
     // anything else.  We just report it.  Note that we need to set aside the
--- a/content/xul/document/src/XULDocument.cpp
+++ b/content/xul/document/src/XULDocument.cpp
@@ -3655,28 +3655,24 @@ XULDocument::ExecuteScript(nsIScriptCont
                            JS::Handle<JSScript*> aScriptObject)
 {
     NS_PRECONDITION(aScriptObject != nullptr && aContext != nullptr, "null ptr");
     if (! aScriptObject || ! aContext)
         return NS_ERROR_NULL_POINTER;
 
     NS_ENSURE_TRUE(mScriptGlobalObject, NS_ERROR_NOT_INITIALIZED);
 
-    if (!aContext->GetScriptsEnabled())
-        return NS_OK;
-
     // Execute the precompiled script with the given version
     nsAutoMicroTask mt;
     JSContext *cx = aContext->GetNativeContext();
     AutoCxPusher pusher(cx);
     JS::Rooted<JSObject*> global(cx, mScriptGlobalObject->GetGlobalJSObject());
-    // XXXkhuey can this ever be null?
-    if (global) {
-      JS::ExposeObjectToActiveJS(global);
-    }
+    NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
+    NS_ENSURE_TRUE(nsContentUtils::GetSecurityManager()->ScriptAllowed(global), NS_OK);
+    JS::ExposeObjectToActiveJS(global);
     xpc_UnmarkGrayScript(aScriptObject);
     JSAutoCompartment ac(cx, global);
     JS::Rooted<JS::Value> unused(cx);
     if (!JS_ExecuteScript(cx, global, aScriptObject, unused.address()))
         nsJSUtils::ReportPendingException(cx);
     return NS_OK;
 }
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -757,16 +757,17 @@ nsDocShell::nsDocShell():
     mAllowAuth(true),
     mAllowKeywordFixup(false),
     mIsOffScreenBrowser(false),
     mIsActive(true),
     mIsAppTab(false),
     mUseGlobalHistory(false),
     mInPrivateBrowsing(false),
     mDeviceSizeIsPageSize(false),
+    mCanExecuteScripts(false),
     mFiredUnloadEvent(false),
     mEODForCurrentDocument(false),
     mURIResultedInDocument(false),
     mIsBeingDestroyed(false),
     mIsExecutingOnLoadHandler(false),
     mIsPrintingOrPP(false),
     mSavingOldViewer(false),
 #ifdef DEBUG
@@ -2072,29 +2073,24 @@ nsDocShell::SetAllowPlugins(bool aAllowP
 }
 
 NS_IMETHODIMP
 nsDocShell::GetAllowJavascript(bool * aAllowJavascript)
 {
     NS_ENSURE_ARG_POINTER(aAllowJavascript);
 
     *aAllowJavascript = mAllowJavascript;
-    if (!mAllowJavascript) {
-        return NS_OK;
-    }
-
-    bool unsafe;
-    *aAllowJavascript = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetAllowJavascript(bool aAllowJavascript)
 {
     mAllowJavascript = aAllowJavascript;
+    RecomputeCanExecuteScripts();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing)
 {
     NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing);
     
@@ -2818,16 +2814,58 @@ nsDocShell::GetParent(nsIDocShellTreeIte
 
 already_AddRefed<nsDocShell>
 nsDocShell::GetParentDocshell()
 {
     nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(GetAsSupports(mParent));
     return docshell.forget().downcast<nsDocShell>();
 }
 
+void
+nsDocShell::RecomputeCanExecuteScripts()
+{
+    bool old = mCanExecuteScripts;
+    nsRefPtr<nsDocShell> parent = GetParentDocshell();
+
+    // If we have no tree owner, that means that we've been detached from the
+    // docshell tree (this is distinct from having no parent dochshell, which
+    // is the case for root docshells). In that case, don't allow script.
+    if (!mTreeOwner) {
+        mCanExecuteScripts = false;
+    // If scripting has been explicitly disabled on our docshell, we're done.
+    } else if (!mAllowJavascript) {
+        mCanExecuteScripts = false;
+    // If we have a parent, inherit.
+    } else if (parent) {
+        mCanExecuteScripts = parent->mCanExecuteScripts;
+    // Otherwise, we're the root of the tree, and we haven't explicitly disabled
+    // script. Allow.
+    } else {
+        mCanExecuteScripts = true;
+    }
+
+    // Inform our active DOM window.
+    //
+    // This will pass the outer, which will be in the scope of the active inner.
+    if (mScriptGlobal && mScriptGlobal->GetGlobalJSObject()) {
+        xpc::Scriptability& scriptability =
+          xpc::Scriptability::Get(mScriptGlobal->GetGlobalJSObject());
+        scriptability.SetDocShellAllowsScript(mCanExecuteScripts);
+    }
+
+    // If our value has changed, our children might be affected. Recompute their
+    // value as well.
+    if (old != mCanExecuteScripts) {
+        nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
+        while (iter.HasMore()) {
+            static_cast<nsDocShell*>(iter.GetNext())->RecomputeCanExecuteScripts();
+        }
+    }
+}
+
 nsresult
 nsDocShell::SetDocLoaderParent(nsDocLoader * aParent)
 {
     nsDocLoader::SetDocLoaderParent(aParent);
 
     // Curse ambiguous nsISupports inheritance!
     nsISupports* parent = GetAsSupports(aParent);
 
@@ -2887,16 +2925,20 @@ nsDocShell::SetDocLoaderParent(nsDocLoad
         NS_SUCCEEDED(parentAsLoadContext->GetUsePrivateBrowsing(&value)))
     {
         SetPrivateBrowsing(value);
     }
     
     nsCOMPtr<nsIURIContentListener> parentURIListener(do_GetInterface(parent));
     if (parentURIListener)
         mContentListener->SetParentContentListener(parentURIListener);
+
+    // Our parent has changed. Recompute scriptability.
+    RecomputeCanExecuteScripts();
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetSameTypeParent(nsIDocShellTreeItem ** aParent)
 {
     NS_ENSURE_ARG_POINTER(aParent);
     *aParent = nullptr;
@@ -3441,16 +3483,26 @@ nsDocShell::SetTreeOwner(nsIDocShellTree
         nsCOMPtr<nsIDocShellTreeItem> child = do_QueryObject(iter.GetNext());
         NS_ENSURE_TRUE(child, NS_ERROR_FAILURE);
         int32_t childType = ~mItemType; // Set it to not us in case the get fails
         child->GetItemType(&childType); // We don't care if this fails, if it does we won't set the owner
         if (childType == mItemType)
             child->SetTreeOwner(aTreeOwner);
     }
 
+    // Our tree owner has changed. Recompute scriptability.
+    //
+    // Note that this is near-redundant with the recomputation in
+    // SetDocLoaderParent(), but not so for the root DocShell, where the call to
+    // SetTreeOwner() happens after the initial AddDocLoaderAsChildOfRoot(),
+    // and we never set another parent. Given that this is neither expensive nor
+    // performance-critical, let's be safe and unconditionally recompute this
+    // state whenever dependent state changes.
+    RecomputeCanExecuteScripts();
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetChildOffset(uint32_t aChildOffset)
 {
     mChildOffset = aChildOffset;
     return NS_OK;
@@ -12652,30 +12704,17 @@ nsDocShell::GetPrintPreview(nsIWebBrowse
 
 #ifdef DEBUG
 unsigned long nsDocShell::gNumberOfDocShells = 0;
 #endif
 
 NS_IMETHODIMP
 nsDocShell::GetCanExecuteScripts(bool *aResult)
 {
-  NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = false; // disallow by default
-
-  nsRefPtr<nsDocShell> docshell = this;
-  do {
-      nsresult rv = docshell->GetAllowJavascript(aResult);
-      if (NS_FAILED(rv)) return rv;
-      if (!*aResult) {
-          return NS_OK;
-      }
-
-      docshell = docshell->GetParentDocshell();
-  } while (docshell);
-
+  *aResult = mCanExecuteScripts;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetIsApp(uint32_t aOwnAppId)
 {
     mOwnOrContainingAppId = aOwnAppId;
     if (aOwnAppId != nsIScriptSecurityManager::NO_APP_ID &&
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -808,16 +808,22 @@ protected:
     bool                       mAllowKeywordFixup;
     bool                       mIsOffScreenBrowser;
     bool                       mIsActive;
     bool                       mIsAppTab;
     bool                       mUseGlobalHistory;
     bool                       mInPrivateBrowsing;
     bool                       mDeviceSizeIsPageSize;
 
+    // Because scriptability depends on the mAllowJavascript values of our
+    // ancestors, we cache the effective scriptability and recompute it when
+    // it might have changed;
+    bool                       mCanExecuteScripts;
+    void RecomputeCanExecuteScripts();
+
     // This boolean is set to true right before we fire pagehide and generally
     // unset when we embed a new content viewer.  While it's true no navigation
     // is allowed in this docshell.
     bool                       mFiredUnloadEvent;
 
     // this flag is for bug #21358. a docshell may load many urls
     // which don't result in new documents being created (i.e. a new
     // content viewer) we want to make sure we don't call a on load
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -565,17 +565,17 @@ interface nsIDocShell : nsIDocShellTreeI
    */
   readonly attribute nsIWebBrowserPrint printPreview;
 
   /**
    * Whether this docshell can execute scripts based on its hierarchy.
    * The rule of thumb here is that we disable js if this docshell or any
    * of its parents disallow scripting.
    */
-  readonly attribute boolean canExecuteScripts;
+  [infallible] readonly attribute boolean canExecuteScripts;
 
   /**
    * Sets whether a docshell is active. An active docshell is one that is
    * visible, and thus is not a good candidate for certain optimizations
    * like image frame discarding. Docshells are active unless told otherwise.
    */
   attribute boolean isActive;
 
index a6e9a68a8514fb48f89a2eedf8b86a6854ca4d0c..ed8234dbc4458834fda75b5184bbae56eef12bd2
GIT binary patch
literal 1000
zc$^FHW@Zs#U|`^2_!ATC9Gbgx%{Cw}hLM4Rhe3uRF)ukIzo=L*qa-&cG=!6ZIXgHh
z2!u;3xEUB(z5<njb<dsP&3DLv$K|_i>r`F^MI{!ehC6qTs<ydJQQ>lHy?uGohsez`
z)i$3~a-Lq2%$d1nB}2fRlAIIrw<fGxv3X5EI<wh9JGS_n;SGfW9uIQXwp{9Tb)5Yp
zLfhM^`r+;u8x!qLMZfw`<)PR0a_;tp@hvCjT~=x{_E-N|XXG5~!}V!J*x9#RZoS!d
zy7Z+%vJ6k9K=bEAVjf+RrnM7melTkVCF!%in){*qYv%lB{`t2)djGWj;bneRH~{W<
zf!JW@OTpW(9s}~GLH(YYR+N~VisbixpuhVu{azb*TByl@XU}I*S-lfIH+CQU=e;1X
zYpPN1$B*BG7W163kTXA-RxX_J_^MV0=hjm#4H=76Q~l)Di#Qnh^j@eib}?PB-0mG)
z`9j}^FKwc}G*!D+GW?W0?eNKK#oRis>(TAMW~{#vvnJ}zR@L|^KbHs}v)Yo9dxC{W
zn5|6djLS8NjY)I5A4sozqinm#M{^rz-#xB*Kdtu7j@$kFo%Y_64L2gDZ2oR@l>KDs
z*TWv-*UYT;C>DROKav*ab*ptM+li88->+V>-XtD?k`7KTv0Ksy<Q-ywghf$mT2X3o
z2ElZo+jo%fkb;2A`x@87OJ`+#TywgvQC--c;j~mouH&w#ztt-Zw$H7c8ChV%$I;j0
zdeP*-JoZB_-(5B{hn_Q)==6O&<MF(C`v0$OGBRp+t(`GL?b@e;U2*4bNtwAX<6m8x
z#9lnVCMD)i%Hd0{lZ|d4{jk6{di5>c6<(eq3r+<WaDVGon)tP|>Rg@SgZtf&*aN&7
zndF#prEUoZ5CAe6mNbG`sA-)Q;zu<9A)APqW)LPeH2wjSs40jQl7jG<h#B(8CUP(_
Xpv40&H?p#UbTI?rHz0k73B&^c?qg`D
--- a/dom/apps/src/PermissionsTable.jsm
+++ b/dom/apps/src/PermissionsTable.jsm
@@ -297,16 +297,27 @@ this.PermissionsTable =  { geolocation: 
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "audio-capture": {
                              app: PROMPT_ACTION,
                              privileged: PROMPT_ACTION,
                              certified: PROMPT_ACTION
                            },
+                           "nfc": {
+                             app: DENY_ACTION,
+                             privileged: DENY_ACTION,
+                             certified: ALLOW_ACTION,
+                             access: ["read", "write"]
+                           },
+                           "nfc-manager": {
+                             app: DENY_ACTION,
+                             privileged: DENY_ACTION,
+                             certified: ALLOW_ACTION
+                           },
                          };
 
 /**
  * Append access modes to the permission name as suffixes.
  *   e.g. permission name 'contacts' with ['read', 'write'] =
  *   ['contacts-read', contacts-write']
  * @param string aPermName
  * @param array aAccess
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1788,16 +1788,28 @@ Navigator::HasBluetoothSupport(JSContext
 bool
 Navigator::HasFMRadioSupport(JSContext* /* unused */, JSObject* aGlobal)
 {
   nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
   return win && CheckPermission(win, "fmradio");
 }
 #endif // MOZ_B2G_FM
 
+#ifdef MOZ_NFC
+/* static */
+bool
+Navigator::HasNfcSupport(JSContext* /* unused */, JSObject* aGlobal)
+{
+  nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
+  return win && (CheckPermission(win, "nfc-read") ||
+                 CheckPermission(win, "nfc-write"));
+}
+#endif // MOZ_NFC
+
+
 #ifdef MOZ_TIME_MANAGER
 /* static */
 bool
 Navigator::HasTimeSupport(JSContext* /* unused */, JSObject* aGlobal)
 {
   nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
   return win && CheckPermission(win, "time");
 }
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -282,16 +282,19 @@ public:
                                    JSObject* aGlobal);
 #endif // MOZ_B2G_RIL
 #ifdef MOZ_B2G_BT
   static bool HasBluetoothSupport(JSContext* /* unused */, JSObject* aGlobal);
 #endif // MOZ_B2G_BT
 #ifdef MOZ_B2G_FM
   static bool HasFMRadioSupport(JSContext* /* unused */, JSObject* aGlobal);
 #endif // MOZ_B2G_FM
+#ifdef MOZ_NFC
+  static bool HasNfcSupport(JSContext* /* unused */, JSObject* aGlobal);
+#endif // MOZ_NFC
 #ifdef MOZ_TIME_MANAGER
   static bool HasTimeSupport(JSContext* /* unused */, JSObject* aGlobal);
 #endif // MOZ_TIME_MANAGER
 #ifdef MOZ_MEDIA_NAVIGATOR
   static bool HasUserMediaSupport(JSContext* /* unused */,
                                   JSObject* /* unused */);
 #endif // MOZ_MEDIA_NAVIGATOR
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -134,16 +134,17 @@
 #include "nsITimedChannel.h"
 #include "nsServiceManagerUtils.h"
 #ifdef MOZ_XUL
 #include "nsIDOMXULControlElement.h"
 #include "nsMenuPopupFrame.h"
 #endif
 #include "nsIDOMCustomEvent.h"
 #include "nsIFrameRequestCallback.h"
+#include "nsIJARChannel.h"
 
 #include "xpcprivate.h"
 
 #ifdef NS_PRINTING
 #include "nsIPrintSettings.h"
 #include "nsIPrintSettingsService.h"
 #include "nsIWebBrowserPrint.h"
 #endif
@@ -2056,16 +2057,19 @@ WindowStateHolder::WindowStateHolder(nsG
   : mInnerWindow(aWindow)
 {
   NS_PRECONDITION(aWindow, "null window");
   NS_PRECONDITION(aWindow->IsInnerWindow(), "Saving an outer window");
 
   mInnerWindowHolder = aHolder;
 
   aWindow->SuspendTimeouts();
+
+  // When a global goes into the bfcache, we disable script.
+  xpc::Scriptability::Get(aWindow->mJSObject).SetDocShellAllowsScript(false);
 }
 
 WindowStateHolder::~WindowStateHolder()
 {
   if (mInnerWindow) {
     // This window was left in the bfcache and is now going away. We need to
     // free it up.
     // Note that FreeInnerObjects may already have been called on the
@@ -2454,16 +2458,20 @@ nsGlobalWindow::SetNewDocument(nsIDocume
           scope->mWaiverWrapperMap->Reparent(cx, newInnerWindow->mJSObject);
         }
       }
     }
 
     // Enter the new global's compartment.
     JSAutoCompartment ac(cx, mJSObject);
 
+    // Set scriptability based on the state of the docshell.
+    bool allow = GetDocShell()->GetCanExecuteScripts();
+    xpc::Scriptability::Get(mJSObject).SetDocShellAllowsScript(allow);
+
     // If we created a new inner window above, we need to do the last little bit
     // of initialization now that the dust has settled.
     if (createdInnerWindow) {
       nsIXPConnect *xpc = nsContentUtils::XPConnect();
       nsCOMPtr<nsIXPConnectWrappedNative> wrapper;
       nsresult rv = xpc->GetWrappedNativeOfJSObject(cx, newInnerWindow->mJSObject,
                                                     getter_AddRefs(wrapper));
       NS_ENSURE_SUCCESS(rv, rv);
@@ -2536,16 +2544,24 @@ nsGlobalWindow::SetNewDocument(nsIDocume
       newInnerWindow->InnerSetNewDocument(aDocument);
 
       // Initialize DOM classes etc on the inner window.
       JS::Rooted<JSObject*> obj(cx, newInnerWindow->mJSObject);
       rv = mContext->InitClasses(obj);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
+    // If the document comes from a JAR, check if the channel was determined
+    // to be unsafe. If so, permanently disable script on the compartment by
+    // calling Block() and throwing away the key.
+    nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aDocument->GetChannel());
+    if (jarChannel && jarChannel->GetIsUnsafe()) {
+      xpc::Scriptability::Get(newInnerWindow->mJSObject).Block();
+    }
+
     if (mArguments) {
       newInnerWindow->DefineArgumentsProperty(mArguments);
       mArguments = nullptr;
     }
 
     // Give the new inner window our chrome event handler (since it
     // doesn't have one).
     newInnerWindow->mChromeEventHandler = mChromeEventHandler;
@@ -3169,29 +3185,16 @@ void
 nsGlobalWindow::PoisonOuterWindowProxy(JSObject *aObject)
 {
   MOZ_ASSERT(IsOuterWindow());
   if (aObject == mJSObject) {
     mJSObject.setToCrashOnTouch();
   }
 }
 
-void
-nsGlobalWindow::SetScriptsEnabled(bool aEnabled, bool aFireTimeouts)
-{
-  FORWARD_TO_INNER_VOID(SetScriptsEnabled, (aEnabled, aFireTimeouts));
-
-  if (aEnabled && aFireTimeouts) {
-    // Scripts are enabled (again?) on this context, run timeouts that
-    // fired on this context while scripts were disabled.
-    void (nsGlobalWindow::*run)() = &nsGlobalWindow::RunTimeout;
-    NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, run));
-  }
-}
-
 nsresult
 nsGlobalWindow::SetArguments(nsIArray *aArguments)
 {
   FORWARD_TO_OUTER(SetArguments, (aArguments),
                    NS_ERROR_NOT_INITIALIZED);
   nsresult rv;
 
   // Historically, we've used the same machinery to handle openDialog arguments
@@ -8937,21 +8940,24 @@ nsGlobalWindow::RemoveEventListener(cons
   return NS_OK;
 }
 
 NS_IMPL_REMOVE_SYSTEM_EVENT_LISTENER(nsGlobalWindow)
 
 NS_IMETHODIMP
 nsGlobalWindow::DispatchEvent(nsIDOMEvent* aEvent, bool* aRetVal)
 {
-  MOZ_ASSERT(!IsInnerWindow() || IsCurrentInnerWindow(),
-             "We should only fire events on the current inner window.");
-
   FORWARD_TO_INNER(DispatchEvent, (aEvent, aRetVal), NS_OK);
 
+  if (!IsCurrentInnerWindow()) {
+    NS_WARNING("DispatchEvent called on non-current inner window, dropping. "
+               "Please check the window in the caller instead.");
+    return NS_ERROR_FAILURE;
+  }
+
   if (!mDoc) {
     return NS_ERROR_FAILURE;
   }
 
   // Obtain a presentation shell
   nsIPresShell *shell = mDoc->GetShell();
   nsRefPtr<nsPresContext> presContext;
   if (shell) {
@@ -11819,30 +11825,16 @@ nsGlobalWindow::RunTimeout(nsTimeout *aT
     nsCOMPtr<nsIScriptContext> scx = GetContextInternal();
 
     if (!scx) {
       // No context means this window was closed or never properly
       // initialized for this language.
       continue;
     }
 
-    // The "scripts disabled" concept is still a little vague wrt
-    // multiple languages.  Prepare for the day when languages can be
-    // disabled independently of the other languages...
-    if (!scx->GetScriptsEnabled()) {
-      // Scripts were enabled once in this window (unless aTimeout ==
-      // nullptr) but now scripts are disabled (we might be in
-      // print-preview, for instance), this means we shouldn't run any
-      // timeouts at this point.
-      //
-      // If scripts are enabled for this language in this window again
-      // we'll fire the timeouts that are due at that point.
-      continue;
-    }
-
     // This timeout is good to run
     ++timeoutsRan;
     bool timeout_was_cleared = RunTimeoutHandler(timeout, scx);
 
     if (timeout_was_cleared) {
       // The running timeout's window was cleared, this means that
       // ClearAllTimeouts() was called from a *nested* call, possibly
       // through a timeout that fired while a modal (to this window)
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -349,17 +349,16 @@ public:
   void TraceGlobalJSObject(JSTracer* aTrc);
 
   virtual nsresult EnsureScriptEnvironment();
 
   virtual nsIScriptContext *GetScriptContext();
 
   void PoisonOuterWindowProxy(JSObject *aObject);
   virtual void OnFinalize(JSObject* aObject);
-  virtual void SetScriptsEnabled(bool aEnabled, bool aFireTimeouts);
 
   virtual bool IsBlackForCC();
 
   // nsIScriptObjectPrincipal
   virtual nsIPrincipal* GetPrincipal();
 
   // nsIDOMWindow
   NS_DECL_NSIDOMWINDOW
--- a/dom/base/nsIScriptContext.h
+++ b/dom/base/nsIScriptContext.h
@@ -22,18 +22,18 @@ class nsIArray;
 class nsIVariant;
 class nsIObjectInputStream;
 class nsIObjectOutputStream;
 class nsIScriptObjectPrincipal;
 class nsIDOMWindow;
 class nsIURI;
 
 #define NS_ISCRIPTCONTEXT_IID \
-{ 0xf3859ce7, 0x7551, 0x4760, \
-  { 0x84, 0x29, 0x64, 0x4f, 0x26, 0x1e, 0xdb, 0x91 } }
+{ 0x513c2c1a, 0xf4f1, 0x44da, \
+  { 0x8e, 0x38, 0xf4, 0x0c, 0x30, 0x9a, 0x5d, 0xef } }
 
 /* This MUST match JSVERSION_DEFAULT.  This version stuff if we don't
    know what language we have is a little silly... */
 #define SCRIPTVERSION_DEFAULT JSVERSION_DEFAULT
 
 class nsIOffThreadScriptReceiver;
 
 /**
@@ -123,22 +123,16 @@ public:
   /**
    * For garbage collected systems, do a synchronous collection pass.
    * May be a no-op on other systems
    *
    * @return NS_OK if the method is successful
    */
   virtual void GC(JS::gcreason::Reason aReason) = 0;
 
-  /**
-   * Called to disable/enable script execution in this context.
-   */
-  virtual bool GetScriptsEnabled() = 0;
-  virtual void SetScriptsEnabled(bool aEnabled, bool aFireTimeouts) = 0;
-
   // SetProperty is suspect and jst believes should not be needed.  Currenly
   // used only for "arguments".
   virtual nsresult SetProperty(JS::Handle<JSObject*> aTarget,
                                const char* aPropName, nsISupports* aVal) = 0;
   /** 
    * Called to set/get information if the script context is
    * currently processing a script tag
    */
--- a/dom/base/nsIScriptGlobalObject.h
+++ b/dom/base/nsIScriptGlobalObject.h
@@ -22,18 +22,18 @@ class nsIScriptGlobalObject;
 // aStatus will be filled in with the status.
 bool
 NS_HandleScriptError(nsIScriptGlobalObject *aScriptGlobal,
                      mozilla::InternalScriptErrorEvent *aErrorEvent,
                      nsEventStatus *aStatus);
 
 
 #define NS_ISCRIPTGLOBALOBJECT_IID \
-{ 0x214fa2f6, 0xcc0c, 0x42cf, \
-  { 0x98, 0x4b, 0x45, 0xf5, 0x73, 0x9c, 0x6b, 0x73 } }
+{ 0xa6c0bfae, 0x8be4, 0x4747, \
+  { 0xaf, 0x1a, 0xe3, 0xf0, 0x3f, 0xb6, 0x0e, 0xb8 } }
 
 /**
  * The global object which keeps a script context for each supported script
  * language. This often used to store per-window global state.
  * This is a heavyweight interface implemented only by DOM globals, and
  * it might go away some time in the future.
  */
 
@@ -66,21 +66,16 @@ public:
    * part of its GC process.  By the time this call is made, the
    * nsIScriptContext for the language has probably already been removed.
    * After this call, the passed object is dead - which should generally be the
    * same object the global is using for a global for that language.
    */
   virtual void OnFinalize(JSObject* aObject) = 0;
 
   /**
-   * Called to enable/disable scripts.
-   */
-  virtual void SetScriptsEnabled(bool aEnabled, bool aFireTimeouts) = 0;
-
-  /**
    * Handle a script error.  Generally called by a script context.
    */
   virtual nsresult HandleScriptError(
                      mozilla::InternalScriptErrorEvent *aErrorEvent,
                      nsEventStatus *aEventStatus) {
     NS_ENSURE_STATE(NS_HandleScriptError(this, aErrorEvent, aEventStatus));
     return NS_OK;
   }
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -834,17 +834,16 @@ nsJSContext::nsJSContext(bool aGCOnDestr
     JS::ContextOptionsRef(mContext).setPrivateIsNSISupports(true)
                                    .setNoDefaultCompartmentObject(true);
 
     // Watch for the JS boolean options
     Preferences::RegisterCallback(JSOptionChangedCallback,
                                   js_options_dot_str, this);
   }
   mIsInitialized = false;
-  mScriptsEnabled = true;
   mProcessingScriptTag = false;
   HoldJSObjects(this);
 }
 
 nsJSContext::~nsJSContext()
 {
   *mPrev = mNext;
   if (mNext) {
@@ -945,20 +944,16 @@ nsresult
 nsJSContext::EvaluateString(const nsAString& aScript,
                             JS::Handle<JSObject*> aScopeObject,
                             JS::CompileOptions& aCompileOptions,
                             bool aCoerceToString,
                             JS::Value* aRetValue,
                             void **aOffThreadToken)
 {
   NS_ENSURE_TRUE(mIsInitialized, NS_ERROR_NOT_INITIALIZED);
-  if (!mScriptsEnabled) {
-    return NS_OK;
-  }
-
   AutoCxPusher pusher(mContext);
   nsJSUtils::EvaluateOptions evalOptions;
   evalOptions.setCoerceToString(aCoerceToString);
   return nsJSUtils::EvaluateString(mContext, aScript, aScopeObject,
                                    aCompileOptions, evalOptions, aRetValue,
                                    aOffThreadToken);
 }
 
@@ -1859,37 +1854,16 @@ nsJSContext::DidInitializeContext()
 
 bool
 nsJSContext::IsContextInitialized()
 {
   return mIsInitialized;
 }
 
 bool
-nsJSContext::GetScriptsEnabled()
-{
-  return mScriptsEnabled;
-}
-
-void
-nsJSContext::SetScriptsEnabled(bool aEnabled, bool aFireTimeouts)
-{
-  // eeek - this seems the wrong way around - the global should callback
-  // into each context, so every language is disabled.
-  mScriptsEnabled = aEnabled;
-
-  nsIScriptGlobalObject *global = GetGlobalObject();
-
-  if (global) {
-    global->SetScriptsEnabled(aEnabled, aFireTimeouts);
-  }
-}
-
-
-bool
 nsJSContext::GetProcessingScriptTag()
 {
   return mProcessingScriptTag;
 }
 
 void
 nsJSContext::SetProcessingScriptTag(bool aFlag)
 {
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -54,19 +54,16 @@ public:
 
   virtual nsIScriptGlobalObject *GetGlobalObject() MOZ_OVERRIDE;
   inline nsIScriptGlobalObject *GetGlobalObjectRef() { return mGlobalObjectRef; }
 
   virtual JSContext* GetNativeContext() MOZ_OVERRIDE;
   virtual nsresult InitContext() MOZ_OVERRIDE;
   virtual bool IsContextInitialized() MOZ_OVERRIDE;
 
-  virtual bool GetScriptsEnabled() MOZ_OVERRIDE;
-  virtual void SetScriptsEnabled(bool aEnabled, bool aFireTimeouts) MOZ_OVERRIDE;
-
   virtual nsresult SetProperty(JS::Handle<JSObject*> aTarget, const char* aPropName, nsISupports* aVal) MOZ_OVERRIDE;
 
   virtual bool GetProcessingScriptTag() MOZ_OVERRIDE;
   virtual void SetProcessingScriptTag(bool aResult) MOZ_OVERRIDE;
 
   virtual nsresult InitClasses(JS::Handle<JSObject*> aGlobalObj) MOZ_OVERRIDE;
 
   virtual void WillInitializeContext() MOZ_OVERRIDE;
@@ -161,17 +158,16 @@ private:
   void DestroyJSContext();
 
   nsrefcnt GetCCRefcnt();
 
   JSContext *mContext;
   JS::Heap<JSObject*> mWindowProxy;
 
   bool mIsInitialized;
-  bool mScriptsEnabled;
   bool mGCOnDestruction;
   bool mProcessingScriptTag;
 
   PRTime mOperationCallbackTime;
 
   PRTime mModalStateTime;
   uint32_t mModalStateDepth;
 
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -236,25 +236,24 @@ nsJSUtils::EvaluateString(JSContext* aCx
   // cases.  So we need to be explicitly told whether the caller cares about the
   // return value.  Callers use null to indicate they don't care.
   if (aRetValue) {
     *aRetValue = JSVAL_VOID;
   }
 
   JS::ExposeObjectToActiveJS(aScopeObject);
   nsAutoMicroTask mt;
+  nsresult rv = NS_OK;
 
   JSPrincipals* p = JS_GetCompartmentPrincipals(js::GetObjectCompartment(aScopeObject));
   aCompileOptions.setPrincipals(p);
 
   bool ok = false;
-  nsresult rv = nsContentUtils::GetSecurityManager()->
-                  CanExecuteScripts(aCx, nsJSPrincipals::get(p), &ok);
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(ok, NS_OK);
+  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+  NS_ENSURE_TRUE(ssm->ScriptAllowed(js::GetGlobalForObjectCrossCompartment(aScopeObject)), NS_OK);
 
   mozilla::Maybe<AutoDontReportUncaught> dontReport;
   if (!aEvaluateOptions.reportUncaught) {
     // We need to prevent AutoLastFrameCheck from reporting and clearing
     // any pending exceptions.
     dontReport.construct(aCx);
   }
 
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -115,24 +115,22 @@ CallbackObject::CallSetup::CallSetup(JS:
   //
   // We can do this even though we're not in the right compartment yet, because
   // Rooted<> does not care about compartments.
   JS::ExposeObjectToActiveJS(aCallback);
   mRootedCallable.construct(cx, aCallback);
 
   if (mIsMainThread) {
     // Check that it's ok to run this callback at all.
-    // FIXME: Bug 807371: we want a less silly check here.
-    // Make sure to unwrap aCallback before passing it in, because
-    // getting principals from wrappers is silly.
-    nsresult rv = nsContentUtils::GetSecurityManager()->
-      CheckFunctionAccess(cx, js::UncheckedUnwrap(aCallback), nullptr);
+    // Make sure to unwrap aCallback before passing it in to get the global of
+    // the callback object, not the wrapper.
+    bool allowed = nsContentUtils::GetSecurityManager()->
+      ScriptAllowed(js::GetGlobalForObjectCrossCompartment(js::UncheckedUnwrap(aCallback)));
 
-    if (NS_FAILED(rv)) {
-      // Security check failed.  We're done here.
+    if (!allowed) {
       return;
     }
   }
 
   // Enter the compartment of our callback, so we can actually work with it.
   mAc.construct(cx, aCallback);
 
   // And now we're ready to go.
--- a/dom/bluetooth/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/BluetoothServiceBluedroid.cpp
@@ -244,17 +244,24 @@ AdapterPropertiesChangeCallback(bt_statu
     } else if (p.type == BT_PROPERTY_BDNAME) {
       // Construct nsCString here because Bd name returned from bluedroid
       // is missing a null terminated character after SetProperty.
       propertyValue = sAdapterBdName = NS_ConvertUTF8toUTF16(
         nsCString((char*)p.val, p.len));
       propertiesArray.AppendElement(
         BluetoothNamedValue(NS_LITERAL_STRING("Name"), propertyValue));
     } else if (p.type == BT_PROPERTY_ADAPTER_SCAN_MODE) {
-      propertyValue = sAdapterDiscoverable = *(uint32_t*)p.val;
+      bt_scan_mode_t newMode = *(bt_scan_mode_t*)p.val;
+
+      if (newMode == BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+        propertyValue = sAdapterDiscoverable = true;
+      } else {
+        propertyValue = sAdapterDiscoverable = false;
+      }
+
       propertiesArray.AppendElement(
         BluetoothNamedValue(NS_LITERAL_STRING("Discoverable"), propertyValue));
     } else if (p.type == BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT) {
       propertyValue = sAdapterDiscoverableTimeout = *(uint32_t*)p.val;
       propertiesArray.AppendElement(
         BluetoothNamedValue(NS_LITERAL_STRING("DiscoverableTimeout"),
                             propertyValue));
     } else if (p.type == BT_PROPERTY_ADAPTER_BONDED_DEVICES) {
@@ -632,19 +639,22 @@ StartStopGonkBluetooth(bool aShouldEnabl
   return (ret == BT_STATUS_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 static void
 ReplyStatusError(BluetoothReplyRunnable* aBluetoothReplyRunnable,
                  int aStatusCode, const nsAString& aCustomMsg)
 {
   MOZ_ASSERT(aBluetoothReplyRunnable, "Reply runnable is nullptr");
-  nsAutoString replyError;
+
+  BT_LOGR("%s: error code(%d)", __FUNCTION__, aStatusCode);
 
+  nsAutoString replyError;
   replyError.Assign(aCustomMsg);
+
   if (aStatusCode == BT_STATUS_BUSY) {
     replyError.AppendLiteral(":BT_STATUS_BUSY");
   } else if (aStatusCode == BT_STATUS_NOT_READY) {
     replyError.AppendLiteral(":BT_STATUS_NOT_READY");
   } else if (aStatusCode == BT_STATUS_DONE) {
     replyError.AppendLiteral(":BT_STATUS_DONE");
   } else if (aStatusCode == BT_STATUS_AUTH_FAILURE) {
     replyError.AppendLiteral(":BT_STATUS_AUTH_FAILURE");
@@ -835,16 +845,17 @@ BluetoothServiceBluedroid::SetProperty(B
     NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
     DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
 
     return NS_OK;
   }
 
   const nsString propName = aValue.name();
   bt_property_t prop;
+  bt_scan_mode_t scanMode;
   nsString str;
 
   // For Bluedroid, it's necessary to check property name for SetProperty
   if (propName.EqualsLiteral("Name")) {
     prop.type = BT_PROPERTY_BDNAME;
   } else if (propName.EqualsLiteral("Discoverable")) {
     prop.type = BT_PROPERTY_ADAPTER_SCAN_MODE;
   } else if (propName.EqualsLiteral("DiscoverableTimeout")) {
@@ -858,32 +869,33 @@ BluetoothServiceBluedroid::SetProperty(B
     prop.val = (void*)aValue.value().get_uint32_t();
   } else if (aValue.value().type() == BluetoothValue::TnsString) {
     // Set name
     str = aValue.value().get_nsString();
     const char* name = NS_ConvertUTF16toUTF8(str).get();
     prop.val = (void*)name;
     prop.len = strlen(name);
   } else if (aValue.value().type() == BluetoothValue::Tbool) {
-    bt_scan_mode_t mode = aValue.value().get_bool() ?
-                            BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE :
-                            BT_SCAN_MODE_CONNECTABLE;
-    bt_scan_mode_t* sss = &mode;
-    prop.val = (void*)sss;
-    prop.len = sizeof(sss);
+    scanMode = aValue.value().get_bool() ?
+                 BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE :
+                 BT_SCAN_MODE_CONNECTABLE;
+
+    prop.val = (void*)&scanMode;
+    prop.len = sizeof(scanMode);
   } else {
     BT_LOGR("SetProperty but the property cannot be recognized correctly.");
     return NS_OK;
   }
 
   sSetPropertyRunnableArray.AppendElement(aRunnable);
+
   int ret = sBtInterface->set_adapter_property(&prop);
-
-  if (ret != BT_STATUS_SUCCESS)
+  if (ret != BT_STATUS_SUCCESS) {
     ReplyStatusError(aRunnable, ret, NS_LITERAL_STRING("SetProperty"));
+  }
 
   return NS_OK;
 }
 
 bool
 BluetoothServiceBluedroid::GetDevicePath(const nsAString& aAdapterPath,
                                          const nsAString& aDeviceAddress,
                                          nsAString& aDevicePath)
--- a/dom/browser-element/mochitest/browserElementTestHelpers.js
+++ b/dom/browser-element/mochitest/browserElementTestHelpers.js
@@ -24,34 +24,41 @@ const browserElementTestHelpers = {
     this.lockTestReady();
     if (value !== undefined && value !== null) {
       SpecialPowers.pushPrefEnv({'set': [[pref, value]]}, this.unlockTestReady.bind(this));
     } else {
       SpecialPowers.pushPrefEnv({'clear': [[pref]]}, this.unlockTestReady.bind(this));
     }
   },
 
+  _setPrefs: function() {
+    this.lockTestReady();
+    SpecialPowers.pushPrefEnv({'set': Array.slice(arguments)}, this.unlockTestReady.bind(this));
+  },
+
   _testReadyLockCount: 0,
   _firedTestReady: false,
   lockTestReady: function() {
     this._testReadyLockCount++;
   },
 
   unlockTestReady: function() {
     this._testReadyLockCount--;
     if (this._testReadyLockCount == 0 && !this._firedTestReady) {
       this._firedTestReady = true;
       dispatchEvent(new Event("testready"));
     }
   },
 
   enableProcessPriorityManager: function() {
-    this._setPref('dom.ipc.processPriorityManager.testMode', true);
-    this._setPref('dom.ipc.processPriorityManager.enabled', true);
-    this._setPref('dom.ipc.processPriorityManager.backgroundLRUPoolLevels', 2);
+    this._setPrefs(
+      ['dom.ipc.processPriorityManager.testMode', true],
+      ['dom.ipc.processPriorityManager.enabled', true],
+      ['dom.ipc.processPriorityManager.backgroundLRUPoolLevels', 2]
+    );
   },
 
   setEnabledPref: function(value) {
     this._setPref('dom.mozBrowserFramesEnabled', value);
   },
 
   getOOPByDefaultPref: function() {
     return this._getBoolPref("dom.ipc.browser_frames.oop_by_default");
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -284,21 +284,22 @@ parent:
      * preventDefaulted. This applies to an entire batch of touch events. It is
      * expected that, if there are any DOM touch listeners, touch events will be
      * batched and only processed for panning and zooming if content does not
      * preventDefault.
      */
     ContentReceivedTouch(ScrollableLayerGuid aGuid, bool aPreventDefault);
 
     /**
-     * Updates the parent's zoom constraints for this tab. The zoom controller
-     * code lives on the parent side and so this allows it to have up-to-date
-     * zoom constraints.
+     * Updates the zoom constraints for a scrollable frame in this tab.
+     * The zoom controller code lives on the parent side and so this allows it to
+     * have up-to-date zoom constraints.
      */
-    UpdateZoomConstraints(bool aAllowZoom, CSSToScreenScale aMinZoom, CSSToScreenScale aMaxZoom);
+    UpdateZoomConstraints(uint32_t aPresShellId, ViewID aViewId,
+                          bool aAllowZoom, CSSToScreenScale aMinZoom, CSSToScreenScale aMaxZoom);
 
     /**
      * Notifies the parent about a scroll event. The pres shell ID and
      * view ID identify which scrollable (sub-)frame was scrolled, and
      * the new scroll offset for that frame is sent.
      */
     UpdateScrollOffset(uint32_t aPresShellId, ViewID aViewId, CSSIntPoint aScrollOffset);
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -532,18 +532,27 @@ TabChild::HandlePossibleViewportChange()
   }
 
   nsCOMPtr<nsIDOMDocument> domDoc;
   mWebNav->GetDocument(getter_AddRefs(domDoc));
   nsCOMPtr<nsIDocument> document(do_QueryInterface(domDoc));
 
   nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
 
+  uint32_t presShellId;
+  ViewID viewId;
+  if (!APZCCallbackHelper::GetScrollIdentifiers(document->GetDocumentElement(),
+                                                &presShellId, &viewId)) {
+    return;
+  }
+
   nsViewportInfo viewportInfo = nsContentUtils::GetViewportInfo(document, mInnerSize);
-  SendUpdateZoomConstraints(viewportInfo.IsZoomAllowed(),
+  SendUpdateZoomConstraints(presShellId,
+                            viewId,
+                            viewportInfo.IsZoomAllowed(),
                             viewportInfo.GetMinZoom(),
                             viewportInfo.GetMaxZoom());
 
   float screenW = mInnerSize.width;
   float screenH = mInnerSize.height;
   CSSSize viewport(viewportInfo.GetSize());
 
   // We're not being displayed in any way; don't bother doing anything because
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1621,22 +1621,24 @@ TabParent::RecvZoomToRect(const uint32_t
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
     rfp->ZoomToRect(aPresShellId, aViewId, aRect);
   }
   return true;
 }
 
 bool
-TabParent::RecvUpdateZoomConstraints(const bool& aAllowZoom,
+TabParent::RecvUpdateZoomConstraints(const uint32_t& aPresShellId,
+                                     const ViewID& aViewId,
+                                     const bool& aAllowZoom,
                                      const CSSToScreenScale& aMinZoom,
                                      const CSSToScreenScale& aMaxZoom)
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
-    rfp->UpdateZoomConstraints(aAllowZoom, aMinZoom, aMaxZoom);
+    rfp->UpdateZoomConstraints(aPresShellId, aViewId, aAllowZoom, aMinZoom, aMaxZoom);
   }
   return true;
 }
 
 bool
 TabParent::RecvUpdateScrollOffset(const uint32_t& aPresShellId,
                                   const ViewID& aViewId,
                                   const CSSIntPoint& aScrollOffset)
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -160,17 +160,19 @@ public:
     virtual bool RecvSetBackgroundColor(const nscolor& aValue);
     virtual bool RecvSetStatus(const uint32_t& aType, const nsString& aStatus);
     virtual bool RecvGetDPI(float* aValue);
     virtual bool RecvGetDefaultScale(double* aValue);
     virtual bool RecvGetWidgetNativeData(WindowsHandle* aValue);
     virtual bool RecvZoomToRect(const uint32_t& aPresShellId,
                                 const ViewID& aViewId,
                                 const CSSRect& aRect);
-    virtual bool RecvUpdateZoomConstraints(const bool& aAllowZoom,
+    virtual bool RecvUpdateZoomConstraints(const uint32_t& aPresShellId,
+                                           const ViewID& aViewId,
+                                           const bool& aAllowZoom,
                                            const CSSToScreenScale& aMinZoom,
                                            const CSSToScreenScale& aMaxZoom);
     virtual bool RecvUpdateScrollOffset(const uint32_t& aPresShellId, const ViewID& aViewId, const CSSIntPoint& aScrollOffset);
     virtual bool RecvContentReceivedTouch(const ScrollableLayerGuid& aGuid,
                                           const bool& aPreventDefault);
     virtual bool RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
                                            const bool& aIsAudio,
                                            const bool& aIsVideo);
--- a/dom/messages/SystemMessagePermissionsChecker.jsm
+++ b/dom/messages/SystemMessagePermissionsChecker.jsm
@@ -90,16 +90,25 @@ this.SystemMessagePermissionsTable = {
     "mobileconnection": []
   },
   "wappush-received": {
     "wappush": []
   },
   "cdma-info-rec-received": {
     "mobileconnection": []
   },
+  "nfc-manager-tech-discovered": {
+    "nfc-manager": []
+  },
+  "nfc-manager-tech-lost": {
+    "nfc-manager": []
+  },
+  "nfc-powerlevel-change": {
+    "settings": ["read", "write"]
+  }
 };
 
 this.SystemMessagePermissionsChecker = {
   /**
    * Return all the needed permission names for the given system message.
    * @param string aSysMsgName
    *        The system messsage name.
    * @returns object
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -92,16 +92,19 @@ if CONFIG['MOZ_B2G_RIL']:
     ]
 
 if CONFIG['MOZ_PAY']:
     PARALLEL_DIRS += ['payment']
 
 if CONFIG['MOZ_GAMEPAD']:
     PARALLEL_DIRS += ['gamepad']
 
+if CONFIG['MOZ_NFC']:
+    PARALLEL_DIRS += ['nfc']
+
 # bindings/test is here, because it needs to build after bindings/, and
 # we build subdirectories before ourselves.
 TEST_DIRS += [
     'tests',
     'imptests',
     'bindings/test',
 ]
 
--- a/dom/network/interfaces/nsIDOMTCPSocket.idl
+++ b/dom/network/interfaces/nsIDOMTCPSocket.idl
@@ -206,36 +206,49 @@ interface nsIDOMTCPSocket : nsISupports
    *
    * If onerror was not called before onclose, then either side cleanly
    * closed the connection.
    */
   attribute jsval onclose;
 };
 
 /*
- * Internal interfaces for use in cross-process socket implementation.
+ * This interface is implemented in TCPSocket.js as an internal interfaces
+ * for use in cross-process socket implementation.
  * Needed to account for multiple possible types that can be provided to
  * the socket callbacks as arguments.
  */
-[scriptable, uuid(234c664c-3d6c-4859-b45c-4e9a98cb5bdc)]
+[scriptable, uuid(017f130f-2477-4215-8783-57eada957699)]
 interface nsITCPSocketInternal : nsISupports {
   // Trigger the callback for |type| and provide a DOMError() object with the given data
   void callListenerError(in DOMString type, in DOMString name);
 
   // Trigger the callback for |type| and provide a string argument
   void callListenerData(in DOMString type, in DOMString data);
 
   // Trigger the callback for |type| and provide an ArrayBuffer argument
   void callListenerArrayBuffer(in DOMString type, in jsval data);
 
   // Trigger the callback for |type| with no argument
   void callListenerVoid(in DOMString type);
 
-  // Update the DOM object's readyState and bufferedAmount values with the provided data
-  void updateReadyStateAndBuffered(in DOMString readyState, in uint32_t bufferedAmount);
+  // Update the DOM object's readyState.
+  // @param readyState
+  //        new ready state to be set to TCPSocket.
+  void updateReadyState(in DOMString readyState);
+
+  // Update the DOM object's bufferedAmount value with a tracking number to
+  // ensure the update request is sent after child's send() invocation.
+  // @param bufferedAmount
+  //        TCPSocket parent's bufferedAmount.
+  // @param trackingNumber
+  //        A number to ensure the bufferedAmount is updated after data
+  //        from child are sent to parent.
+  void updateBufferedAmount(in uint32_t bufferedAmount,
+                            in uint32_t trackingNumber);
 
   // Create a socket object on the parent side.
   // This is called in accepting any open request on the parent side.
   // 
   // @param transport
   //        The accepted socket transport.
   // @param binaryType
   //        "arraybuffer" to use ArrayBuffer instances 
@@ -254,16 +267,28 @@ interface nsITCPSocketInternal : nsISupp
   // @param window
   //        An object to create ArrayBuffer for this window. See Bug 831107.
   nsIDOMTCPSocket createAcceptedChild(in nsITCPSocketChild socketChild,
                                       in DOMString binaryType,
                                       in nsIDOMWindow window);
 
   // Set App ID.
   void setAppId(in unsigned long appId);
+
+  // Set a callback that handles the request from a TCP socket parent when that
+  // socket parent wants to notify that its bufferedAmount is updated.
+  void setOnUpdateBufferedAmountHandler(in jsval handler);
+
+  // Providing child process with ability to pass more arguments to parent's
+  // send() function.
+  // @param trackingNumber
+  //        To ensure the request to update bufferedAmount in child is after
+  //        lastest send() invocation from child.
+  void onRecvSendFromChild(in jsval data, in unsigned long byteOffset,
+                           in unsigned long byteLength, in unsigned long trackingNumber);
 };
 
 /**
  * nsITCPSocketEvent is the event object which is passed as the
  * first argument to all the event handler callbacks. It contains
  * the socket that was associated with the event, the type of event,
  * and the data associated with the event (if any).
  */
--- a/dom/network/interfaces/nsITCPSocketChild.idl
+++ b/dom/network/interfaces/nsITCPSocketChild.idl
@@ -3,32 +3,36 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "domstubs.idl"
 
 interface nsITCPSocketInternal;
 interface nsIDOMWindow;
 
 // Interface to allow the content process socket to reach the IPC bridge.
-[scriptable, uuid(ada5342d-6d45-4ff1-a7d3-6a4b150d0385)]
+// Implemented in C++ as TCPSocketChild, referenced as _socketBridge in TCPSocket.js
+[scriptable, uuid(292ebb3a-beac-4e06-88b0-b5b4e88ebd1c)]
 interface nsITCPSocketChild : nsISupports
 {
   // Tell the chrome process to open a corresponding connection with the given parameters
   [implicit_jscontext]
-  void open(in nsITCPSocketInternal socket, in DOMString host,
-            in unsigned short port, in boolean ssl, in DOMString binaryType,
-            in nsIDOMWindow window, in jsval windowVal);
+  void sendOpen(in nsITCPSocketInternal socket, in DOMString host,
+                in unsigned short port, in boolean ssl, in DOMString binaryType,
+                in nsIDOMWindow window, in jsval windowVal);
+
+  // Tell the chrome process to perform send and update the tracking number.
+  [implicit_jscontext] 
+  void sendSend(in jsval data, in unsigned long byteOffset,
+                in unsigned long byteLength, in unsigned long trackingNumber);
 
   // Tell the chrome process to perform equivalent operations to all following methods
-  [implicit_jscontext] 
-  void send(in jsval data, in unsigned long byteOffset, in unsigned long byteLength);
-  void resume();
-  void suspend();
-  void close();
-  void startTLS();
+  void sendResume();
+  void sendSuspend();
+  void sendClose();
+  void sendStartTLS();
 
   /**
    * Initialize the TCP socket on the child side for IPC. It is called from the child side,
    * which is generated in receiving a notification of accepting any open request
    * on the parent side. We use single implementation that works on a child process 
    * as well as in the single process model.
    *
    * @param socket
--- a/dom/network/interfaces/nsITCPSocketParent.idl
+++ b/dom/network/interfaces/nsITCPSocketParent.idl
@@ -4,56 +4,80 @@
 
 #include "domstubs.idl"
 
 interface nsIDOMTCPSocket;
 interface nsIDOMTCPServerSocket;
 interface nsITCPServerSocketParent;
 interface nsITCPSocketIntermediary;
 
-// Interface required to allow the TCP socket object in the parent process
-// to talk to the parent IPC actor
-[scriptable, uuid(123f654b-4435-43c8-8447-db1b5420a1c2)]
+// Interface required to allow the TCP socket object (TCPSocket.js) in the
+// parent process to talk to the parent IPC actor, TCPSocketParent, which
+// is written in C++.
+[scriptable, uuid(868662a4-681c-4b89-9f02-6fe5b7ace265)]
 interface nsITCPSocketParent : nsISupports
 {
   [implicit_jscontext] void initJS(in jsval intermediary);
 
   // Trigger a callback in the content process for |type|, providing a serialized
-  // argument of |data|, and update the child's readyState and bufferedAmount values
-  // with the given values.
-  [implicit_jscontext] void sendCallback(in DOMString type,
-                                         in jsval data,
-                                         in DOMString readyState,
-                                         in uint32_t bufferedAmount);
+  // argument of |data|, and update the child's readyState value with the given
+  // values.
+  //
+  // @param type
+  //        Event type: 'onopen', 'ondata', 'onerror' or 'onclose'. 'odrain' is
+  //        controlled by child.
+  // @param data
+  //        Serialized data that is passed to event handler.
+  // @param readyState
+  //        Current ready state.
+  [implicit_jscontext] void sendEvent(in DOMString type,
+                                      in jsval data,
+                                      in DOMString readyState);
 
   // Initialize a parent socket object. It is called from the parent side socket,
   // which is generated in accepting any open request on the parent side.
   // The socket after being initialized will be established.
   //
   // @param socket
   //        The socket on the parent side.
   // @param intermediary
   //        Intermediate class object. See nsITCPSocketIntermediary.
   [implicit_jscontext] void setSocketAndIntermediary(in nsIDOMTCPSocket socket,
                                                      in nsITCPSocketIntermediary intermediary);
+
+  // When parent's buffered amount is updated and it wants to inform child to
+  // update the bufferedAmount as well.
+  //
+  // @param bufferedAmount
+  //        The new value of bufferedAmount that is going to be set to child's
+  //        bufferedAmount.
+  // @param trackingNumber
+  //        Parent's current tracking number, reflecting the number of calls to
+  //        send() on the child process. This number is sent back to the child
+  //        to make sure the bufferedAmount updated on the child will correspond
+  //        to the latest call of send().
+  void sendUpdateBufferedAmount(in uint32_t bufferedAmount, in uint32_t trackingNumber);
 };
 
 // Intermediate class to handle sending multiple possible data types
 // and kicking off the chrome process socket object's connection.
-[scriptable, uuid(be67b1b8-03b0-4171-a791-d004458021b6)]
+// This interface is the bridge of TCPSocketParent, which is written in C++,
+// and TCPSocket, which is written in Javascript. TCPSocketParentIntermediary
+// implements nsITCPSocketIntermediary in Javascript.
+[scriptable, uuid(c434224a-dbb7-4869-8b2b-e49cee990e85)]
 interface nsITCPSocketIntermediary : nsISupports {
   // Open the connection to the server with the given parameters
   nsIDOMTCPSocket open(in nsITCPSocketParent parent,
                        in DOMString host, in unsigned short port,
                        in boolean useSSL, in DOMString binaryType,
                        in unsigned long appId);
 
   // Listen on a port
   nsIDOMTCPServerSocket listen(in nsITCPServerSocketParent parent,
                                in unsigned short port, in unsigned short backlog,
                                in DOMString binaryType);
 
-  // Send a basic string along the connection
-  void sendString(in DOMString data);
+  // Called when received a child request to send a string.
+  void onRecvSendString(in DOMString data, in uint32_t trackingNumber);
 
-  // Send a typed array
-  void sendArrayBuffer(in jsval data);
+  // Called when received a child request to send an array buffer.
+  void onRecvSendArrayBuffer(in jsval data, in uint32_t trackingNumber);
 };
--- a/dom/network/src/NetworkStatsService.jsm
+++ b/dom/network/src/NetworkStatsService.jsm
@@ -357,16 +357,17 @@ this.NetworkStatsService = {
   updateStats: function updateStats(aNetId, aCallback) {
     // Check if the connection is in the main queue, push a new element
     // if it is not being processed or add a callback if it is.
     let index = this.updateQueueIndex(aNetId);
     if (index == -1) {
       this.updateQueue.push({netId: aNetId, callbacks: [aCallback]});
     } else {
       this.updateQueue[index].callbacks.push(aCallback);
+      return;
     }
 
     // Call the function that process the elements of the queue.
     this.processQueue();
   },
 
   /*
    * Find if a connection is in the main queue array and return its
--- a/dom/network/src/PTCPSocket.ipdl
+++ b/dom/network/src/PTCPSocket.ipdl
@@ -30,26 +30,45 @@ namespace mozilla {
 namespace net {
 
 //-------------------------------------------------------------------
 protocol PTCPSocket
 {
   manager PNecko;
 
 parent:
+  // Forward calling to child's open() method to parent, expect TCPOptions
+  // is expanded to |useSSL| (from TCPOptions.useSecureTransport) and
+  // |binaryType| (from TCPOption.binaryType).
   Open(nsString host, uint16_t port, bool useSSL, nsString binaryType);
-  Data(SendableData data);
+
+  // When child's send() is called, this message requrests parent to send
+  // data and update it's trackingNumber.
+  Data(SendableData data, uint32_t trackingNumber);
+
+  // Forward calling to child's upgradeToSecure() method to parent.
   StartTLS();
+
+  // Forward calling to child's send() method to parent.
   Suspend();
+
+  // Forward calling to child's resume() method to parent.
   Resume();
+
+  // Forward calling to child's close() method to parent.
   Close();
 
 child:
-  Callback(nsString type, CallbackData data,
-           nsString readyState, uint32_t bufferedAmount);
+  // Forward events that are dispatched by parent.
+  Callback(nsString type, CallbackData data, nsString readyState);
+
+  // Update child's bufferedAmount when parent's bufferedAmount is updated.
+  // trackingNumber is also passed back to child to ensure the bufferedAmount
+  // is corresponding the last call to send().
+  UpdateBufferedAmount(uint32_t bufferedAmount, uint32_t trackingNumber);
 
 both:
   RequestDelete();
   __delete__();
 };
 
 
 } // namespace net
--- a/dom/network/src/TCPSocket.js
+++ b/dom/network/src/TCPSocket.js
@@ -157,16 +157,20 @@ TCPSocket.prototype = {
 
   // IPC socket actor
   _socketBridge: null,
 
   // StartTLS
   _waitingForStartTLS: false,
   _pendingDataAfterStartTLS: [],
 
+  // Used to notify when update bufferedAmount is updated.
+  _onUpdateBufferedAmount: null,
+  _trackingNumber: 0,
+
 #ifdef MOZ_WIDGET_GONK
   // Network statistics (Gonk-specific feature)
   _txBytes: 0,
   _rxBytes: 0,
   _appId: Ci.nsIScriptSecurityManager.NO_APP_ID,
   _activeNetwork: null,
 #endif
 
@@ -237,28 +241,35 @@ TCPSocket.prototype = {
     } else {
       options = ['starttls'];
     }
     return Cc["@mozilla.org/network/socket-transport-service;1"]
              .getService(Ci.nsISocketTransportService)
              .createTransport(options, 1, host, port, null);
   },
 
+  _sendBufferedAmount: function ts_sendBufferedAmount() {
+    if (this._onUpdateBufferedAmount) {
+      this._onUpdateBufferedAmount(this.bufferedAmount, this._trackingNumber);
+    }
+  },
+
   _ensureCopying: function ts_ensureCopying() {
     let self = this;
     if (this._asyncCopierActive) {
       return;
     }
     this._asyncCopierActive = true;
     this._multiplexStreamCopier.asyncCopy({
       onStartRequest: function ts_output_onStartRequest() {
       },
       onStopRequest: function ts_output_onStopRequest(request, context, status) {
         self._asyncCopierActive = false;
         self._multiplexStream.removeStream(0);
+        self._sendBufferedAmount();
 
         if (!Components.isSuccessCode(status)) {
           // Note that we can/will get an error here as well as in the
           // onStopRequest for inbound data.
           self._maybeReportErrorAndCloseIfOpen(status);
           return;
         }
 
@@ -275,17 +286,19 @@ TCPSocket.prototype = {
             if (self._pendingDataAfterStartTLS.length > 0) {
               while (self._pendingDataAfterStartTLS.length)
                 self._multiplexStream.appendStream(self._pendingDataAfterStartTLS.shift());
               self._ensureCopying();
               return;
             }
           }
 
-          if (self._waitingForDrain) {
+          // If we have a callback to update bufferedAmount, we let child to
+          // decide whether ondrain should be dispatched.
+          if (self._waitingForDrain && !self._onUpdateBufferedAmount) {
             self._waitingForDrain = false;
             self.callListener("drain");
           }
           if (self._readyState === kCLOSING) {
             self._socketOutputStream.close();
             self._readyState = kCLOSED;
             self.callListener("close");
           }
@@ -377,19 +390,46 @@ TCPSocket.prototype = {
   callListenerArrayBuffer: function ts_callListenerArrayBuffer(type, data) {
     this.callListener(type, data);
   },
 
   callListenerVoid: function ts_callListenerVoid(type) {
     this.callListener(type);
   },
 
-  updateReadyStateAndBuffered: function ts_setReadyState(readyState, bufferedAmount) {
+  /**
+   * This method is expected to be called by TCPSocketChild to update child's
+   * readyState.
+   */
+  updateReadyState: function ts_updateReadyState(readyState) {
+    if (!this._inChild) {
+      LOG("Calling updateReadyState in parent, which should only be called " +
+          "in child");
+      return;
+    }
     this._readyState = readyState;
+  },
+
+  updateBufferedAmount: function ts_updateBufferedAmount(bufferedAmount, trackingNumber) {
+    if (trackingNumber != this._trackingNumber) {
+      LOG("updateBufferedAmount is called but trackingNumber is not matched " +
+          "parent's trackingNumber: " + trackingNumber + ", child's trackingNumber: " +
+          this._trackingNumber);
+      return;
+    }
     this._bufferedAmount = bufferedAmount;
+    if (bufferedAmount == 0) {
+      if (this._waitingForDrain) {
+        this._waitingForDrain = false;
+        this.callListener("drain");
+      }
+    } else {
+      LOG("bufferedAmount is updated but haven't reaches zero. bufferedAmount: " +
+          bufferedAmount);
+    }
   },
 
   createAcceptedParent: function ts_createAcceptedParent(transport, binaryType) {
     let that = new TCPSocket();
     that._transport = transport;
     that._initStream(binaryType);
 
     // ReadyState is kOpen since accepted transport stream has already been connected
@@ -415,16 +455,35 @@ TCPSocket.prototype = {
   setAppId: function ts_setAppId(appId) {
 #ifdef MOZ_WIDGET_GONK
     this._appId = appId;
 #else
     // Do nothing because _appId only exists on Gonk-specific platform.
 #endif
   },
 
+  setOnUpdateBufferedAmountHandler: function(aFunction) {
+    if (typeof(aFunction) == 'function') {
+      this._onUpdateBufferedAmount = aFunction;
+    } else {
+      throw new Error("only function can be passed to " +
+                      "setOnUpdateBufferedAmountHandler");
+    }
+  },
+
+  /**
+   * Handle the requst of sending data and update trackingNumber from
+   * child.
+   * This function is expected to be called by TCPSocketChild.
+   */
+  onRecvSendFromChild: function(data, byteOffset, byteLength, trackingNumber) {
+    this._trackingNumber = trackingNumber;
+    this.send(data, byteOffset, byteLength);
+  },
+
   /* end nsITCPSocketInternal methods */
 
   initWindowless: function ts_initWindowless() {
     try {
       return Services.prefs.getBoolPref("dom.mozTCPSocket.enabled");
     } catch (e) {
       // no pref means return false
       return false;
@@ -512,18 +571,18 @@ TCPSocket.prototype = {
       that._binaryType = options.binaryType || that._binaryType;
     }
 
     LOG("SSL: " + that.ssl);
 
     if (this._inChild) {
       that._socketBridge = Cc["@mozilla.org/tcp-socket-child;1"]
                              .createInstance(Ci.nsITCPSocketChild);
-      that._socketBridge.open(that, host, port, !!that._ssl,
-                              that._binaryType, this.useWin, this.useWin || this);
+      that._socketBridge.sendOpen(that, host, port, !!that._ssl,
+                                  that._binaryType, this.useWin, this.useWin || this);
       return that;
     }
 
     let transport = that._transport = this._createTransport(host, port, that._ssl);
     transport.setEventSink(that, Services.tm.currentThread);
     that._initStream(that._binaryType);
 
 #ifdef MOZ_WIDGET_GONK
@@ -546,17 +605,17 @@ TCPSocket.prototype = {
     if (this._ssl == 'ssl') {
       // Already SSL
       return;
     }
 
     this._ssl = 'ssl';
 
     if (this._inChild) {
-      this._socketBridge.startTLS();
+      this._socketBridge.sendStartTLS();
       return;
     }
 
     if (this._multiplexStream.count == 0) {
       this._activateTLS();
     } else {
       this._waitingForStartTLS = true;
     }
@@ -580,17 +639,17 @@ TCPSocket.prototype = {
   close: function ts_close() {
     if (this._readyState === kCLOSED || this._readyState === kCLOSING)
       return;
 
     LOG("close called");
     this._readyState = kCLOSING;
 
     if (this._inChild) {
-      this._socketBridge.close();
+      this._socketBridge.sendClose();
       return;
     }
 
     if (!this._multiplexStream.count) {
       this._socketOutputStream.close();
     }
     this._socketInputStream.close();
   },
@@ -600,25 +659,37 @@ TCPSocket.prototype = {
       throw new Error("Socket not open.");
     }
 
     if (this._binaryType === "arraybuffer") {
       byteLength = byteLength || data.byteLength;
     }
 
     if (this._inChild) {
-      this._socketBridge.send(data, byteOffset, byteLength);
+      this._socketBridge.sendSend(data, byteOffset, byteLength, ++this._trackingNumber);
     }
 
     let length = this._binaryType === "arraybuffer" ? byteLength : data.length;
+    let newBufferedAmount = this.bufferedAmount + length;
+    let bufferFull = newBufferedAmount >= BUFFER_SIZE;
 
-    var newBufferedAmount = this.bufferedAmount + length;
-    var bufferNotFull = newBufferedAmount < BUFFER_SIZE;
+    if (bufferFull) {
+      // If we buffered more than some arbitrary amount of data,
+      // (65535 right now) we should tell the caller so they can
+      // wait until ondrain is called if they so desire. Once all the
+      // buffered data has been written to the socket, ondrain is
+      // called.
+      this._waitingForDrain = true;
+    }
+
     if (this._inChild) {
-      return bufferNotFull;
+      // In child, we just add buffer length to our bufferedAmount and let
+      // parent to update our bufferedAmount when data have been sent.
+      this._bufferedAmount = newBufferedAmount;
+      return !bufferFull;
     }
 
     let new_stream;
     if (this._binaryType === "arraybuffer") {
       new_stream = new ArrayBufferInputStream();
       new_stream.setData(data, byteOffset, byteLength);
     } else {
       new_stream = new StringInputStream();
@@ -628,52 +699,43 @@ TCPSocket.prototype = {
     if (this._waitingForStartTLS) {
       // When we are waiting for starttls, new_stream is added to pendingData
       // and will be appended to multiplexStream after tls had been set up.
       this._pendingDataAfterStartTLS.push(new_stream);
     } else {
       this._multiplexStream.appendStream(new_stream);
     }
 
-    if (newBufferedAmount >= BUFFER_SIZE) {
-      // If we buffered more than some arbitrary amount of data,
-      // (65535 right now) we should tell the caller so they can
-      // wait until ondrain is called if they so desire. Once all the
-      //buffered data has been written to the socket, ondrain is
-      // called.
-      this._waitingForDrain = true;
-    }
-
     this._ensureCopying();
 
 #ifdef MOZ_WIDGET_GONK
     // Collect transmitted amount for network statistics.
     this._txBytes += length;
     this._saveNetworkStats(false);
 #endif
 
-    return bufferNotFull;
+    return !bufferFull;
   },
 
   suspend: function ts_suspend() {
     if (this._inChild) {
-      this._socketBridge.suspend();
+      this._socketBridge.sendSuspend();
       return;
     }
 
     if (this._inputStreamPump) {
       this._inputStreamPump.suspend();
     } else {
       ++this._suspendCount;
     }
   },
 
   resume: function ts_resume() {
     if (this._inChild) {
-      this._socketBridge.resume();
+      this._socketBridge.sendResume();
       return;
     }
 
     if (this._inputStreamPump) {
       this._inputStreamPump.resume();
     } else if (this._suspendCount < 1) {
       throw new Error(kRESUME_ERROR);
     } else {
--- a/dom/network/src/TCPSocketChild.cpp
+++ b/dom/network/src/TCPSocketChild.cpp
@@ -72,31 +72,33 @@ NS_IMETHODIMP_(nsrefcnt) TCPSocketChild:
 }
 
 TCPSocketChild::TCPSocketChild()
 : mWindowObj(nullptr)
 {
 }
 
 NS_IMETHODIMP
-TCPSocketChild::Open(nsITCPSocketInternal* aSocket, const nsAString& aHost,
-                     uint16_t aPort, bool aUseSSL, const nsAString& aBinaryType,
-                     nsIDOMWindow* aWindow, const JS::Value& aWindowObj,
-                     JSContext* aCx)
+TCPSocketChild::SendOpen(nsITCPSocketInternal* aSocket,
+                         const nsAString& aHost, uint16_t aPort,
+                         bool aUseSSL, const nsAString& aBinaryType,
+                         nsIDOMWindow* aWindow, const JS::Value& aWindowObj,
+                         JSContext* aCx)
 {
   mSocket = aSocket;
 
   MOZ_ASSERT(aWindowObj.isObject());
   mWindowObj = js::CheckedUnwrap(&aWindowObj.toObject());
   if (!mWindowObj) {
     return NS_ERROR_FAILURE;
   }
   AddIPDLReference();
   gNeckoChild->SendPTCPSocketConstructor(this);
-  SendOpen(nsString(aHost), aPort, aUseSSL, nsString(aBinaryType));
+  PTCPSocketChild::SendOpen(nsString(aHost), aPort,
+                            aUseSSL, nsString(aBinaryType));
   return NS_OK;
 }
 
 void
 TCPSocketChildBase::ReleaseIPDLReference()
 {
   MOZ_ASSERT(mIPCOpen);
   mIPCOpen = false;
@@ -111,22 +113,31 @@ TCPSocketChildBase::AddIPDLReference()
   this->AddRef();
 }
 
 TCPSocketChild::~TCPSocketChild()
 {
 }
 
 bool
+TCPSocketChild::RecvUpdateBufferedAmount(const uint32_t& aBuffered,
+                                         const uint32_t& aTrackingNumber)
+{
+  if (NS_FAILED(mSocket->UpdateBufferedAmount(aBuffered, aTrackingNumber))) {
+    NS_ERROR("Shouldn't fail!");
+  }
+  return true;
+}
+
+bool
 TCPSocketChild::RecvCallback(const nsString& aType,
                              const CallbackData& aData,
-                             const nsString& aReadyState,
-                             const uint32_t& aBuffered)
+                             const nsString& aReadyState)
 {
-  if (NS_FAILED(mSocket->UpdateReadyStateAndBuffered(aReadyState, aBuffered)))
+  if (NS_FAILED(mSocket->UpdateReadyState(aReadyState)))
     NS_ERROR("Shouldn't fail!");
 
   nsresult rv = NS_ERROR_FAILURE;
   if (aData.type() == CallbackData::Tvoid_t) {
     rv = mSocket->CallListenerVoid(aType);
 
   } else if (aData.type() == CallbackData::TTCPError) {
     const TCPError& err(aData.get_TCPError());
@@ -154,56 +165,56 @@ TCPSocketChild::RecvCallback(const nsStr
   } else {
     MOZ_CRASH("Invalid callback type!");
   }
   NS_ENSURE_SUCCESS(rv, true);
   return true;
 }
 
 NS_IMETHODIMP
-TCPSocketChild::StartTLS()
+TCPSocketChild::SendStartTLS()
 {
-  SendStartTLS();
+  PTCPSocketChild::SendStartTLS();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TCPSocketChild::Suspend()
+TCPSocketChild::SendSuspend()
 {
-  SendSuspend();
+  PTCPSocketChild::SendSuspend();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TCPSocketChild::Resume()
+TCPSocketChild::SendResume()
 {
-  SendResume();
+  PTCPSocketChild::SendResume();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TCPSocketChild::Close()
+TCPSocketChild::SendClose()
 {
-  SendClose();
+  PTCPSocketChild::SendClose();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TCPSocketChild::Send(const JS::Value& aData,
-                     uint32_t aByteOffset,
-                     uint32_t aByteLength,
-                     JSContext* aCx)
+TCPSocketChild::SendSend(const JS::Value& aData,
+                         uint32_t aByteOffset,
+                         uint32_t aByteLength,
+                         uint32_t aTrackingNumber,
+                         JSContext* aCx)
 {
   if (aData.isString()) {
     JSString* jsstr = aData.toString();
     nsDependentJSString str;
     bool ok = str.init(aCx, jsstr);
     NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
-    SendData(str);
-
+    SendData(str, aTrackingNumber);
   } else {
     NS_ENSURE_TRUE(aData.isObject(), NS_ERROR_FAILURE);
     JS::Rooted<JSObject*> obj(aCx, &aData.toObject());
     NS_ENSURE_TRUE(JS_IsArrayBufferObject(obj), NS_ERROR_FAILURE);
     uint32_t buflen = JS_GetArrayBufferByteLength(obj);
     aByteOffset = std::min(buflen, aByteOffset);
     uint32_t nbytes = std::min(buflen - aByteOffset, aByteLength);
     uint8_t* data = JS_GetArrayBufferData(obj);
@@ -211,25 +222,25 @@ TCPSocketChild::Send(const JS::Value& aD
       return NS_ERROR_OUT_OF_MEMORY;
     }
     FallibleTArray<uint8_t> fallibleArr;
     if (!fallibleArr.InsertElementsAt(0, data, nbytes)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
     InfallibleTArray<uint8_t> arr;
     arr.SwapElements(fallibleArr);
-    SendData(arr);
+    SendData(arr, aTrackingNumber);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TCPSocketChild::SetSocketAndWindow(nsITCPSocketInternal *aSocket,
-                          const JS::Value& aWindowObj,
-                          JSContext* aCx)
+                                   const JS::Value& aWindowObj,
+                                   JSContext* aCx)
 {
   mSocket = aSocket;
   MOZ_ASSERT(aWindowObj.isObject());
   mWindowObj = js::CheckedUnwrap(&aWindowObj.toObject());
   if (!mWindowObj) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
--- a/dom/network/src/TCPSocketChild.h
+++ b/dom/network/src/TCPSocketChild.h
@@ -39,17 +39,18 @@ public:
   NS_DECL_NSITCPSOCKETCHILD
   NS_IMETHOD_(nsrefcnt) Release() MOZ_OVERRIDE;
 
   TCPSocketChild();
   ~TCPSocketChild();
 
   virtual bool RecvCallback(const nsString& aType,
                             const CallbackData& aData,
-                            const nsString& aReadyState,
-                            const uint32_t& aBuffered) MOZ_OVERRIDE;
+                            const nsString& aReadyState) MOZ_OVERRIDE;
   virtual bool RecvRequestDelete() MOZ_OVERRIDE;
+  virtual bool RecvUpdateBufferedAmount(const uint32_t& aBufferred,
+                                        const uint32_t& aTrackingNumber) MOZ_OVERRIDE;
 private:
   JSObject* mWindowObj;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/network/src/TCPSocketParent.cpp
+++ b/dom/network/src/TCPSocketParent.cpp
@@ -30,17 +30,17 @@ namespace mozilla {
 namespace dom {
 
 static void
 FireInteralError(mozilla::net::PTCPSocketParent* aActor, uint32_t aLineNo)
 {
   mozilla::unused <<
       aActor->SendCallback(NS_LITERAL_STRING("onerror"),
                            TCPError(NS_LITERAL_STRING("InvalidStateError")),
-                           NS_LITERAL_STRING("connecting"), 0);
+                           NS_LITERAL_STRING("connecting"));
 }
 
 NS_IMPL_CYCLE_COLLECTION_2(TCPSocketParentBase, mSocket, mIntermediary)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketParentBase)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketParentBase)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketParentBase)
   NS_INTERFACE_MAP_ENTRY(nsITCPSocketParent)
@@ -151,35 +151,36 @@ TCPSocketParent::RecvResume()
 {
   NS_ENSURE_TRUE(mSocket, true);
   nsresult rv = mSocket->Resume();
   NS_ENSURE_SUCCESS(rv, true);
   return true;
 }
 
 bool
-TCPSocketParent::RecvData(const SendableData& aData)
+TCPSocketParent::RecvData(const SendableData& aData,
+                          const uint32_t& aTrackingNumber)
 {
   NS_ENSURE_TRUE(mIntermediary, true);
 
   nsresult rv;
   switch (aData.type()) {
     case SendableData::TArrayOfuint8_t: {
       AutoSafeJSContext cx;
       JSAutoRequest ar(cx);
       JS::Rooted<JS::Value> val(cx);
       JS::Rooted<JSObject*> obj(cx, mIntermediaryObj);
       IPC::DeserializeArrayBuffer(obj, aData.get_ArrayOfuint8_t(), &val);
-      rv = mIntermediary->SendArrayBuffer(val);
+      rv = mIntermediary->OnRecvSendArrayBuffer(val, aTrackingNumber);
       NS_ENSURE_SUCCESS(rv, true);
       break;
     }
 
     case SendableData::TnsString:
-      rv = mIntermediary->SendString(aData.get_nsString());
+      rv = mIntermediary->OnRecvSendString(aData.get_nsString(), aTrackingNumber);
       NS_ENSURE_SUCCESS(rv, true);
       break;
 
     default:
       MOZ_CRASH("unexpected SendableData type");
   }
   return true;
 }
@@ -189,19 +190,18 @@ TCPSocketParent::RecvClose()
 {
   NS_ENSURE_TRUE(mSocket, true);
   nsresult rv = mSocket->Close();
   NS_ENSURE_SUCCESS(rv, true);
   return true;
 }
 
 NS_IMETHODIMP
-TCPSocketParent::SendCallback(const nsAString& aType, const JS::Value& aDataVal,
-                              const nsAString& aReadyState, uint32_t aBuffered,
-                              JSContext* aCx)
+TCPSocketParent::SendEvent(const nsAString& aType, const JS::Value& aDataVal,
+                           const nsAString& aReadyState, JSContext* aCx)
 {
   if (!mIPCOpen) {
     NS_WARNING("Dropping callback due to no IPC connection");
     return NS_OK;
   }
 
   CallbackData data;
   if (aDataVal.isString()) {
@@ -250,30 +250,39 @@ TCPSocketParent::SendCallback(const nsAS
     }
   } else {
     NS_ERROR("Unexpected JS value encountered");
     FireInteralError(this, __LINE__);
     return NS_ERROR_FAILURE;
   }
   mozilla::unused <<
       PTCPSocketParent::SendCallback(nsString(aType), data,
-                                     nsString(aReadyState), aBuffered);
+                                     nsString(aReadyState));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TCPSocketParent::SetSocketAndIntermediary(nsIDOMTCPSocket *socket,
                                           nsITCPSocketIntermediary *intermediary,
                                           JSContext* cx)
 {
   mSocket = socket;
   mIntermediary = intermediary;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+TCPSocketParent::SendUpdateBufferedAmount(uint32_t aBufferedAmount,
+                                          uint32_t aTrackingNumber)
+{
+  mozilla::unused << PTCPSocketParent::SendUpdateBufferedAmount(aBufferedAmount,
+                                                                aTrackingNumber);
+  return NS_OK;
+}
+
 void
 TCPSocketParent::ActorDestroy(ActorDestroyReason why)
 {
   if (mSocket) {
     mSocket->Close();
   }
   mSocket = nullptr;
   mIntermediaryObj = nullptr;
--- a/dom/network/src/TCPSocketParent.h
+++ b/dom/network/src/TCPSocketParent.h
@@ -46,17 +46,18 @@ public:
 
   virtual bool RecvOpen(const nsString& aHost, const uint16_t& aPort,
                         const bool& useSSL, const nsString& aBinaryType);
 
   virtual bool RecvStartTLS() MOZ_OVERRIDE;
   virtual bool RecvSuspend() MOZ_OVERRIDE;
   virtual bool RecvResume() MOZ_OVERRIDE;
   virtual bool RecvClose() MOZ_OVERRIDE;
-  virtual bool RecvData(const SendableData& aData) MOZ_OVERRIDE;
+  virtual bool RecvData(const SendableData& aData,
+                        const uint32_t& aTrackingNumber) MOZ_OVERRIDE;
   virtual bool RecvRequestDelete() MOZ_OVERRIDE;
 
 private:
   virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
   JSObject* mIntermediaryObj;
 };
 
--- a/dom/network/src/TCPSocketParentIntermediary.js
+++ b/dom/network/src/TCPSocketParentIntermediary.js
@@ -15,37 +15,47 @@ function TCPSocketParentIntermediary() {
 
 TCPSocketParentIntermediary.prototype = {
   _setCallbacks: function(aParentSide, socket) {
     aParentSide.initJS(this);
     this._socket = socket;
 
     // Create handlers for every possible callback that attempt to trigger
     // corresponding callbacks on the child object.
-    ["open", "drain", "data", "error", "close"].forEach(
+    // ondrain event is not forwarded, since the decision of firing ondrain
+    // is made in child.
+    ["open", "data", "error", "close"].forEach(
       function(p) {
         socket["on" + p] = function(data) {
-          aParentSide.sendCallback(p, data.data, socket.readyState,
-                                   socket.bufferedAmount);
+          aParentSide.sendEvent(p, data.data, socket.readyState,
+                                socket.bufferedAmount);
         };
       }
     );
- },
+  },
+
+  _onUpdateBufferedAmountHandler: function(aParentSide, aBufferedAmount, aTrackingNumber) {
+    aParentSide.sendUpdateBufferedAmount(aBufferedAmount, aTrackingNumber);
+  },
 
   open: function(aParentSide, aHost, aPort, aUseSSL, aBinaryType, aAppId) {
     let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
     let socket = baseSocket.open(aHost, aPort, {useSecureTransport: aUseSSL, binaryType: aBinaryType});
     if (!socket)
       return null;
 
     let socketInternal = socket.QueryInterface(Ci.nsITCPSocketInternal);
     if (socketInternal) {
       socketInternal.setAppId(aAppId);
     }
 
+    // Handle parent's request to update buffered amount.
+    socketInternal.setOnUpdateBufferedAmountHandler(
+      this._onUpdateBufferedAmountHandler.bind(this, aParentSide));
+
     // Handlers are set to the JS-implemented socket object on the parent side.
     this._setCallbacks(aParentSide, socket);
     return socket;
   },
 
   listen: function(aTCPServerSocketParent, aLocalPort, aBacklog, aBinaryType) {
     let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
     let serverSocket = baseSocket.listen(aLocalPort, { binaryType: aBinaryType }, aBacklog);
@@ -74,22 +84,25 @@ TCPSocketParentIntermediary.prototype = 
 
         aTCPServerSocketParent.sendCallbackError(error.message, error.filename,
                                                  error.lineNumber, error.columnNumber);
     };
 
     return serverSocket;
   },
 
-  sendString: function(aData) {
-    return this._socket.send(aData);
+  onRecvSendString: function(aData, aTrackingNumber) {
+    let socketInternal = this._socket.QueryInterface(Ci.nsITCPSocketInternal);
+    return socketInternal.onRecvSendFromChild(aData, 0, 0, aTrackingNumber);
   },
 
-  sendArrayBuffer: function(aData) {
-    return this._socket.send(aData, 0, aData.byteLength);
+  onRecvSendArrayBuffer: function(aData, aTrackingNumber) {
+    let socketInternal = this._socket.QueryInterface(Ci.nsITCPSocketInternal);
+    return socketInternal.onRecvSendFromChild(aData, 0, aData.byteLength,
+                                              aTrackingNumber);
   },
 
   classID: Components.ID("{afa42841-a6cb-4a91-912f-93099f6a3d18}"),
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsITCPSocketIntermediary
   ])
 };
 
--- a/dom/network/tests/unit/test_tcpsocket.js
+++ b/dom/network/tests/unit/test_tcpsocket.js
@@ -75,16 +75,21 @@ Cu.import("resource://gre/modules/Servic
  */
 
 function get_platform() {
   var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
                               .getService(Components.interfaces.nsIXULRuntime);
   return xulRuntime.OS;
 }
 
+function is_content() {
+  return this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+                            .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
 /**
  * Spin up a listening socket and associate at most one live, accepted socket
  * with ourselves.
  */
 function TestServer() {
   this.listener = ServerSocket(-1, true, -1);
   do_print('server: listening on', this.listener.port);
   this.listener.asyncListen(this);
@@ -413,37 +418,55 @@ function badConnect() {
  * and buffering again causes ondrain to be fired again.
  */
 
 function drainTwice() {
   let yays = makeJointSuccess(
     ['ondrain', 'ondrain2',
     'ondata', 'ondata2',
     'serverclose', 'clientclose']);
+  let ondrainCalled = false,
+      ondataCalled = false;
 
-  function serverSideCallback() {
-    yays.ondata();
+  function maybeSendNextData() {
+    if (!ondrainCalled || !ondataCalled) {
+      // make sure server got data and client got ondrain.
+      return;
+    }
+
     server.ondata = makeExpectData(
       "ondata2", BIG_TYPED_ARRAY_2, false, yays.ondata2);
 
     sock.ondrain = yays.ondrain2;
 
     if (sock.send(BIG_ARRAY_BUFFER_2)) {
       do_throw("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering");
     }
 
     sock.close();
   }
 
+  function clientOndrain() {
+    yays.ondrain();
+    ondrainCalled = true;
+    maybeSendNextData();
+  }
+
+  function serverSideCallback() {
+    yays.ondata();
+    ondataCalled = true;
+    maybeSendNextData();
+  }
+
   server.onclose = yays.serverclose;
   server.ondata = makeExpectData(
     "ondata", BIG_TYPED_ARRAY, false, serverSideCallback);
 
   sock.onclose = yays.clientclose;
-  sock.ondrain = yays.ondrain;
+  sock.ondrain = clientOndrain;
 
   if (sock.send(BIG_ARRAY_BUFFER)) {
     throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
   }
 }
 
 function cleanup() {
   do_print("Cleaning up");
@@ -477,16 +500,72 @@ function bufferTwice() {
   if (sock.send(BIG_ARRAY_BUFFER)) {
     throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
   }
   if (sock.send(BIG_ARRAY_BUFFER_2)) {
     throw new Error("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering on second synchronous call to send");
   }
 }
 
+// Test child behavior when child thinks it's buffering but parent doesn't
+// buffer.
+// 1. set bufferedAmount of content socket to a value that will make next
+//    send() call return false.
+// 2. send a small data to make send() return false, but it won't make
+//    parent buffer.
+// 3. we should get a ondrain.
+function childbuffered() {
+  let yays = makeJointSuccess(['ondrain', 'serverdata',
+                               'clientclose', 'serverclose']);
+  sock.ondrain = function() {
+    yays.ondrain();
+    sock.close();
+  };
+
+  server.ondata = makeExpectData(
+    'ondata', DATA_ARRAY, false, yays.serverdata);
+
+  let internalSocket = sock.QueryInterface(Ci.nsITCPSocketInternal);
+  internalSocket.updateBufferedAmount(65535, // almost reach buffering threshold
+                                      0);
+  if (sock.send(DATA_ARRAY_BUFFER)) {
+    do_throw("expected sock.send to return false.");
+  }
+
+  sock.onclose = yays.clientclose;
+  server.onclose = yays.serverclose;
+}
+
+// Test child's behavior when send() of child return true but parent buffers
+// data.
+// 1. send BIG_ARRAY to make parent buffer. This would make child wait for
+//    drain as well.
+// 2. set child's bufferedAmount to zero, so child will no longer wait for
+//    drain but parent will dispatch a drain event.
+// 3. wait for 1 second, to make sure there's no ondrain event dispatched in
+//    child.
+function childnotbuffered() {
+  let yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']);
+  server.ondata = makeExpectData('ondata', BIG_ARRAY, false, yays.serverdata);
+  if (sock.send(BIG_ARRAY_BUFFER)) {
+    do_throw("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
+  }
+  let internalSocket = sock.QueryInterface(Ci.nsITCPSocketInternal);
+  internalSocket.updateBufferedAmount(0, // setting zero will clear waitForDrain in sock.
+                                      1);
+
+  // shouldn't get ondrain, even after parent have cleared its buffer.
+  sock.ondrain = makeFailureCase('drain');
+  sock.onclose = yays.clientclose;
+  server.onclose = yays.serverclose;
+  do_timeout(1000, function() {
+    sock.close();
+  });
+};
+
 // - connect, data and events work both ways
 add_test(connectSock);
 add_test(sendData);
 add_test(sendBig);
 add_test(receiveData);
 // - server closes on us
 add_test(serverCloses);
 
@@ -508,16 +587,24 @@ if (get_platform() !== "Darwin") {
 // send a buffer, get a drain, send a buffer, get a drain
 add_test(connectSock);
 add_test(drainTwice);
 
 // send a buffer, get a drain, send a buffer, get a drain
 add_test(connectSock);
 add_test(bufferTwice);
 
+if (is_content()) {
+  add_test(connectSock);
+  add_test(childnotbuffered);
+
+  add_test(connectSock);
+  add_test(childbuffered);
+}
+
 // clean up
 add_test(cleanup);
 
 function run_test() {
   if (!gInChild)
     Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true);
 
   server = new TestServer();
new file mode 100644
--- /dev/null
+++ b/dom/nfc/MozNdefRecord.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Copyright © 2013 Deutsche Telekom, Inc. */
+
+#include "MozNdefRecord.h"
+#include "mozilla/dom/MozNdefRecordBinding.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(MozNdefRecord, mWindow)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MozNdefRecord)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MozNdefRecord)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MozNdefRecord)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/* static */
+already_AddRefed<MozNdefRecord>
+MozNdefRecord::Constructor(const GlobalObject& aGlobal,
+                           uint8_t aTnf, const nsAString& aType,
+                           const nsAString& aId, const nsAString& aPayload,
+                           ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!win) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+  nsRefPtr<MozNdefRecord> ndefrecord =
+    new MozNdefRecord(win, aTnf, aType, aId, aPayload);
+  return ndefrecord.forget();
+}
+
+MozNdefRecord::MozNdefRecord(nsPIDOMWindow* aWindow,
+                             uint8_t aTnf, const nsAString& aType,
+                             const nsAString& aId, const nsAString& aPayload)
+  : mTnf(aTnf)
+  , mType(aType)
+  , mId(aId)
+  , mPayload(aPayload)
+{
+  mWindow = aWindow;
+  SetIsDOMBinding();
+}
+
+MozNdefRecord::~MozNdefRecord()
+{
+}
+
+JSObject*
+MozNdefRecord::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return MozNdefRecordBinding::Wrap(aCx, aScope, this);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/nfc/MozNdefRecord.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Copyright © 2013 Deutsche Telekom, Inc. */
+
+#ifndef mozilla_dom_MozNdefRecord_h__
+#define mozilla_dom_MozNdefRecord_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "jsapi.h"
+
+#include "nsIDocument.h"
+
+struct JSContext;
+
+namespace mozilla {
+namespace dom {
+
+class MozNdefRecord MOZ_FINAL : public nsISupports,
+                                public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MozNdefRecord)
+
+public:
+
+  MozNdefRecord(nsPIDOMWindow* aWindow,
+                uint8_t aTnf, const nsAString& aType,
+                const nsAString& aId, const nsAString& aPlayload);
+
+  ~MozNdefRecord();
+
+  nsIDOMWindow* GetParentObject() const
+  {
+    return mWindow;
+  }
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  static already_AddRefed<MozNdefRecord> Constructor(
+                                           const GlobalObject& aGlobal,
+                                           uint8_t aTnf, const nsAString& aType,
+                                           const nsAString& aId,
+                                           const nsAString& aPayload,
+                                           ErrorResult& aRv);
+
+  uint8_t Tnf() const
+  {
+    return mTnf;
+  }
+
+  void GetType(nsString& aType) const
+  {
+    aType = mType;
+  }
+
+  void GetId(nsString& aId) const
+  {
+    aId = mId;
+  }
+
+  void GetPayload(nsString& aPayload) const
+  {
+    aPayload = mPayload;
+  }
+
+private:
+  MozNdefRecord() MOZ_DELETE;
+  nsRefPtr<nsPIDOMWindow> mWindow;
+
+  uint8_t mTnf;
+  nsString mType;
+  nsString mId;
+  nsString mPayload;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MozNdefRecord_h__
new file mode 100644
--- /dev/null
+++ b/dom/nfc/moz.build
@@ -0,0 +1,25 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright © 2013 Deutsche Telekom, Inc.
+
+if CONFIG['MOZ_NFC']:
+    MODULE = 'dom'
+    EXPORTS.mozilla.dom += [
+        'MozNdefRecord.h',
+    ]
+    SOURCES += [
+        'MozNdefRecord.cpp',
+    ]
+    EXTRA_COMPONENTS += [
+      'nsNfc.js',
+      'nsNfc.manifest',
+    ]
+
+FAIL_ON_WARNINGS = True
+
+LIBRARY_NAME = 'dom_nfc_s'
+
+LIBXUL_LIBRARY = True
new file mode 100644
--- /dev/null
+++ b/dom/nfc/nsNfc.js
@@ -0,0 +1,178 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Copyright © 2013, Deutsche Telekom, Inc. */
+
+"use strict";
+
+const DEBUG = false;
+function debug(s) {
+  if (DEBUG) dump("-*- Nfc DOM: " + s + "\n");
+}
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/ObjectWrapper.jsm");
+
+/**
+ * NFCTag
+ */
+function MozNFCTag() {
+  debug("In MozNFCTag Constructor");
+  this._nfcContentHelper = Cc["@mozilla.org/nfc/content-helper;1"]
+                             .getService(Ci.nsINfcContentHelper);
+  this.session = null;
+  // Map WebIDL declared enum map names to integer
+  this._techTypesMap = [];
+  this._techTypesMap['NFC_A'] = 0;
+  this._techTypesMap['NFC_B'] = 1;
+  this._techTypesMap['NFC_ISO_DEP'] = 2;
+  this._techTypesMap['NFC_F'] = 3;
+  this._techTypesMap['NFC_V'] = 4;
+  this._techTypesMap['NDEF'] = 5;
+  this._techTypesMap['NDEF_FORMATABLE'] = 6;
+  this._techTypesMap['MIFARE_CLASSIC'] = 7;
+  this._techTypesMap['MIFARE_ULTRALIGHT'] = 8;
+  this._techTypesMap['NFC_BARCODE'] = 9;
+  this._techTypesMap['P2P'] = 10;
+}
+MozNFCTag.prototype = {
+  _nfcContentHelper: null,
+  _window: null,
+
+  initialize: function(aWindow, aSessionToken) {
+    this._window = aWindow;
+    this.setSessionToken(aSessionToken);
+  },
+
+  // ChromeOnly interface
+  setSessionToken: function setSessionToken(aSessionToken) {
+    debug("Setting session token.");
+    this.session = aSessionToken;
+    // report to NFC worker:
+    this._nfcContentHelper.setSessionToken(aSessionToken);
+  },
+
+  _techTypesMap: null,
+
+  // NFCTag interface:
+  getDetailsNDEF: function getDetailsNDEF() {
+    return this._nfcContentHelper.getDetailsNDEF(this._window, this.session);
+  },
+  readNDEF: function readNDEF() {
+    return this._nfcContentHelper.readNDEF(this._window, this.session);
+  },
+  writeNDEF: function writeNDEF(records) {
+    return this._nfcContentHelper.writeNDEF(this._window, records, this.session);
+  },
+  makeReadOnlyNDEF: function makeReadOnlyNDEF() {
+    return this._nfcContentHelper.makeReadOnlyNDEF(this._window, this.session);
+  },
+  connect: function connect(enum_tech_type) {
+    let int_tech_type = this._techTypesMap[enum_tech_type];
+    return this._nfcContentHelper.connect(this._window, int_tech_type, this.session);
+  },
+  close: function close() {
+    return this._nfcContentHelper.close(this._window, this.session);
+  },
+
+  classID: Components.ID("{4e1e2e90-3137-11e3-aa6e-0800200c9a66}"),
+  contractID: "@mozilla.org/nfc/NFCTag;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+                                         Ci.nsIDOMGlobalPropertyInitializer]),
+};
+
+/**
+ * NFCPeer
+ */
+function MozNFCPeer() {
+  debug("In MozNFCPeer Constructor");
+  this._nfcContentHelper = Cc["@mozilla.org/nfc/content-helper;1"]
+                             .getService(Ci.nsINfcContentHelper);
+  this.session = null;
+}
+MozNFCPeer.prototype = {
+  _nfcContentHelper: null,
+  _window: null,
+
+  initialize: function(aWindow, aSessionToken) {
+    this._window = aWindow;
+    this.setSessionToken(aSessionToken);
+  },
+
+  // ChromeOnly interface
+  setSessionToken: function setSessionToken(aSessionToken) {
+    debug("Setting session token.");
+    this.session = aSessionToken;
+    // report to NFC worker:
+    return this._nfcContentHelper.setSessionToken(aSessionToken);
+  },
+
+  // NFCPeer interface:
+  sendNDEF: function sendNDEF(records) {
+    // Just forward sendNDEF to writeNDEF
+    return this._nfcContentHelper.writeNDEF(this._window, records);
+  },
+
+  classID: Components.ID("{c1b2bcf0-35eb-11e3-aa6e-0800200c9a66}"),
+  contractID: "@mozilla.org/nfc/NFCPeer;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+                                         Ci.nsIDOMGlobalPropertyInitializer]),
+};
+
+/**
+ * Navigator NFC object
+ */
+function mozNfc() {
+  debug("In mozNfc Constructor");
+}
+mozNfc.prototype = {
+  _nfcContentHelper: null,
+  _window: null,
+  _wrap: function _wrap(obj) {
+    return ObjectWrapper.wrap(obj, this._window);
+  },
+
+  init: function init(aWindow) {
+    debug("mozNfc init called");
+    this._window = aWindow;
+  },
+
+  getNFCTag: function getNFCTag(sessionToken) {
+    let obj = new MozNFCTag();
+    let nfcTag = this._window.MozNFCTag._create(this._window, obj);
+    if (nfcTag) {
+      obj.initialize(this._window, sessionToken);
+      return nfcTag;
+    } else {
+      debug("Error: Unable to create NFCTag");
+      return null;
+    }
+  },
+
+  getNFCPeer: function getNFCPeer(sessionToken) {
+    let obj = new MozNFCPeer();
+    let nfcPeer = this._window.MozNFCTag._create(this._window, obj);
+    if (nfcPeer) {
+      obj.initialize(this._window, sessionToken);
+      return nfcPeer;
+    } else {
+      debug("Error: Unable to create NFCPeer");
+      return null;
+    }
+  },
+
+  // get/set onpeerfound/lost onforegrounddispatch
+
+  classID: Components.ID("{6ff2b290-2573-11e3-8224-0800200c9a66}"),
+  contractID: "@mozilla.org/navigatorNfc;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+                                         Ci.nsIDOMGlobalPropertyInitializer]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MozNFCTag, MozNFCPeer, mozNfc]);
new file mode 100644
--- /dev/null
+++ b/dom/nfc/nsNfc.manifest
@@ -0,0 +1,8 @@
+component {6ff2b290-2573-11e3-8224-0800200c9a66} nsNfc.js
+contract @mozilla.org/navigatorNfc;1 {6ff2b290-2573-11e3-8224-0800200c9a66}
+
+component {4e1e2e90-3137-11e3-aa6e-0800200c9a66} nsNfc.js
+contract @mozilla.org/nfc/NFCTag;1 {4e1e2e90-3137-11e3-aa6e-0800200c9a66}
+
+component {c1b2bcf0-35eb-11e3-aa6e-0800200c9a66} nsNfc.js
+contract @mozilla.org/nfc/NFCPeer;1 {c1b2bcf0-35eb-11e3-aa6e-0800200c9a66}
--- a/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html
+++ b/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html
@@ -5,16 +5,18 @@
     <meta><charset="utf-8"/>
     <title>Test Refreshing navigator.plugins (bug 820708)</title>
     <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
     <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
     <script type="application/javascript" src="utils.js"></script>
   </head>
   <body>
     <script class="testbody" type="application/javascript">
+      "use strict";
+
       SimpleTest.waitForExplicitFinish();
       setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
 
       var pluginHost = Components.classes["@mozilla.org/plugin/host;1"]
                        .getService(Components.interfaces.nsIPluginHost);
       var pluginTags = pluginHost.getPluginTags();
       var nextTest = null;
       var obsService = Components.classes["@mozilla.org/observer-service;1"]
@@ -23,44 +25,45 @@
         observe: function(aSubject, aTopic, aData) {
           if (aTopic == "plugin-info-updated") {
             SimpleTest.executeSoon(nextTest);
           }
         }
       };
       obsService.addObserver(observer, "plugin-info-updated", false);
 
-      var navTestPlugin = navigator.plugins.namedItem("Test Plug-in");
-      ok(navTestPlugin, "navigator.plugins should have Test Plug-in");
+      var navTestPlugin1 = navigator.plugins.namedItem("Test Plug-in");
+      ok(navTestPlugin1, "navigator.plugins should have Test Plug-in");
       var tagTestPlugin = null;
       for (var plugin of pluginTags) {
-        if (plugin.name == navTestPlugin.name) {
+        if (plugin.name == navTestPlugin1.name) {
           tagTestPlugin = plugin;
           break;
         }
       }
       ok(tagTestPlugin, "plugin tags should have Test Plug-in");
       var mimeType = tagTestPlugin.getMimeTypes()[0];
       ok(mimeType, "should have a MIME type for Test Plug-in");
       ok(navigator.mimeTypes[mimeType], "navigator.mimeTypes should have an entry for '" + mimeType + "'");
       ok(!tagTestPlugin.disabled, "test plugin should not be disabled");
 
       nextTest = testPart2;
       tagTestPlugin.enabledState = Components.interfaces.nsIPluginTag.STATE_DISABLED;
 
       function testPart2() {
-        var navTestPlugin = navigator.plugins.namedItem("Test Plug-in");
-        ok(!navTestPlugin, "now navigator.plugins should not have Test Plug-in");
+        var navTestPlugin2 = navigator.plugins.namedItem("Test Plug-in");
+        ok(!navTestPlugin2, "now navigator.plugins should not have Test Plug-in");
         ok(!navigator.mimeTypes[mimeType], "now navigator.mimeTypes should not have an entry for '" + mimeType + "'");
 
         nextTest = testPart3;
         tagTestPlugin.enabledState = Components.interfaces.nsIPluginTag.STATE_ENABLED;
       }
 
       function testPart3() {
-        ok(navTestPlugin, "now navigator.plugins should have Test Plug-in again");
+        var navTestPlugin3 = navigator.plugins.namedItem("Test Plug-in");
+        ok(navTestPlugin3, "now navigator.plugins should have Test Plug-in again");
         ok(navigator.mimeTypes[mimeType], "now navigator.mimeTypes should have an entry for '" + mimeType + "' again");
         obsService.removeObserver(observer, "plugin-info-updated");
         SimpleTest.finish();
       }
     </script>
   </body>
 </html>
--- a/dom/push/src/PushService.jsm
+++ b/dom/push/src/PushService.jsm
@@ -1429,18 +1429,18 @@ this.PushService = {
       return;
     }
 
     if (!prefs.get("udp.wakeupEnabled")) {
       debug("UDP support disabled");
       return;
     }
 
-    this._udpServer = Cc["@mozilla.org/network/socket-udp;1"]
-                        .createInstance(Ci.nsIUDPServerSocket);
+    this._udpServer = Cc["@mozilla.org/network/udp-socket;1"]
+                        .createInstance(Ci.nsIUDPSocket);
     this._udpServer.init(-1, false);
     this._udpServer.asyncListen(this);
     debug("listenForUDPWakeup listening on " + this._udpPort);
 
     return this._udpServer.port;
   },
 
   /**
--- a/dom/src/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/src/jsurl/nsJSProtocolHandler.cpp
@@ -235,16 +235,17 @@ nsresult nsJSThunk::EvaluateScript(nsICh
     if (NS_FAILED(rv))
         return rv;
 
     bool useSandbox =
         (aExecutionPolicy == nsIScriptChannel::EXECUTE_IN_SANDBOX);
 
     AutoPushJSContext cx(scriptContext->GetNativeContext());
     JS::Rooted<JSObject*> globalJSObject(cx, innerGlobal->GetGlobalJSObject());
+    NS_ENSURE_TRUE(globalJSObject, NS_ERROR_UNEXPECTED);
 
     if (!useSandbox) {
         //-- Don't outside a sandbox unless the script principal subsumes the
         //   principal of the context.
         nsIPrincipal* objectPrincipal = nsContentUtils::GetObjectPrincipal(globalJSObject);
 
         bool subsumes;
         rv = principal->Subsumes(objectPrincipal, &subsumes);
@@ -259,23 +260,17 @@ nsresult nsJSThunk::EvaluateScript(nsICh
     if (useSandbox) {
         // We were asked to use a sandbox, or the channel owner isn't allowed
         // to execute in this context.  Evaluate the javascript URL in a
         // sandbox to prevent it from accessing data it doesn't have
         // permissions to access.
 
         // First check to make sure it's OK to evaluate this script to
         // start with.  For example, script could be disabled.
-        bool ok;
-        rv = securityManager->CanExecuteScripts(cx, principal, &ok);
-        if (NS_FAILED(rv)) {
-            return rv;
-        }
-
-        if (!ok) {
+        if (!securityManager->ScriptAllowed(globalJSObject)) {
             // Treat this as returning undefined from the script.  That's what
             // nsJSContext does.
             return NS_ERROR_DOM_RETVAL_UNDEFINED;
         }
 
         nsIXPConnect *xpc = nsContentUtils::XPConnect();
 
         nsCOMPtr<nsIXPConnectJSObjectHolder> sandbox;
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/Nfc.js
@@ -0,0 +1,486 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Copyright © 2013, Deutsche Telekom, Inc. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+let NFC = {};
+Cu.import("resource://gre/modules/nfc_consts.js", NFC);
+
+// set to true in nfc_consts.js to see debug messages
+let DEBUG = NFC.DEBUG_NFC;
+
+let debug;
+if (DEBUG) {
+  debug = function (s) {
+    dump("-*- Nfc: " + s + "\n");
+  };
+} else {
+  debug = function (s) {};
+}
+
+const NFC_CONTRACTID = "@mozilla.org/nfc;1";
+const NFC_CID =
+  Components.ID("{2ff24790-5e74-11e1-b86c-0800200c9a66}");
+
+const NFC_IPC_MSG_NAMES = [
+  "NFC:SetSessionToken",
+  "NFC:ReadNDEF",
+  "NFC:WriteNDEF",
+  "NFC:GetDetailsNDEF",
+  "NFC:MakeReadOnlyNDEF",
+  "NFC:Connect",
+  "NFC:Close"
+];
+
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+                                   "@mozilla.org/parentprocessmessagemanager;1",
+                                   "nsIMessageBroadcaster");
+XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
+                                   "@mozilla.org/system-message-internal;1",
+                                   "nsISystemMessagesInternal");
+XPCOMUtils.defineLazyServiceGetter(this, "gSystemWorkerManager",
+                                   "@mozilla.org/telephony/system-worker-manager;1",
+                                   "nsISystemWorkerManager");
+XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
+                                   "@mozilla.org/settingsService;1",
+                                   "nsISettingsService");
+XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
+                                    "@mozilla.org/uuid-generator;1",
+                                    "nsIUUIDGenerator");
+XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
+  return {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
+                                           Ci.nsIObserver]),
+
+    nfc: null,
+
+    // Manage message targets in terms of sessionToken. Only the authorized and
+    // registered contents can receive related messages.
+    targetsBySessionTokens: {},
+    sessionTokens: [],
+
+    init: function init(nfc) {
+      this.nfc = nfc;
+
+      Services.obs.addObserver(this, "xpcom-shutdown", false);
+      this._registerMessageListeners();
+    },
+
+    _shutdown: function _shutdown() {
+      this.nfc = null;
+
+      Services.obs.removeObserver(this, "xpcom-shutdown");
+      this._unregisterMessageListeners();
+    },
+
+    _registerMessageListeners: function _registerMessageListeners() {
+      ppmm.addMessageListener("child-process-shutdown", this);
+      for (let msgname of NFC_IPC_MSG_NAMES) {
+        ppmm.addMessageListener(msgname, this);
+      }
+    },
+
+    _unregisterMessageListeners: function _unregisterMessageListeners() {
+      ppmm.removeMessageListener("child-process-shutdown", this);
+      for (let msgname of NFC_IPC_MSG_NAMES) {
+        ppmm.removeMessageListener(msgname, this);
+      }
+      ppmm = null;
+    },
+
+    _registerMessageTarget: function _registerMessageTarget(sessionToken, target) {
+      let targets = this.targetsBySessionTokens[sessionToken];
+      if (!targets) {
+        targets = this.targetsBySessionTokens[sessionToken] = [];
+        let list = this.sessionTokens;
+        if (list.indexOf(sessionToken) == -1) {
+          list.push(sessionToken);
+        }
+      }
+
+      if (targets.indexOf(target) != -1) {
+        debug("Already registered this target!");
+        return;
+      }
+
+      targets.push(target);
+      debug("Registered :" + sessionToken + " target: " + target);
+    },
+
+    _unregisterMessageTarget: function _unregisterMessageTarget(sessionToken, target) {
+      if (sessionToken == null) {
+        // Unregister the target for every sessionToken when no sessionToken is specified.
+        for (let session of this.sessionTokens) {
+          this._unregisterMessageTarget(session, target);
+        }
+        return;
+      }
+
+      // Unregister the target for a specified sessionToken.
+      let targets = this.targetsBySessionTokens[sessionToken];
+      if (!targets) {
+        return;
+      }
+
+      if (target == null) {
+        debug("Unregistered all targets for the " + sessionToken + " targets: " + targets);
+        targets = [];
+        let list = this.sessionTokens;
+        if (sessionToken !== null) {
+          let index = list.indexOf(sessionToken);
+          if (index > -1) {
+            list.splice(index, 1);
+          }
+        }
+        return;
+      }
+
+      let index = targets.indexOf(target);
+      if (index != -1) {
+        targets.splice(index, 1);
+      }
+    },
+
+    _sendTargetMessage: function _sendTargetMessage(sessionToken, message, options) {
+      let targets = this.targetsBySessionTokens[sessionToken];
+      if (!targets) {
+        return;
+      }
+
+      for (let target of targets) {
+        target.sendAsyncMessage(message, options);
+      }
+    },
+
+    /**
+     * nsIMessageListener interface methods.
+     */
+
+    receiveMessage: function receiveMessage(msg) {
+      debug("Received '" + msg.name + "' message from content process");
+      if (msg.name == "child-process-shutdown") {
+        // By the time we receive child-process-shutdown, the child process has
+        // already forgotten its permissions so we need to unregister the target
+        // for every permission.
+        this._unregisterMessageTarget(null, msg.target);
+        return null;
+      }
+
+      if (NFC_IPC_MSG_NAMES.indexOf(msg.name) != -1) {
+        if (!msg.target.assertPermission("nfc-read")) {
+          debug("Nfc message " + msg.name +
+                " from a content process with no 'nfc-read' privileges.");
+          return null;
+        }
+      } else {
+        debug("Ignoring unknown message type: " + msg.name);
+        return null;
+      }
+
+      switch (msg.name) {
+        case "NFC:SetSessionToken":
+          this._registerMessageTarget(this.nfc.sessionTokenMap[this.nfc._currentSessionId], msg.target);
+          debug("Registering target for this SessionToken : " +
+                this.nfc.sessionTokenMap[this.nfc._currentSessionId]);
+          return null;
+      }
+
+      return null;
+    },
+
+    /**
+     * nsIObserver interface methods.
+     */
+
+    observe: function observe(subject, topic, data) {
+      switch (topic) {
+        case "xpcom-shutdown":
+          this._shutdown();
+          break;
+      }
+    },
+
+    sendNfcResponseMessage: function sendNfcResponseMessage(message, data) {
+      this._sendTargetMessage(this.nfc.sessionTokenMap[this.nfc._currentSessionId], message, data);
+    },
+  };
+});
+
+function Nfc() {
+  debug("Starting Worker");
+  this.worker = new ChromeWorker("resource://gre/modules/nfc_worker.js");
+  this.worker.onerror = this.onerror.bind(this);
+  this.worker.onmessage = this.onmessage.bind(this);
+
+  for each (let msgname in NFC_IPC_MSG_NAMES) {
+    ppmm.addMessageListener(msgname, this);
+  }
+
+  Services.obs.addObserver(this, NFC.TOPIC_MOZSETTINGS_CHANGED, false);
+  Services.obs.addObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN, false);
+
+  gMessageManager.init(this);
+  let lock = gSettingsService.createLock();
+  lock.get(NFC.SETTING_NFC_POWER_LEVEL, this);
+  lock.get(NFC.SETTING_NFC_ENABLED, this);
+  // Maps sessionId (that are generated from nfcd) with a unique guid : 'SessionToken'
+  this.sessionTokenMap = {};
+
+  gSystemWorkerManager.registerNfcWorker(this.worker);
+}
+
+Nfc.prototype = {
+
+  classID:   NFC_CID,
+  classInfo: XPCOMUtils.generateCI({classID: NFC_CID,
+                                    classDescription: "Nfc",
+                                    interfaces: [Ci.nsIWorkerHolder]}),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder,
+                                         Ci.nsIObserver,
+                                         Ci.nsISettingsServiceCallback]),
+
+  _currentSessionId: null,
+  _enabled: false,
+
+  onerror: function onerror(event) {
+    debug("Got an error: " + event.filename + ":" +
+          event.lineno + ": " + event.message + "\n");
+    event.preventDefault();
+  },
+
+  /**
+   * Send arbitrary message to worker.
+   *
+   * @param nfcMessageType
+   *        A text message type.
+   * @param message [optional]
+   *        An optional message object to send.
+   */
+  sendToWorker: function sendToWorker(nfcMessageType, message) {
+    message = message || {};
+    message.type = nfcMessageType;
+    this.worker.postMessage(message);
+  },
+
+  /**
+   * Send Error response to content.
+   *
+   * @param message
+   *        An nsIMessageListener's message parameter.
+   */
+  sendNfcErrorResponse: function sendNfcErrorResponse(message) {
+    if (!message.target) {
+      return;
+    }
+
+    let nfcMsgType = message.name + "Response";
+    message.target.sendAsyncMessage(nfcMsgType, {
+      sessionId: message.json.sessionToken,
+      requestId: message.json.requestId,
+      status: NFC.GECKO_NFC_ERROR_GENERIC_FAILURE
+    });
+  },
+
+  /**
+   * Process the incoming message from the NFC worker
+   */
+  onmessage: function onmessage(event) {
+    let message = event.data;
+    debug("Received message from NFC worker: " + JSON.stringify(message));
+
+    switch (message.type) {
+      case "techDiscovered":
+        this._currentSessionId = message.sessionId;
+        // Check if the session token already exists. If exists, continue to use the same one.
+        // If not, generate a new token.
+        if (!this.sessionTokenMap[this._currentSessionId]) {
+          this.sessionTokenMap[this._currentSessionId] = UUIDGenerator.generateUUID().toString();
+        }
+        // Update the upper layers with a session token (alias)
+        message.sessionToken = this.sessionTokenMap[this._currentSessionId];
+        // Do not expose the actual session to the content
+        delete message.sessionId;
+        gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", message);
+        break;
+      case "techLost":
+        gMessageManager._unregisterMessageTarget(this.sessionTokenMap[this._currentSessionId], null);
+        // Update the upper layers with a session token (alias)
+        message.sessionToken = this.sessionTokenMap[this._currentSessionId];
+        // Do not expose the actual session to the content
+        delete message.sessionId;
+        gSystemMessenger.broadcastMessage("nfc-manager-tech-lost", message);
+        delete this.sessionTokenMap[this._currentSessionId];
+        this._currentSessionId = null;
+        break;
+     case "ConfigResponse":
+        gSystemMessenger.broadcastMessage("nfc-powerlevel-change", message);
+        break;
+      case "ConnectResponse": // Fall through.
+      case "CloseResponse":
+      case "GetDetailsNDEFResponse":
+      case "ReadNDEFResponse":
+      case "MakeReadOnlyNDEFResponse":
+      case "WriteNDEFResponse":
+        message.sessionToken = this.sessionTokenMap[this._currentSessionId];
+        // Do not expose the actual session to the content
+        delete message.sessionId;
+        gMessageManager.sendNfcResponseMessage("NFC:" + message.type, message);
+        break;
+      default:
+        throw new Error("Don't know about this message type: " + message.type);
+    }
+  },
+
+  // nsINfcWorker
+  worker: null,
+
+  powerLevel: NFC.NFC_POWER_LEVEL_DISABLED,
+
+  sessionTokenMap: null,
+
+  /**
+   * Process a message from the content process.
+   */
+  receiveMessage: function receiveMessage(message) {
+    debug("Received '" + JSON.stringify(message) + "' message from content process");
+
+    if (!this._enabled) {
+      debug("NFC is not enabled.");
+      this.sendNfcErrorResponse(message);
+      return null;
+    }
+
+    // Enforce bare minimums for NFC permissions
+    switch (message.name) {
+      case "NFC:Connect": // Fall through
+      case "NFC:Close":
+      case "NFC:GetDetailsNDEF":
+      case "NFC:ReadNDEF":
+        if (!message.target.assertPermission("nfc-read")) {
+          debug("NFC message " + message.name +
+                " from a content process with no 'nfc-read' privileges.");
+          this.sendNfcErrorResponse(message);
+          return null;
+        }
+        break;
+      case "NFC:WriteNDEF": // Fall through
+      case "NFC:MakeReadOnlyNDEF":
+        if (!message.target.assertPermission("nfc-write")) {
+          debug("NFC message " + message.name +
+                " from a content process with no 'nfc-write' privileges.");
+          this.sendNfcErrorResponse(message);
+          return null;
+        }
+        break;
+      case "NFC:SetSessionToken":
+        //Do nothing here. No need to process this message further
+        return null;
+    }
+
+    // Sanity check on sessionId
+    if (message.json.sessionToken !== this.sessionTokenMap[this._currentSessionId]) {
+      debug("Invalid Session Token: " + message.json.sessionToken +
+            " Expected Session Token: " + this.sessionTokenMap[this._currentSessionId]);
+      this.sendNfcErrorResponse(message);
+      return null;
+    }
+
+    // Update the current sessionId before sending to the worker
+    message.sessionId = this._currentSessionId;
+
+    switch (message.name) {
+      case "NFC:GetDetailsNDEF":
+        this.sendToWorker("getDetailsNDEF", message.json);
+        break;
+      case "NFC:ReadNDEF":
+        this.sendToWorker("readNDEF", message.json);
+        break;
+      case "NFC:WriteNDEF":
+        this.sendToWorker("writeNDEF", message.json);
+        break;
+      case "NFC:MakeReadOnlyNDEF":
+        this.sendToWorker("makeReadOnlyNDEF", message.json);
+        break;
+      case "NFC:Connect":
+        this.sendToWorker("connect", message.json);
+        break;
+      case "NFC:Close":
+        this.sendToWorker("close", message.json);
+        break;
+      default:
+        debug("UnSupported : Message Name " + message.name);
+        return null;
+    }
+  },
+
+  /**
+   * nsISettingsServiceCallback
+   */
+
+  handle: function handle(aName, aResult) {
+    switch(aName) {
+      case NFC.SETTING_NFC_ENABLED:
+        debug("'nfc.enabled' is now " + aResult);
+        this._enabled = aResult;
+        // General power setting
+        let powerLevel = this._enabled ? NFC.NFC_POWER_LEVEL_ENABLED :
+                                         NFC.NFC_POWER_LEVEL_DISABLED;
+        // Only if the value changes, set the power config and persist
+        if (powerLevel !== this.powerLevel) {
+          debug("New Power Level " + powerLevel);
+          this.setConfig({powerLevel: powerLevel});
+          this.powerLevel = powerLevel;
+        }
+        break;
+    }
+  },
+
+  /**
+   * nsIObserver
+   */
+
+  observe: function observe(subject, topic, data) {
+    switch (topic) {
+      case NFC.TOPIC_XPCOM_SHUTDOWN:
+        for each (let msgname in NFC_IPC_MSG_NAMES) {
+          ppmm.removeMessageListener(msgname, this);
+        }
+        ppmm = null;
+        Services.obs.removeObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN);
+        break;
+      case NFC.TOPIC_MOZSETTINGS_CHANGED:
+        let setting = JSON.parse(data);
+        if (setting) {
+          let setting = JSON.parse(data);
+          this.handle(setting.key, setting.value);
+        }
+        break;
+    }
+  },
+
+  setConfig: function setConfig(prop) {
+    this.sendToWorker("config", prop);
+  }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Nfc]);
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/Nfc.manifest
@@ -0,0 +1,15 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright © 2013 Deutsche Telekom, Inc.
+
+# Nfc.js
+component {2ff24790-5e74-11e1-b86c-0800200c9a66} Nfc.js
+contract @mozilla.org/nfc;1 {2ff24790-5e74-11e1-b86c-0800200c9a66}
+category profile-after-change Nfc @mozilla.org/nfc;1
+
+# NfcContentHelper.js
+component {4d72c120-da5f-11e1-9b23-0800200c9a66} NfcContentHelper.js
+contract @mozilla.org/nfc/content-helper;1 {4d72c120-da5f-11e1-9b23-0800200c9a66}
+category profile-after-change NfcContentHelper @mozilla.org/nfc/content-helper;1
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/NfcContentHelper.js
@@ -0,0 +1,314 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Copyright © 2013, Deutsche Telekom, Inc. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+
+let NFC = {};
+Cu.import("resource://gre/modules/nfc_consts.js", NFC);
+
+// set to true to in nfc_consts.js to see debug messages
+let DEBUG = NFC.DEBUG_CONTENT_HELPER;
+
+let debug;
+if (DEBUG) {
+  debug = function (s) {
+    dump("-*- NfcContentHelper: " + s + "\n");
+  };
+} else {
+  debug = function (s) {};
+}
+
+const NFCCONTENTHELPER_CID =
+  Components.ID("{4d72c120-da5f-11e1-9b23-0800200c9a66}");
+
+const NFC_IPC_MSG_NAMES = [
+  "NFC:ReadNDEFResponse",
+  "NFC:WriteNDEFResponse",
+  "NFC:GetDetailsNDEFResponse",
+  "NFC:MakeReadOnlyNDEFResponse",
+  "NFC:ConnectResponse",
+  "NFC:CloseResponse"
+];
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+                                   "@mozilla.org/childprocessmessagemanager;1",
+                                   "nsISyncMessageSender");
+
+function NfcContentHelper() {
+  this.initDOMRequestHelper(/* aWindow */ null, NFC_IPC_MSG_NAMES);
+  Services.obs.addObserver(this, "xpcom-shutdown", false);
+
+  this._requestMap = [];
+}
+
+NfcContentHelper.prototype = {
+  __proto__: DOMRequestIpcHelper.prototype,
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsINfcContentHelper,
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver]),
+  classID:   NFCCONTENTHELPER_CID,
+  classInfo: XPCOMUtils.generateCI({
+    classID:          NFCCONTENTHELPER_CID,
+    classDescription: "NfcContentHelper",
+    interfaces:       [Ci.nsINfcContentHelper]
+  }),
+
+  _requestMap: null,
+
+  /* TODO: Bug 815526: This is a limitation when a DOMString is used in sequences of Moz DOM Objects.
+   *       Strings such as 'type', 'id' 'payload' will not be acccessible to NfcWorker.
+   *       Therefore this function exists till the bug is addressed.
+   */
+  encodeNdefRecords: function encodeNdefRecords(records) {
+    let encodedRecords = [];
+    for (let i = 0; i < records.length; i++) {
+      let record = records[i];
+      encodedRecords.push({
+        tnf: record.tnf,
+        type: record.type,
+        id: record.id,
+        payload: record.payload,
+      });
+    }
+    return encodedRecords;
+  },
+
+  // NFC interface:
+  setSessionToken: function setSessionToken(sessionToken) {
+    if (sessionToken == null) {
+      throw Components.Exception("No session token!",
+                                  Cr.NS_ERROR_UNEXPECTED);
+      return;
+    }
+    // Report session to Nfc.js only.
+    cpmm.sendAsyncMessage("NFC:SetSessionToken", {
+      sessionToken: sessionToken,
+    });
+  },
+
+  // NFCTag interface
+  getDetailsNDEF: function getDetailsNDEF(window, sessionToken) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+    let request = Services.DOMRequest.createRequest(window);
+    let requestId = btoa(this.getRequestId(request));
+    this._requestMap[requestId] = window;
+
+    cpmm.sendAsyncMessage("NFC:GetDetailsNDEF", {
+      requestId: requestId,
+      sessionToken: sessionToken
+    });
+    return request;
+  },
+
+  readNDEF: function readNDEF(window, sessionToken) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+    let request = Services.DOMRequest.createRequest(window);
+    let requestId = btoa(this.getRequestId(request));
+    this._requestMap[requestId] = window;
+
+    cpmm.sendAsyncMessage("NFC:ReadNDEF", {
+      requestId: requestId,
+      sessionToken: sessionToken
+    });
+    return request;
+  },
+
+  writeNDEF: function writeNDEF(window, records, sessionToken) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+
+    let request = Services.DOMRequest.createRequest(window);
+    let requestId = btoa(this.getRequestId(request));
+    this._requestMap[requestId] = window;
+
+    let encodedRecords = this.encodeNdefRecords(records);
+
+    cpmm.sendAsyncMessage("NFC:WriteNDEF", {
+      requestId: requestId,
+      sessionToken: sessionToken,
+      records: encodedRecords
+    });
+    return request;
+  },
+
+  makeReadOnlyNDEF: function makeReadOnlyNDEF(window, sessionToken) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+
+    let request = Services.DOMRequest.createRequest(window);
+    let requestId = btoa(this.getRequestId(request));
+    this._requestMap[requestId] = window;
+
+    cpmm.sendAsyncMessage("NFC:MakeReadOnlyNDEF", {
+      requestId: requestId,
+      sessionToken: sessionToken
+    });
+    return request;
+  },
+
+  connect: function connect(window, techType, sessionToken) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+    let request = Services.DOMRequest.createRequest(window);
+    let requestId = btoa(this.getRequestId(request));
+    this._requestMap[requestId] = window;
+
+    cpmm.sendAsyncMessage("NFC:Connect", {
+      requestId: requestId,
+      sessionToken: sessionToken,
+      techType: techType
+    });
+    return request;
+  },
+
+  close: function close(window, sessionToken) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+    let request = Services.DOMRequest.createRequest(window);
+    let requestId = btoa(this.getRequestId(request));
+    this._requestMap[requestId] = window;
+
+    cpmm.sendAsyncMessage("NFC:Close", {
+      requestId: requestId,
+      sessionToken: sessionToken
+    });
+    return request;
+  },
+
+  // nsIObserver
+
+  observe: function observe(subject, topic, data) {
+    if (topic == "xpcom-shutdown") {
+      this.removeMessageListener();
+      Services.obs.removeObserver(this, "xpcom-shutdown");
+      cpmm = null;
+    }
+  },
+
+  // nsIMessageListener
+
+  fireRequestSuccess: function fireRequestSuccess(requestId, result) {
+    let request = this.takeRequest(requestId);
+    if (!request) {
+      debug("not firing success for id: " + requestId +
+            ", result: " + JSON.stringify(result));
+      return;
+    }
+
+    debug("fire request success, id: " + requestId +
+          ", result: " + JSON.stringify(result));
+    Services.DOMRequest.fireSuccess(request, result);
+  },
+
+  fireRequestError: function fireRequestError(requestId, error) {
+    let request = this.takeRequest(requestId);
+    if (!request) {
+      debug("not firing error for id: " + requestId +
+            ", error: " + JSON.stringify(error));
+      return;
+    }
+
+    debug("fire request error, id: " + requestId +
+          ", result: " + JSON.stringify(error));
+    Services.DOMRequest.fireError(request, error);
+  },
+
+  receiveMessage: function receiveMessage(message) {
+    debug("Message received: " + JSON.stringify(message));
+    switch (message.name) {
+      case "NFC:ReadNDEFResponse":
+        this.handleReadNDEFResponse(message.json);
+        break;
+      case "NFC:ConnectResponse": // Fall through.
+      case "NFC:CloseResponse":
+      case "NFC:WriteNDEFResponse":
+      case "NFC:MakeReadOnlyNDEFResponse":
+      case "NFC:GetDetailsNDEFResponse":
+        this.handleResponse(message.json);
+        break;
+    }
+  },
+
+  handleReadNDEFResponse: function handleReadNDEFResponse(message) {
+    debug("ReadNDEFResponse(" + JSON.stringify(message) + ")");
+    let requester = this._requestMap[message.requestId];
+    if (!requester) {
+       debug("ReadNDEFResponse Invalid requester=" + requester +
+             " message.sessionToken=" + message.sessionToken);
+       return; // Nothing to do in this instance.
+    }
+    delete this._requestMap[message.requestId];
+    let records = message.records;
+    let requestId = atob(message.requestId);
+
+    if (message.status !== NFC.GECKO_NFC_ERROR_SUCCESS) {
+      this.fireRequestError(requestId, message.status);
+    } else {
+      let ndefMsg = [];
+      for (let i = 0; i < records.length; i++) {
+        let record = records[i];
+        ndefMsg.push(new requester.MozNdefRecord(record.tnf,
+                                                 record.type,
+                                                 record.id,
+                                                 record.payload));
+      }
+      this.fireRequestSuccess(requestId, ndefMsg);
+    }
+  },
+
+  handleResponse: function handleResponse(message) {
+    debug("Response(" + JSON.stringify(message) + ")");
+    let requester = this._requestMap[message.requestId];
+    if (!requester) {
+       debug("Response Invalid requester=" + requester +
+             " message.sessionToken=" + message.sessionToken);
+       return; // Nothing to do in this instance.
+    }
+    delete this._requestMap[message.requestId];
+    let result = message;
+    let requestId = atob(message.requestId);
+
+    if (message.status !== NFC.GECKO_NFC_ERROR_SUCCESS) {
+      this.fireRequestError(requestId, result.status);
+    } else {
+      this.fireRequestSuccess(requestId, result);
+    }
+  },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NfcContentHelper]);
--- a/dom/system/gonk/moz.build
+++ b/dom/system/gonk/moz.build
@@ -80,24 +80,39 @@ if CONFIG['MOZ_B2G_RIL']:
         'RadioInterfaceLayer.manifest',
         'RILContentHelper.js',
     ]
     EXTRA_JS_MODULES += [
         'ril_consts.js',
         'ril_worker.js',
     ]
 
+if CONFIG['MOZ_NFC']:
+    XPIDL_SOURCES += [
+        'nsINfcContentHelper.idl',
+    ]
+    EXTRA_COMPONENTS += [
+        'Nfc.js',
+        'Nfc.manifest',
+        'NfcContentHelper.js',
+    ]
+    EXTRA_JS_MODULES += [
+    'nfc_consts.js',
+    'nfc_worker.js',
+    ]
+
 FAIL_ON_WARNINGS = True
 
 LIBXUL_LIBRARY = True
 
 LIBRARY_NAME = 'domsystemgonk_s'
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '/content/events/src',
     '/dom/base',
     '/dom/bluetooth',
+    '/dom/nfc',
     '/dom/src/geolocation',
     '/dom/wifi',
 ]
 
--- a/dom/system/gonk/net_worker.js
+++ b/dom/system/gonk/net_worker.js
@@ -550,26 +550,18 @@ function setDnsForwarders(params, callba
 
 function enableNat(params, callback) {
   let command = "nat enable " + params.internalIfname + " " +
                 params.externalIfname + " " + "0";
   return doCommand(command, callback);
 }
 
 function disableNat(params, callback) {
-  let command;
-
-  // Don't disable nat because others interface still need it.
-  // Send the dummy command to continue the function chain.
-  if ("interfaceList" in params && params.interfaceList.length > 1) {
-    command = DUMMY_COMMAND;
-  } else {
-    command = "nat disable " + params.internalIfname + " " +
-              params.externalIfname + " " + "0";
-  }
+  let command = "nat disable " + params.internalIfname + " " +
+                 params.externalIfname + " " + "0";
   return doCommand(command, callback);
 }
 
 function wifiFirmwareReload(params, callback) {
   let command = "softap fwreload " + params.ifname + " " + params.mode;
   return doCommand(command, callback);
 }
 
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/nfc_consts.js
@@ -0,0 +1,71 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Copyright © 2013, Deutsche Telekom, Inc. */
+
+// Set to true to debug all NFC layers
+this.DEBUG_ALL = false;
+
+// Set individually to debug specific layers
+this.DEBUG_WORKER = false || DEBUG_ALL;
+this.DEBUG_CONTENT_HELPER = false || DEBUG_ALL;
+this.DEBUG_NFC = false || DEBUG_ALL;
+
+// Current version
+this.NFC_MAJOR_VERSION = 1;
+this.NFC_MINOR_VERSION = 7;
+
+this.NFC_REQUEST_CONFIG = 0;
+this.NFC_REQUEST_CONNECT = 1;
+this.NFC_REQUEST_CLOSE = 2;
+this.NFC_REQUEST_GET_DETAILS = 3;
+this.NFC_REQUEST_READ_NDEF = 4;
+this.NFC_REQUEST_WRITE_NDEF = 5;
+this.NFC_REQUEST_MAKE_NDEF_READ_ONLY = 6;
+
+this.NFC_RESPONSE_GENERAL = 1000;
+this.NFC_RESPONSE_CONFIG = 1001;
+this.NFC_RESPONSE_READ_NDEF_DETAILS = 1002;
+this.NFC_RESPONSE_READ_NDEF = 1003;
+
+this.NFC_NOTIFICATION_INITIALIZED = 2000;
+this.NFC_NOTIFICATION_TECH_DISCOVERED = 2001;
+this.NFC_NOTIFICATION_TECH_LOST = 2002;
+
+this.NFC_TECHS = {
+  0:'NDEF',
+  1:'NDEF_WRITEABLE',
+  2:'NDEF_FORMATABLE',
+  3:'P2P',
+  4:'NFC_A'
+};
+
+// TODO: Bug 933595. Fill-in all error codes for Gonk/nfcd protocol
+this.GECKO_NFC_ERROR_SUCCESS             = 0;
+this.GECKO_NFC_ERROR_GENERIC_FAILURE     = 1;
+
+// NFC powerlevels must match config PDUs.
+this.NFC_POWER_LEVEL_UNKNOWN        = -1;
+this.NFC_POWER_LEVEL_DISABLED       = 0;
+this.NFC_POWER_LEVEL_LOW            = 1;
+this.NFC_POWER_LEVEL_ENABLED        = 2;
+
+this.TOPIC_MOZSETTINGS_CHANGED      = "mozsettings-changed";
+this.TOPIC_XPCOM_SHUTDOWN           = "xpcom-shutdown";
+this.SETTING_NFC_ENABLED            = "nfc.enabled";
+this.SETTING_NFC_POWER_LEVEL        = "nfc.powerlevel";
+
+// Allow this file to be imported via Components.utils.import().
+this.EXPORTED_SYMBOLS = Object.keys(this);
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/nfc_worker.js
@@ -0,0 +1,440 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Copyright © 2013, Deutsche Telekom, Inc. */
+
+"use strict";
+
+importScripts("systemlibs.js", "nfc_consts.js");
+importScripts("resource://gre/modules/workers/require.js");
+
+// set to true in nfc_consts.js to see debug messages
+let DEBUG = DEBUG_WORKER;
+
+function getPaddingLen(len) {
+  return (len % 4) ? (4 - len % 4) : 0;
+}
+
+let Buf = {
+  __proto__: (function(){
+    return require("resource://gre/modules/workers/worker_buf.js").Buf;
+  })(),
+
+  init: function init() {
+    this._init();
+  },
+
+  /**
+   * Process one parcel.
+   */
+  processParcel: function processParcel() {
+    let pduType = this.readInt32();
+    if (DEBUG) debug("Number of bytes available in Parcel : " + this.readAvailable);
+    NfcWorker.handleParcel(pduType, this.mCallback);
+  },
+
+  /**
+   * Start a new outgoing parcel.
+   *
+   * @param type
+   *        Integer specifying the request type.
+   * @param callback
+   */
+  newParcel: function newParcel(type, callback) {
+    if (DEBUG) debug("New outgoing parcel of type " + type);
+    this.mCallback = callback;
+    // We're going to leave room for the parcel size at the beginning.
+    this.outgoingIndex = this.PARCEL_SIZE_SIZE;
+    this.writeInt32(type);
+  },
+
+  simpleRequest: function simpleRequest(type) {
+    this.newParcel(type);
+    this.sendParcel();
+  },
+
+  onSendParcel: function onSendParcel(parcel) {
+    postNfcMessage(parcel);
+  },
+
+  /**
+   * TODO: Bug 933593. Callback map of NFC_RESPONSE_XXX and RequestID
+   *       needs to be maintained
+   */
+  mCallback: null,
+};
+
+/**
+ * Provide a high-level API representing NFC capabilities.
+ * Rensponsible for converting NFC requests from Content process to binary data
+ * and NFC Responses from binary data to dictionary objects.
+ */
+let NfcWorker = {
+  /**
+   * Handle incoming messages from the main UI thread.
+   *
+   * @param message
+   *        Object containing the message. Messages are supposed
+   */
+  handleDOMMessage: function handleMessage(message) {
+    if (DEBUG) debug("Received DOM message " + JSON.stringify(message));
+    let method = this[message.type];
+    if (typeof method != "function") {
+      if (DEBUG) {
+        debug("Don't know what to do with message " + JSON.stringify(message));
+      }
+      return;
+    }
+    method.call(this, message);
+  },
+
+  /**
+   * Unmarshals a NDEF message
+   */
+  unMarshallNdefMessage: function unMarshallNdefMessage() {
+    let numOfRecords = Buf.readInt32();
+    debug("numOfRecords = " + numOfRecords);
+    if (numOfRecords <= 0) {
+      return null;
+    }
+    let records = [];
+
+    for (let i = 0; i < numOfRecords; i++) {
+      let tnf        = Buf.readInt32();
+      let typeLength = Buf.readInt32();
+      let type = [];
+      for (let i = 0; i < typeLength; i++) {
+        type.push(Buf.readUint8());
+      }
+      let padding    = getPaddingLen(typeLength);
+      for (let i = 0; i < padding; i++) {
+        Buf.readUint8();
+      }
+
+      let idLength = Buf.readInt32();
+      let id = [];
+      for (let i = 0; i < idLength; i++) {
+        id.push(Buf.readUint8());
+      }
+      padding      = getPaddingLen(idLength);
+      for (let i = 0; i < padding; i++) {
+        Buf.readUint8();
+      }
+
+      let payloadLength = Buf.readInt32();
+      let payload = [];
+      for (let i = 0; i < payloadLength; i++) {
+        payload.push(Buf.readUint8());
+      }
+      padding = getPaddingLen(payloadLength);
+      for (let i = 0; i < padding; i++) {
+        Buf.readUint8();
+      }
+      records.push({tnf: tnf,
+                    type: type,
+                    id: id,
+                    payload: payload});
+    }
+    return records;
+  },
+
+  /**
+   * Read and return NDEF data, if present.
+   */
+  readNDEF: function readNDEF(message) {
+    let cb = function callback() {
+      let error        = Buf.readInt32();
+      let sessionId    = Buf.readInt32();
+      let records      = this.unMarshallNdefMessage();
+
+      message.type      = "ReadNDEFResponse";
+      message.sessionId = sessionId;
+      message.records   = records;
+      message.status = (error === 0) ? GECKO_NFC_ERROR_SUCCESS :
+                                       GECKO_NFC_ERROR_GENERIC_FAILURE;
+      this.sendDOMMessage(message);
+    }
+
+    Buf.newParcel(NFC_REQUEST_READ_NDEF, cb);
+    Buf.writeInt32(message.sessionId);
+    Buf.sendParcel();
+  },
+
+  /**
+   * Write to a target that accepts NDEF formattable data
+   */
+  writeNDEF: function writeNDEF(message) {
+    let cb = function callback() {
+      let error         = Buf.readInt32();
+      let sessionId     = Buf.readInt32();
+
+      message.type      = "WriteNDEFResponse";
+      message.sessionId = sessionId;
+      message.status = (error === 0) ? GECKO_NFC_ERROR_SUCCESS :
+                                       GECKO_NFC_ERROR_GENERIC_FAILURE;
+      this.sendDOMMessage(message);
+    };
+
+    Buf.newParcel(NFC_REQUEST_WRITE_NDEF, cb);
+    Buf.writeInt32(message.sessionId);
+    let records    = message.records;
+    let numRecords = records.length;
+    Buf.writeInt32(numRecords);
+    for (let i = 0; i < numRecords; i++) {
+      let record = records[i];
+      Buf.writeInt32(record.tnf);
+
+      let typeLength = record.type.length;
+      Buf.writeInt32(typeLength);
+      for (let j = 0; j < typeLength; j++) {
+        Buf.writeUint8(record.type.charCodeAt(j));
+      }
+      let padding = getPaddingLen(typeLength);
+      for (let i = 0; i < padding; i++) {
+        Buf.writeUint8(0x00);
+      }
+
+      let idLength = record.id.length;
+      Buf.writeInt32(idLength);
+      for (let j = 0; j < idLength; j++) {
+        Buf.writeUint8(record.id.charCodeAt(j));
+      }
+      padding = getPaddingLen(idLength);
+      for (let i = 0; i < padding; i++) {
+        Buf.writeUint8(0x00);
+      }
+
+      let payloadLength = record.payload && record.payload.length;
+      Buf.writeInt32(payloadLength);
+      for (let j = 0; j < payloadLength; j++) {
+        Buf.writeUint8(record.payload.charCodeAt(j));
+      }
+      padding = getPaddingLen(payloadLength);
+      for (let i = 0; i < padding; i++) {
+        Buf.writeUint8(0x00);
+      }
+    }
+
+    Buf.sendParcel();
+  },
+
+  /**
+   * Make the NFC NDEF tag permanently read only
+   */
+  makeReadOnlyNDEF: function makeReadOnlyNDEF(message) {
+    let cb = function callback() {
+      let error         = Buf.readInt32();
+      let sessionId     = Buf.readInt32();
+
+      message.type      = "MakeReadOnlyNDEFResponse";
+      message.sessionId = sessionId;
+      message.status = (error === 0) ? GECKO_NFC_ERROR_SUCCESS :
+                                       GECKO_NFC_ERROR_GENERIC_FAILURE;
+      this.sendDOMMessage(message);
+    };
+
+    Buf.newParcel(NFC_REQUEST_MAKE_NDEF_READ_ONLY, cb);
+    Buf.writeInt32(message.sessionId);
+    Buf.sendParcel();
+  },
+
+  /**
+   * Retrieve metadata describing the NDEF formatted data, if present.
+   */
+  getDetailsNDEF: function getDetailsNDEF(message) {
+    let cb = function callback() {
+      let error                  = Buf.readInt32();
+      let sessionId              = Buf.readInt32();
+      let isReadOnly             = Buf.readUint8();
+      let canBeMadeReadOnly      = Buf.readUint8();
+      // Ensure that padding is taken care here after reading two successive uint8's
+      Buf.readUint8();
+      Buf.readUint8();
+      let maxSupportedLength     = Buf.readInt32();
+
+      message.type               = "GetDetailsNDEFResponse";
+      message.sessionId          = sessionId;
+      message.isReadOnly         = isReadOnly;
+      message.canBeMadeReadOnly  = canBeMadeReadOnly;
+      message.maxSupportedLength = maxSupportedLength;
+      message.status = (error === 0) ? GECKO_NFC_ERROR_SUCCESS :
+                                       GECKO_NFC_ERROR_GENERIC_FAILURE;
+      this.sendDOMMessage(message);
+    };
+    Buf.newParcel(NFC_REQUEST_GET_DETAILS, cb);
+    Buf.writeInt32(message.sessionId);
+    Buf.sendParcel();
+  },
+
+
+  /**
+   * Open a connection to the NFC target.
+   */
+  connect: function connect(message) {
+    let cb = function callback() {
+      let error         = Buf.readInt32();
+      let sessionId     = Buf.readInt32();
+
+      message.type      = "ConnectResponse";
+      message.sessionId = sessionId;
+      message.status = (error === 0) ? GECKO_NFC_ERROR_SUCCESS :
+                                       GECKO_NFC_ERROR_GENERIC_FAILURE;
+      this.sendDOMMessage(message);
+    };
+
+    Buf.newParcel(NFC_REQUEST_CONNECT, cb);
+    Buf.writeInt32(message.sessionId);
+    Buf.writeInt32(message.techType);
+    Buf.sendParcel();
+  },
+
+  /**
+   * NFC Configuration
+   */
+  config: function config(message) {
+    let cb = function callback() {
+      let error         = Buf.readInt32();
+
+      message.type      = "ConfigResponse";
+      message.status = (error === 0) ? GECKO_NFC_ERROR_SUCCESS :
+                                       GECKO_NFC_ERROR_GENERIC_FAILURE;
+      this.sendDOMMessage(message);
+    };
+
+    Buf.newParcel(NFC_REQUEST_CONFIG , cb);
+    Buf.writeInt32(message.powerLevel);
+    Buf.sendParcel();
+  },
+
+  /**
+   * Close connection to the NFC target.
+   */
+  close: function close(message) {
+    let cb = function callback() {
+      let error         = Buf.readInt32();
+      let sessionId     = Buf.readInt32();
+
+      message.type      = "CloseResponse";
+      message.sessionId = sessionId;
+      message.status = (error === 0) ? GECKO_NFC_ERROR_SUCCESS :
+                                       GECKO_NFC_ERROR_GENERIC_FAILURE;
+      this.sendDOMMessage(message);
+    };
+
+    Buf.newParcel(NFC_REQUEST_CLOSE , cb);
+    Buf.writeInt32(message.sessionId);
+    Buf.sendParcel();
+  },
+
+  handleParcel: function handleParcel(request_type, callback) {
+    let method = this[request_type];
+    if (typeof method == "function") {
+      if (DEBUG) debug("Handling parcel as " + method.name);
+      method.call(this);
+    } else if (typeof callback == "function") {
+      callback.call(this, request_type);
+      this.mCallback = null;
+    } else {
+      debug("Unable to handle ReqType:"+request_type);
+    }
+  },
+
+  /**
+   * Send messages to the main UI thread.
+   */
+  sendDOMMessage: function sendDOMMessage(message) {
+    postMessage(message);
+  }
+};
+
+/**
+ * Notification Handlers
+ */
+NfcWorker[NFC_NOTIFICATION_INITIALIZED] = function NFC_NOTIFICATION_INITIALIZED () {
+  let status       = Buf.readInt32();
+  let majorVersion = Buf.readInt32();
+  let minorVersion = Buf.readInt32();
+  debug("NFC_NOTIFICATION_INITIALIZED status:" + status);
+  if ((majorVersion != NFC_MAJOR_VERSION) || (minorVersion != NFC_MINOR_VERSION)) {
+    debug("Version Mismatch! Current Supported Version : " +
+            NFC_MAJOR_VERSION + "." + NFC_MINOR_VERSION  +
+           " Received Version : " + majorVersion + "." + minorVersion);
+  }
+};
+
+NfcWorker[NFC_NOTIFICATION_TECH_DISCOVERED] = function NFC_NOTIFICATION_TECH_DISCOVERED() {
+  debug("NFC_NOTIFICATION_TECH_DISCOVERED");
+  let techs     = [];
+  let ndefMsgs  = [];
+
+  let sessionId = Buf.readInt32();
+  let techCount = Buf.readInt32();
+  for (let count = 0; count < techCount; count++) {
+    techs.push(NFC_TECHS[Buf.readUint8()]);
+  }
+
+  let padding   = getPaddingLen(techCount);
+  for (let i = 0; i < padding; i++) {
+    Buf.readUint8();
+  }
+
+  let ndefMsgCount = Buf.readInt32();
+  for (let count = 0; count < ndefMsgCount; count++) {
+    ndefMsgs.push(this.unMarshallNdefMessage());
+  }
+  this.sendDOMMessage({type: "techDiscovered",
+                       sessionId: sessionId,
+                       tech: techs,
+                       ndef: ndefMsgs
+                       });
+};
+
+NfcWorker[NFC_NOTIFICATION_TECH_LOST] = function NFC_NOTIFICATION_TECH_LOST() {
+  debug("NFC_NOTIFICATION_TECH_LOST");
+  let sessionId = Buf.readInt32();
+  debug("sessionId = " + sessionId);
+  this.sendDOMMessage({type: "techLost",
+                       sessionId: sessionId,
+                       });
+};
+
+/**
+ * Global stuff.
+ */
+
+if (!this.debug) {
+  // Debugging stub that goes nowhere.
+  this.debug = function debug(message) {
+    dump("Nfc Worker: " + message + "\n");
+  };
+}
+
+// Initialize buffers. This is a separate function so that unit tests can
+// re-initialize the buffers at will.
+Buf.init();
+
+function onNfcMessage(data) {
+  Buf.processIncoming(data);
+};
+
+onmessage = function onmessage(event) {
+  NfcWorker.handleDOMMessage(event.data);
+};
+
+onerror = function onerror(event) {
+  debug("OnError: event: " + JSON.stringify(event));
+  debug("NFC Worker error " + event.message + " " + event.filename + ":" +
+        event.lineno + ":\n");
+};
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/nsINfcContentHelper.idl
@@ -0,0 +1,22 @@
+/* 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 "nsIDOMDOMRequest.idl"
+
+interface nsIVariant;
+
+[scriptable, uuid(28c8f240-da8c-11e1-9b23-0800200c9a66)]
+interface nsINfcContentHelper : nsISupports
+{
+  void setSessionToken(in DOMString sessionToken);
+
+  nsIDOMDOMRequest getDetailsNDEF(in nsIDOMWindow window, in DOMString sessionToken);
+  nsIDOMDOMRequest readNDEF(in nsIDOMWindow window, in DOMString sessionToken);
+  nsIDOMDOMRequest writeNDEF(in nsIDOMWindow window, in nsIVariant records, in DOMString sessionToken);
+  nsIDOMDOMRequest makeReadOnlyNDEF(in nsIDOMWindow window, in DOMString sessionToken);
+
+  nsIDOMDOMRequest connect(in nsIDOMWindow window, in unsigned long techType, in DOMString sessionToken);
+  nsIDOMDOMRequest close(in nsIDOMWindow window, in DOMString sessionToken);
+};
--- a/dom/system/nsDeviceSensors.cpp
+++ b/dom/system/nsDeviceSensors.cpp
@@ -170,17 +170,17 @@ NS_IMETHODIMP nsDeviceSensors::RemoveWin
 }
 
 static bool
 WindowCannotReceiveSensorEvent (nsPIDOMWindow* aWindow)
 {
   // Check to see if this window is in the background.  If
   // it is and it does not have the "background-sensors" permission,
   // don't send any device motion events to it.
-  if (!aWindow || !aWindow->GetOuterWindow()) {
+  if (!aWindow || !aWindow->IsCurrentInnerWindow()) {
     return true;
   }
 
   if (aWindow->GetOuterWindow()->IsBackground()) {
     nsCOMPtr<nsIPermissionManager> permMgr =
       do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
     NS_ENSURE_TRUE(permMgr, false);
     uint32_t permission = nsIPermissionManager::DENY_ACTION;
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -350,16 +350,20 @@ var interfaceNamesInGlobalScope =
     {name: "MozInputMethodManager", b2g: true},
     "MozMmsEvent",
     "MozMmsMessage",
     {name: "MozMobileConnection", b2g: true, pref: "dom.mobileconnection.enabled"},
     {name: "MozMobileConnectionArray", b2g: true, pref: "dom.mobileconnection.enabled"},
     "MozMobileMessageManager",
     "MozMobileMessageThread",
     "MozNamedAttrMap",
+    {name: "MozNdefRecord", b2g: true},
+    {name: "MozNfc", b2g: true},
+    {name: "MozNFCPeer", b2g: true},
+    {name: "MozNFCTag", b2g: true},
     {name: "MozOtaStatusEvent", b2g: true, pref: "dom.mobileconnection.enabled"},
     "MozPowerManager",
     "mozRTCIceCandidate",
     "mozRTCPeerConnection",
     "mozRTCSessionDescription",
     "MozSettingsEvent",
     "MozSmsEvent",
     "MozSmsFilter",
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MozNFCPeer.webidl
@@ -0,0 +1,22 @@
+/* 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/.
+ *
+ * Part of this IDL file is from:
+ * http://w3c.github.io/nfc/proposals/common/nfc.html#idl-def-NFCPeer
+ *
+ * Copyright © 2013 Deutsche Telekom, Inc.
+ */
+
+[JSImplementation="@mozilla.org/nfc/NFCPeer;1"]
+interface MozNFCPeer {
+  DOMRequest sendNDEF(sequence<MozNdefRecord> records);
+};
+
+// Mozilla Only
+partial interface MozNFCPeer {
+  [ChromeOnly]
+  attribute DOMString session;
+  [ChromeOnly]
+  void setSessionToken(DOMString sessionToken);
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MozNFCTag.webidl
@@ -0,0 +1,43 @@
+/* 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/.
+ *
+ * Part of this idl is from:
+ * http://w3c.github.io/nfc/proposals/common/nfc.html#nfctag-interface
+ *
+ * Copyright © 2013 Deutsche Telekom, Inc.
+ */
+
+enum NFCTechType {
+  "NFC_A",
+  "NFC_B",
+  "NFC_ISO_DEP",
+  "NFC_F",
+  "NFC_V",
+  "NDEF",
+  "NDEF_FORMATABLE",
+  "MIFARE_CLASSIC",
+  "MIFARE_ULTRALIGHT",
+  "NFC_BARCODE",
+  "P2P",
+  "UNKNOWN_TECH"
+};
+
+[JSImplementation="@mozilla.org/nfc/NFCTag;1"]
+interface MozNFCTag {
+  DOMRequest getDetailsNDEF();
+  DOMRequest readNDEF();
+  DOMRequest writeNDEF(sequence<MozNdefRecord> records);
+  DOMRequest makeReadOnlyNDEF();
+
+  DOMRequest connect(NFCTechType techType);
+  DOMRequest close();
+};
+
+// Mozilla Only
+partial interface MozNFCTag {
+  [ChromeOnly]
+  attribute DOMString session;
+  [ChromeOnly]
+  void setSessionToken(DOMString sessionToken);
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MozNdefRecord.webidl
@@ -0,0 +1,38 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Copyright © 2013 Deutsche Telekom, Inc. */
+
+[Constructor(octet tnf, DOMString type, DOMString id, DOMString payload)]
+interface MozNdefRecord
+{
+  /**
+   * Type Name Field (3-bits) - Specifies the NDEF record type in general.
+   *   tnf_empty: 0x00
+   *   tnf_well_known: 0x01
+   *   tnf_mime_media: 0x02
+   *   tnf_absolute_uri: 0x03
+   *   tnf_external type: 0x04
+   *   tnf_unknown: 0x05
+   *   tnf_unchanged: 0x06
+   *   tnf_reserved: 0x07
+   */
+  readonly attribute octet tnf;
+
+  /**
+   * type - Describes the content of the payload. This can be a mime type.
+   */
+  readonly attribute DOMString type;
+
+  /**
+   * id - Identifer is application dependent.
+   */
+  readonly attribute DOMString id;
+
+  /**
+   * payload - Binary data blob. The meaning of this field is application dependent.
+   */
+  readonly attribute DOMString payload;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MozNfc.webidl
@@ -0,0 +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/. */
+
+ /* Copyright © 2013 Deutsche Telekom, Inc. */
+
+[JSImplementation="@mozilla.org/navigatorNfc;1",
+ NavigatorProperty="mozNfc"]
+interface MozNfc : EventTarget {
+   MozNFCTag getNFCTag(DOMString sessionId);
+   MozNFCPeer getNFCPeer(DOMString sessionId);
+
+   /*attribute EventHandler onpeerfound;
+   attribute EventHandler onpeerlost;
+   attribute EventHandler onforegrounddispatch;*/
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -517,16 +517,24 @@ if CONFIG['MOZ_B2G_RIL']:
         'MozEmergencyCbModeEvent.webidl',
         'MozMobileConnectionArray.webidl',
         'MozOtaStatusEvent.webidl',
         'MozVoicemail.webidl',
         'MozVoicemailEvent.webidl',
         'USSDReceivedEvent.webidl',
     ]
 
+if CONFIG['MOZ_NFC']:
+    WEBIDL_FILES += [
+         'MozNdefRecord.webidl',
+         'MozNfc.webidl',
+         'MozNFCPeer.webidl',
+         'MozNFCTag.webidl',
+    ]
+
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     WEBIDL_FILES += [
         'MozWifiConnectionInfoEvent.webidl',
         'MozWifiStatusChangeEvent.webidl',
     ]
 
 if CONFIG['MOZ_WEBSPEECH']:
     WEBIDL_FILES += [
--- a/embedding/components/windowwatcher/src/nsAutoWindowStateHelper.cpp
+++ b/embedding/components/windowwatcher/src/nsAutoWindowStateHelper.cpp
@@ -42,17 +42,17 @@ nsAutoWindowStateHelper::~nsAutoWindowSt
     DispatchEventToChrome("DOMModalDialogClosed");
   }
 }
 
 bool
 nsAutoWindowStateHelper::DispatchEventToChrome(const char *aEventName)
 {
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mWindow);
-  if (!window) {
+  if (!window || (window->IsInnerWindow() && !window->IsCurrentInnerWindow())) {
     return true;
   }
 
   // The functions of nsContentUtils do not provide the required behavior,
   // so the following is inlined.
   nsIDocument* doc = window->GetExtantDoc();
   if (!doc) {
     return true;
--- a/gfx/gl/GLContextProviderGLX.cpp
+++ b/gfx/gl/GLContextProviderGLX.cpp
@@ -10,20 +10,20 @@
 #elif defined(MOZ_WIDGET_QT)
 #include <QWidget>
 #define GET_NATIVE_WINDOW(aWidget) static_cast<QWidget*>(aWidget->GetNativeData(NS_NATIVE_SHELLWIDGET))->winId()
 #endif
 
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
 
+#include "mozilla/MathAlgorithms.h"
 #include "mozilla/X11Util.h"
 
 #include "prenv.h"
-#include "prbit.h" // for PR_FLOOR_LOG2
 #include "GLContextProvider.h"
 #include "GLLibraryLoader.h"
 #include "nsDebug.h"
 #include "nsIWidget.h"
 #include "GLXLibrary.h"
 #include "gfxXlibSurface.h"
 #include "gfxContext.h"
 #include "gfxImageSurface.h"
@@ -294,18 +294,17 @@ GLXLibrary::CreatePixmap(gfxASurface* aS
     }
 
     gfxXlibSurface *xs = static_cast<gfxXlibSurface*>(aSurface);
     const XRenderPictFormat *format = xs->XRenderFormat();
     if (!format || format->type != PictTypeDirect) {
         return None;
     }
     const XRenderDirectFormat& direct = format->direct;
-    int alphaSize;
-    PR_FLOOR_LOG2(alphaSize, direct.alphaMask + 1);
+    int alphaSize = FloorLog2(direct.alphaMask + 1);
     NS_ASSERTION((1 << alphaSize) - 1 == direct.alphaMask,
                  "Unexpected render format with non-adjacent alpha bits");
 
     int attribs[] = { GLX_DOUBLEBUFFER, False,
                       GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
                       GLX_ALPHA_SIZE, alphaSize,
                       (alphaSize ? GLX_BIND_TO_TEXTURE_RGBA_EXT
                        : GLX_BIND_TO_TEXTURE_RGB_EXT), True,
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -209,16 +209,17 @@ MemoryTextureClient::ToSurfaceDescriptor
   return true;
 }
 
 bool
 MemoryTextureClient::Allocate(uint32_t aSize)
 {
   MOZ_ASSERT(!mBuffer);
   mBuffer = new uint8_t[aSize];
+  GfxMemoryImageReporter::DidAlloc(mBuffer);
   mBufSize = aSize;
   return true;
 }
 
 MemoryTextureClient::MemoryTextureClient(CompositableClient* aCompositable,
                                          gfx::SurfaceFormat aFormat,
                                          TextureFlags aFlags)
   : BufferTextureClient(aCompositable, aFormat, aFlags)
@@ -226,19 +227,20 @@ MemoryTextureClient::MemoryTextureClient
   , mBufSize(0)
 {
   MOZ_COUNT_CTOR(MemoryTextureClient);
 }
 
 MemoryTextureClient::~MemoryTextureClient()
 {
   MOZ_COUNT_DTOR(MemoryTextureClient);
-  if (ShouldDeallocateInDestructor()) {
+  if (ShouldDeallocateInDestructor() && mBuffer) {
     // if the buffer has never been shared we must deallocate it or ir would
     // leak.
+    GfxMemoryImageReporter::WillFree(mBuffer);
     delete mBuffer;
   }
 }
 
 BufferTextureClient::BufferTextureClient(CompositableClient* aCompositable,
                                          gfx::SurfaceFormat aFormat,
                                          TextureFlags aFlags)
   : TextureClient(aFlags)
--- a/gfx/layers/composite/APZCTreeManager.cpp
+++ b/gfx/layers/composite/APZCTreeManager.cpp
@@ -116,16 +116,32 @@ APZCTreeManager::UpdatePanZoomController
       const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId);
       if (state && state->mController.get()) {
         // If we get here, aLayer is a scrollable container layer and somebody
         // has registered a GeckoContentController for it, so we need to ensure
         // it has an APZC instance to manage its scrolling.
 
         apzc = container->GetAsyncPanZoomController();
 
+        // If the container doesn't have an APZC already, try to find one of our
+        // pre-existing ones that matches. In particular, if we find an APZC whose
+        // ScrollableLayerGuid is the same, then we know what happened is that the
+        // layout of the page changed causing the layer tree to be rebuilt, but the
+        // underlying content for which the APZC was originally created is still
+        // there. So it makes sense to pick up that APZC instance again and use it here.
+        if (apzc == nullptr) {
+          ScrollableLayerGuid target(aLayersId, container->GetFrameMetrics());
+          for (size_t i = 0; i < aApzcsToDestroy->Length(); i++) {
+            if (aApzcsToDestroy->ElementAt(i)->Matches(target)) {
+              apzc = aApzcsToDestroy->ElementAt(i);
+              break;
+            }
+          }
+        }
+
         // The APZC we get off the layer may have been destroyed previously if the layer was inactive
         // or omitted from the layer tree for whatever reason from a layers update. If it later comes
         // back it will have a reference to a destroyed APZC and so we need to throw that out and make
         // a new one.
         bool newApzc = (apzc == nullptr || apzc->IsDestroyed());
         if (newApzc) {
           apzc = new AsyncPanZoomController(aLayersId, this, state->mController,
                                             AsyncPanZoomController::USE_GESTURE_DETECTOR);
@@ -164,17 +180,17 @@ APZCTreeManager::UpdatePanZoomController
         aParent = apzc;
 
         if (newApzc && apzc->IsRootForLayersId()) {
           // If we just created a new apzc that is the root for its layers ID, then
           // we need to update its zoom constraints which might have arrived before this
           // was created
           bool allowZoom;
           CSSToScreenScale minZoom, maxZoom;
-          if (state->mController->GetZoomConstraints(&allowZoom, &minZoom, &maxZoom)) {
+          if (state->mController->GetRootZoomConstraints(&allowZoom, &minZoom, &maxZoom)) {
             apzc->UpdateZoomConstraints(allowZoom, minZoom, maxZoom);
           }
         }
       }
     }
 
     container->SetAsyncPanZoomController(apzc);
   }
@@ -503,19 +519,23 @@ APZCTreeManager::ReceiveInputEvent(Widge
     }
   }
 }
 
 void
 APZCTreeManager::UpdateRootCompositionBounds(const uint64_t& aLayersId,
                                              const ScreenIntRect& aCompositionBounds)
 {
-  nsRefPtr<AsyncPanZoomController> apzc = GetRootAPZCFor(aLayersId);
-  if (apzc) {
-    apzc->UpdateCompositionBounds(aCompositionBounds);
+  // There can be multiple root APZCs for a given layers id (e.g. tabs in
+  // a single-process setup) and in such a case we probably want to notify
+  // all of them.
+  nsTArray< nsRefPtr<AsyncPanZoomController> > rootApzcs;
+  GetRootAPZCsFor(aLayersId, &rootApzcs);
+  for (size_t i = 0; i < rootApzcs.Length(); i++) {
+    rootApzcs[i]->UpdateCompositionBounds(aCompositionBounds);
   }
 }
 
 void
 APZCTreeManager::ZoomToRect(const ScrollableLayerGuid& aGuid,
                             const CSSRect& aRect)
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
@@ -530,22 +550,22 @@ APZCTreeManager::ContentReceivedTouch(co
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
   if (apzc) {
     apzc->ContentReceivedTouch(aPreventDefault);
   }
 }
 
 void
-APZCTreeManager::UpdateZoomConstraints(const uint64_t& aLayersId,
+APZCTreeManager::UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
                                        bool aAllowZoom,
                                        const CSSToScreenScale& aMinScale,
                                        const CSSToScreenScale& aMaxScale)
 {
-  nsRefPtr<AsyncPanZoomController> apzc = GetRootAPZCFor(aLayersId);
+  nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
   if (apzc) {
     apzc->UpdateZoomConstraints(aAllowZoom, aMinScale, aMaxScale);
   }
 }
 
 void
 APZCTreeManager::UpdateScrollOffset(const ScrollableLayerGuid& aGuid,
                                     const CSSPoint& aScrollOffset)
@@ -579,30 +599,38 @@ APZCTreeManager::ClearTree()
     apzcsToDestroy[i]->Destroy();
   }
   mRootApzc = nullptr;
 }
 
 void
 APZCTreeManager::HandleOverscroll(AsyncPanZoomController* aChild, ScreenPoint aStartPoint, ScreenPoint aEndPoint)
 {
-  AsyncPanZoomController* parent = aChild->GetParent();
+  nsRefPtr<AsyncPanZoomController> parent;
+  {
+    // The tree lock needs to be held while navigating from an apzc to its
+    // parent. We don't hold it any longer though because GetInputTransforms()
+    // does its own locking, and AttemptScroll() can call HandleOverscroll()
+    // recursively.
+    MonitorAutoLock lock(mTreeLock);
+    parent = aChild->GetParent();
+  }
   if (parent == nullptr)
     return;
 
   gfx3DMatrix transformToApzc;
   gfx3DMatrix transformToGecko;  // ignored
 
   // Convert start and end points to untransformed screen coordinates.
   GetInputTransforms(aChild, transformToApzc, transformToGecko);
   ApplyTransform(&aStartPoint, transformToApzc.Inverse());
   ApplyTransform(&aEndPoint, transformToApzc.Inverse());
 
   // Convert start and end points to parent's transformed screen coordinates.
-  GetInputTransforms(parent, transformToApzc, transformToGecko);
+  GetInputTransforms(parent.get(), transformToApzc, transformToGecko);
   ApplyTransform(&aStartPoint, transformToApzc);
   ApplyTransform(&aEndPoint, transformToApzc);
 
   parent->AttemptScroll(aStartPoint, aEndPoint);
 }
 
 bool
 APZCTreeManager::HitTestAPZC(const ScreenIntPoint& aPoint)
@@ -646,29 +674,25 @@ APZCTreeManager::GetTargetAPZC(const Scr
     target = GetAPZCAtPoint(apzc, point);
     if (target) {
       break;
     }
   }
   return target.forget();
 }
 
-already_AddRefed<AsyncPanZoomController>
-APZCTreeManager::GetRootAPZCFor(const uint64_t& aLayersId)
+void
+APZCTreeManager::GetRootAPZCsFor(const uint64_t& aLayersId,
+                                 nsTArray< nsRefPtr<AsyncPanZoomController> >* aOutRootApzcs)
 {
   MonitorAutoLock lock(mTreeLock);
-  nsRefPtr<AsyncPanZoomController> target;
   // The root may have siblings, check those too
   for (AsyncPanZoomController* apzc = mRootApzc; apzc; apzc = apzc->GetPrevSibling()) {
-    target = FindRootAPZC(apzc, aLayersId);
-    if (target) {
-      break;
-    }
+    FindRootAPZCs(apzc, aLayersId, aOutRootApzcs);
   }
-  return target.forget();
 }
 
 AsyncPanZoomController*
 APZCTreeManager::FindTargetAPZC(AsyncPanZoomController* aApzc, const ScrollableLayerGuid& aGuid)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
   // This walks the tree in depth-first, reverse order, so that it encounters
@@ -737,31 +761,33 @@ APZCTreeManager::GetAPZCAtPoint(AsyncPan
   if (aApzc->VisibleRegionContains(ScreenPoint(hitTestPointForThisLayer.x, hitTestPointForThisLayer.y))) {
     APZC_LOG("Successfully matched untransformed point %f %f to visible region for APZC %p\n",
              hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, aApzc);
     return aApzc;
   }
   return nullptr;
 }
 
-AsyncPanZoomController*
-APZCTreeManager::FindRootAPZC(AsyncPanZoomController* aApzc, const uint64_t& aLayersId)
+void
+APZCTreeManager::FindRootAPZCs(AsyncPanZoomController* aApzc,
+                               const uint64_t& aLayersId,
+                               nsTArray< nsRefPtr<AsyncPanZoomController> >* aOutRootApzcs)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
   if (aApzc->IsRootForLayersId(aLayersId)) {
-    return aApzc;
+    aOutRootApzcs->AppendElement(aApzc);
+    // If this APZC is a root for this layers id then we know nothing else
+    // in the subtree rooted here will match so we can early-exit
+    return;
   }
+
   for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
-    AsyncPanZoomController* match = FindRootAPZC(child, aLayersId);
-    if (match) {
-      return match;
-    }
+    FindRootAPZCs(child, aLayersId, aOutRootApzcs);
   }
-  return nullptr;
 }
 
 /* This function sets the aTransformToApzcOut and aTransformToGeckoOut out-parameters
    to some useful transformations that input events may need applied. This is best
    illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L
    is the layer that corresponds to the returned APZC instance, and layer R is the root
    of the layer tree. Layer M is the parent of L, N is the parent of M, and so on.
    When layer L is displayed to the screen by the compositor, the set of transforms that
--- a/gfx/layers/composite/APZCTreeManager.h
+++ b/gfx/layers/composite/APZCTreeManager.h
@@ -157,22 +157,21 @@ public:
    * definitively whether or not content has preventDefaulted any touch events
    * that have come in. If |aPreventDefault| is true, any touch events in the
    * queue will be discarded.
    */
   void ContentReceivedTouch(const ScrollableLayerGuid& aGuid,
                             bool aPreventDefault);
 
   /**
-   * Updates any zoom constraints on the root APZC for the given layers id.
-   * Generally the zoom constraints come from the <meta name="viewport"> tag.
+   * Updates any zoom constraints contained in the <meta name="viewport"> tag.
    * We try to obey everything it asks us elsewhere, but here we only handle
    * minimum-scale, maximum-scale, and user-scalable.
    */
-  void UpdateZoomConstraints(const uint64_t& aLayersId,
+  void UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
                              bool aAllowZoom,
                              const CSSToScreenScale& aMinScale,
                              const CSSToScreenScale& aMaxScale);
 
   /**
    * Update mFrameMetrics.mScrollOffset to the given offset.
    * This is necessary in cases where a scroll is not caused by user
    * input (for example, a content scrollTo()).
@@ -235,24 +234,27 @@ public:
   /* Some helper functions to find an APZC given some identifying input. These functions
      lock the tree of APZCs while they find the right one, and then return an addref'd
      pointer to it. This allows caller code to just use the target APZC without worrying
      about it going away. These are public for testing code and generally should not be
      used by other production code.
   */
   already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScrollableLayerGuid& aGuid);
   already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint);
-  already_AddRefed<AsyncPanZoomController> GetRootAPZCFor(const uint64_t& aLayersId);
+  void GetRootAPZCsFor(const uint64_t& aLayersId,
+                       nsTArray< nsRefPtr<AsyncPanZoomController> >* aOutRootApzcs);
   void GetInputTransforms(AsyncPanZoomController *aApzc, gfx3DMatrix& aTransformToApzcOut,
                           gfx3DMatrix& aTransformToGeckoOut);
 private:
   /* Helpers */
   AsyncPanZoomController* FindTargetAPZC(AsyncPanZoomController* aApzc, const ScrollableLayerGuid& aGuid);
   AsyncPanZoomController* GetAPZCAtPoint(AsyncPanZoomController* aApzc, const gfxPoint& aHitTestPoint);
-  AsyncPanZoomController* FindRootAPZC(AsyncPanZoomController* aApzc, const uint64_t& aLayersId);
+  void FindRootAPZCs(AsyncPanZoomController* aApzc,
+                     const uint64_t& aLayersId,
+                     nsTArray< nsRefPtr<AsyncPanZoomController> >* aOutRootApzcs);
   already_AddRefed<AsyncPanZoomController> CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2);
   already_AddRefed<AsyncPanZoomController> RootAPZCForLayersId(AsyncPanZoomController* aApzc);
   already_AddRefed<AsyncPanZoomController> GetTouchInputBlockAPZC(const WidgetTouchEvent& aEvent, ScreenPoint aPoint);
   nsEventStatus ProcessTouchEvent(const WidgetTouchEvent& touchEvent, ScrollableLayerGuid* aOutTargetGuid, WidgetTouchEvent* aOutEvent);
   nsEventStatus ProcessMouseEvent(const WidgetMouseEvent& mouseEvent, ScrollableLayerGuid* aOutTargetGuid, WidgetMouseEvent* aOutEvent);
   nsEventStatus ProcessEvent(const WidgetInputEvent& inputEvent, ScrollableLayerGuid* aOutTargetGuid, WidgetInputEvent* aOutEvent);
 
   /**
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -532,16 +532,19 @@ MemoryTextureHost::~MemoryTextureHost()
 {
   DeallocateDeviceData();
   MOZ_COUNT_DTOR(MemoryTextureHost);
 }
 
 void
 MemoryTextureHost::DeallocateSharedData()
 {
+  if (mBuffer) {
+    GfxMemoryImageReporter::WillFree(mBuffer);
+  }
   delete[] mBuffer;
 }
 
 uint8_t* MemoryTextureHost::GetBuffer()
 {
   return mBuffer;
 }
 
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -47,16 +47,18 @@
 #include "nsMathUtils.h"                // for NS_hypot
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsStyleConsts.h"
 #include "nsStyleStruct.h"              // for nsTimingFunction
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl, etc
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "nsTraceRefcnt.h"              // for MOZ_COUNT_CTOR, etc
 
+// #define APZC_ENABLE_RENDERTRACE
+
 #define APZC_LOG(...)
 // #define APZC_LOG(...) printf_stderr("APZC: " __VA_ARGS__)
 #define APZC_LOG_FM(fm, prefix, ...) \
   APZC_LOG(prefix ":" \
            " i=(%ld %lld) cb=(%d %d %d %d) dp=(%.3f %.3f %.3f %.3f) v=(%.3f %.3f %.3f %.3f) " \
            "s=(%.3f %.3f) sr=(%.3f %.3f %.3f %.3f) z=(%.3f %.3f %.3f %.3f)\n", \
            __VA_ARGS__, \
            fm.mPresShellId, fm.mScrollId, \
@@ -205,16 +207,26 @@ static bool IsCloseToHorizontal(float aA
 }
 
 // As above, but for the vertical axis.
 static bool IsCloseToVertical(float aAngle, float aThreshold)
 {
   return (fabs(aAngle - (M_PI / 2)) < aThreshold);
 }
 
+static inline void LogRendertraceRect(const char* aDesc, const char* aColor, const CSSRect& aRect)
+{
+#ifdef APZC_ENABLE_RENDERTRACE
+  static const TimeStamp sRenderStart = TimeStamp::Now();
+  TimeDuration delta = TimeStamp::Now() - sRenderStart;
+  printf_stderr("%s RENDERTRACE %f rect %s %f %f %f %f\n",
+    aDesc, delta.ToMilliseconds(), aColor,
+    aRect.x, aRect.y, aRect.width, aRect.height);
+#endif
+}
 
 static TimeStamp sFrameTime;
 
 static TimeStamp
 GetFrameTime() {
   if (sFrameTime.IsNull()) {
     return TimeStamp::Now();
   }
@@ -669,19 +681,25 @@ nsEventStatus AsyncPanZoomController::On
     mLastZoomFocus = focusPoint;
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent) {
   APZC_LOG("%p got a scale-end in state %d\n", this, mState);
-  SetState(PANNING);
-  mX.StartTouch(aEvent.mFocusPoint.x);
-  mY.StartTouch(aEvent.mFocusPoint.y);
+  // When a pinch ends, it might either turn into a pan (if only one finger
+  // was lifted) or not (if both fingers were lifted). GestureEventListener
+  // sets mCurrentSpan to a negative value in the latter case, and sets
+  // mFocusPoint to the remaining touch point in the former case.
+  if (aEvent.mCurrentSpan >= 0) {
+    SetState(PANNING);
+    mX.StartTouch(aEvent.mFocusPoint.x);
+    mY.StartTouch(aEvent.mFocusPoint.y);
+  }
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
     ScheduleComposite();
     RequestContentRepaint();
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
@@ -851,18 +869,24 @@ void AsyncPanZoomController::AttemptScro
       TimeDuration timePaintDelta = mPaintThrottler.TimeSinceLastRequest(GetFrameTime());
       if (timePaintDelta.ToMilliseconds() > gPanRepaintInterval) {
         RequestContentRepaint();
       }
     }
   }
 
   if (fabs(overscroll.x) > EPSILON || fabs(overscroll.y) > EPSILON) {
-    // "+ overscroll" rather than "- overscroll" for the same reason as above.
-    mTreeManager->HandleOverscroll(this, aEndPoint + overscroll, aEndPoint);
+    // Make a local copy of the tree manager pointer and check if it's not
+    // null before calling HandleOverscroll(). This is necessary because
+    // Destroy(), which nulls out mTreeManager, could be called concurrently.
+    APZCTreeManager* treeManagerLocal = mTreeManager;
+    if (treeManagerLocal) {
+      // "+ overscroll" rather than "- overscroll" for the same reason as above.
+      treeManagerLocal->HandleOverscroll(this, aEndPoint + overscroll, aEndPoint);
+    }
   }
 }
 
 void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) {
   ScreenIntPoint prevTouchPoint(mX.GetPos(), mY.GetPos());
   ScreenIntPoint touchPoint = GetFirstTouchScreenPoint(aEvent);
   TimeDuration timeDelta = TimeDuration().FromMilliseconds(aEvent.mTime - mLastEventTime);
 
@@ -1126,16 +1150,18 @@ void AsyncPanZoomController::RequestCont
 
   // This message is compressed, so fire whether or not we already have a paint
   // queued up. We need to know whether or not a paint was requested anyways,
   // for the purposes of content calling window.scrollTo().
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     APZC_LOG_FM(mFrameMetrics, "%p requesting content repaint", this);
 
+    LogRendertraceRect("requested displayport", "yellow", newDisplayPort);
+
     mPaintThrottler.PostTask(
       FROM_HERE,
       NewRunnableMethod(controller.get(),
                         &GeckoContentController::RequestContentRepaint,
                         mFrameMetrics),
       GetFrameTime());
   }
   mFrameMetrics.mPresShellId = mLastContentPaintMetrics.mPresShellId;
@@ -1206,16 +1232,20 @@ bool AsyncPanZoomController::SampleConte
     }
     default:
       break;
     }
 
     aScrollOffset = mFrameMetrics.mScrollOffset * mFrameMetrics.mZoom;
     *aNewTransform = GetCurrentAsyncTransform();
 
+    LogRendertraceRect("viewport", "red",
+      CSSRect(mFrameMetrics.mScrollOffset,
+              ScreenSize(mFrameMetrics.mCompositionBounds.Size()) / mFrameMetrics.mZoom));
+
     mCurrentAsyncScrollOffset = mFrameMetrics.mScrollOffset;
   }
 
   // Cancel the mAsyncScrollTimeoutTask because we will fire a
   // mozbrowserasyncscroll event or renew the mAsyncScrollTimeoutTask again.
   if (mAsyncScrollTimeoutTask) {
     mAsyncScrollTimeoutTask->Cancel();
     mAsyncScrollTimeoutTask = nullptr;
@@ -1273,29 +1303,29 @@ void AsyncPanZoomController::NotifyLayer
   ReentrantMonitorAutoEnter lock(mMonitor);
 
   mLastContentPaintMetrics = aLayerMetrics;
 
   bool isDefault = mFrameMetrics.IsDefault();
   mFrameMetrics.mMayHaveTouchListeners = aLayerMetrics.mMayHaveTouchListeners;
   APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d", this, aIsFirstPaint);
 
+  LogRendertraceRect("page", "brown", aLayerMetrics.mScrollableRect);
+  LogRendertraceRect("painted displayport", "green",
+    aLayerMetrics.mDisplayPort + aLayerMetrics.mScrollOffset);
+
   mPaintThrottler.TaskComplete(GetFrameTime());
   bool needContentRepaint = false;
   if (aLayerMetrics.mCompositionBounds.width == mFrameMetrics.mCompositionBounds.width &&
       aLayerMetrics.mCompositionBounds.height == mFrameMetrics.mCompositionBounds.height) {
     // Remote content has sync'd up to the composition geometry
     // change, so we can accept the viewport it's calculated.
-    CSSToScreenScale previousResolution = mFrameMetrics.CalculateIntrinsicScale();
+    if (mFrameMetrics.mViewport.width != aLayerMetrics.mViewport.width)
+      needContentRepaint = true;
     mFrameMetrics.mViewport = aLayerMetrics.mViewport;
-    CSSToScreenScale newResolution = mFrameMetrics.CalculateIntrinsicScale();
-    if (previousResolution != newResolution) {
-      needContentRepaint = true;
-      mFrameMetrics.mZoom.scale *= newResolution.scale / previousResolution.scale;
-    }
   }
 
   if (aIsFirstPaint || isDefault) {
     mPaintThrottler.ClearHistory();
     mPaintThrottler.SetMaxDurations(gNumPaintDurationSamples);
 
     mX.CancelTouch();
     mY.CancelTouch();
@@ -1534,19 +1564,17 @@ void AsyncPanZoomController::UpdateScrol
     aScrollOffset.x, aScrollOffset.y);
 
   ReentrantMonitorAutoEnter lock(mMonitor);
   mFrameMetrics.mScrollOffset = aScrollOffset;
 }
 
 bool AsyncPanZoomController::Matches(const ScrollableLayerGuid& aGuid)
 {
-  // TODO: also check the presShellId, once that is fully propagated
-  // everywhere in RenderFrameParent and AndroidJNI.
-  return aGuid.mLayersId == mLayersId && aGuid.mScrollId == mFrameMetrics.mScrollId;
+  return aGuid == ScrollableLayerGuid(mLayersId, mFrameMetrics);
 }
 
 void AsyncPanZoomController::GetGuid(ScrollableLayerGuid* aGuidOut)
 {
   if (!aGuidOut) {
     return;
   }
   aGuidOut->mLayersId = mLayersId;
--- a/gfx/layers/ipc/AsyncPanZoomController.h
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -8,16 +8,17 @@
 #define mozilla_layers_AsyncPanZoomController_h
 
 #include "GeckoContentController.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/Atomics.h"
 #include "InputData.h"
 #include "Axis.h"
 #include "TaskThrottler.h"
 #include "gfx3DMatrix.h"
 
 #include "base/message_loop.h"
 
 namespace mozilla {
@@ -647,17 +648,17 @@ public:
   }
 
 private:
   // This is a raw pointer to avoid introducing a reference cycle between
   // AsyncPanZoomController and APZCTreeManager. Since these objects don't
   // live on the main thread, we can't use the cycle collector with them.
   // The APZCTreeManager owns the lifetime of the APZCs, so nulling this
   // pointer out in Destroy() will prevent accessing deleted memory.
-  APZCTreeManager* mTreeManager;
+  Atomic<APZCTreeManager*> mTreeManager;
 
   nsRefPtr<AsyncPanZoomController> mLastChild;
   nsRefPtr<AsyncPanZoomController> mPrevSibling;
   nsRefPtr<AsyncPanZoomController> mParent;
 
   /* The functions and members in this section are used to maintain the
    * area that this APZC instance is responsible for. This is used when
    * hit-testing to see which APZC instance should handle touch events.
--- a/gfx/layers/ipc/GeckoContentController.h
+++ b/gfx/layers/ipc/GeckoContentController.h
@@ -60,22 +60,23 @@ public:
 
   /**
    * Schedules a runnable to run on the controller/UI thread at some time
    * in the future.
    */
   virtual void PostDelayedTask(Task* aTask, int aDelayMs) = 0;
 
   /**
-   * Retrieves the last known zoom constraints. This function should return
-   * false if there are no last known zoom constraints.
+   * Retrieves the last known zoom constraints for the root scrollable layer
+   * for this layers tree. This function should return false if there are no
+   * last known zoom constraints.
    */
-  virtual bool GetZoomConstraints(bool* aOutAllowZoom,
-                                  CSSToScreenScale* aOutMinZoom,
-                                  CSSToScreenScale* aOutMaxZoom)
+  virtual bool GetRootZoomConstraints(bool* aOutAllowZoom,
+                                      CSSToScreenScale* aOutMinZoom,
+                                      CSSToScreenScale* aOutMaxZoom)
   {
     return false;
   }
 
   /**
    * Request any special actions be performed when panning starts
    */
   virtual void HandlePanBegin() {}
--- a/gfx/layers/ipc/GestureEventListener.cpp
+++ b/gfx/layers/ipc/GestureEventListener.cpp
@@ -189,18 +189,17 @@ nsEventStatus GestureEventListener::Hand
     }
 
     break;
   }
   case MultiTouchInput::MULTITOUCH_CANCEL:
     // This gets called if there's a touch that has to bail for weird reasons
     // like pinching and then moving away from the window that the pinch was
     // started in without letting go of the screen.
-    HandlePinchGestureEvent(event, true);
-    break;
+    return HandlePinchGestureEvent(event, true);
   }
 
   return HandlePinchGestureEvent(event, false);
 }
 
 nsEventStatus GestureEventListener::HandlePinchGestureEvent(const MultiTouchInput& aEvent, bool aClearTouches)
 {
   nsEventStatus rv = nsEventStatus_eIgnore;
@@ -252,19 +251,29 @@ nsEventStatus GestureEventListener::Hand
     }
 
     mPreviousSpan = currentSpan;
 
     rv = nsEventStatus_eConsumeNoDefault;
   } else if (mState == GESTURE_PINCH) {
     PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
                                  aEvent.mTime,
-                                 mTouches[0].mScreenPoint,
-                                 1.0f,
-                                 1.0f);
+                                 ScreenPoint(),  // may change below
+                                 1.0f,           // may change below
+                                 1.0f);          // may change below
+
+    if (mTouches.Length() > 0) {
+      // Pinch is changing to pan. APZC will start a pan at mFocusPoint
+      // (which isn't really a focus point in this case...).
+      pinchEvent.mFocusPoint = mTouches[0].mScreenPoint;
+    } else {
+      // Pinch is ending, no pan to follow. APZC will check for the spans
+      // being negative.
+      pinchEvent.mCurrentSpan = pinchEvent.mPreviousSpan = -1.0f;
+    }
 
     mAsyncPanZoomController->HandleInputEvent(pinchEvent);
 
     mState = GESTURE_NONE;
 
     rv = nsEventStatus_eConsumeNoDefault;
   }
 
--- a/gfx/layers/ipc/ISurfaceAllocator.cpp
+++ b/gfx/layers/ipc/ISurfaceAllocator.cpp
@@ -22,16 +22,18 @@
 #include "prenv.h"
 #endif
 
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace layers {
 
+mozilla::Atomic<int32_t> GfxMemoryImageReporter::sAmount(0);
+
 SharedMemory::SharedMemoryType OptimalShmemType()
 {
   return SharedMemory::TYPE_BASIC;
 }
 
 bool
 IsSurfaceDescriptorValid(const SurfaceDescriptor& aSurface)
 {
@@ -83,16 +85,17 @@ ISurfaceAllocator::AllocSurfaceDescripto
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     gfxImageFormat format =
       gfxPlatform::GetPlatform()->OptimalFormatForContent(aContent);
     int32_t stride = gfxASurface::FormatStrideForWidth(format, aSize.width);
     uint8_t *data = new (std::nothrow) uint8_t[stride * aSize.height];
     if (!data) {
       return false;
     }
+    GfxMemoryImageReporter::DidAlloc(data);
 #ifdef XP_MACOSX
     // Workaround a bug in Quartz where drawing an a8 surface to another a8
     // surface with OPERATOR_SOURCE still requires the destination to be clear.
     if (format == gfxImageFormatA8) {
       memset(data, 0, stride * aSize.height);
     }
 #endif
     *aBuffer = MemoryImage((uintptr_t)data, aSize, stride, format);
@@ -130,17 +133,18 @@ ISurfaceAllocator::DestroySharedSurface(
     case SurfaceDescriptor::TRGBImage:
       DeallocShmem(aSurface->get_RGBImage().data());
       break;
     case SurfaceDescriptor::TSurfaceDescriptorD3D9:
     case SurfaceDescriptor::TSurfaceDescriptorDIB:
     case SurfaceDescriptor::TSurfaceDescriptorD3D10:
       break;
     case SurfaceDescriptor::TMemoryImage:
-      delete [] (unsigned char *)aSurface->get_MemoryImage().data();
+      GfxMemoryImageReporter::WillFree((uint8_t*)aSurface->get_MemoryImage().data());
+      delete [] (uint8_t*)aSurface->get_MemoryImage().data();
       break;
     case SurfaceDescriptor::Tnull_t:
     case SurfaceDescriptor::T__None:
       break;
     default:
       NS_RUNTIMEABORT("surface type not implemented!");
   }
   *aSurface = SurfaceDescriptor();
--- a/gfx/layers/ipc/ISurfaceAllocator.h
+++ b/gfx/layers/ipc/ISurfaceAllocator.h
@@ -7,16 +7,18 @@
 #define GFX_LAYERS_ISURFACEDEALLOCATOR
 
 #include <stddef.h>                     // for size_t
 #include <stdint.h>                     // for uint32_t
 #include "gfxTypes.h"
 #include "gfxPoint.h"                   // for gfxIntSize
 #include "mozilla/ipc/SharedMemory.h"   // for SharedMemory, etc
 #include "mozilla/WeakPtr.h"
+#include "nsIMemoryReporter.h"          // for MemoryUniReporter
+#include "mozilla/Atomics.h"            // for Atomic
 
 /*
  * FIXME [bjacob] *** PURE CRAZYNESS WARNING ***
  *
  * This #define is actually needed here, because subclasses of ISurfaceAllocator,
  * namely ShadowLayerForwarder, will or will not override AllocGrallocBuffer
  * depending on whether MOZ_HAVE_SURFACEDESCRIPTORGRALLOC is defined.
  */
@@ -34,16 +36,18 @@ namespace mozilla {
 namespace ipc {
 class Shmem;
 }
 
 namespace layers {
 
 class PGrallocBufferChild;
 class MaybeMagicGrallocBufferHandle;
+class MemoryTextureClient;
+class MemoryTextureHost;
 
 enum BufferCapabilities {
   DEFAULT_BUFFER_CAPS = 0,
   /**
    * The allocated buffer must be efficiently mappable as a
    * gfxImageSurface.
    */
   MAP_AS_IMAGE_SURFACE = 1 << 0,
@@ -128,12 +132,44 @@ protected:
                                               gfxContentType aContent,
                                               uint32_t aCaps,
                                               SurfaceDescriptor* aBuffer);
 
 
   ~ISurfaceAllocator() {}
 };
 
+class GfxMemoryImageReporter MOZ_FINAL : public mozilla::MemoryUniReporter
+{
+public:
+  GfxMemoryImageReporter()
+    : MemoryUniReporter("explicit/gfx/heap-textures", KIND_HEAP, UNITS_BYTES,
+                        "Heap memory shared between threads by texture clients and hosts.")
+  {
+#ifdef DEBUG
+    // There must be only one instance of this class, due to |sAmount|
+    // being static.
+    static bool hasRun = false;
+    MOZ_ASSERT(!hasRun);
+    hasRun = true;
+#endif
+  }
+
+  static void DidAlloc(void* aPointer)
+  {
+    sAmount += MallocSizeOfOnAlloc(aPointer);
+  }
+
+  static void WillFree(void* aPointer)
+  {
+    sAmount -= MallocSizeOfOnFree(aPointer);
+  }
+
+private:
+  int64_t Amount() MOZ_OVERRIDE { return sAmount; }
+
+  static mozilla::Atomic<int32_t> sAmount;
+};
+
 } // namespace
 } // namespace
 
 #endif
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -123,18 +123,20 @@ ApzcPinch(AsyncPanZoomController* aApzc,
   aApzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE,
                                             0,
                                             ScreenPoint(aFocusX, aFocusY),
                                             10.0 * aScale,
                                             10.0));
   aApzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_END,
                                             0,
                                             ScreenPoint(aFocusX, aFocusY),
-                                            10.0 * aScale,
-                                            10.0 * aScale));
+                                            // note: negative values here tell APZC
+                                            //       not to turn the pinch into a pan
+                                            -1.0,
+                                            -1.0));
 }
 
 TEST(AsyncPanZoomController, Constructor) {
   // RefCounted class can't live in the stack
   nsRefPtr<MockContentController> mcc = new MockContentController();
   nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
   apzc->SetFrameMetrics(TestFrameMetrics());
 }
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -4162,24 +4162,24 @@ gfxFontGroup *
 gfxFontGroup::Copy(const gfxFontStyle *aStyle)
 {
     return new gfxFontGroup(mFamilies, aStyle, mUserFontSet);
 }
 
 bool 
 gfxFontGroup::IsInvalidChar(uint8_t ch)
 {
-    return ((ch & 0x7f) < 0x20);
+    return ((ch & 0x7f) < 0x20 || ch == 0x7f);
 }
 
 bool 
 gfxFontGroup::IsInvalidChar(PRUnichar ch)
 {
     // All printable 7-bit ASCII values are OK
-    if (ch >= ' ' && ch < 0x80) {
+    if (ch >= ' ' && ch < 0x7f) {
         return false;
     }
     // No point in sending non-printing control chars through font shaping
     if (ch <= 0x9f) {
         return true;
     }
     return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ &&
              (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/)) ||
--- a/gfx/thebes/gfxFontconfigUtils.h
+++ b/gfx/thebes/gfxFontconfigUtils.h
@@ -3,21 +3,21 @@
  * 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/. */
 
 #ifndef GFX_FONTCONFIG_UTILS_H
 #define GFX_FONTCONFIG_UTILS_H
 
 #include "gfxPlatform.h"
 
+#include "mozilla/MathAlgorithms.h"
 #include "nsAutoRef.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"
 #include "nsISupportsImpl.h"
-#include "prbit.h" // for PR_ROTATE_LEFT32
 
 #include <fontconfig/fontconfig.h>
 
 
 template <>
 class nsAutoRefTraits<FcPattern> : public nsPointerRefTraits<FcPattern>
 {
 public:
@@ -160,17 +160,17 @@ protected:
         // except for whitespace in the first character, while FcFontList
         // and config subsitution tests require whitespace to match
         // exactly.  CSS 2.1 implies that whitespace is important in the
         // font-family property.  FcStrCmpIgnoreCase considers whitespace
         // important.
         static PLDHashNumber HashKey(const FcChar8 *aKey) {
             uint32_t hash = 0;
             for (const FcChar8 *c = aKey; *c != '\0'; ++c) {
-                hash = PR_ROTATE_LEFT32(hash, 3) ^ FcToLower(*c);
+                hash = mozilla::RotateLeft(hash, 3) ^ FcToLower(*c);
             }
             return hash;
         }
         enum { ALLOW_MEMMOVE = true };
     };
 
 public:
     // Hash entry with a dependent const FcChar8* pointer to an external
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -5,16 +5,17 @@
 
 #ifdef MOZ_LOGGING
 #define FORCE_PR_LOG /* Allow logging in the release build */
 #endif
 
 #include "mozilla/layers/CompositorChild.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/layers/ISurfaceAllocator.h"     // for GfxMemoryImageReporter
 
 #include "prlog.h"
 
 #include "gfxPlatform.h"
 
 #ifdef XP_WIN
 #include <process.h>
 #define getpid _getpid
@@ -492,16 +493,18 @@ gfxPlatform::Init()
 #endif
 
     // Listen to memory pressure event so we can purge DrawTarget caches
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
         gPlatform->mMemoryPressureObserver = new MemoryPressureObserver();
         obs->AddObserver(gPlatform->mMemoryPressureObserver, "memory-pressure", false);
     }
+
+    NS_RegisterMemoryReporter(new GfxMemoryImageReporter());
 }
 
 void
 gfxPlatform::Shutdown()
 {
     // These may be called before the corresponding subsystems have actually
     // started up. That's OK, they can handle it.
     gfxFontCache::Shutdown();
--- a/hal/gonk/GonkFMRadio.cpp
+++ b/hal/gonk/GonkFMRadio.cpp
@@ -32,16 +32,17 @@ namespace hal_impl {
 
 uint32_t GetFMRadioFrequency();
 
 static int sRadioFD;
 static bool sRadioEnabled;
 static pthread_t sRadioThread;
 static hal::FMRadioSettings sRadioSettings;
 static int sTavaruaVersion;
+static bool sTavaruaMode;
 
 static int
 setControl(uint32_t id, int32_t value)
 {
   struct v4l2_control control;
   control.id = id;
   control.value = value;
   return ioctl(sRadioFD, VIDIOC_S_CTRL, &control);
@@ -143,16 +144,17 @@ initTavaruaRadio(hal::FMRadioSettings &a
    * HAL uses units of 1k for frequencies
    * V4L2 uses units of 62.5kHz
    * Multiplying by (10000 / 625) converts from HAL units to V4L2.
    */
 
   struct v4l2_tuner tuner = {0};
   tuner.rangelow = (aInfo.lowerLimit() * 10000) / 625;
   tuner.rangehigh = (aInfo.upperLimit() * 10000) / 625;
+  tuner.audmode = V4L2_TUNER_MODE_STEREO;
   rc = ioctl(fd, VIDIOC_S_TUNER, &tuner);
   if (rc < 0) {
     HAL_LOG(("Unable to adjust band limits"));
     return;
   }
 
   rc = setControl(V4L2_CID_PRIVATE_TAVARUA_REGION, TAVARUA_REGION_OTHER);
   if (rc < 0) {
@@ -215,26 +217,29 @@ runTavaruaRadio(void *)
 
   while (sRadioEnabled) {
     if (ioctl(sRadioFD, VIDIOC_DQBUF, &buffer) < 0) {
       if (errno == EINTR)
         continue;
       break;
     }
 
+    /* The tavarua driver reports a number of things asynchronously.
+     * In those cases, the status update comes from this thread. */
     for (unsigned int i = 0; i < buffer.bytesused; i++) {
       switch (buf[i]) {
       case TAVARUA_EVT_RADIO_READY:
         // The driver sends RADIO_READY both when we turn the radio on and when we turn 
         // the radio off.
         if (sRadioEnabled) {
           NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE,
                                                   hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
         }
         break;
+
       case TAVARUA_EVT_SEEK_COMPLETE:
         NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_SEEK,
                                                 hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
         break;
       case TAVARUA_EVT_TUNE_SUCC:
         NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_TUNE,
                                                 hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
         break;
@@ -265,72 +270,113 @@ EnableFMRadio(const hal::FMRadioSettings
 
   struct v4l2_capability cap;
   int rc = ioctl(fd, VIDIOC_QUERYCAP, &cap);
   if (rc < 0) {
     HAL_LOG(("Unable to query radio device"));
     return;
   }
 
+  sTavaruaMode = !strcmp((char *)cap.driver, "radio-tavarua");
   HAL_LOG(("Radio: %s (%s)\n", cap.driver, cap.card));
 
   if (!(cap.capabilities & V4L2_CAP_RADIO)) {
     HAL_LOG(("/dev/radio0 isn't a radio"));
     return;
   }
 
   if (!(cap.capabilities & V4L2_CAP_TUNER)) {
     HAL_LOG(("/dev/radio0 doesn't support the tuner interface"));
     return;
   }
-  sRadioFD = fd.forget();
   sRadioSettings = aInfo;
 
-  // Tavarua specific start
-  sTavaruaVersion = cap.version;
-  pthread_create(&sRadioThread, nullptr, runTavaruaRadio, nullptr);
-  // Tavarua specific end
+  if (sTavaruaMode) {
+    sRadioFD = fd.forget();
+    sTavaruaVersion = cap.version;
+    pthread_create(&sRadioThread, nullptr, runTavaruaRadio, nullptr);
+    return;
+  }
+
+  struct v4l2_tuner tuner = {0};
+  tuner.type = V4L2_TUNER_RADIO;
+  tuner.rangelow = (aInfo.lowerLimit() * 10000) / 625;
+  tuner.rangehigh = (aInfo.upperLimit() * 10000) / 625;
+  tuner.audmode = V4L2_TUNER_MODE_STEREO;
+  rc = ioctl(fd, VIDIOC_S_TUNER, &tuner);
+  if (rc < 0) {
+    HAL_LOG(("Unable to adjust band limits"));
+  }
+
+  sRadioFD = fd.forget();
+  sRadioEnabled = true;
+
+  hal::FMRadioOperationInformation info;
+  info.operation() = hal::FM_RADIO_OPERATION_ENABLE;
+  info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS;
+  hal::NotifyFMRadioStatus(info);
 }
 
 void
 DisableFMRadio()
 {
   if (!sRadioEnabled)
     return;
 
   sRadioEnabled = false;
 
-  // Tavarua specific start
-  int rc = setControl(V4L2_CID_PRIVATE_TAVARUA_STATE, FM_OFF);
-  if (rc < 0) {
-    HAL_LOG(("Unable to turn off radio"));
+  if (sTavaruaMode) {
+    int rc = setControl(V4L2_CID_PRIVATE_TAVARUA_STATE, FM_OFF);
+    if (rc < 0) {
+      HAL_LOG(("Unable to turn off radio"));
+    }
+
+    pthread_join(sRadioThread, nullptr);
   }
-  // Tavarua specific end
-
-  pthread_join(sRadioThread, nullptr);
 
   close(sRadioFD);
 
   hal::FMRadioOperationInformation info;
   info.operation() = hal::FM_RADIO_OPERATION_DISABLE;
   info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS;
   hal::NotifyFMRadioStatus(info);
 }
 
 void
 FMRadioSeek(const hal::FMRadioSeekDirection& aDirection)
 {
   struct v4l2_hw_freq_seek seek = {0};
   seek.type = V4L2_TUNER_RADIO;
   seek.seek_upward = aDirection == hal::FMRadioSeekDirection::FM_RADIO_SEEK_DIRECTION_UP;
+
+  /* ICS and older don't have the spacing field */
+#if ANDROID_VERSION == 15
+  seek.reserved[0] = sRadioSettings.spaceType() * 1000;
+#else
+  seek.spacing = sRadioSettings.spaceType() * 1000;
+#endif
+
   int rc = ioctl(sRadioFD, VIDIOC_S_HW_FREQ_SEEK, &seek);
+  if (sTavaruaMode && rc >= 0)
+    return;
+
+  hal::FMRadioOperationInformation info;
+  info.operation() = hal::FM_RADIO_OPERATION_SEEK;
+  info.status() = rc < 0 ? hal::FM_RADIO_OPERATION_STATUS_FAIL :
+                           hal::FM_RADIO_OPERATION_STATUS_SUCCESS;
+  hal::NotifyFMRadioStatus(info);
+
   if (rc < 0) {
     HAL_LOG(("Could not initiate hardware seek"));
     return;
   }
+
+  info.operation() = hal::FM_RADIO_OPERATION_TUNE;
+  info.status() = hal::FM_RADIO_OPERATION_STATUS_SUCCESS;
+  hal::NotifyFMRadioStatus(info);
 }
 
 void
 GetFMRadioSettings(hal::FMRadioSettings* aInfo)
 {
   if (!sRadioEnabled) {
     return;
   }
@@ -351,16 +397,25 @@ SetFMRadioFrequency(const uint32_t frequ
 {
   struct v4l2_frequency freq = {0};
   freq.type = V4L2_TUNER_RADIO;
   freq.frequency = (frequency * 10000) / 625;
 
   int rc = ioctl(sRadioFD, VIDIOC_S_FREQUENCY, &freq);
   if (rc < 0)
     HAL_LOG(("Could not set radio frequency"));
+
+  if (sTavaruaMode && rc >= 0)
+    return;
+
+  hal::FMRadioOperationInformation info;
+  info.operation() = hal::FM_RADIO_OPERATION_TUNE;
+  info.status() = rc < 0 ? hal::FM_RADIO_OPERATION_STATUS_FAIL :
+                           hal::FM_RADIO_OPERATION_STATUS_SUCCESS;
+  hal::NotifyFMRadioStatus(info);
 }
 
 uint32_t
 GetFMRadioFrequency()
 {
   if (!sRadioEnabled)
     return 0;
 
--- a/image/src/FrameAnimator.cpp
+++ b/image/src/FrameAnimator.cpp
@@ -53,21 +53,23 @@ TimeStamp
 FrameAnimator::GetCurrentImgFrameEndTime() const
 {
   imgFrame* currentFrame = mFrameBlender.RawGetFrame(mCurrentAnimationFrameIndex);
   TimeStamp currentFrameTime = mCurrentAnimationFrameTime;
   int64_t timeout = currentFrame->GetTimeout();
 
   if (timeout < 0) {
     // We need to return a sentinel value in this case, because our logic
-    // doesn't work correctly if we have a negative timeout value. The reason
-    // this positive infinity was chosen was because it works with the loop in
-    // RequestRefresh() below.
-    return TimeStamp() +
-           TimeDuration::FromMilliseconds(static_cast<double>(UINT64_MAX));
+    // doesn't work correctly if we have a negative timeout value. We use
+    // one year in the future as the sentinel because it works with the loop
+    // in RequestRefresh() below.
+    // XXX(seth): It'd be preferable to make our logic work correctly with
+    // negative timeouts.
+    return TimeStamp::NowLoRes() +
+           TimeDuration::FromMilliseconds(31536000.0);
   }
 
   TimeDuration durationOfTimeout =
     TimeDuration::FromMilliseconds(static_cast<double>(timeout));
   TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
 
   return currentFrameEndTime;
 }
--- a/image/src/Image.cpp
+++ b/image/src/Image.cpp
@@ -131,14 +131,13 @@ ImageResource::SetAnimationModeInternal(
 void
 ImageResource::EvaluateAnimation()
 {
   if (!mAnimating && ShouldAnimate()) {
     nsresult rv = StartAnimation();
     mAnimating = NS_SUCCEEDED(rv);
   } else if (mAnimating && !ShouldAnimate()) {
     StopAnimation();
-    mAnimating = false;
   }
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -1431,43 +1431,47 @@ RasterImage::StartAnimation()
   if (mError)
     return NS_ERROR_FAILURE;
 
   NS_ABORT_IF_FALSE(ShouldAnimate(), "Should not animate!");
 
   EnsureAnimExists();
 
   imgFrame* currentFrame = GetCurrentImgFrame();
-  if (currentFrame) {
-    if (currentFrame->GetTimeout() < 0) { // -1 means display this frame forever
-      mAnimationFinished = true;
-      return NS_ERROR_ABORT;
-    }
-
+  // A timeout of -1 means we should display this frame forever.
+  if (currentFrame && currentFrame->GetTimeout() < 0) {
+    mAnimationFinished = true;
+    return NS_ERROR_ABORT;
+  }
+
+  if (mAnim) {
     // We need to set the time that this initial frame was first displayed, as
     // this is used in AdvanceFrame().
     mAnim->InitAnimationFrameTimeIfNecessary();
   }
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* void stopAnimation (); */
 nsresult
 RasterImage::StopAnimation()
 {
   NS_ABORT_IF_FALSE(mAnimating, "Should be animating!");
 
-  if (mError)
-    return NS_ERROR_FAILURE;
-
-  mAnim->SetAnimationFrameTime(TimeStamp());
-
-  return NS_OK;
+  nsresult rv = NS_OK;
+  if (mError) {
+    rv = NS_ERROR_FAILURE;
+  } else {
+    mAnim->SetAnimationFrameTime(TimeStamp());
+  }
+
+  mAnimating = false;
+  return rv;
 }
 
 //******************************************************************************
 /* void resetAnimation (); */
 NS_IMETHODIMP
 RasterImage::ResetAnimation()
 {
   if (mError)
@@ -1478,38 +1482,32 @@ RasterImage::ResetAnimation()
     return NS_OK;
 
   mAnimationFinished = false;
 
   if (mAnimating)
     StopAnimation();
 
   mFrameBlender.ResetAnimation();
-  if (mAnim) {
-    mAnim->ResetAnimation();
-  }
+  mAnim->ResetAnimation();
 
   UpdateImageContainer();
 
   // Note - We probably want to kick off a redecode somewhere around here when
   // we fix bug 500402.
 
   // Update display
   if (mStatusTracker) {
     nsIntRect rect = mAnim->GetFirstFrameRefreshArea();
     mStatusTracker->FrameChanged(&rect);
   }
 
-  if (ShouldAnimate()) {
-    StartAnimation();
-    // The animation may not have been running before, if mAnimationFinished
-    // was false (before we changed it to true in this function). So, mark the
-    // animation as running.
-    mAnimating = true;
-  }
+  // Start the animation again. It may not have been running before, if
+  // mAnimationFinished was true before entering this function.
+  EvaluateAnimation();
 
   return NS_OK;
 }
 
 //******************************************************************************
 // [notxpcom] void setAnimationStartTime ([const] in TimeStamp aTime);
 NS_IMETHODIMP_(void)
 RasterImage::SetAnimationStartTime(const mozilla::TimeStamp& aTime)
@@ -1568,20 +1566,18 @@ RasterImage::AddSourceData(const char *a
     return NS_OK;
   }
 
   // Starting a new part's frames, let's clean up before we add any
   // This needs to happen just before we start getting EnsureFrame() call(s),
   // so that there's no gap for anything to miss us