Merge m-c to fx-team
authorWes Kocher <wkocher@mozilla.com>
Fri, 13 Sep 2013 18:22:41 -0700
changeset 160084 37452cc13fa16d4d1360ec01cfe3cbeabaaf1d0e
parent 160083 c33d415d991ecd8b9bf948913926f5491c2641c9 (current diff)
parent 160071 53d5e43e23cc12d8d2d48d191e4d0e7685000e3a (diff)
child 160085 d60477f4cb2061b03c7cb98196fc895d83699597
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone26.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 fx-team
content/canvas/src/ImageEncoder.cpp
content/canvas/src/ImageEncoder.h
widget/gonk/hardware/hwcomposer.h
--- a/addon-sdk/source/lib/sdk/window/utils.js
+++ b/addon-sdk/source/lib/sdk/window/utils.js
@@ -292,16 +292,17 @@ exports.openDialog = openDialog;
 function windows(type, options) {
   options = options || {};
   let list = [];
   let winEnum = WM.getEnumerator(type);
   while (winEnum.hasMoreElements()) {
     let window = winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
     // Only add non-private windows when pb permission isn't set,
     // unless an option forces the addition of them.
+    // XXXbz should this be checking window.closed?
     if (options.includePrivate || !isWindowPrivate(window)) {
       list.push(window);
     }
   }
   return list;
 }
 exports.windows = windows;
 
--- a/b2g/components/ContentPermissionPrompt.js
+++ b/b2g/components/ContentPermissionPrompt.js
@@ -244,23 +244,18 @@ ContentPermissionPrompt.prototype = {
       remember: remember
     };
 
     if (!isApp) {
       browser.shell.sendChromeEvent(details);
       return;
     }
 
-    // When it's an app, get the manifest to add the l10n application name.
-    let app = DOMApplicationRegistry.getAppByLocalId(principal.appId);
-    DOMApplicationRegistry.getManifestFor(app.manifestURL, function getManifest(aManifest) {
-      let helper = new ManifestHelper(aManifest, app.origin);
-      details.appName = helper.name;
-      browser.shell.sendChromeEvent(details);
-    });
+    details.manifestURL = DOMApplicationRegistry.getManifestURLByLocalId(principal.appId);
+    browser.shell.sendChromeEvent(details);
   },
 
   classID: Components.ID("{8c719f03-afe0-4aac-91ff-6c215895d467}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt])
 };
 
 
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "4ab5602bc5338c921426cf44e8fbc1b2ddd69290", 
+    "revision": "092d3aabd1ec799c748809a484009f0bdde8c51f", 
     "repo_path": "/integration/gaia-central"
 }
new file mode 100644
--- /dev/null
+++ b/b2g/config/mako/config.json
@@ -0,0 +1,36 @@
+{
+    "config_version": 2,
+    "tooltool_manifest": "releng-mako.tt",
+    "mock_target": "mozilla-centos6-x86_64",
+    "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git", "libxml2"],
+    "mock_files": [["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"]],
+    "build_targets": [],
+    "upload_files": [
+        "{objdir}/dist/b2g-*.crashreporter-symbols.zip",
+        "{objdir}/dist/b2g-*.tar.gz",
+        "{workdir}/sources.xml"
+    ],
+    "zip_files": [
+        ["{workdir}/out/target/product/mako/*.img", "out/target/product/mako/"],
+        ["{workdir}/boot.img", "out/target/product/mako/"],
+        "{workdir}/flash.sh",
+        "{workdir}/load-config.sh",
+        "{workdir}/.config",
+        "{workdir}/sources.xml"
+    ],
+    "env": {
+        "VARIANT": "user",
+        "MOZILLA_OFFICIAL": "1",
+        "B2GUPDATER": "1"
+    },
+    "b2g_manifest": "nexus-4.xml",
+    "b2g_manifest_branch": "master",
+    "additional_source_tarballs": [],
+    "gecko_l10n_root": "http://hg.mozilla.org/l10n-central",
+    "gaia": {
+        "l10n": {
+            "vcs": "hgtool",
+            "root": "http://hg.mozilla.org/gaia-l10n"
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/b2g/config/mako/releng-mako.tt
@@ -0,0 +1,21 @@
+[
+{
+"size": 13111,
+"digest": "09373556ddb4325897b9e008184228f9088b4c8c22bacf4fa2d39793ecfd264316ad69c2bc8082229ad7fdb80f89154c7b995a60f9b18beb1847e7111e7e69b2",
+"algorithm": "sha512",
+"filename": "broadcom-mako-jwr66v-cbde0d61.tgz"
+},
+{
+"size": 12658359,
+"digest": "2483df1a949df53d02ca33a87731cedd8f7cd07114d723bde1addf63fd71154c23b6f11f64f390b9849121725fb53a402db8df2f96a3673ec52416f45260f79d",
+"algorithm": "sha512",
+"filename": "qcom-mako-jwr66v-30ef957c.tgz"
+},
+{
+"size": 378532,
+"digest": "27aced8feb0e757d61df37839e62410ff30a059cfa8f04897d29ab74b787c765313acf904b1f9cf311c3e682883514df7da54197665251ef9b8bdad6bd0f62c5",
+"algorithm": "sha512",
+"filename": "lge-mako-jwr66v-985845e4.tgz"
+}
+]
+
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -466,16 +466,18 @@
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 @BINPATH@/components/NetworkStatsManager.js
 @BINPATH@/components/NetworkStatsManager.manifest
 @BINPATH@/components/NetworkInterfaceListService.manifest
 @BINPATH@/components/NetworkInterfaceListService.js
 @BINPATH@/components/TelephonyProvider.manifest
 @BINPATH@/components/TelephonyProvider.js
+@BINPATH@/components/NetworkStatsServiceProxy.manifest
+@BINPATH@/components/NetworkStatsServiceProxy.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
--- a/browser/base/content/abouthome/aboutHome.js
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -337,24 +337,37 @@ function setupSearchEngine()
   else {
     logoElt.parentNode.hidden = true;
     searchText.placeholder = searchEngineName;
   }
 
 }
 
 /**
+ * Inform the test harness that we're done loading the page.
+ */
+function loadSucceeded()
+{
+  var event = new CustomEvent("AboutHomeLoadSnippetsSucceeded", {bubbles:true});
+  document.dispatchEvent(event);
+}
+
+/**
  * Update the local snippets from the remote storage, then show them through
  * showSnippets.
  */
 function loadSnippets()
 {
   if (!gSnippetsMap)
     throw new Error("Snippets map has not properly been initialized");
 
+  // Allow tests to modify the snippets map before using it.
+  var event = new CustomEvent("AboutHomeLoadSnippets", {bubbles:true});
+  document.dispatchEvent(event);
+
   // Check cached snippets version.
   let cachedVersion = gSnippetsMap.get("snippets-cached-version") || 0;
   let currentVersion = document.documentElement.getAttribute("snippetsVersion");
   if (cachedVersion < currentVersion) {
     // The cached snippets are old and unsupported, restart from scratch.
     gSnippetsMap.clear();
   }
 
@@ -365,35 +378,38 @@ function loadSnippets()
                      Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS;
   if (updateURL && shouldUpdate) {
     // Try to update from network.
     let xhr = new XMLHttpRequest();
     try {
       xhr.open("GET", updateURL, true);
     } catch (ex) {
       showSnippets();
+      loadSucceeded();
       return;
     }
     // Even if fetching should fail we don't want to spam the server, thus
     // set the last update time regardless its results.  Will retry tomorrow.
     gSnippetsMap.set("snippets-last-update", Date.now());
     xhr.onerror = function (event) {
       showSnippets();
     };
     xhr.onload = function (event)
     {
       if (xhr.status == 200) {
         gSnippetsMap.set("snippets", xhr.responseText);
         gSnippetsMap.set("snippets-cached-version", currentVersion);
       }
       showSnippets();
+      loadSucceeded();
     };
     xhr.send(null);
   } else {
     showSnippets();
+    loadSucceeded();
   }
 }
 
 /**
  * Shows locally cached remote snippets, or default ones when not available.
  *
  * @note: snippets should never invoke showSnippets(), or they may cause
  *        a "too much recursion" exception.
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2172,16 +2172,19 @@ function BrowserPageInfo(doc, initialTab
   var args = {doc: doc, initialTab: initialTab, imageElement: imageElement};
   var windows = Services.wm.getEnumerator("Browser:page-info");
 
   var documentURL = doc ? doc.location : window.content.document.location;
 
   // Check for windows matching the url
   while (windows.hasMoreElements()) {
     var currentWindow = windows.getNext();
+    if (currentWindow.closed) {
+      continue;
+    }
     if (currentWindow.document.documentElement.getAttribute("relatedUrl") == documentURL) {
       currentWindow.focus();
       currentWindow.resetPageInfo(args);
       return currentWindow;
     }
   }
 
   // We didn't find a matching window, so open a new one.
@@ -6035,17 +6038,17 @@ function warnAboutClosingWindow() {
     return gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
 
   // Figure out if there's at least one other browser window around.
   let e = Services.wm.getEnumerator("navigator:browser");
   let otherPBWindowExists = false;
   let nonPopupPresent = false;
   while (e.hasMoreElements()) {
     let win = e.getNext();
-    if (win != window) {
+    if (!win.closed && win != window) {
       if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win))
         otherPBWindowExists = true;
       if (win.toolbar.visible)
         nonPopupPresent = true;
       // If the current window is not in private browsing mode we don't need to 
       // look for other pb windows, we can leave the loop when finding the 
       // first non-popup window. If however the current window is in private 
       // browsing mode then we need at least one other pb and one non-popup 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2748,16 +2748,20 @@
       <property name="pageReport"
                 onget="return this.mCurrentBrowser.pageReport;"
                 readonly="true"/>
 
       <property name="currentURI"
                 onget="return this.mCurrentBrowser.currentURI;"
                 readonly="true"/>
 
+      <property name="finder"
+                onget="return this.mCurrentBrowser.finder"
+                readonly="true"/>
+
       <property name="docShell"
                 onget="return this.mCurrentBrowser.docShell"
                 readonly="true"/>
 
       <property name="webNavigation"
                 onget="return this.mCurrentBrowser.webNavigation"
                 readonly="true"/>
 
--- a/browser/base/content/test/browser_aboutHome.js
+++ b/browser/base/content/test/browser_aboutHome.js
@@ -12,16 +12,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 let gRightsVersion = Services.prefs.getIntPref("browser.rights.version");
 
 registerCleanupFunction(function() {
   // Ensure we don't pollute prefs for next tests.
   Services.prefs.clearUserPref("network.cookies.cookieBehavior");
   Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
   Services.prefs.clearUserPref("browser.rights.override");
   Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown");
+  Services.prefs.clearUserPref("browser.aboutHomeSnippets.updateUrl");
 });
 
 let gTests = [
 
 {
   desc: "Check that clearing cookies does not clear storage",
   setup: function ()
   {
@@ -333,123 +334,128 @@ function test()
   waitForExplicitFinish();
   requestLongerTimeout(2);
   ignoreAllUncaughtExceptions();
 
   Task.spawn(function () {
     for (let test of gTests) {
       info(test.desc);
 
+      // Make sure we don't try to load snippets from the network.
+      Services.prefs.setCharPref("browser.aboutHomeSnippets.updateUrl", "nonexistent://test");
+
       if (test.beforeRun)
         yield test.beforeRun();
 
-      let tab = yield promiseNewTabLoadEvent("about:home", "DOMContentLoaded");
+      // Create a tab to run the test.
+      let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+      // Add an event handler to modify the snippets map once it's ready.
+      let snippetsPromise = promiseSetupSnippetsMap(tab, test.setup);
 
-      // Must wait for both the snippets map and the browser attributes, since
-      // can't guess the order they will happen.
-      // So, start listening now, but verify the promise is fulfilled only
-      // after the snippets map setup.
-      let promise = promiseBrowserAttributes(tab);
-      // Prepare the snippets map with default values, then run the test setup.
-      let snippetsMap = yield promiseSetupSnippetsMap(tab, test.setup);
-      // Ensure browser has set attributes already, or wait for them.
-      yield promise;
+      // Start loading about:home and wait for it to complete.
+      yield promiseTabLoadEvent(tab, "about:home", "AboutHomeLoadSnippetsSucceeded");
+
+      // This promise should already be resolved since the page is done,
+      // but we still want to get the snippets map out of it.
+      let snippetsMap = yield snippetsPromise;
+
       info("Running test");
       yield test.run(snippetsMap);
       info("Cleanup");
       gBrowser.removeCurrentTab();
     }
   }).then(finish, ex => {
     ok(false, "Unexpected Exception: " + ex);
     finish();
   });
 }
 
 /**
- * Creates a new tab and waits for a load event.
+ * Starts a load in an existing tab and waits for it to finish (via some event).
  *
+ * @param aTab
+ *        The tab to load into.
  * @param aUrl
- *        The url to load in a new tab.
+ *        The url to load.
  * @param aEvent
  *        The load event type to wait for.  Defaults to "load".
- * @return {Promise} resolved when the event is handled.  Gets the new tab.
+ * @return {Promise} resolved when the event is handled.
  */
-function promiseNewTabLoadEvent(aUrl, aEventType="load")
+function promiseTabLoadEvent(aTab, aURL, aEventType="load")
 {
   let deferred = Promise.defer();
-  let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
   info("Wait tab event: " + aEventType);
-  tab.linkedBrowser.addEventListener(aEventType, function load(event) {
-    if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+  aTab.linkedBrowser.addEventListener(aEventType, function load(event) {
+    if (event.originalTarget != aTab.linkedBrowser.contentDocument ||
         event.target.location.href == "about:blank") {
       info("skipping spurious load event");
       return;
     }
-    tab.linkedBrowser.removeEventListener(aEventType, load, true);
+    aTab.linkedBrowser.removeEventListener(aEventType, load, true);
     info("Tab event received: " + aEventType);
-    deferred.resolve(tab);
-  }, true);
+    deferred.resolve();
+  }, true, true);
+  aTab.linkedBrowser.loadURI(aURL);
   return deferred.promise;
 }
 
 /**
  * Cleans up snippets and ensures that by default we don't try to check for
  * remote snippets since that may cause network bustage or slowness.
  *
  * @param aTab
  *        The tab containing about:home.
  * @param aSetupFn
  *        The setup function to be run.
  * @return {Promise} resolved when the snippets are ready.  Gets the snippets map.
  */
 function promiseSetupSnippetsMap(aTab, aSetupFn)
 {
   let deferred = Promise.defer();
-  let cw = aTab.linkedBrowser.contentWindow.wrappedJSObject;
   info("Waiting for snippets map");
-  cw.ensureSnippetsMapThen(function (aSnippetsMap) {
-    info("Got snippets map: " +
-         "{ last-update: " + aSnippetsMap.get("snippets-last-update") +
-         ", cached-version: " + aSnippetsMap.get("snippets-cached-version") +
-         " }");
-    // Don't try to update.
-    aSnippetsMap.set("snippets-last-update", Date.now());
-    aSnippetsMap.set("snippets-cached-version", AboutHomeUtils.snippetsVersion);
-    // Clear snippets.
-    aSnippetsMap.delete("snippets");
-    aSetupFn(aSnippetsMap);
-    // Must be sure to continue after the page snippets map setup.
-    executeSoon(function() deferred.resolve(aSnippetsMap));
-  });
+  aTab.linkedBrowser.addEventListener("AboutHomeLoadSnippets", function load(event) {
+    aTab.linkedBrowser.removeEventListener("AboutHomeLoadSnippets", load, true);
+
+    let cw = aTab.linkedBrowser.contentWindow.wrappedJSObject;
+    // The snippets should already be ready by this point. Here we're
+    // just obtaining a reference to the snippets map.
+    cw.ensureSnippetsMapThen(function (aSnippetsMap) {
+      info("Got snippets map: " +
+           "{ last-update: " + aSnippetsMap.get("snippets-last-update") +
+           ", cached-version: " + aSnippetsMap.get("snippets-cached-version") +
+           " }");
+      // Don't try to update.
+      aSnippetsMap.set("snippets-last-update", Date.now());
+      aSnippetsMap.set("snippets-cached-version", AboutHomeUtils.snippetsVersion);
+      // Clear snippets.
+      aSnippetsMap.delete("snippets");
+      aSetupFn(aSnippetsMap);
+      deferred.resolve(aSnippetsMap);
+    });
+  }, true, true);
   return deferred.promise;
 }
 
 /**
- * Waits for the attributes being set by browser.js and overwrites snippetsURL
- * to ensure we won't try to hit the network and we can force xhr to throw.
+ * Waits for the attributes being set by browser.js.
  *
  * @param aTab
  *        The tab containing about:home.
  * @return {Promise} resolved when the attributes are ready.
  */
 function promiseBrowserAttributes(aTab)
 {
   let deferred = Promise.defer();
 
   let docElt = aTab.linkedBrowser.contentDocument.documentElement;
-  //docElt.setAttribute("snippetsURL", "nonexistent://test");
   let observer = new MutationObserver(function (mutations) {
     for (let mutation of mutations) {
       info("Got attribute mutation: " + mutation.attributeName +
                                     " from " + mutation.oldValue); 
-      if (mutation.attributeName == "snippetsURL" &&
-          docElt.getAttribute("snippetsURL") != "nonexistent://test") {
-        docElt.setAttribute("snippetsURL", "nonexistent://test");
-      }
-
       // Now we just have to wait for the last attribute.
       if (mutation.attributeName == "searchEngineName") {
         info("Remove attributes observer");
         observer.disconnect();
         // Must be sure to continue after the page mutation observer.
         executeSoon(function() deferred.resolve());
         break;
       }
--- a/browser/base/content/test/browser_zbug569342.js
+++ b/browser/base/content/test/browser_zbug569342.js
@@ -36,17 +36,21 @@ let urls = [
 ];
 
 function nextTest() {
   let url = urls.shift();
   if (url) {
     testFindDisabled(url, nextTest);
   } else {
     // Make sure the find bar is re-enabled after disabled page is closed.
-    testFindEnabled("about:blank", finish);
+    testFindEnabled("about:blank", function () {
+      EventUtils.synthesizeKey("VK_ESCAPE", { });
+      ok(gFindBar.hidden, "Find bar should now be hidden");
+      finish();
+    });
   }
 }
 
 function testFindDisabled(url, cb) {
   load(url, function() {
     ok(gFindBar.hidden, "Find bar should not be visible");
     EventUtils.synthesizeKey("/", {}, gTab.linkedBrowser.contentWindow);
     ok(gFindBar.hidden, "Find bar should not be visible");
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -449,16 +449,19 @@ function isBidiEnabled() {
   return rv;
 }
 
 function openAboutDialog() {
   var enumerator = Services.wm.getEnumerator("Browser:About");
   while (enumerator.hasMoreElements()) {
     // Only open one about window (Bug 599573)
     let win = enumerator.getNext();
+    if (win.closed) {
+      continue;
+    }
     win.focus();
     return;
   }
 
 #ifdef XP_WIN
   var features = "chrome,centerscreen,dependent";
 #elifdef XP_MACOSX
   var features = "chrome,resizable=no,minimizable=no";
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -764,16 +764,17 @@ BrowserGlue.prototype = {
     if (aQuitType == "restart")
       return;
 
     var windowcount = 0;
     var pagecount = 0;
     var browserEnum = Services.wm.getEnumerator("navigator:browser");
     let allWindowsPrivate = true;
     while (browserEnum.hasMoreElements()) {
+      // XXXbz should we skip closed windows here?
       windowcount++;
 
       var browser = browserEnum.getNext();
       if (!PrivateBrowsingUtils.isWindowPrivate(browser))
         allWindowsPrivate = false;
       var tabbrowser = browser.document.getElementById("content");
       if (tabbrowser)
         pagecount += tabbrowser.browsers.length - tabbrowser._numPinnedTabs;
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -2576,17 +2576,17 @@ WebConsoleFrame.prototype = {
     // Make the location clickable.
     this._addMessageLinkCallback(locationNode, () => {
       if (isScratchpad) {
         let wins = Services.wm.getEnumerator("devtools:scratchpad");
 
         while (wins.hasMoreElements()) {
           let win = wins.getNext();
 
-          if (win.Scratchpad.uniqueName === aSourceURL) {
+          if (!win.closed && win.Scratchpad.uniqueName === aSourceURL) {
             win.focus();
             return;
           }
         }
       }
       else if (locationNode.parentNode.category == CATEGORY_CSS) {
         this.owner.viewSourceInStyleEditor(fullURL, aSourceLine);
       }
--- a/browser/modules/AboutHome.jsm
+++ b/browser/modules/AboutHome.jsm
@@ -173,31 +173,37 @@ let AboutHome = {
         window.loadURI(submission.uri.spec, null, submission.postData);
         break;
     }
   },
 
   // Send all the chrome-privileged data needed by about:home. This
   // gets re-sent when the search engine changes.
   sendAboutHomeData: function(target) {
-    let ss = Cc["@mozilla.org/browser/sessionstore;1"].
-               getService(Ci.nsISessionStore);
-    let data = {
-      showRestoreLastSession: ss.canRestoreLastSession,
-      snippetsURL: AboutHomeUtils.snippetsURL,
-      showKnowYourRights: AboutHomeUtils.showKnowYourRights,
-      snippetsVersion: AboutHomeUtils.snippetsVersion
-    };
+    let wrapper = {};
+    Components.utils.import("resource:///modules/sessionstore/SessionStore.jsm",
+      wrapper);
+    let ss = wrapper.SessionStore;
+    ss.promiseInitialized.then(function() {
+      let data = {
+        showRestoreLastSession: ss.canRestoreLastSession,
+        snippetsURL: AboutHomeUtils.snippetsURL,
+        showKnowYourRights: AboutHomeUtils.showKnowYourRights,
+        snippetsVersion: AboutHomeUtils.snippetsVersion
+      };
 
-    if (AboutHomeUtils.showKnowYourRights) {
-      // Set pref to indicate we've shown the notification.
-      let currentVersion = Services.prefs.getIntPref("browser.rights.version");
-      Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
-    }
+      if (AboutHomeUtils.showKnowYourRights) {
+        // Set pref to indicate we've shown the notification.
+        let currentVersion = Services.prefs.getIntPref("browser.rights.version");
+        Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
+      }
 
-    if (target) {
-      target.messageManager.sendAsyncMessage("AboutHome:Update", data);
-    } else {
-      let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
-      mm.broadcastAsyncMessage("AboutHome:Update", data);
-    }
+      if (target) {
+        target.messageManager.sendAsyncMessage("AboutHome:Update", data);
+      } else {
+        let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+        mm.broadcastAsyncMessage("AboutHome:Update", data);
+      }
+    }).then(null, function onError(x) {
+      Cu.reportError("Error in AboutHome.sendAboutHomeData " + x);
+    });
   },
 };
--- a/browser/modules/webappsUI.jsm
+++ b/browser/modules/webappsUI.jsm
@@ -76,16 +76,19 @@ this.webappsUI = {
   openURL: function(aUrl, aOrigin) {
     let browserEnumerator = Services.wm.getEnumerator("navigator:browser");
     let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
 
     // Check each browser instance for our URL
     let found = false;
     while (!found && browserEnumerator.hasMoreElements()) {
       let browserWin = browserEnumerator.getNext();
+      if (browserWin.closed) {
+        continue;
+      }
       let tabbrowser = browserWin.gBrowser;
 
       // Check each tab of this browser instance
       let numTabs = tabbrowser.tabs.length;
       for (let index = 0; index < numTabs; index++) {
         let tab = tabbrowser.tabs[index];
         let appURL = ss.getTabValue(tab, "appOrigin");
         if (appURL == aOrigin) {
@@ -151,20 +154,20 @@ this.webappsUI = {
 
         if (app) {
           let localDir = null;
           if (app.appProfile) {
             localDir = app.appProfile.localDir;
           }
 
           DOMApplicationRegistry.confirmInstall(aData, localDir,
-            (aManifest) => {
+            (aManifest, aZipPath) => {
               Task.spawn(function() {
                 try {
-                  yield WebappsInstaller.install(aData, aManifest);
+                  yield WebappsInstaller.install(aData, aManifest, aZipPath);
                   if (this.downloads[manifestURL]) {
                     yield this.downloads[manifestURL].promise;
                   }
                   installationSuccessNotification(aData, app, bundle);
                 } catch (ex) {
                   Cu.reportError("Error installing webapp: " + ex);
                   // TODO: Notify user that the installation has failed
                 } finally {
--- a/build/mobile/robocop/Actions.java.in
+++ b/build/mobile/robocop/Actions.java.in
@@ -44,16 +44,39 @@ public interface Actions {
     /**
      * Sends an event to Gecko.
      * 
      * @param geckoEvent The geckoEvent JSONObject's type
      */
     void sendGeckoEvent(String geckoEvent, String data);
 
     /**
+     * Sends a preferences get event to Gecko.
+     *
+     * @param requestId The id of this request.
+     * @param prefNames The preferences being requested.
+     */
+    void sendPreferencesGetEvent(int requestId, String[] prefNames);
+
+    /**
+     * Sends a preferences observe event to Gecko.
+     *
+     * @param requestId The id of this request.
+     * @param prefNames The preferences being requested.
+     */
+    void sendPreferencesObserveEvent(int requestId, String[] prefNames);
+
+    /**
+     * Sends a preferences remove observers event to Gecko.
+     *
+     * @param requestId The id of this request.
+     */
+    void sendPreferencesRemoveObserversEvent(int requestid);
+
+    /**
      * Listens for a gecko event to be sent from the Gecko instance.
      * The returned object can be used to test if the event has been
      * received. Note that only one event is listened for.
      * 
      * @param geckoEvent The geckoEvent JSONObject's type
      */
     RepeatedEventExpecter expectGeckoEvent(String geckoEvent);
 
--- a/build/mobile/robocop/FennecNativeActions.java.in
+++ b/build/mobile/robocop/FennecNativeActions.java.in
@@ -39,16 +39,19 @@ public class FennecNativeActions impleme
     // Objects for reflexive access of fennec classes.
     private ClassLoader mClassLoader;
     private Class mApiClass;
     private Class mEventListenerClass;
     private Class mDrawListenerClass;
     private Method mRegisterEventListener;
     private Method mUnregisterEventListener;
     private Method mBroadcastEvent;
+    private Method mPreferencesGetEvent;
+    private Method mPreferencesObserveEvent;
+    private Method mPreferencesRemoveObserversEvent;
     private Method mSetDrawListener;
     private Method mQuerySql;
     private Object mRobocopApi;
 
     private static final String LOGTAG = "FennecNativeActions";
 
     public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation, Assert asserter) {
         mSolo = robocop;
@@ -61,16 +64,19 @@ public class FennecNativeActions impleme
 
             mApiClass = mClassLoader.loadClass("org.mozilla.gecko.RobocopAPI");
             mEventListenerClass = mClassLoader.loadClass("org.mozilla.gecko.util.GeckoEventListener");
             mDrawListenerClass = mClassLoader.loadClass("org.mozilla.gecko.gfx.GeckoLayerClient$DrawListener");
 
             mRegisterEventListener = mApiClass.getMethod("registerEventListener", String.class, mEventListenerClass);
             mUnregisterEventListener = mApiClass.getMethod("unregisterEventListener", String.class, mEventListenerClass);
             mBroadcastEvent = mApiClass.getMethod("broadcastEvent", String.class, String.class);
+            mPreferencesGetEvent = mApiClass.getMethod("preferencesGetEvent", Integer.TYPE, String[].class);
+            mPreferencesObserveEvent = mApiClass.getMethod("preferencesObserveEvent", Integer.TYPE, String[].class);
+            mPreferencesRemoveObserversEvent = mApiClass.getMethod("preferencesRemoveObserversEvent", Integer.TYPE);
             mSetDrawListener = mApiClass.getMethod("setDrawListener", mDrawListenerClass);
             mQuerySql = mApiClass.getMethod("querySql", String.class, String.class);
 
             mRobocopApi = mApiClass.getConstructor(Activity.class).newInstance(activity);
         } catch (Exception e) {
             FennecNativeDriver.log(LogLevel.ERROR, e);
         }
     }
@@ -260,16 +266,44 @@ public class FennecNativeActions impleme
             mBroadcastEvent.invoke(mRobocopApi, geckoEvent, data);
         } catch (IllegalAccessException e) {
             FennecNativeDriver.log(LogLevel.ERROR, e);
         } catch (InvocationTargetException e) {
             FennecNativeDriver.log(LogLevel.ERROR, e);
         }
     }
 
+    private void sendPreferencesEvent(Method method, int requestId, String[] prefNames) {
+        try {
+            method.invoke(mRobocopApi, requestId, prefNames);
+        } catch (IllegalAccessException e) {
+            FennecNativeDriver.log(LogLevel.ERROR, e);
+        } catch (InvocationTargetException e) {
+            FennecNativeDriver.log(LogLevel.ERROR, e);
+        }
+    }
+
+    public void sendPreferencesGetEvent(int requestId, String[] prefNames) {
+        sendPreferencesEvent(mPreferencesGetEvent, requestId, prefNames);
+    }
+
+    public void sendPreferencesObserveEvent(int requestId, String[] prefNames) {
+        sendPreferencesEvent(mPreferencesObserveEvent, requestId, prefNames);
+    }
+
+    public void sendPreferencesRemoveObserversEvent(int requestId) {
+        try {
+            mPreferencesRemoveObserversEvent.invoke(mRobocopApi, requestId);
+        } catch (IllegalAccessException e) {
+            FennecNativeDriver.log(LogLevel.ERROR, e);
+        } catch (InvocationTargetException e) {
+            FennecNativeDriver.log(LogLevel.ERROR, e);
+        } 
+    }
+
     class DrawListenerProxy implements InvocationHandler {
         private final PaintExpecter mPaintExpecter;
 
         DrawListenerProxy(PaintExpecter paintExpecter) {
             mPaintExpecter = paintExpecter;
         }
 
         public Object invoke(Object proxy, Method method, Object[] args) {
--- a/config/check_spidermonkey_style.py
+++ b/config/check_spidermonkey_style.py
@@ -212,17 +212,17 @@ class FileKind(object):
         if filename.endswith('.msg'):
             return FileKind.MSG
 
         error(filename, None, 'unknown file kind')
 
 
 def get_all_filenames():
     '''Get a list of all the files in the (Mercurial or Git) repository.'''
-    cmds = [['hg', 'manifest', '-q'], ['git', 'ls-files']]
+    cmds = [['hg', 'manifest', '-q'], ['git', 'ls-files', '--full-name', '../..']]
     for cmd in cmds:
         try:
             all_filenames = subprocess.check_output(cmd, universal_newlines=True,
                                                     stderr=subprocess.PIPE).split('\n')
             return all_filenames
         except:
             continue
     else:
--- a/content/canvas/public/nsICanvasRenderingContextInternal.h
+++ b/content/canvas/public/nsICanvasRenderingContextInternal.h
@@ -9,18 +9,18 @@
 #include "nsISupports.h"
 #include "nsIInputStream.h"
 #include "nsIDocShell.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "gfxPattern.h"
 #include "mozilla/RefPtr.h"
 
 #define NS_ICANVASRENDERINGCONTEXTINTERNAL_IID \
-{ 0x9a6a5bdf, 0x1261, 0x4057, \
-  { 0x85, 0xcc, 0xaf, 0x97, 0x6c, 0x36, 0x99, 0xa9 } }
+{ 0x8b8da863, 0xd151, 0x4014, \
+  { 0x8b, 0xdc, 0x62, 0xb5, 0x0d, 0xc0, 0x2b, 0x62 } }
 
 class gfxContext;
 class gfxASurface;
 class nsDisplayListBuilder;
 
 namespace mozilla {
 namespace layers {
 class CanvasLayer;
@@ -60,19 +60,16 @@ public:
 
   NS_IMETHOD InitializeWithSurface(nsIDocShell *docShell, gfxASurface *surface, int32_t width, int32_t height) = 0;
 
   // Render the canvas at the origin of the given gfxContext
   NS_IMETHOD Render(gfxContext *ctx,
                     gfxPattern::GraphicsFilter aFilter,
                     uint32_t aFlags = RenderFlagPremultAlpha) = 0;
 
-  // Creates an image buffer. Returns null on failure.
-  virtual void GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat) = 0;
-
   // Gives you a stream containing the image represented by this context.
   // The format is given in aMimeTime, for example "image/png".
   //
   // If the image format does not support transparency or aIncludeTransparency
   // is false, alpha will be discarded and the result will be the image
   // composited on black.
   NS_IMETHOD GetInputStream(const char *aMimeType,
                             const PRUnichar *aEncoderOptions,
--- a/content/canvas/src/CanvasRenderingContext2D.cpp
+++ b/content/canvas/src/CanvasRenderingContext2D.cpp
@@ -36,17 +36,17 @@
 #include "nsGfxCIID.h"
 #include "nsIDocShell.h"
 #include "nsIDOMWindow.h"
 #include "nsPIDOMWindow.h"
 #include "nsDisplayList.h"
 
 #include "nsTArray.h"
 
-#include "ImageEncoder.h"
+#include "imgIEncoder.h"
 
 #include "gfxContext.h"
 #include "gfxASurface.h"
 #include "gfxImageSurface.h"
 #include "gfxPlatform.h"
 #include "gfxFont.h"
 #include "gfxBlur.h"
 #include "gfxUtils.h"
@@ -1031,81 +1031,81 @@ CanvasRenderingContext2D::Render(gfxCont
       NS_ABORT_IF_FALSE(gis, "If non-premult alpha, must be able to get image surface!");
 
       gfxUtils::UnpremultiplyImageSurface(gis);
   }
 
   return rv;
 }
 
-void
-CanvasRenderingContext2D::GetImageBuffer(uint8_t** aImageBuffer,
-                                         int32_t* aFormat)
+NS_IMETHODIMP
+CanvasRenderingContext2D::GetInputStream(const char *aMimeType,
+                                         const PRUnichar *aEncoderOptions,
+                                         nsIInputStream **aStream)
 {
-  *aImageBuffer = nullptr;
-  *aFormat = 0;
+  EnsureTarget();
+  if (!IsTargetValid()) {
+    return NS_ERROR_FAILURE;
+  }
 
   nsRefPtr<gfxASurface> surface;
-  nsresult rv = GetThebesSurface(getter_AddRefs(surface));
-  if (NS_FAILED(rv)) {
-    return;
+
+  if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv;
+  const char encoderPrefix[] = "@mozilla.org/image/encoder;2?type=";
+  static const fallible_t fallible = fallible_t();
+  nsAutoArrayPtr<char> conid(new (fallible) char[strlen(encoderPrefix) + strlen(aMimeType) + 1]);
+
+  if (!conid) {
+    return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  static const fallible_t fallible = fallible_t();
-  uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4];
+  strcpy(conid, encoderPrefix);
+  strcat(conid, aMimeType);
+
+  nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(conid);
+  if (!encoder) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsAutoArrayPtr<uint8_t> imageBuffer(new (fallible) uint8_t[mWidth * mHeight * 4]);
   if (!imageBuffer) {
-    return;
+    return NS_ERROR_OUT_OF_MEMORY;
   }
 
   nsRefPtr<gfxImageSurface> imgsurf =
-    new gfxImageSurface(imageBuffer,
+    new gfxImageSurface(imageBuffer.get(),
                         gfxIntSize(mWidth, mHeight),
                         mWidth * 4,
                         gfxASurface::ImageFormatARGB32);
 
   if (!imgsurf || imgsurf->CairoStatus()) {
-    delete[] imageBuffer;
-    return;
+    return NS_ERROR_FAILURE;
   }
 
   nsRefPtr<gfxContext> ctx = new gfxContext(imgsurf);
+
   if (!ctx || ctx->HasError()) {
-    delete[] imageBuffer;
-    return;
+    return NS_ERROR_FAILURE;
   }
 
   ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
   ctx->SetSource(surface, gfxPoint(0, 0));
   ctx->Paint();
 
-  *aImageBuffer = imageBuffer;
-  *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
-}
-
-NS_IMETHODIMP
-CanvasRenderingContext2D::GetInputStream(const char *aMimeType,
-                                         const PRUnichar *aEncoderOptions,
-                                         nsIInputStream **aStream)
-{
-  uint8_t* imageBuffer = nullptr;
-  int32_t format = 0;
-  GetImageBuffer(&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) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format,
-                                      encoder, aEncoderOptions, aStream);
+  rv = encoder->InitFromData(imageBuffer.get(),
+                              mWidth * mHeight * 4, mWidth, mHeight, mWidth * 4,
+                              imgIEncoder::INPUT_FORMAT_HOSTARGB,
+                              nsDependentString(aEncoderOptions));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return CallQueryInterface(encoder, aStream);
 }
 
 SurfaceFormat
 CanvasRenderingContext2D::GetSurfaceFormat() const
 {
   return mOpaque ? FORMAT_B8G8R8X8 : FORMAT_B8G8R8A8;
 }
 
@@ -3734,19 +3734,16 @@ CanvasRenderingContext2D::PutImageData_e
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CanvasRenderingContext2D::GetThebesSurface(gfxASurface **surface)
 {
   EnsureTarget();
-  if (!IsTargetValid()) {
-    return NS_ERROR_FAILURE;
-  }
 
   nsRefPtr<gfxASurface> thebesSurface =
       gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget);
 
   if (!thebesSurface) {
     return NS_ERROR_FAILURE;
   }
 
--- a/content/canvas/src/CanvasRenderingContext2D.h
+++ b/content/canvas/src/CanvasRenderingContext2D.h
@@ -15,17 +15,16 @@
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "CanvasUtils.h"
 #include "gfxFont.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/CanvasGradient.h"
 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
 #include "mozilla/dom/CanvasPattern.h"
 #include "mozilla/gfx/Rect.h"
-#include "imgIEncoder.h"
 
 class nsXULElement;
 
 namespace mozilla {
 namespace gfx {
 class SourceSurface;
 }
 
@@ -444,18 +443,16 @@ public:
       mDSPathBuilder->BezierTo(transform * aCP1,
                                 transform * aCP2,
                                 transform * aCP3);
     }
   }
 
   friend class CanvasRenderingContext2DUserData;
 
-  virtual void GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat);
-
 protected:
   nsresult GetImageDataArray(JSContext* aCx, int32_t aX, int32_t aY,
                              uint32_t aWidth, uint32_t aHeight,
                              JSObject** aRetval);
 
   nsresult PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h,
                                  unsigned char *aData, uint32_t aDataLen,
                                  bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY,
deleted file mode 100644
--- a/content/canvas/src/ImageEncoder.cpp
+++ /dev/null
@@ -1,299 +0,0 @@
-/* -*- Mode: C++; 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/. */
-
-#include "ImageEncoder.h"
-
-#include "mozilla/dom/CanvasRenderingContext2D.h"
-
-namespace mozilla {
-namespace dom {
-
-class EncodingCompleteEvent : public nsRunnable
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-  EncodingCompleteEvent(nsIScriptContext* aScriptContext,
-                        nsIThread* aEncoderThread,
-                        FileCallback& aCallback)
-    : mImgSize(0)
-    , mType()
-    , mImgData(nullptr)
-    , mScriptContext(aScriptContext)
-    , mEncoderThread(aEncoderThread)
-    , mCallback(&aCallback)
-  {}
-  virtual ~EncodingCompleteEvent() {}
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    nsRefPtr<nsDOMMemoryFile> blob =
-      new nsDOMMemoryFile(mImgData, mImgSize, mType);
-
-    if (mScriptContext) {
-      JSContext* jsContext = mScriptContext->GetNativeContext();
-      if (jsContext) {
-        JS_updateMallocCounter(jsContext, mImgSize);
-      }
-    }
-
-    mozilla::ErrorResult rv;
-    mCallback->Call(blob, rv);
-    NS_ENSURE_SUCCESS(rv.ErrorCode(), rv.ErrorCode());
-
-    mEncoderThread->Shutdown();
-    return rv.ErrorCode();
-  }
-
-  void SetMembers(void* aImgData, uint64_t aImgSize, const nsAutoString& aType)
-  {
-    mImgData = aImgData;
-    mImgSize = aImgSize;
-    mType = aType;
-  }
-
-private:
-  uint64_t mImgSize;
-  nsAutoString mType;
-  void* mImgData;
-  nsCOMPtr<nsIScriptContext> mScriptContext;
-  nsCOMPtr<nsIThread> mEncoderThread;
-  nsRefPtr<FileCallback> mCallback;
-};
-
-NS_IMPL_ISUPPORTS1(EncodingCompleteEvent, nsIRunnable);
-
-class EncodingRunnable : public nsRunnable
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-  EncodingRunnable(const nsAString& aType,
-                   const nsAString& aOptions,
-                   uint8_t* aImageBuffer,
-                   imgIEncoder* aEncoder,
-                   EncodingCompleteEvent* aEncodingCompleteEvent,
-                   int32_t aFormat,
-                   const nsIntSize aSize,
-                   bool aUsingCustomOptions)
-    : mType(aType)
-    , mOptions(aOptions)
-    , mImageBuffer(aImageBuffer)
-    , mEncoder(aEncoder)
-    , mEncodingCompleteEvent(aEncodingCompleteEvent)
-    , mFormat(aFormat)
-    , mSize(aSize)
-    , mUsingCustomOptions(aUsingCustomOptions)
-  {}
-  virtual ~EncodingRunnable() {}
-
-  NS_IMETHOD Run()
-  {
-    nsCOMPtr<nsIInputStream> stream;
-    nsresult rv = ImageEncoder::ExtractDataInternal(mType,
-                                                    mOptions,
-                                                    mImageBuffer,
-                                                    mFormat,
-                                                    mSize,
-                                                    nullptr,
-                                                    getter_AddRefs(stream),
-                                                    mEncoder);
-
-    // If there are unrecognized custom parse options, we should fall back to
-    // the default values for the encoder without any options at all.
-    if (rv == NS_ERROR_INVALID_ARG && mUsingCustomOptions) {
-      rv = ImageEncoder::ExtractDataInternal(mType,
-                                             EmptyString(),
-                                             mImageBuffer,
-                                             mFormat,
-                                             mSize,
-                                             nullptr,
-                                             getter_AddRefs(stream),
-                                             mEncoder);
-    }
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    uint64_t imgSize;
-    rv = stream->Available(&imgSize);
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_ENSURE_TRUE(imgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
-
-    void* imgData = nullptr;
-    rv = NS_ReadInputStreamToBuffer(stream, &imgData, imgSize);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType);
-    rv = NS_DispatchToMainThread(mEncodingCompleteEvent, NS_DISPATCH_NORMAL);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    return rv;
-  }
-
-private:
-  nsAutoString mType;
-  nsAutoString mOptions;
-  nsAutoArrayPtr<uint8_t> mImageBuffer;
-  nsCOMPtr<imgIEncoder> mEncoder;
-  nsRefPtr<EncodingCompleteEvent> mEncodingCompleteEvent;
-  int32_t mFormat;
-  const nsIntSize mSize;
-  bool mUsingCustomOptions;
-};
-
-NS_IMPL_ISUPPORTS1(EncodingRunnable, nsIRunnable)
-
-/* static */
-nsresult
-ImageEncoder::ExtractData(nsAString& aType,
-                          const nsAString& aOptions,
-                          const nsIntSize aSize,
-                          nsICanvasRenderingContextInternal* aContext,
-                          nsIInputStream** aStream)
-{
-  nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
-  if (!encoder) {
-    return NS_IMAGELIB_ERROR_NO_ENCODER;
-  }
-
-  return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, aContext,
-                             aStream, encoder);
-}
-
-/* static */
-nsresult
-ImageEncoder::ExtractDataAsync(nsAString& aType,
-                               const nsAString& aOptions,
-                               bool aUsingCustomOptions,
-                               uint8_t* aImageBuffer,
-                               int32_t aFormat,
-                               const nsIntSize aSize,
-                               nsICanvasRenderingContextInternal* aContext,
-                               nsIScriptContext* aScriptContext,
-                               FileCallback& aCallback)
-{
-  nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
-  if (!encoder) {
-    return NS_IMAGELIB_ERROR_NO_ENCODER;
-  }
-
-  nsCOMPtr<nsIThread> encoderThread;
-  nsresult rv = NS_NewThread(getter_AddRefs(encoderThread), nullptr);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsRefPtr<EncodingCompleteEvent> completeEvent =
-    new EncodingCompleteEvent(aScriptContext, encoderThread, aCallback);
-
-  nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
-                                                     aOptions,
-                                                     aImageBuffer,
-                                                     encoder,
-                                                     completeEvent,
-                                                     aFormat,
-                                                     aSize,
-                                                     aUsingCustomOptions);
-  return encoderThread->Dispatch(event, NS_DISPATCH_NORMAL);
-}
-
-/*static*/ nsresult
-ImageEncoder::GetInputStream(int32_t aWidth,
-                             int32_t aHeight,
-                             uint8_t* aImageBuffer,
-                             int32_t aFormat,
-                             imgIEncoder* aEncoder,
-                             const PRUnichar* aEncoderOptions,
-                             nsIInputStream** aStream)
-{
-  nsresult rv =
-    aEncoder->InitFromData(aImageBuffer,
-                           aWidth * aHeight * 4, aWidth, aHeight, aWidth * 4,
-                           aFormat,
-                           nsDependentString(aEncoderOptions));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return CallQueryInterface(aEncoder, aStream);
-}
-
-/* static */
-nsresult
-ImageEncoder::ExtractDataInternal(const nsAString& aType,
-                                  const nsAString& aOptions,
-                                  uint8_t* aImageBuffer,
-                                  int32_t aFormat,
-                                  const nsIntSize aSize,
-                                  nsICanvasRenderingContextInternal* aContext,
-                                  nsIInputStream** aStream,
-                                  imgIEncoder* aEncoder)
-{
-  nsCOMPtr<nsIInputStream> imgStream;
-
-  // get image bytes
-  nsresult rv;
-  if (aImageBuffer) {
-    rv = ImageEncoder::GetInputStream(
-      aSize.width,
-      aSize.height,
-      aImageBuffer,
-      aFormat,
-      aEncoder,
-      nsPromiseFlatString(aOptions).get(),
-      getter_AddRefs(imgStream));
-  } else if (aContext) {
-    NS_ConvertUTF16toUTF8 encoderType(aType);
-    rv = aContext->GetInputStream(encoderType.get(),
-                                  nsPromiseFlatString(aOptions).get(),
-                                  getter_AddRefs(imgStream));
-  } else {
-    // no context, so we have to encode an empty image
-    // note that if we didn't have a current context, the spec says we're
-    // supposed to just return transparent black pixels of the canvas
-    // dimensions.
-    nsRefPtr<gfxImageSurface> emptyCanvas =
-      new gfxImageSurface(gfxIntSize(aSize.width, aSize.height),
-                          gfxASurface::ImageFormatARGB32);
-    if (emptyCanvas->CairoStatus()) {
-      return NS_ERROR_INVALID_ARG;
-    }
-    rv = aEncoder->InitFromData(emptyCanvas->Data(),
-                                aSize.width * aSize.height * 4,
-                                aSize.width,
-                                aSize.height,
-                                aSize.width * 4,
-                                imgIEncoder::INPUT_FORMAT_HOSTARGB,
-                                aOptions);
-    if (NS_SUCCEEDED(rv)) {
-      imgStream = do_QueryInterface(aEncoder);
-    }
-  }
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  imgStream.forget(aStream);
-  return rv;
-}
-
-/* static */
-already_AddRefed<imgIEncoder>
-ImageEncoder::GetImageEncoder(nsAString& aType)
-{
-  // Get an image encoder for the media type.
-  nsCString encoderCID("@mozilla.org/image/encoder;2?type=");
-  NS_ConvertUTF16toUTF8 encoderType(aType);
-  encoderCID += encoderType;
-  nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
-
-  if (!encoder && aType != NS_LITERAL_STRING("image/png")) {
-    // Unable to create an encoder instance of the specified type. Falling back
-    // to PNG.
-    aType.AssignLiteral("image/png");
-    nsCString PNGEncoderCID("@mozilla.org/image/encoder;2?type=image/png");
-    encoder = do_CreateInstance(PNGEncoderCID.get());
-  }
-
-  return encoder.forget();
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/content/canvas/src/ImageEncoder.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/* -*- Mode: C++; 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/. */
-
-#ifndef ImageEncoder_h
-#define ImageEncoder_h
-
-#include "imgIEncoder.h"
-#include "nsDOMFile.h"
-#include "nsError.h"
-#include "mozilla/dom/HTMLCanvasElementBinding.h"
-#include "nsLayoutUtils.h"
-#include "nsNetUtil.h"
-#include "nsSize.h"
-
-class nsICanvasRenderingContextInternal;
-
-namespace mozilla {
-namespace dom {
-
-class EncodingRunnable;
-
-class ImageEncoder
-{
-public:
-  // Extracts data synchronously and gives you a stream containing the image
-  // represented by aContext. aType may change to "image/png" if we had to fall
-  // back to a PNG encoder. A return value of NS_OK implies successful data
-  // extraction. If there are any unrecognized custom parse options in
-  // aOptions, NS_ERROR_INVALID_ARG will be returned. When encountering this
-  // error it is usual to call this function again without any options at all.
-  static nsresult ExtractData(nsAString& aType,
-                              const nsAString& aOptions,
-                              const nsIntSize aSize,
-                              nsICanvasRenderingContextInternal* aContext,
-                              nsIInputStream** aStream);
-
-  // Extracts data asynchronously. aType may change to "image/png" if we had to
-  // fall back to a PNG encoder. aOptions are the options to be passed to the
-  // encoder and aUsingCustomOptions specifies whether custom parse options were
-  // used (i.e. by using -moz-parse-options). If there are any unrecognized
-  // custom parse options, we fall back to the default values for the encoder
-  // without any options at all. A return value of NS_OK only implies
-  // successful dispatching of the extraction step to the encoding thread.
-  static nsresult ExtractDataAsync(nsAString& aType,
-                                   const nsAString& aOptions,
-                                   bool aUsingCustomOptions,
-                                   uint8_t* aImageBuffer,
-                                   int32_t aFormat,
-                                   const nsIntSize aSize,
-                                   nsICanvasRenderingContextInternal* aContext,
-                                   nsIScriptContext* aScriptContext,
-                                   FileCallback& aCallback);
-
-  // Gives you a stream containing the image represented by aImageBuffer.
-  // The format is given in aFormat, for example
-  // imgIEncoder::INPUT_FORMAT_HOSTARGB.
-  static nsresult GetInputStream(int32_t aWidth,
-                                 int32_t aHeight,
-                                 uint8_t* aImageBuffer,
-                                 int32_t aFormat,
-                                 imgIEncoder* aEncoder,
-                                 const PRUnichar* aEncoderOptions,
-                                 nsIInputStream** aStream);
-
-private:
-  // When called asynchronously, aContext is null.
-  static nsresult
-  ExtractDataInternal(const nsAString& aType,
-                      const nsAString& aOptions,
-                      uint8_t* aImageBuffer,
-                      int32_t aFormat,
-                      const nsIntSize aSize,
-                      nsICanvasRenderingContextInternal* aContext,
-                      nsIInputStream** aStream,
-                      imgIEncoder* aEncoder);
-
-  // Creates and returns an encoder instance of the type specified in aType.
-  // aType may change to "image/png" if no instance of the original type could
-  // be created and we had to fall back to a PNG encoder. A return value of
-  // NULL should be interpreted as NS_IMAGELIB_ERROR_NO_ENCODER and aType is
-  // undefined in this case.
-  static already_AddRefed<imgIEncoder> GetImageEncoder(nsAString& aType);
-
-  friend class EncodingRunnable;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // ImageEncoder_h
\ No newline at end of file
--- a/content/canvas/src/Makefile.in
+++ b/content/canvas/src/Makefile.in
@@ -17,11 +17,10 @@ CXXFLAGS	+= $(MOZ_CAIRO_CFLAGS) $(MOZ_PI
 INCLUDES	+= \
 		-I$(srcdir)/../../../layout/xul/base/src \
 		-I$(srcdir)/../../../layout/style \
 		-I$(srcdir)/../../../layout/generic \
 		-I$(srcdir)/../../base/src \
 		-I$(srcdir)/../../html/content/src \
 		-I$(srcdir)/../../../js/xpconnect/src \
 		-I$(srcdir)/../../../dom/base \
-		-I$(srcdir)/../../../image/src \
 		-I$(topsrcdir)/content/xul/content/src \
 		$(NULL)
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -22,17 +22,17 @@
 #include "nsContentUtils.h"
 #include "nsIXPConnect.h"
 #include "nsError.h"
 #include "nsIGfxInfo.h"
 #include "nsIWidget.h"
 
 #include "nsIVariant.h"
 
-#include "ImageEncoder.h"
+#include "imgIEncoder.h"
 
 #include "gfxContext.h"
 #include "gfxPattern.h"
 #include "gfxUtils.h"
 
 #include "CanvasUtils.h"
 #include "nsDisplayList.h"
 
@@ -716,89 +716,67 @@ void WebGLContext::LoseOldestWebGLContex
     } else if (numContexts > kMaxWebGLContexts) {
         GenerateWarning("Exceeded %d live WebGL contexts, losing the least recently used one.",
                         kMaxWebGLContexts);
         MOZ_ASSERT(oldestContext); // if we reach this point, this can't be null
         const_cast<WebGLContext*>(oldestContext)->LoseContext();
     }
 }
 
-void
-WebGLContext::GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat)
-{
-    *aImageBuffer = nullptr;
-    *aFormat = 0;
-
-    nsRefPtr<gfxImageSurface> imgsurf =
-        new gfxImageSurface(gfxIntSize(mWidth, mHeight),
-                            gfxASurface::ImageFormatARGB32);
-
-    if (!imgsurf || imgsurf->CairoStatus()) {
-        return;
-    }
-
-    nsRefPtr<gfxContext> ctx = new gfxContext(imgsurf);
-    if (!ctx || ctx->HasError()) {
-        return;
-    }
-
-    // Use Render() to make sure that appropriate y-flip gets applied
-    uint32_t flags = mOptions.premultipliedAlpha ? RenderFlagPremultAlpha : 0;
-    nsresult rv = Render(ctx, gfxPattern::FILTER_NEAREST, flags);
-    if (NS_FAILED(rv)) {
-        return;
-    }
-
-    int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
-    if (!mOptions.premultipliedAlpha) {
-        // We need to convert to INPUT_FORMAT_RGBA, otherwise
-        // we are automatically considered premult, and unpremult'd.
-        // Yes, it is THAT silly.
-        // Except for different lossy conversions by color,
-        // we could probably just change the label, and not change the data.
-        gfxUtils::ConvertBGRAtoRGBA(imgsurf);
-        format = imgIEncoder::INPUT_FORMAT_RGBA;
-    }
-
-    static const fallible_t fallible = fallible_t();
-    uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4];
-    if (!imageBuffer) {
-        return;
-    }
-    memcpy(imageBuffer, imgsurf->Data(), mWidth * mHeight * 4);
-
-    *aImageBuffer = imageBuffer;
-    *aFormat = format;
-}
-
 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;
-    int32_t format = 0;
-    GetImageBuffer(&imageBuffer, &format);
-    if (!imageBuffer) {
+    nsRefPtr<gfxImageSurface> surf = new gfxImageSurface(gfxIntSize(mWidth, mHeight),
+                                                         gfxASurface::ImageFormatARGB32);
+    if (surf->CairoStatus() != 0)
         return NS_ERROR_FAILURE;
+
+    nsRefPtr<gfxContext> tmpcx = new gfxContext(surf);
+    // Use Render() to make sure that appropriate y-flip gets applied
+    uint32_t flags = mOptions.premultipliedAlpha ? RenderFlagPremultAlpha : 0;
+    nsresult rv = Render(tmpcx, gfxPattern::FILTER_NEAREST, flags);
+    if (NS_FAILED(rv))
+        return rv;
+
+    const char encoderPrefix[] = "@mozilla.org/image/encoder;2?type=";
+    nsAutoArrayPtr<char> conid(new char[strlen(encoderPrefix) + strlen(aMimeType) + 1]);
+
+    strcpy(conid, encoderPrefix);
+    strcat(conid, aMimeType);
+
+    nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(conid);
+    if (!encoder)
+        return NS_ERROR_FAILURE;
+
+    int format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
+    if (!mOptions.premultipliedAlpha) {
+        // We need to convert to INPUT_FORMAT_RGBA, otherwise
+        // we are automatically considered premult, and unpremult'd.
+        // Yes, it is THAT silly.
+        // Except for different lossy conversions by color,
+        // we could probably just change the label, and not change the data.
+        gfxUtils::ConvertBGRAtoRGBA(surf);
+        format = imgIEncoder::INPUT_FORMAT_RGBA;
     }
 
-    nsCString enccid("@mozilla.org/image/encoder;2?type=");
-    enccid += aMimeType;
-    nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
-    if (!encoder) {
-        return NS_ERROR_FAILURE;
-    }
+    rv = encoder->InitFromData(surf->Data(),
+                               mWidth * mHeight * 4,
+                               mWidth, mHeight,
+                               surf->Stride(),
+                               format,
+                               nsDependentString(aEncoderOptions));
+    NS_ENSURE_SUCCESS(rv, rv);
 
-    return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format,
-                                        encoder, aEncoderOptions, aStream);
+    return CallQueryInterface(encoder, aStream);
 }
 
 NS_IMETHODIMP
 WebGLContext::GetThebesSurface(gfxASurface **surface)
 {
     return NS_ERROR_NOT_AVAILABLE;
 }
 
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -155,17 +155,16 @@ public:
     NS_IMETHOD SetDimensions(int32_t width, int32_t height) MOZ_OVERRIDE;
     NS_IMETHOD InitializeWithSurface(nsIDocShell *docShell, gfxASurface *surface, int32_t width, int32_t height) MOZ_OVERRIDE
         { return NS_ERROR_NOT_IMPLEMENTED; }
     NS_IMETHOD Reset() MOZ_OVERRIDE
         { /* (InitializeWithSurface) */ return NS_ERROR_NOT_IMPLEMENTED; }
     NS_IMETHOD Render(gfxContext *ctx,
                       gfxPattern::GraphicsFilter f,
                       uint32_t aFlags = RenderFlagPremultAlpha) MOZ_OVERRIDE;
-    virtual void GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat);
     NS_IMETHOD GetInputStream(const char* aMimeType,
                               const PRUnichar* aEncoderOptions,
                               nsIInputStream **aStream) MOZ_OVERRIDE;
     NS_IMETHOD GetThebesSurface(gfxASurface **surface) MOZ_OVERRIDE;
     mozilla::TemporaryRef<mozilla::gfx::SourceSurface> GetSurfaceSnapshot() MOZ_OVERRIDE
         { return nullptr; }
 
     NS_IMETHOD SetIsOpaque(bool b) MOZ_OVERRIDE { return NS_OK; };
--- a/content/canvas/src/moz.build
+++ b/content/canvas/src/moz.build
@@ -17,17 +17,16 @@ EXPORTS.mozilla.dom += [
 
 CPP_SOURCES += [
     'CanvasImageCache.cpp',
     'CanvasRenderingContext2D.cpp',
     'CanvasUtils.cpp',
     'DocumentRendererChild.cpp',
     'DocumentRendererParent.cpp',
     'ImageData.cpp',
-    'ImageEncoder.cpp',
 ]
 
 if CONFIG['MOZ_WEBGL']:
     CPP_SOURCES += [
         'WebGLActiveInfo.cpp',
         'WebGLBuffer.cpp',
         'WebGL1Context.cpp',
         'WebGL2Context.cpp',
--- a/content/canvas/test/test_mozGetAsFile.html
+++ b/content/canvas/test/test_mozGetAsFile.html
@@ -2,50 +2,49 @@
 <title>Canvas test: mozGetAsFile</title>
 <script src="/MochiKit/MochiKit.js"></script>
 <script src="/tests/SimpleTest/SimpleTest.js"></script>
 <link rel="stylesheet" href="/tests/SimpleTest/test.css">
 <body>
 <canvas id="c" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
 <script>
 
-function compareAsync(file, canvas, type, callback)
+var gCompares = 0;
+
+function compareAsync(file, canvas, type)
 {
+  ++gCompares;
+
   var reader = new FileReader();
   reader.onload = 
     function(e) {
       is(e.target.result, canvas.toDataURL(type),
  "<canvas>.mozGetAsFile().getAsDataURL() should equal <canvas>.toDataURL()");
-      callback(canvas);
+      if (--gCompares == 0) {
+        SimpleTest.finish();
+      }
     };
   reader.readAsDataURL(file);
 }
 
-function test1(canvas)
-{
-  var pngfile = canvas.mozGetAsFile("foo.png");
-  is(pngfile.type, "image/png", "Default type for mozGetAsFile should be PNG");
-  compareAsync(pngfile, canvas, "image/png", test2);
-  is(pngfile.name, "foo.png", "File name should be what we passed in");
-}
-
-function test2(canvas)
-{
-  var jpegfile = canvas.mozGetAsFile("bar.jpg", "image/jpeg");
-  is(jpegfile.type, "image/jpeg",
-     "When a valid type is specified that should be returned");
-  compareAsync(jpegfile, canvas, "image/jpeg", SimpleTest.finish);
-  is(jpegfile.name, "bar.jpg", "File name should be what we passed in");
-}
-
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function () {
 
 var canvas = document.getElementById('c');
 var ctx = canvas.getContext('2d');
+
 ctx.drawImage(document.getElementById('yellow75.png'), 0, 0);
 
-test1(canvas);
+var pngfile = canvas.mozGetAsFile("foo.png");
+is(pngfile.type, "image/png", "Default type for mozGetAsFile should be PNG");
+compareAsync(pngfile, canvas, "image/png");
+is(pngfile.name, "foo.png", "File name should be what we passed in");
+
+var jpegfile = canvas.mozGetAsFile("bar.jpg", "image/jpeg");
+is(jpegfile.type, "image/jpeg",
+   "When a valid type is specified that should be returned");
+compareAsync(jpegfile, canvas, "image/jpeg");
+is(jpegfile.name, "bar.jpg", "File name should be what we passed in");
 
 });
 </script>
 <img src="image_yellow75.png" id="yellow75.png" class="resource">
 
--- a/content/canvas/test/test_toBlob.html
+++ b/content/canvas/test/test_toBlob.html
@@ -1,48 +1,42 @@
 <!DOCTYPE HTML>
-<title>Canvas test: toBlob</title>
+<title>Canvas test: mozGetAsFile</title>
 <script src="/MochiKit/MochiKit.js"></script>
 <script src="/tests/SimpleTest/SimpleTest.js"></script>
 <link rel="stylesheet" href="/tests/SimpleTest/test.css">
 <body>
 <canvas id="c" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
 <script>
 
-function BlobListener(type, canvas, callback, file)
+var gCompares = 2;
+
+function BlobListener(type, canvas, file)
 {
   is(file.type, type,
      "When a valid type is specified that should be returned");
   var reader = new FileReader();
-  reader.onload =
+  reader.onload = 
     function(e) {
       is(e.target.result, canvas.toDataURL(type),
-  "<canvas>.mozGetAsFile().getAsDataURL() should equal <canvas>.toDataURL()");
-      callback(canvas);
+ "<canvas>.mozGetAsFile().getAsDataURL() should equal <canvas>.toDataURL()");
+      if (--gCompares == 0) {
+        SimpleTest.finish();
+      }
     };
   reader.readAsDataURL(file);
 }
 
-function test1(canvas)
-{
-  canvas.toBlob(BlobListener.bind(undefined, "image/png", canvas, test2));
-}
-
-function test2(canvas)
-{
-  canvas.toBlob(
-    BlobListener.bind(undefined, "image/jpeg", canvas, SimpleTest.finish),
-    "image/jpeg");
-}
-
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function () {
 
 var canvas = document.getElementById('c');
 var ctx = canvas.getContext('2d');
+
 ctx.drawImage(document.getElementById('yellow75.png'), 0, 0);
 
-test1(canvas);
+canvas.toBlob(BlobListener.bind(undefined, "image/png", canvas));
+canvas.toBlob(BlobListener.bind(undefined, "image/jpeg", canvas), "image/jpeg");
 
 });
 </script>
 <img src="image_yellow75.png" id="yellow75.png" class="resource">
 
--- a/content/html/content/public/HTMLCanvasElement.h
+++ b/content/html/content/public/HTMLCanvasElement.h
@@ -218,19 +218,20 @@ protected:
   nsIntSize GetWidthHeight();
 
   nsresult UpdateContext(JSContext* aCx, JS::Handle<JS::Value> options);
   nsresult ParseParams(JSContext* aCx,
                        const nsAString& aType,
                        const JS::Value& aEncoderOptions,
                        nsAString& aParams,
                        bool* usingCustomParseOptions);
-  nsresult ExtractData(nsAString& aType,
+  nsresult ExtractData(const nsAString& aType,
                        const nsAString& aOptions,
-                       nsIInputStream** aStream);
+                       nsIInputStream** aStream,
+                       bool& aFellBackToPNG);
   nsresult ToDataURLImpl(JSContext* aCx,
                          const nsAString& aMimeType,
                          const JS::Value& aEncoderOptions,
                          nsAString& aDataURL);
   nsresult MozGetAsFileImpl(const nsAString& aName,
                             const nsAString& aType,
                             nsIDOMFile** aResult);
   nsresult GetContextHelper(const nsAString& aContextId,
--- a/content/html/content/src/HTMLCanvasElement.cpp
+++ b/content/html/content/src/HTMLCanvasElement.cpp
@@ -1,33 +1,32 @@
 /* -*- Mode: C++; 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/. */
 
 #include "mozilla/dom/HTMLCanvasElement.h"
 
-#include "ImageEncoder.h"
+#include "Layers.h"
+#include "imgIEncoder.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
-#include "Layers.h"
 #include "mozilla/Base64.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/CanvasRenderingContext2D.h"
 #include "mozilla/dom/HTMLCanvasElementBinding.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "nsAsyncDOMEvent.h"
 #include "nsAttrValueInlines.h"
 #include "nsContentUtils.h"
 #include "nsDisplayList.h"
 #include "nsDOMFile.h"
-#include "nsDOMJSUtils.h"
 #include "nsFrameManager.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsITimer.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsIXPConnect.h"
 #include "nsJSUtils.h"
 #include "nsMathUtils.h"
 #include "nsNetUtil.h"
@@ -41,16 +40,39 @@ using namespace mozilla::layers;
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas)
 
 namespace {
 
 typedef mozilla::dom::HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement
 HTMLImageOrCanvasOrVideoElement;
 
+class ToBlobRunnable : public nsRunnable
+{
+public:
+  ToBlobRunnable(mozilla::dom::FileCallback& aCallback,
+                 nsIDOMBlob* aBlob)
+    : mCallback(&aCallback),
+      mBlob(aBlob)
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  }
+
+  NS_IMETHOD Run()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+    mozilla::ErrorResult rv;
+    mCallback->Call(mBlob, rv);
+    return rv.ErrorCode();
+  }
+private:
+  nsRefPtr<mozilla::dom::FileCallback> mCallback;
+  nsCOMPtr<nsIDOMBlob> mBlob;
+};
+
 } // anonymous namespace
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_3(HTMLCanvasPrintState, mCanvas,
                                         mContext, mCallback)
 
@@ -325,20 +347,20 @@ HTMLCanvasElement::ToDataURL(const nsASt
 NS_IMETHODIMP
 HTMLCanvasElement::MozFetchAsStream(nsIInputStreamCallback *aCallback,
                                     const nsAString& aType)
 {
   if (!nsContentUtils::IsCallerChrome())
     return NS_ERROR_FAILURE;
 
   nsresult rv;
+  bool fellBackToPNG = false;
   nsCOMPtr<nsIInputStream> inputData;
 
-  nsAutoString type(aType);
-  rv = ExtractData(type, EmptyString(), getter_AddRefs(inputData));
+  rv = ExtractData(aType, EmptyString(), getter_AddRefs(inputData), fellBackToPNG);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIAsyncInputStream> asyncData = do_QueryInterface(inputData, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIThread> mainThread;
   rv = NS_GetMainThread(getter_AddRefs(mainThread));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -360,25 +382,78 @@ HTMLCanvasElement::GetMozPrintCallback()
 {
   if (mOriginalCanvas) {
     return mOriginalCanvas->GetMozPrintCallback();
   }
   return mPrintCallback;
 }
 
 nsresult
-HTMLCanvasElement::ExtractData(nsAString& aType,
+HTMLCanvasElement::ExtractData(const nsAString& aType,
                                const nsAString& aOptions,
-                               nsIInputStream** aStream)
+                               nsIInputStream** aStream,
+                               bool& aFellBackToPNG)
 {
-  return ImageEncoder::ExtractData(aType,
-                                   aOptions,
-                                   GetSize(),
-                                   mCurrentContext,
-                                   aStream);
+  // note that if we don't have a current context, the spec says we're
+  // supposed to just return transparent black pixels of the canvas
+  // dimensions.
+  nsRefPtr<gfxImageSurface> emptyCanvas;
+  nsIntSize size = GetWidthHeight();
+  if (!mCurrentContext) {
+    emptyCanvas = new gfxImageSurface(gfxIntSize(size.width, size.height), gfxASurface::ImageFormatARGB32);
+    if (emptyCanvas->CairoStatus()) {
+      return NS_ERROR_INVALID_ARG;
+    }
+  }
+
+  nsresult rv;
+
+  // get image bytes
+  nsCOMPtr<nsIInputStream> imgStream;
+  NS_ConvertUTF16toUTF8 encoderType(aType);
+
+ try_again:
+  if (mCurrentContext) {
+    rv = mCurrentContext->GetInputStream(encoderType.get(),
+                                         nsPromiseFlatString(aOptions).get(),
+                                         getter_AddRefs(imgStream));
+  } else {
+    // no context, so we have to encode the empty image we created above
+    nsCString enccid("@mozilla.org/image/encoder;2?type=");
+    enccid += encoderType;
+
+    nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get(), &rv);
+    if (NS_SUCCEEDED(rv) && encoder) {
+      rv = encoder->InitFromData(emptyCanvas->Data(),
+                                 size.width * size.height * 4,
+                                 size.width,
+                                 size.height,
+                                 size.width * 4,
+                                 imgIEncoder::INPUT_FORMAT_HOSTARGB,
+                                 aOptions);
+      if (NS_SUCCEEDED(rv)) {
+        imgStream = do_QueryInterface(encoder);
+      }
+    } else {
+      rv = NS_ERROR_FAILURE;
+    }
+  }
+
+  if (NS_FAILED(rv) && !aFellBackToPNG) {
+    // Try image/png instead.
+    // XXX ERRMSG we need to report an error to developers here! (bug 329026)
+    aFellBackToPNG = true;
+    encoderType.AssignLiteral("image/png");
+    goto try_again;
+  }
+
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  imgStream.forget(aStream);
+  return NS_OK;
 }
 
 nsresult
 HTMLCanvasElement::ParseParams(JSContext* aCx,
                                const nsAString& aType,
                                const JS::Value& aEncoderOptions,
                                nsAString& aParams,
                                bool* usingCustomParseOptions)
@@ -419,16 +494,18 @@ HTMLCanvasElement::ParseParams(JSContext
 }
 
 nsresult
 HTMLCanvasElement::ToDataURLImpl(JSContext* aCx,
                                  const nsAString& aMimeType,
                                  const JS::Value& aEncoderOptions,
                                  nsAString& aDataURL)
 {
+  bool fallbackToPNG = false;
+
   nsIntSize size = GetWidthHeight();
   if (size.height == 0 || size.width == 0) {
     aDataURL = NS_LITERAL_STRING("data:,");
     return NS_OK;
   }
 
   nsAutoString type;
   nsresult rv = nsContentUtils::ASCIIToLower(aMimeType, type);
@@ -439,37 +516,43 @@ HTMLCanvasElement::ToDataURLImpl(JSConte
   nsAutoString params;
   bool usingCustomParseOptions;
   rv = ParseParams(aCx, type, aEncoderOptions, params, &usingCustomParseOptions);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   nsCOMPtr<nsIInputStream> stream;
-  rv = ExtractData(type, params, getter_AddRefs(stream));
+  rv = ExtractData(type, params, getter_AddRefs(stream), fallbackToPNG);
 
   // If there are unrecognized custom parse options, we should fall back to
   // the default values for the encoder without any options at all.
   if (rv == NS_ERROR_INVALID_ARG && usingCustomParseOptions) {
-    rv = ExtractData(type, EmptyString(), getter_AddRefs(stream));
+    fallbackToPNG = false;
+    rv = ExtractData(type, EmptyString(), getter_AddRefs(stream), fallbackToPNG);
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
 
   // build data URL string
-  aDataURL = NS_LITERAL_STRING("data:") + type + NS_LITERAL_STRING(";base64,");
+  if (fallbackToPNG)
+    aDataURL = NS_LITERAL_STRING("data:image/png;base64,");
+  else
+    aDataURL = NS_LITERAL_STRING("data:") + type +
+      NS_LITERAL_STRING(";base64,");
 
   uint64_t count;
   rv = stream->Available(&count);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(count <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
 
   return Base64EncodeInputStream(stream, aDataURL, (uint32_t)count, aDataURL.Length());
 }
 
+// XXXkhuey the encoding should be off the main thread, but we're lazy.
 void
 HTMLCanvasElement::ToBlob(JSContext* aCx,
                           FileCallback& aCallback,
                           const nsAString& aType,
                           const Optional<JS::Handle<JS::Value> >& aParams,
                           ErrorResult& aRv)
 {
   // do a trust check if this is a write-only canvas
@@ -490,34 +573,62 @@ HTMLCanvasElement::ToBlob(JSContext* aCx
 
   nsAutoString params;
   bool usingCustomParseOptions;
   aRv = ParseParams(aCx, type, encoderOptions, params, &usingCustomParseOptions);
   if (aRv.Failed()) {
     return;
   }
 
-  nsCOMPtr<nsIScriptContext> scriptContext =
-    GetScriptContextFromJSContext(nsContentUtils::GetCurrentJSContext());
+  bool fallbackToPNG = false;
 
-  uint8_t* imageBuffer = nullptr;
-  int32_t format = 0;
-  if (mCurrentContext) {
-    mCurrentContext->GetImageBuffer(&imageBuffer, &format);
+  nsCOMPtr<nsIInputStream> stream;
+  aRv = ExtractData(type, params, getter_AddRefs(stream), fallbackToPNG);
+  // If there are unrecognized custom parse options, we should fall back to
+  // the default values for the encoder without any options at all.
+  if (aRv.ErrorCode() == NS_ERROR_INVALID_ARG && usingCustomParseOptions) {
+    fallbackToPNG = false;
+    aRv = ExtractData(type, EmptyString(), getter_AddRefs(stream), fallbackToPNG);
+  }
+
+  if (aRv.Failed()) {
+    return;
+  }
+
+  if (fallbackToPNG) {
+    type.AssignLiteral("image/png");
   }
 
-  aRv = ImageEncoder::ExtractDataAsync(type,
-                                       params,
-                                       usingCustomParseOptions,
-                                       imageBuffer,
-                                       format,
-                                       GetSize(),
-                                       mCurrentContext,
-                                       scriptContext,
-                                       aCallback);
+  uint64_t imgSize;
+  aRv = stream->Available(&imgSize);
+  if (aRv.Failed()) {
+    return;
+  }
+  if (imgSize > UINT32_MAX) {
+    aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+    return;
+  }
+
+  void* imgData = nullptr;
+  aRv = NS_ReadInputStreamToBuffer(stream, &imgData, imgSize);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  // The DOMFile takes ownership of the buffer
+  nsRefPtr<nsDOMMemoryFile> blob =
+    new nsDOMMemoryFile(imgData, imgSize, type);
+
+  JSContext* cx = nsContentUtils::GetCurrentJSContext();
+  if (cx) {
+    JS_updateMallocCounter(cx, imgSize);
+  }
+
+  nsRefPtr<ToBlobRunnable> runnable = new ToBlobRunnable(aCallback, blob);
+  aRv = NS_DispatchToCurrentThread(runnable);
 }
 
 already_AddRefed<nsIDOMFile>
 HTMLCanvasElement::MozGetAsFile(const nsAString& aName,
                                 const nsAString& aType,
                                 ErrorResult& aRv)
 {
   nsCOMPtr<nsIDOMFile> file;
@@ -541,20 +652,27 @@ HTMLCanvasElement::MozGetAsFile(const ns
   return MozGetAsFileImpl(aName, aType, aResult);
 }
 
 nsresult
 HTMLCanvasElement::MozGetAsFileImpl(const nsAString& aName,
                                     const nsAString& aType,
                                     nsIDOMFile** aResult)
 {
+  bool fallbackToPNG = false;
+
   nsCOMPtr<nsIInputStream> stream;
+  nsresult rv = ExtractData(aType, EmptyString(), getter_AddRefs(stream),
+                            fallbackToPNG);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   nsAutoString type(aType);
-  nsresult rv = ExtractData(type, EmptyString(), getter_AddRefs(stream));
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (fallbackToPNG) {
+    type.AssignLiteral("image/png");
+  }
 
   uint64_t imgSize;
   rv = stream->Available(&imgSize);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(imgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
 
   void* imgData = nullptr;
   rv = NS_ReadInputStreamToBuffer(stream, &imgData, (uint32_t)imgSize);
--- a/content/html/content/src/Makefile.in
+++ b/content/html/content/src/Makefile.in
@@ -17,13 +17,12 @@ INCLUDES	+= \
 		-I$(srcdir)/../../../../layout/tables \
 		-I$(srcdir)/../../../../layout/xul/base/src \
 		-I$(srcdir)/../../../../layout/generic \
 		-I$(srcdir)/../../../../dom/base \
 		-I$(srcdir)/../../../../editor/libeditor/base \
 		-I$(srcdir)/../../../../editor/libeditor/text \
 		-I$(srcdir)/../../../../editor/txmgr/src \
 		-I$(srcdir)/../../../../netwerk/base/src \
-		-I$(srcdir)/../../../../content/canvas/src \
 		-I$(srcdir) \
 		-I$(topsrcdir)/xpcom/ds \
 		-I$(topsrcdir)/content/media/ \
 		$(NULL)
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -4,19 +4,20 @@
 
 "use strict";
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/WebappOSUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
   return Cc["@mozilla.org/network/util;1"]
            .getService(Ci.nsINetUtil);
 });
 
 // Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
 
@@ -182,31 +183,33 @@ this.AppsUtils = {
     try {
       return FileUtils.getDir("coreAppsDir", ["webapps"], false).path;
     } catch(e) {
       return null;
     }
   },
 
   getAppInfo: function getAppInfo(aApps, aAppId) {
-    if (!aApps[aAppId]) {
+    let app = aApps[aAppId];
+
+    if (!app) {
       debug("No webapp for " + aAppId);
       return null;
     }
 
     // We can have 3rd party apps that are non-removable,
     // so we can't use the 'removable' property for isCoreApp
     // Instead, we check if the app is installed under /system/b2g
     let isCoreApp = false;
-    let app = aApps[aAppId];
+
 #ifdef MOZ_WIDGET_GONK
     isCoreApp = app.basePath == this.getCoreAppsBasePath();
 #endif
     debug(app.basePath + " isCoreApp: " + isCoreApp);
-    return { "basePath":  app.basePath + "/",
+    return { "path":  WebappOSUtils.getInstallPath(app),
              "isCoreApp": isCoreApp };
   },
 
   /**
     * Remove potential HTML tags from displayable fields in the manifest.
     * We check name, description, developer name, and permission description
     */
   sanitizeManifest: function(aManifest) {
--- a/dom/apps/src/PermissionsTable.jsm
+++ b/dom/apps/src/PermissionsTable.jsm
@@ -279,17 +279,17 @@ this.PermissionsTable =  { geolocation: 
                            },
                            "open-remote-window": {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "keyboard": {
                              app: DENY_ACTION,
-                             privileged: DENY_ACTION,
+                             privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "wappush": {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                          };
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -1945,18 +1945,18 @@ this.DOMApplicationRegistry = {
 
         let manifest = app.updateManifest = xhr.response;
         if (!manifest) {
           sendError("MANIFEST_PARSE_ERROR");
           return;
         }
 
         // Disallow reinstalls from the same manifest URL for now.
-        if (this._appIdForManifestURL(app.manifestURL) !== null &&
-            this._isLaunchable(app)) {
+        let id = this._appIdForManifestURL(app.manifestURL);
+        if (id !== null && this._isLaunchable(this.webapps[id])) {
           sendError("REINSTALL_FORBIDDEN");
           return;
         }
 
         if (!(AppsUtils.checkManifest(manifest, app) &&
               manifest.package_path)) {
           sendError("INVALID_MANIFEST");
         } else if (!AppsUtils.checkInstallAllowed(manifest, app.installOrigin)) {
@@ -2064,17 +2064,17 @@ this.DOMApplicationRegistry = {
           }
           debug("About to fire Webapps:PackageEvent 'installed'");
           this.broadcastMessage("Webapps:PackageEvent",
                                 { type: "installed",
                                   manifestURL: appObject.manifestURL,
                                   app: app,
                                   manifest: aManifest });
           if (installSuccessCallback) {
-            installSuccessCallback(aManifest);
+            installSuccessCallback(aManifest, zipFile.path);
           }
         }).bind(this));
       }).bind(this));
     }
   },
 
   confirmInstall: function(aData, aProfileDir, aInstallSuccessCallback) {
     let isReinstall = false;
@@ -2204,16 +2204,21 @@ this.DOMApplicationRegistry = {
         app: appObject,
         callback: aInstallSuccessCallback
       }
     }
   },
 
   _nextLocalId: function() {
     let id = Services.prefs.getIntPref("dom.mozApps.maxLocalId") + 1;
+
+    while (this.getManifestURLByLocalId(id)) {
+      id++;
+    }
+
     Services.prefs.setIntPref("dom.mozApps.maxLocalId", id);
     Services.prefs.savePrefFile(null);
     return id;
   },
 
   _appIdForManifestURL: function(aURI) {
     for (let id in this.webapps) {
       if (this.webapps[id].manifestURL == aURI)
@@ -2933,17 +2938,18 @@ this.DOMApplicationRegistry = {
     }).bind(this));
   },
 
   checkInstalled: function(aData, aMm) {
     aData.app = null;
     let tmp = [];
 
     for (let appId in this.webapps) {
-      if (this.webapps[appId].manifestURL == aData.manifestURL) {
+      if (this.webapps[appId].manifestURL == aData.manifestURL &&
+          this._isLaunchable(this.webapps[appId])) {
         aData.app = AppsUtils.cloneAppObject(this.webapps[appId]);
         tmp.push({ id: appId });
         break;
       }
     }
 
     this._readManifests(tmp, (function(aResult) {
       for (let i = 0; i < aResult.length; i++) {
--- a/dom/bindings/BindingDeclarations.h
+++ b/dom/bindings/BindingDeclarations.h
@@ -253,16 +253,23 @@ public:
   }
 
   template <class T1, class T2>
   void Construct(const T1 &t1, const T2 &t2)
   {
     mImpl.construct(t1, t2);
   }
 
+  void Reset()
+  {
+    if (WasPassed()) {
+      mImpl.destroy();
+    }
+  }
+
   const T& Value() const
   {
     return mImpl.ref();
   }
 
   // Return InternalType here so we can work with it usefully.
   InternalType& Value()
   {
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -8337,42 +8337,82 @@ if (""",
                         if typeNeedsRooting(m.type, self.descriptorProvider)]
 
         body += "\n\n".join(memberTraces)
 
         return ClassMethod("TraceDictionary", "void", [
             Argument("JSTracer*", "trc"),
         ], body=body)
 
+    def assignmentOperator(self):
+        body = CGList([], "\n")
+        if self.dictionary.parent:
+            body.append(CGGeneric(
+                    "%s::operator=(aOther);" %
+                    self.makeClassName(self.dictionary.parent)))
+        for (m, _) in self.memberInfo:
+            memberName = self.makeMemberName(m.identifier.name)
+            if not m.defaultValue:
+                memberAssign = CGIfElseWrapper(
+                    "aOther.%s.WasPassed()" % memberName,
+                    CGGeneric("%s.Construct();\n"
+                              "%s.Value() = aOther.%s.Value();" %
+                              (memberName, memberName, memberName)),
+                    CGGeneric("%s.Reset();" % memberName))
+            else:
+                memberAssign = CGGeneric(
+                    "%s = aOther.%s;" % (memberName, memberName))
+            body.append(memberAssign)
+        return ClassMethod(
+            "operator=", "void", [
+                Argument("const %s&" % self.makeClassName(self.dictionary),
+                         "aOther")
+                ],
+            body=body.define())
+
     def getStructs(self):
         d = self.dictionary
         selfName = self.makeClassName(d)
         members = [ClassMember(self.makeMemberName(m[0].identifier.name),
                                self.getMemberType(m),
                                visibility="public",
                                body=self.getMemberInitializer(m))
                    for m in self.memberInfo]
-        ctor = ClassConstructor([], bodyInHeader=True, visibility="public")
+        ctors = [ClassConstructor([], bodyInHeader=True, visibility="public")]
         methods = []
 
         if self.needToInitIds:
             methods.append(self.initIdsMethod())
 
         methods.append(self.initMethod())
         methods.append(self.initFromJSONMethod())
         methods.append(self.toObjectMethod())
         methods.append(self.traceDictionaryMethod())
 
+        if CGDictionary.isDictionaryCopyConstructible(d):
+            disallowCopyConstruction = False
+            # Note: no base constructors because our operator= will
+            # deal with that.
+            ctors.append(ClassConstructor([Argument("const %s&" % selfName,
+                                                    "aOther")],
+                                          bodyInHeader=True,
+                                          visibility="public",
+                                          explicit=True,
+                                          body="*this = aOther;"))
+            methods.append(self.assignmentOperator())
+        else:
+            disallowCopyConstruction = True
+
         struct = CGClass(selfName,
             bases=[ClassBase(self.base())],
             members=members,
-            constructors=[ctor],
+            constructors=ctors,
             methods=methods,
             isStruct=True,
-            disallowCopyConstruction=True)
+            disallowCopyConstruction=disallowCopyConstruction)
 
 
         initializerCtor = ClassConstructor([],
             visibility="public",
             body=(
                 "// Safe to pass a null context if we pass a null value\n"
                 "Init(nullptr, JS::NullHandleValue);"))
         initializerStruct = CGClass(selfName + "Initializer",
@@ -8575,16 +8615,28 @@ if (""",
     def getDictionaryDependencies(dictionary):
         deps = set();
         if dictionary.parent:
             deps.add(dictionary.parent)
         for member in dictionary.members:
             deps |= CGDictionary.getDictionaryDependenciesFromType(member.type)
         return deps
 
+    @staticmethod
+    def isDictionaryCopyConstructible(dictionary):
+        def isTypeCopyConstructible(type):
+            # Nullable and sequence stuff doesn't affect copy/constructibility
+            type = type.unroll()
+            return (type.isPrimitive() or type.isString() or type.isEnum() or
+                    (type.isDictionary() and
+                     CGDictionary.isDictionaryCopyConstructible(type.inner)))
+        if (dictionary.parent and
+            not CGDictionary.isDictionaryCopyConstructible(dictionary.parent)):
+            return False
+        return all(isTypeCopyConstructible(m.type) for m in dictionary.members)
 
 class CGRegisterProtos(CGAbstractMethod):
     def __init__(self, config):
         CGAbstractMethod.__init__(self, None, 'Register', 'void',
                                   [Argument('nsScriptNameSpaceManager*', 'aNameSpaceManager')])
         self.config = config
 
     def _defineMacro(self):
--- a/dom/browser-element/BrowserElementParent.cpp
+++ b/dom/browser-element/BrowserElementParent.cpp
@@ -297,25 +297,34 @@ private:
   nsRefPtr<TabParent> mTabParent;
   const CSSRect mContentRect;
   const CSSSize mContentSize;
 };
 
 NS_IMETHODIMP DispatchAsyncScrollEventRunnable::Run()
 {
   nsCOMPtr<Element> frameElement = mTabParent->GetOwnerElement();
+  nsIDocument *doc = frameElement->OwnerDoc();
+  nsCOMPtr<nsIGlobalObject> globalObject = doc->GetScopeObject();
+  NS_ENSURE_TRUE(globalObject, NS_ERROR_UNEXPECTED);
+
   // Create the event's detail object.
   AsyncScrollEventDetailInitializer detail;
   detail.mLeft = mContentRect.x;
   detail.mTop = mContentRect.y;
   detail.mWidth = mContentRect.width;
   detail.mHeight = mContentRect.height;
   detail.mScrollWidth = mContentRect.width;
   detail.mScrollHeight = mContentRect.height;
+
   AutoSafeJSContext cx;
+  JS::Rooted<JSObject*> globalJSObject(cx, globalObject->GetGlobalJSObject());
+  NS_ENSURE_TRUE(globalJSObject, NS_ERROR_UNEXPECTED);
+
+  JSAutoCompartment ac(cx, globalJSObject);
   JS::Rooted<JS::Value> val(cx);
 
   // We can get away with a null global here because
   // AsyncScrollEventDetail only contains numeric values.
   if (!detail.ToObject(cx, JS::NullPtr(), &val)) {
     MOZ_CRASH("Failed to convert dictionary to JS::Value due to OOM.");
     return NS_ERROR_FAILURE;
   }
--- a/dom/indexedDB/CheckPermissionsHelper.cpp
+++ b/dom/indexedDB/CheckPermissionsHelper.cpp
@@ -14,62 +14,51 @@
 #include "nsIPrincipal.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIURI.h"
 
 #include "CheckQuotaHelper.h"
 #include "nsContentUtils.h"
 #include "nsNetUtil.h"
 #include "nsThreadUtils.h"
-#include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 
 #include "IndexedDatabaseManager.h"
 
 #define PERMISSION_INDEXEDDB "indexedDB"
-#define PREF_INDEXEDDB_ENABLED "dom.indexedDB.enabled"
 #define TOPIC_PERMISSIONS_PROMPT "indexedDB-permissions-prompt"
 #define TOPIC_PERMISSIONS_RESPONSE "indexedDB-permissions-response"
 
 // This is a little confusing, but our default behavior (UNKNOWN_ACTION) is to
 // allow access without a prompt. If the "indexedDB" permission is set to
 // ALLOW_ACTION then we will issue a prompt before allowing access. Otherwise
 // (DENY_ACTION) we deny access.
 #define PERMISSION_ALLOWED nsIPermissionManager::UNKNOWN_ACTION
 #define PERMISSION_DENIED nsIPermissionManager::DENY_ACTION
 #define PERMISSION_PROMPT nsIPermissionManager::ALLOW_ACTION
 
 USING_INDEXEDDB_NAMESPACE
 using namespace mozilla::services;
 using mozilla::dom::quota::CheckQuotaHelper;
-using mozilla::Preferences;
 
 namespace {
 
 inline
 uint32_t
 GetIndexedDBPermissions(nsIDOMWindow* aWindow)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!Preferences::GetBool(PREF_INDEXEDDB_ENABLED)) {
-    return PERMISSION_DENIED;
-  }
-
-  // No window here means chrome access.
-  if (!aWindow) {
-    return PERMISSION_ALLOWED;
-  }
+  NS_ASSERTION(aWindow, "Chrome shouldn't check the permission!");
 
   nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(aWindow));
   NS_ENSURE_TRUE(sop, nsIPermissionManager::DENY_ACTION);
 
-  if (nsContentUtils::IsSystemPrincipal(sop->GetPrincipal())) {
-    return PERMISSION_ALLOWED;
-  }
+  NS_ASSERTION(!nsContentUtils::IsSystemPrincipal(sop->GetPrincipal()),
+               "Chrome windows shouldn't check the permission!");
 
   nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow);
   nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
   if (loadContext && loadContext->UsePrivateBrowsing()) {
     // TODO Support private browsing indexedDB?
     NS_WARNING("IndexedDB may not be used while in private browsing mode!");
     return PERMISSION_DENIED;
   }
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -16,16 +16,17 @@
 #include <algorithm>
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/IDBFactoryBinding.h"
 #include "mozilla/dom/PBrowserChild.h"
 #include "mozilla/dom/quota/OriginOrPatternString.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/TabChild.h"
+#include "mozilla/Preferences.h"
 #include "mozilla/storage.h"
 #include "nsComponentManagerUtils.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsContentUtils.h"
 #include "nsCxPusher.h"
 #include "nsDOMClassInfoID.h"
 #include "nsGlobalWindow.h"
 #include "nsHashKeys.h"
@@ -41,26 +42,29 @@
 #include "IDBEvents.h"
 #include "IDBKeyRange.h"
 #include "IndexedDatabaseManager.h"
 #include "Key.h"
 #include "ProfilerHelpers.h"
 
 #include "ipc/IndexedDBChild.h"
 
+#define PREF_INDEXEDDB_ENABLED "dom.indexedDB.enabled"
+
 USING_INDEXEDDB_NAMESPACE
 USING_QUOTA_NAMESPACE
 
 using mozilla::dom::ContentChild;
 using mozilla::dom::ContentParent;
 using mozilla::dom::IDBOpenDBOptions;
 using mozilla::dom::NonNull;
 using mozilla::dom::Optional;
 using mozilla::dom::TabChild;
 using mozilla::ErrorResult;
+using mozilla::Preferences;
 
 namespace {
 
 struct ObjectStoreInfoMap
 {
   ObjectStoreInfoMap()
   : id(INT64_MIN), info(nullptr) { }
 
@@ -597,32 +601,43 @@ IDBFactory::OpenInternal(const nsAString
     nsRefPtr<OpenDatabaseHelper> openHelper =
       new OpenDatabaseHelper(request, aName, aGroup, aASCIIOrigin, aVersion,
                              aPersistenceType, aDeleting, mContentParent,
                              aPrivilege);
 
     rv = openHelper->Init();
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-    if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
-      nsRefPtr<CheckPermissionsHelper> permissionHelper =
-        new CheckPermissionsHelper(openHelper, window);
-
-      QuotaManager* quotaManager = QuotaManager::Get();
-      NS_ASSERTION(quotaManager, "This should never be null!");
-
-      rv = quotaManager->
-        WaitForOpenAllowed(OriginOrPatternString::FromOrigin(aASCIIOrigin),
-                           Nullable<PersistenceType>(aPersistenceType),
-                           openHelper->Id(), permissionHelper);
+    if (!Preferences::GetBool(PREF_INDEXEDDB_ENABLED)) {
+      openHelper->SetError(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+      rv = openHelper->WaitForOpenAllowed();
     }
     else {
-      NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY, "Huh?");
+      StoragePrivilege openerPrivilege;
+      rv = QuotaManager::GetInfoFromWindow(window, nullptr, nullptr,
+                                           &openerPrivilege, nullptr);
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+      if (openerPrivilege != Chrome &&
+          aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+        nsRefPtr<CheckPermissionsHelper> permissionHelper =
+          new CheckPermissionsHelper(openHelper, window);
 
-      rv = openHelper->WaitForOpenAllowed();
+        QuotaManager* quotaManager = QuotaManager::Get();
+        NS_ASSERTION(quotaManager, "This should never be null!");
+
+        rv = quotaManager->
+          WaitForOpenAllowed(OriginOrPatternString::FromOrigin(aASCIIOrigin),
+                             Nullable<PersistenceType>(aPersistenceType),
+                             openHelper->Id(), permissionHelper);
+      }
+      else {
+        // Chrome and temporary storage doesn't need to check the permission.
+        rv = openHelper->WaitForOpenAllowed();
+      }
     }
     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
   }
   else if (aDeleting) {
     nsCOMPtr<nsIAtom> databaseId =
       QuotaManager::GetStorageId(aPersistenceType, aASCIIOrigin, aName);
     NS_ENSURE_TRUE(databaseId, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
--- a/dom/indexedDB/OpenDatabaseHelper.cpp
+++ b/dom/indexedDB/OpenDatabaseHelper.cpp
@@ -1732,17 +1732,18 @@ OpenDatabaseHelper::DispatchToIOThread()
   NS_ASSERTION(quotaManager, "This should never be null!");
 
   return Dispatch(quotaManager->IOThread());
 }
 
 nsresult
 OpenDatabaseHelper::RunImmediately()
 {
-  NS_ASSERTION(mState == eCreated, "We've already been dispatched?");
+  NS_ASSERTION(mState == eCreated || mState == eOpenPending,
+               "We've already been dispatched?");
   NS_ASSERTION(NS_FAILED(mResultCode),
                "Should only be short-circuiting if we failed!");
   NS_ASSERTION(NS_IsMainThread(), "All hell is about to break lose!");
 
   mState = eFiringEvents;
 
   return this->Run();
 }
@@ -2163,16 +2164,20 @@ NS_IMETHODIMP
 OpenDatabaseHelper::Run()
 {
   NS_ASSERTION(mState != eCreated, "Dispatch was not called?!?");
 
   if (NS_IsMainThread()) {
     PROFILER_MAIN_THREAD_LABEL("IndexedDB", "OpenDatabaseHelper::Run");
 
     if (mState == eOpenPending) {
+      if (NS_FAILED(mResultCode)) {
+        return RunImmediately();
+      }
+
       return DispatchToIOThread();
     }
 
     // If we need to queue up a SetVersionHelper, do that here.
     if (mState == eSetVersionPending) {
       nsresult rv = StartSetVersion();
 
       if (NS_SUCCEEDED(rv)) {
--- a/dom/network/interfaces/moz.build
+++ b/dom/network/interfaces/moz.build
@@ -21,11 +21,12 @@ if CONFIG['MOZ_B2G_RIL']:
     XPIDL_SOURCES += [
         'nsIDOMCFStateChangeEvent.idl',
         'nsIDOMMobileConnection.idl',
         'nsIDOMMozEmergencyCbModeEvent.idl',
         'nsIDOMMozOtaStatusEvent.idl',
         'nsIDOMNetworkStats.idl',
         'nsIDOMNetworkStatsManager.idl',
         'nsIMobileConnectionProvider.idl',
+        'nsINetworkStatsServiceProxy.idl',
     ]
 
 XPIDL_MODULE = 'dom_network'
--- a/dom/network/interfaces/nsIDOMNetworkStats.idl
+++ b/dom/network/interfaces/nsIDOMNetworkStats.idl
@@ -7,20 +7,26 @@
 [scriptable, builtinclass, uuid(3b16fe17-5583-483a-b486-b64a3243221c)]
 interface nsIDOMMozNetworkStatsData : nsISupports
 {
   readonly attribute unsigned long   rxBytes;   // Received bytes.
   readonly attribute unsigned long   txBytes;   // Sent bytes.
   readonly attribute jsval           date;      // Date.
 };
 
-[scriptable, builtinclass, uuid(037435a6-f563-48f3-99b3-a0106d8ba5bd)]
+[scriptable, builtinclass, uuid(6613ea55-b99c-44f9-91bf-d07da10b9b74)]
 interface nsIDOMMozNetworkStats : nsISupports
 {
   /**
+   * Manifest URL of an application for specifying the per-app
+   * stats of the specified app. If null, system stats are returned.
+   */
+  readonly attribute DOMString    manifestURL;
+
+  /**
    * Can be 'mobile', 'wifi' or null.
    * If null, stats for both mobile and wifi are returned.
    */
   readonly attribute DOMString    connectionType;
 
   /**
    * Stats for connectionType
    */
--- a/dom/network/interfaces/nsIDOMNetworkStatsManager.idl
+++ b/dom/network/interfaces/nsIDOMNetworkStatsManager.idl
@@ -7,31 +7,37 @@
 interface nsIDOMDOMRequest;
 
 dictionary NetworkStatsOptions
 {
   /**
    * Connection type used to filter which network stats will be returned:
    * 'mobile', 'wifi' or null.
    * If null, stats for both mobile and wifi are returned.
+   *
+   * Manifest URL used to retrieve network stats per app.
+   * If null, system stats (regardless of the app) are returned.
    */
   DOMString connectionType;
+  DOMString manifestURL;
   jsval start;              // date
   jsval end;                // date
 };
 
 [scriptable,  uuid(87529a6c-aef6-11e1-a595-4f034275cfa6)]
 interface nsIDOMMozNetworkStatsManager : nsISupports
 {
   /**
-   * Query network interface statistics.
+   * Query network statistics.
    *
    * If options.connectionType is not provided, return statistics for all known
    * network interfaces.
    *
+   * If options.manifestURL is not provided, return statistics regardless of the app.
+   *
    * If successful, the request result will be an nsIDOMMozNetworkStats object.
    *
    * If network stats are not available for some dates, then rxBytes &
    * txBytes are undefined for those dates.
    */
   nsIDOMDOMRequest               getNetworkStats(in jsval options);
 
   /**
new file mode 100644
--- /dev/null
+++ b/dom/network/interfaces/nsINetworkStatsServiceProxy.idl
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, function, uuid(5f821529-1d80-4ab5-a933-4e1b3585b6bc)]
+interface nsINetworkStatsServiceProxyCallback : nsISupports
+{
+  /*
+   * @param aResult callback result with boolean value
+   * @param aMessage message
+   */
+  void notify(in boolean aResult, in jsval aMessage);
+};
+
+[scriptable, uuid(8fbd115d-f590-474c-96dc-e2b6803ca975)]
+interface nsINetworkStatsServiceProxy : nsISupports
+{
+  /*
+   * An interface used to record per-app traffic data.
+   * @param aAppId app id
+   * @param aConnectionType network connection type (0 for wifi, 1 for mobile)
+   * @param aTimeStamp time stamp
+   * @param aRxBytes received data amount
+   * @param aTxBytes transmitted data amount
+   * @param aCallback an optional callback
+   */
+  void saveAppStats(in unsigned long aAppId,
+                    in long aConnectionType,
+                    in unsigned long long aTimeStamp,
+                    in unsigned long long aRxBytes,
+                    in unsigned long long aTxBytes,
+         [optional] in nsINetworkStatsServiceProxyCallback aCallback);
+};
--- a/dom/network/src/NetworkStatsDB.jsm
+++ b/dom/network/src/NetworkStatsDB.jsm
@@ -10,45 +10,46 @@ const DEBUG = false;
 function debug(s) { dump("-*- NetworkStatsDB: " + s + "\n"); }
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 
 const DB_NAME = "net_stats";
-const DB_VERSION = 1;
-const STORE_NAME = "net_stats";
+const DB_VERSION = 2;
+const STORE_NAME = "net_stats"; // Deprecated. Use "net_stats_v2" instead.
+const STORE_NAME_V2 = "net_stats_v2";
 
 // Constant defining the maximum values allowed per interface. If more, older
 // will be erased.
 const VALUES_MAX_LENGTH = 6 * 30;
 
 // Constant defining the rate of the samples. Daily.
 const SAMPLE_RATE = 1000 * 60 * 60 * 24;
 
 this.NetworkStatsDB = function NetworkStatsDB(aGlobal, aConnectionTypes) {
   if (DEBUG) {
     debug("Constructor");
   }
   this._connectionTypes = aConnectionTypes;
-  this.initDBHelper(DB_NAME, DB_VERSION, [STORE_NAME], aGlobal);
+  this.initDBHelper(DB_NAME, DB_VERSION, [STORE_NAME_V2], aGlobal);
 }
 
 NetworkStatsDB.prototype = {
   __proto__: IndexedDBHelper.prototype,
 
   dbNewTxn: function dbNewTxn(txn_type, callback, txnCb) {
     function successCb(result) {
       txnCb(null, result);
     }
     function errorCb(error) {
       txnCb(error, null);
     }
-    return this.newTxn(txn_type, STORE_NAME, callback, successCb, errorCb);
+    return this.newTxn(txn_type, STORE_NAME_V2, callback, successCb, errorCb);
   },
 
   upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
     if (DEBUG) {
       debug("upgrade schema from: " + aOldVersion + " to " + aNewVersion + " called!");
     }
     let db = aDb;
     let objectStore;
@@ -84,60 +85,104 @@ NetworkStatsDB.prototype = {
                        txBytes:        0,
                        rxTotalBytes:   0,
                        txTotalBytes:   0 });
         }
         this._saveStats(aTransaction, objectStore, stats);
         if (DEBUG) {
           debug("Database initialized");
         }
+      } else if (currVersion == 1) {
+        // In order to support per-app traffic data storage, the original
+        // objectStore needs to be replaced by a new objectStore with new
+        // key path ("appId") and new index ("appId").
+        let newObjectStore;
+        newObjectStore = db.createObjectStore(STORE_NAME_V2, { keyPath: ["appId", "connectionType", "timestamp"] });
+        newObjectStore.createIndex("appId", "appId", { unique: false });
+        newObjectStore.createIndex("connectionType", "connectionType", { unique: false });
+        newObjectStore.createIndex("timestamp", "timestamp", { unique: false });
+        newObjectStore.createIndex("rxBytes", "rxBytes", { unique: false });
+        newObjectStore.createIndex("txBytes", "txBytes", { unique: false });
+        newObjectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
+        newObjectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
+        if (DEBUG) {
+          debug("Created new object stores and indexes");
+        }
+
+        // Copy the data from the original objectStore to the new objectStore.
+        objectStore = aTransaction.objectStore(STORE_NAME);
+        objectStore.openCursor().onsuccess = function(event) {
+          let cursor = event.target.result;
+          if (!cursor) {
+            // Delete the original object store.
+            db.deleteObjectStore(STORE_NAME);
+            return;
+          }
+
+          let oldStats = cursor.value;
+          let newStats = { appId:          0,
+                           connectionType: oldStats.connectionType,
+                           timestamp:      oldStats.timestamp,
+                           rxBytes:        oldStats.rxBytes,
+                           txBytes:        oldStats.txBytes,
+                           rxTotalBytes:   oldStats.rxTotalBytes,
+                           txTotalBytes:   oldStats.txTotalBytes };
+          this._saveStats(aTransaction, newObjectStore, newStats);
+          cursor.continue();
+        }.bind(this);
       }
     }
   },
 
-   normalizeDate: function normalizeDate(aDate) {
+  normalizeDate: function normalizeDate(aDate) {
     // Convert to UTC according to timezone and
     // filter timestamp to get SAMPLE_RATE precission
-    let timestamp = aDate.getTime() - (new Date()).getTimezoneOffset() * 60 * 1000;
+    let timestamp = aDate.getTime() - aDate.getTimezoneOffset() * 60 * 1000;
     timestamp = Math.floor(timestamp / SAMPLE_RATE) * SAMPLE_RATE;
     return timestamp;
   },
 
   saveStats: function saveStats(stats, aResultCb) {
     let timestamp = this.normalizeDate(stats.date);
 
-    stats = {connectionType: stats.connectionType,
-             timestamp:      timestamp,
-             rxBytes:        0,
-             txBytes:        0,
-             rxTotalBytes:   stats.rxBytes,
-             txTotalBytes:   stats.txBytes};
+    stats = { appId:          stats.appId,
+              connectionType: stats.connectionType,
+              timestamp:      timestamp,
+              rxBytes:        (stats.appId == 0) ? 0 : stats.rxBytes,
+              txBytes:        (stats.appId == 0) ? 0 : stats.txBytes,
+              rxTotalBytes:   (stats.appId == 0) ? stats.rxBytes : 0,
+              txTotalBytes:   (stats.appId == 0) ? stats.txBytes : 0 };
 
     this.dbNewTxn("readwrite", function(txn, store) {
       if (DEBUG) {
         debug("Filtered time: " + new Date(timestamp));
         debug("New stats: " + JSON.stringify(stats));
       }
 
       let request = store.index("connectionType").openCursor(stats.connectionType, "prev");
       request.onsuccess = function onsuccess(event) {
         let cursor = event.target.result;
         if (!cursor) {
           // Empty, so save first element.
           this._saveStats(txn, store, stats);
           return;
         }
 
+        if (stats.appId != cursor.value.appId) {
+          cursor.continue();
+          return;
+        }
+
         // There are old samples
         if (DEBUG) {
           debug("Last value " + JSON.stringify(cursor.value));
         }
 
         // Remove stats previous to now - VALUE_MAX_LENGTH
-        this._removeOldStats(txn, store, stats.connectionType, stats.timestamp);
+        this._removeOldStats(txn, store, stats.appId, stats.connectionType, stats.timestamp);
 
         // Process stats before save
         this._processSamplesDiff(txn, store, cursor, stats);
       }.bind(this);
     }.bind(this), aResultCb);
   },
 
   /*
@@ -155,42 +200,58 @@ NetworkStatsDB.prototype = {
       txn.abort();
       throw new Error("Error processing samples");
     }
 
     if (DEBUG) {
       debug("New: " + newSample.timestamp + " - Last: " + lastSample.timestamp + " - diff: " + diff);
     }
 
-    let rxDiff = newSample.rxTotalBytes - lastSample.rxTotalBytes;
-    let txDiff = newSample.txTotalBytes - lastSample.txTotalBytes;
-    if (rxDiff < 0 || txDiff < 0) {
-      rxDiff = newSample.rxTotalBytes;
-      txDiff = newSample.txTotalBytes;
+    // If the incoming data is obtained from netd (|newSample.appId| is 0),
+    // the new |txBytes|/|rxBytes| is assigend by the differnce between the new
+    // |txTotalBytes|/|rxTotalBytes| and the last |txTotalBytes|/|rxTotalBytes|.
+    // Else, the incoming data is per-app data (|newSample.appId| is not 0),
+    // the |txBytes|/|rxBytes| is directly the new |txBytes|/|rxBytes|.
+    if (newSample.appId == 0) {
+      let rxDiff = newSample.rxTotalBytes - lastSample.rxTotalBytes;
+      let txDiff = newSample.txTotalBytes - lastSample.txTotalBytes;
+      if (rxDiff < 0 || txDiff < 0) {
+        rxDiff = newSample.rxTotalBytes;
+        txDiff = newSample.txTotalBytes;
+      }
+      newSample.rxBytes = rxDiff;
+      newSample.txBytes = txDiff;
     }
-    newSample.rxBytes = rxDiff;
-    newSample.txBytes = txDiff;
 
     if (diff == 1) {
       // New element.
+
+      // If the incoming data is per-data data, new |rxTotalBytes|/|txTotalBytes|
+      // needs to be obtained by adding new |rxBytes|/|txBytes| to last
+      // |rxTotalBytes|/|txTotalBytes|.
+      if (newSample.appId != 0) {
+        newSample.rxTotalBytes = newSample.rxBytes + lastSample.rxTotalBytes;
+        newSample.txTotalBytes = newSample.txBytes + lastSample.txTotalBytes;
+      }
       this._saveStats(txn, store, newSample);
       return;
     }
     if (diff > 1) {
       // Some samples lost. Device off during one or more samplerate periods.
       // Time or timezone changed
       // Add lost samples with 0 bytes and the actual one.
       if (diff > VALUES_MAX_LENGTH) {
         diff = VALUES_MAX_LENGTH;
       }
 
       let data = [];
       for (let i = diff - 2; i >= 0; i--) {
         let time = newSample.timestamp - SAMPLE_RATE * (i + 1);
-        let sample = {connectionType: newSample.connectionType,
+        let sample = {appId:          newSample.appId,
+                      connectionType: newSample.connectionType,
                       timestamp:      time,
                       rxBytes:        0,
                       txBytes:        0,
                       rxTotalBytes:   lastSample.rxTotalBytes,
                       txTotalBytes:   lastSample.txTotalBytes};
         data.push(sample);
       }
 
@@ -200,20 +261,31 @@ NetworkStatsDB.prototype = {
     }
     if (diff == 0 || diff < 0) {
       // New element received before samplerate period.
       // It means that device has been restarted (or clock / timezone change).
       // Update element.
 
       // If diff < 0, clock or timezone changed back. Place data in the last sample.
 
-      lastSample.rxBytes += rxDiff;
-      lastSample.txBytes += txDiff;
-      lastSample.rxTotalBytes = newSample.rxTotalBytes;
-      lastSample.txTotalBytes = newSample.txTotalBytes;
+      lastSample.rxBytes += newSample.rxBytes;
+      lastSample.txBytes += newSample.txBytes;
+
+      // If incoming data is obtained from netd, last |rxTotalBytes|/|txTotalBytes|
+      // needs to get updated by replacing the new |rxTotalBytes|/|txTotalBytes|.
+      if (newSample.appId == 0) {
+        lastSample.rxTotalBytes = newSample.rxTotalBytes;
+        lastSample.txTotalBytes = newSample.txTotalBytes;
+      } else {
+        // Else, the incoming data is per-app data, old |rxTotalBytes|/
+        // |txTotalBytes| needs to get updated by adding the new
+        // |rxBytes|/|txBytes| to last |rxTotalBytes|/|txTotalBytes|.
+        lastSample.rxTotalBytes += newSample.rxBytes;
+        lastSample.txTotalBytes += newSample.txBytes;
+      }
       if (DEBUG) {
         debug("Update: " + JSON.stringify(lastSample));
       }
       let req = lastSampleCursor.update(lastSample);
     }
   },
 
   _saveStats: function _saveStats(txn, store, networkStats) {
@@ -226,22 +298,22 @@ NetworkStatsDB.prototype = {
       for (let i = 0; i <= len; i++) {
         store.put(networkStats[i]);
       }
     } else {
       store.put(networkStats);
     }
   },
 
-  _removeOldStats: function _removeOldStats(txn, store, connType, date) {
+  _removeOldStats: function _removeOldStats(txn, store, appId, connType, date) {
     // Callback function to remove old items when new ones are added.
     let filterDate = date - (SAMPLE_RATE * VALUES_MAX_LENGTH - 1);
-    let lowFilter = [connType, 0];
-    let upFilter = [connType, filterDate];
-    let range = this.dbGlobal.IDBKeyRange.bound(lowFilter, upFilter, false, false);
+    let lowerFilter = [appId, connType, 0];
+    let upperFilter = [appId, connType, filterDate];
+    let range = this.dbGlobal.IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
     store.openCursor(range).onsuccess = function(event) {
       var cursor = event.target.result;
       if (cursor) {
         cursor.delete();
         cursor.continue();
       }
     }.bind(this);
   },
@@ -256,25 +328,26 @@ NetworkStatsDB.prototype = {
   },
 
   find: function find(aResultCb, aOptions) {
     let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
     let start = this.normalizeDate(aOptions.start);
     let end = this.normalizeDate(aOptions.end);
 
     if (DEBUG) {
-      debug("Find: connectionType:" + aOptions.connectionType + " start: " + start + " end: " + end);
+      debug("Find: appId: " + aOptions.appId + " connectionType:" +
+            aOptions.connectionType + " start: " + start + " end: " + end);
       debug("Start time: " + new Date(start));
       debug("End time: " + new Date(end));
     }
 
     this.dbNewTxn("readonly", function(txn, store) {
-      let lowFilter = [aOptions.connectionType, start];
-      let upFilter = [aOptions.connectionType, end];
-      let range = this.dbGlobal.IDBKeyRange.bound(lowFilter, upFilter, false, false);
+      let lowerFilter = [aOptions.appId, aOptions.connectionType, start];
+      let upperFilter = [aOptions.appId, aOptions.connectionType, end];
+      let range = this.dbGlobal.IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
 
       let data = [];
 
       if (!txn.result) {
         txn.result = {};
       }
 
       let request = store.openCursor(range).onsuccess = function(event) {
@@ -286,64 +359,72 @@ NetworkStatsDB.prototype = {
           cursor.continue();
           return;
         }
 
         // When requested samples (start / end) are not in the range of now and
         // now - VALUES_MAX_LENGTH, fill with empty samples.
         this.fillResultSamples(start + offset, end + offset, data);
 
+        txn.result.manifestURL = aOptions.manifestURL;
         txn.result.connectionType = aOptions.connectionType;
         txn.result.start = aOptions.start;
         txn.result.end = aOptions.end;
         txn.result.data = data;
       }.bind(this);
     }.bind(this), aResultCb);
   },
 
   findAll: function findAll(aResultCb, aOptions) {
     let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
     let start = this.normalizeDate(aOptions.start);
     let end = this.normalizeDate(aOptions.end);
 
     if (DEBUG) {
-      debug("FindAll: start: " + start + " end: " + end + "\n");
+      debug("FindAll: appId: " + aOptions.appId +
+            " start: " + start + " end: " + end + "\n");
     }
 
     let self = this;
     this.dbNewTxn("readonly", function(txn, store) {
-      let lowFilter = start;
-      let upFilter = end;
-      let range = this.dbGlobal.IDBKeyRange.bound(lowFilter, upFilter, false, false);
+      let lowerFilter = start;
+      let upperFilter = end;
+      let range = this.dbGlobal.IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
 
       let data = [];
 
       if (!txn.result) {
         txn.result = {};
       }
 
       let request = store.index("timestamp").openCursor(range).onsuccess = function(event) {
         var cursor = event.target.result;
         if (cursor) {
+          if (cursor.value.appId != aOptions.appId) {
+            cursor.continue();
+            return;
+          }
+
           if (data.length > 0 &&
               data[data.length - 1].date.getTime() == cursor.value.timestamp + offset) {
             // Time is the same, so add values.
             data[data.length - 1].rxBytes += cursor.value.rxBytes;
             data[data.length - 1].txBytes += cursor.value.txBytes;
           } else {
             data.push({ rxBytes: cursor.value.rxBytes,
                         txBytes: cursor.value.txBytes,
                         date: new Date(cursor.value.timestamp + offset) });
           }
           cursor.continue();
           return;
         }
 
         this.fillResultSamples(start + offset, end + offset, data);
 
+        txn.result.manifestURL = aOptions.manifestURL;
         txn.result.connectionType = aOptions.connectionType;
         txn.result.start = aOptions.start;
         txn.result.end = aOptions.end;
         txn.result.data = data;
       }.bind(this);
     }.bind(this), aResultCb);
   },
 
--- a/dom/network/src/NetworkStatsManager.js
+++ b/dom/network/src/NetworkStatsManager.js
@@ -52,35 +52,37 @@ NetworkStatsData.prototype = {
                                      interfaces: [nsIDOMMozNetworkStatsData],
                                      flags: nsIClassInfo.DOM_OBJECT}),
 
   QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsData])
 };
 
 // NetworkStats
 const NETWORKSTATS_CONTRACTID = "@mozilla.org/networkstats;1";
-const NETWORKSTATS_CID        = Components.ID("{037435a6-f563-48f3-99b3-a0106d8ba5bd}");
+const NETWORKSTATS_CID        = Components.ID("{6613ea55-b99c-44f9-91bf-d07da10b9b74}");
 const nsIDOMMozNetworkStats   = Components.interfaces.nsIDOMMozNetworkStats;
 
 function NetworkStats(aWindow, aStats) {
   if (DEBUG) {
     debug("NetworkStats Constructor");
   }
+  this.manifestURL = aStats.manifestURL || null;
   this.connectionType = aStats.connectionType || null;
   this.start = aStats.start || null;
   this.end = aStats.end || null;
 
   let samples = this.data = Cu.createArrayIn(aWindow);
   for (let i = 0; i < aStats.data.length; i++) {
     samples.push(new NetworkStatsData(aStats.data[i]));
   }
 }
 
 NetworkStats.prototype = {
   __exposedProps__: {
+                      manifestURL: 'r',
                       connectionType: 'r',
                       start: 'r',
                       end:  'r',
                       data:  'r',
                     },
 
   classID : NETWORKSTATS_CID,
   classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATS_CID,
--- a/dom/network/src/NetworkStatsManager.manifest
+++ b/dom/network/src/NetworkStatsManager.manifest
@@ -1,9 +1,9 @@
 component {3b16fe17-5583-483a-b486-b64a3243221c} NetworkStatsManager.js
 contract @mozilla.org/networkStatsdata;1 {3b16fe17-5583-483a-b486-b64a3243221c}
 
-component {037435a6-f563-48f3-99b3-a0106d8ba5bd} NetworkStatsManager.js
-contract @mozilla.org/networkStats;1 {037435a6-f563-48f3-99b3-a0106d8ba5bd}
+component {6613ea55-b99c-44f9-91bf-d07da10b9b74} NetworkStatsManager.js
+contract @mozilla.org/networkStats;1 {6613ea55-b99c-44f9-91bf-d07da10b9b74}
 
 component {87529a6c-aef6-11e1-a595-4f034275cfa6} NetworkStatsManager.js
 contract @mozilla.org/networkStatsManager;1 {87529a6c-aef6-11e1-a595-4f034275cfa6}
 category JavaScript-navigator-property mozNetworkStats @mozilla.org/networkStatsManager;1
--- a/dom/network/src/NetworkStatsService.jsm
+++ b/dom/network/src/NetworkStatsService.jsm
@@ -18,28 +18,35 @@ Cu.import("resource://gre/modules/Networ
 const NET_NETWORKSTATSSERVICE_CONTRACTID = "@mozilla.org/network/netstatsservice;1";
 const NET_NETWORKSTATSSERVICE_CID = Components.ID("{18725604-e9ac-488a-8aa0-2471e7f6c0a4}");
 
 const TOPIC_INTERFACE_REGISTERED   = "network-interface-registered";
 const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
 const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
 const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
 
+// The maximum traffic amount can be saved in the |cachedAppStats|.
+const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB
+
 XPCOMUtils.defineLazyServiceGetter(this, "gIDBManager",
                                    "@mozilla.org/dom/indexeddb/manager;1",
                                    "nsIIndexedDatabaseManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageListenerManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "networkManager",
                                    "@mozilla.org/network/manager;1",
                                    "nsINetworkManager");
 
+XPCOMUtils.defineLazyServiceGetter(this, "appsService",
+                                   "@mozilla.org/AppsService;1",
+                                   "nsIAppsService");
+
 let myGlobal = this;
 
 this.NetworkStatsService = {
   init: function() {
     if (DEBUG) {
       debug("Service started");
     }
 
@@ -69,16 +76,20 @@ this.NetworkStatsService = {
 
     gIDBManager.initWindowless(myGlobal);
     this._db = new NetworkStatsDB(myGlobal, this._connectionTypes);
 
     // Stats for all interfaces are updated periodically
     this.timer.initWithCallback(this, this._db.sampleRate,
                                 Ci.nsITimer.TYPE_REPEATING_PRECISE);
 
+    // App stats are firstly stored in the cached.
+    this.cachedAppStats = Object.create(null);
+    this.cachedAppStatsDate = new Date();
+
     this.updateQueue = [];
     this.isQueueRunning = false;
   },
 
   receiveMessage: function(aMessage) {
     if (!aMessage.target.assertPermission("networkstats-manage")) {
       return;
     }
@@ -159,25 +170,48 @@ this.NetworkStatsService = {
     this.updateAllStats();
   },
 
   /*
    * Function called from manager to get stats from database.
    * In order to return updated stats, first is performed a call to
    * updateAllStats function, which will get last stats from netd
    * and update the database.
-   * Then, depending on the request (stats per interface or total stats)
+   * Then, depending on the request (stats per appId or total stats)
    * it retrieve them from database and return to the manager.
    */
   getStats: function getStats(mm, msg) {
     this.updateAllStats(function onStatsUpdated(aResult, aMessage) {
 
-      let options = msg.data;
+      let data = msg.data;
+
+      let options = { appId:          0,
+                      connectionType: data.connectionType,
+                      start:          data.start,
+                      end:            data.end };
+
+      let manifestURL = data.manifestURL;
+      if (manifestURL) {
+        let appId = appsService.getAppLocalIdByManifestURL(manifestURL);
+        if (DEBUG) {
+          debug("get appId: " + appId + " from manifestURL: " + manifestURL);
+        }
+
+        if (!appId) {
+          mm.sendAsyncMessage("NetworkStats:Get:Return",
+                              { id: msg.id, error: "Invalid manifestURL", result: null });
+          return;
+        }
+
+        options.appId = appId;
+        options.manifestURL = manifestURL;
+      }
+
       if (DEBUG) {
-        debug("getstats for: - " + options.connectionType + " -");
+        debug("getStats for options: " + JSON.stringify(options));
       }
 
       if (!options.connectionType || options.connectionType.length == 0) {
         this._db.findAll(function onStatsFound(error, result) {
           mm.sendAsyncMessage("NetworkStats:Get:Return",
                               { id: msg.id, error: error, result: result });
         }, options);
         return;
@@ -202,16 +236,19 @@ this.NetworkStatsService = {
   clearDB: function clearDB(mm, msg) {
     this._db.clear(function onDBCleared(error, result) {
       mm.sendAsyncMessage("NetworkStats:Clear:Return",
                           { id: msg.id, error: error, result: result });
     });
   },
 
   updateAllStats: function updateAllStats(callback) {
+    // Update |cachedAppStats|.
+    this.updateCachedAppStats();
+
     let elements = [];
     let lastElement;
 
     // For each connectionType create an object containning the type
     // and the 'queueIndex', the 'queueIndex' is an integer representing
     // the index of a connection type in the global queue array. So, if
     // the connection type is already in the queue it is not appended again,
     // else it is pushed in 'elements' array, which later will be pushed to
@@ -351,20 +388,21 @@ this.NetworkStatsService = {
   networkStatsAvailable: function networkStatsAvailable(callback, connType, result, rxBytes, txBytes, date) {
     if (!result) {
       if (callback) {
         callback(false, "Netd IPC error");
       }
       return;
     }
 
-    let stats = { connectionType: this._connectionTypes[connType].name,
+    let stats = { appId:          0,
+                  connectionType: this._connectionTypes[connType].name,
                   date:           date,
-                  rxBytes:        txBytes,
-                  txBytes:        rxBytes};
+                  rxBytes:        rxBytes,
+                  txBytes:        txBytes };
 
     if (DEBUG) {
       debug("Update stats for " + stats.connectionType + ": rx=" + stats.rxBytes +
             " tx=" + stats.txBytes + " timestamp=" + stats.date);
     }
     this._db.saveStats(stats, function onSavedStats(error, result) {
       if (callback) {
         if (error) {
@@ -372,16 +410,140 @@ this.NetworkStatsService = {
           return;
         }
 
         callback(true, "OK");
       }
     });
   },
 
+  /*
+   * Function responsible for receiving per-app stats.
+   */
+  saveAppStats: function saveAppStats(aAppId, aConnectionType, aTimeStamp, aRxBytes, aTxBytes, aCallback) {
+    if (DEBUG) {
+      debug("saveAppStats: " + aAppId + " " + aConnectionType + " " +
+            aTimeStamp + " " + aRxBytes + " " + aTxBytes);
+    }
+
+    // |aAppId| can not be 0 or null in this case.
+    if (!aAppId) {
+      return;
+    }
+
+    let stats = { appId: aAppId,
+                  connectionType: this._connectionTypes[aConnectionType].name,
+                  date: new Date(aTimeStamp),
+                  rxBytes: aRxBytes,
+                  txBytes: aTxBytes };
+
+    // Generate an unique key from |appId| and |connectionType|,
+    // which is used to retrieve data in |cachedAppStats|.
+    let key = stats.appId + stats.connectionType;
+
+    // |cachedAppStats| only keeps the data with the same date.
+    // If the incoming date is different from |cachedAppStatsDate|,
+    // both |cachedAppStats| and |cachedAppStatsDate| will get updated.
+    let diff = (this._db.normalizeDate(stats.date) -
+                this._db.normalizeDate(this.cachedAppStatsDate)) /
+               this._db.sampleRate;
+    if (diff != 0) {
+      this.updateCachedAppStats(function onUpdated(success, message) {
+        this.cachedAppStatsDate = stats.date;
+        this.cachedAppStats[key] = stats;
+
+        if (!aCallback) {
+          return;
+        }
+
+        if (!success) {
+          aCallback.notify(false, message);
+          return;
+        }
+
+        aCallback.notify(true, "ok");
+      }.bind(this));
+
+      return;
+    }
+
+    // Try to find the matched row in the cached by |appId| and |connectionType|.
+    // If not found, save the incoming data into the cached.
+    let appStats = this.cachedAppStats[key];
+    if (!appStats) {
+      this.cachedAppStats[key] = stats;
+      return;
+    }
+
+    // Find matched row, accumulate the traffic amount.
+    appStats.rxBytes += stats.rxBytes;
+    appStats.txBytes += stats.txBytes;
+
+    // If new rxBytes or txBytes exceeds MAX_CACHED_TRAFFIC
+    // the corresponding row will be saved to indexedDB.
+    // Then, the row will be removed from the cached.
+    if (appStats.rxBytes > MAX_CACHED_TRAFFIC ||
+        appStats.txBytes > MAX_CACHED_TRAFFIC) {
+      this._db.saveStats(appStats,
+        function (error, result) {
+          if (DEBUG) {
+            debug("Application stats inserted in indexedDB");
+          }
+        }
+      );
+      delete this.cachedAppStats[key];
+    }
+  },
+
+  updateCachedAppStats: function updateCachedAppStats(callback) {
+    if (DEBUG) {
+      debug("updateCachedAppStats: " + this.cachedAppStatsDate);
+    }
+
+    let stats = Object.keys(this.cachedAppStats);
+    if (stats.length == 0) {
+      // |cachedAppStats| is empty, no need to update.
+      return;
+    }
+
+    let index = 0;
+    this._db.saveStats(this.cachedAppStats[stats[index]],
+      function onSavedStats(error, result) {
+        if (DEBUG) {
+          debug("Application stats inserted in indexedDB");
+        }
+
+        // Clean up the |cachedAppStats| after updating.
+        if (index == stats.length - 1) {
+          this.cachedAppStats = Object.create(null);
+
+          if (!callback) {
+            return;
+          }
+
+          if (error) {
+            callback(false, error);
+            return;
+          }
+
+          callback(true, "ok");
+          return;
+        }
+
+        // Update is not finished, keep updating.
+        index += 1;
+        this._db.saveStats(this.cachedAppStats[stats[index]],
+                           onSavedStats.bind(this, error, result));
+      }.bind(this));
+  },
+
+  get maxCachedTraffic () {
+    return MAX_CACHED_TRAFFIC;
+  },
+
   logAllRecords: function logAllRecords() {
     this._db.logAllRecords(function onResult(error, result) {
       if (error) {
         debug("Error: " + error);
         return;
       }
 
       debug("===== LOG =====");
new file mode 100644
--- /dev/null
+++ b/dom/network/src/NetworkStatsServiceProxy.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const DEBUG = false;
+function debug(s) { dump("-*- NetworkStatsServiceProxy: " + s + "\n"); }
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+this.EXPORTED_SYMBOLS = ["NetworkStatsServiceProxy"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetworkStatsService.jsm");
+
+const NETWORKSTATSSERVICEPROXY_CONTRACTID = "@mozilla.org/networkstatsServiceProxy;1";
+const NETWORKSTATSSERVICEPROXY_CID = Components.ID("8fbd115d-f590-474c-96dc-e2b6803ca975");
+const nsINetworkStatsServiceProxy = Ci.nsINetworkStatsServiceProxy;
+
+function NetworkStatsServiceProxy() {
+  if (DEBUG) {
+    debug("Proxy started");
+  }
+}
+
+NetworkStatsServiceProxy.prototype = {
+  /*
+   * Function called in the protocol layer (HTTP, FTP, WebSocket ...etc)
+   * to pass the per-app stats to NetworkStatsService.
+   */
+  saveAppStats: function saveAppStats(aAppId, aConnectionType, aTimeStamp,
+                                      aRxBytes, aTxBytes, aCallback) {
+    if (DEBUG) {
+      debug("saveAppStats: " + aAppId + " " + aConnectionType + " " +
+            aTimeStamp + " " + aRxBytes + " " + aTxBytes);
+    }
+
+    NetworkStatsService.saveAppStats(aAppId, aConnectionType, aTimeStamp,
+                                     aRxBytes, aTxBytes, aCallback);
+  },
+
+  classID : NETWORKSTATSSERVICEPROXY_CID,
+  QueryInterface : XPCOMUtils.generateQI([nsINetworkStatsServiceProxy]),
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsServiceProxy]);
new file mode 100644
--- /dev/null
+++ b/dom/network/src/NetworkStatsServiceProxy.manifest
@@ -0,0 +1,2 @@
+component {8fbd115d-f590-474c-96dc-e2b6803ca975} NetworkStatsServiceProxy.js
+contract @mozilla.org/networkstatsServiceProxy;1 {8fbd115d-f590-474c-96dc-e2b6803ca975}
--- a/dom/network/src/moz.build
+++ b/dom/network/src/moz.build
@@ -36,16 +36,18 @@ EXTRA_COMPONENTS += [
     'TCPSocket.manifest',
     'TCPSocketParentIntermediary.js',
 ]
 
 if CONFIG['MOZ_B2G_RIL']:
     EXTRA_COMPONENTS += [
         'NetworkStatsManager.js',
         'NetworkStatsManager.manifest',
+        'NetworkStatsServiceProxy.js',
+        'NetworkStatsServiceProxy.manifest',
     ]
 
 IPDL_SOURCES += [
     'PTCPServerSocket.ipdl',
     'PTCPSocket.ipdl',
 ]
 
 FAIL_ON_WARNINGS = True
--- a/dom/network/tests/test_networkstats_basics.html
+++ b/dom/network/tests/test_networkstats_basics.html
@@ -138,17 +138,17 @@ var steps = [
         next();
         return;
       }
 
       ok(false, "getNetworkStats launch exception when end param does not exist");
     }, 1000);
   },
   function () {
-    ok(true, "Get stats for a connectionType and dates adapted to samplerate");
+    ok(true, "Get system stats for a connectionType and dates adapted to samplerate");
     // Prepare get params
     var type = netStats.connectionTypes[0];
     var diff = 2;
     // Get samplerate in millis
     var sampleRate = netStats.sampleRate * 1000;
     // Get date with samplerate's precision
     var offset = new Date().getTimezoneOffset() * 60 * 1000;
     var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
@@ -156,32 +156,33 @@ var steps = [
     var startDate = new Date(endDate.getTime() - (sampleRate * diff));
     // Calculate the number of samples that should be returned based on the
     // the samplerate and including final and initial samples.
     var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
 
     // Launch request
     req = netStats.getNetworkStats({start: startDate, end: endDate, connectionType: type});
     req.onsuccess = function () {
-      ok(true, "Get stats request ok");
+      ok(true, "Get system stats request ok");
+      ok(req.result.manifestURL == null, "manifestURL should be null");
       ok(req.result.connectionType == type, "connectionTypes should be equals");
       ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
       ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
       var data = req.result.data;
       ok(Array.isArray(data) && data.length == samples,
          "data is an array of length " + samples);
       checkDataDates(data, startDate, endDate, sampleRate);
       next();
     };
     req.onerror = function () {
-      ok(false, "Get stats for a connectionType failure!");
+      ok(false, "Get system stats for a connectionType failure!");
     }
   },
   function () {
-    ok(true, "Get stats for all connectionTypes and dates adapted to samplerate");
+    ok(true, "Get system stats for all connectionTypes and dates adapted to samplerate");
     // Prepare get params
     var diff = 2;
     // Get samplerate in millis
     var sampleRate = netStats.sampleRate * 1000;
     // Get date with samplerate's precision
     var offset = new Date().getTimezoneOffset() * 60 * 1000;
     var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
                            * sampleRate + offset);
@@ -189,63 +190,139 @@ var steps = [
     // Calculate the number of samples that should be returned based on the
     // the samplerate and including final and initial samples.
     var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
 
     // Launch request
     req = netStats.getNetworkStats({start: startDate, end: endDate});
     req.onsuccess = function () {
       ok(true, "Get stats request ok");
+      ok(req.result.manifestURL == null, "manifestURL should be null");
       ok(req.result.connectionType == null, "connectionTypes should be null");
       ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
       ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
       var data = req.result.data;
       ok(Array.isArray(data) && data.length == samples,
          "data is an array of length " + samples);
       checkDataDates(data, startDate, endDate, sampleRate);
       next();
     };
     req.onerror = function () {
-      ok(false, "Get stats for all connectionTypes failure!");
+      ok(false, "Get system stats for all connectionTypes failure!");
     }
   },
   function () {
-    ok(true, "Get stats for a connectionType and dates not adapted to samplerate");
+    ok(true, "Get app stats for a connectionType and dates adapted to samplerate");
+    // Prepare get params
+    var url = 'app://browser.gaiamobile.org/manifest.webapp';
+    var type = netStats.connectionTypes[0];
+    var diff = 2;
+    // Get samplerate in millis
+    var sampleRate = netStats.sampleRate * 1000;
+    // Get date with samplerate's precision
+    var offset = new Date().getTimezoneOffset() * 60 * 1000;
+    var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
+                           * sampleRate + offset);
+    var startDate = new Date(endDate.getTime() - (sampleRate * diff));
+    // Calculate the number of samples that should be returned based on the
+    // the samplerate and including final and initial samples.
+    var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
+
+    // Launch request
+    req = netStats.getNetworkStats({start:          startDate,
+                                    end:            endDate,
+                                    connectionType: type,
+                                    manifestURL:    url});
+    req.onsuccess = function () {
+      ok(true, "Get app stats request ok");
+      ok(req.result.manifestURL == url, "manifestURL should be equals");
+      ok(req.result.connectionType == type, "connectionTypes should be equals");
+      ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
+      ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
+      var data = req.result.data;
+      ok(Array.isArray(data) && data.length == samples,
+         "data is an array of length " + samples);
+      checkDataDates(data, startDate, endDate, sampleRate);
+      next();
+    };
+    req.onerror = function () {
+      ok(false, "Get app stats for a connectionType failure!");
+    }
+  },
+  function () {
+    ok(true, "Get app stats for all connectionTypes and dates adapted to samplerate");
+    // Prepare get params
+    var url = 'app://browser.gaiamobile.org/manifest.webapp';
+    var diff = 2;
+    // Get samplerate in millis
+    var sampleRate = netStats.sampleRate * 1000;
+    // Get date with samplerate's precision
+    var offset = new Date().getTimezoneOffset() * 60 * 1000;
+    var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
+                           * sampleRate + offset);
+    var startDate = new Date(endDate.getTime() - (sampleRate * diff));
+    // Calculate the number of samples that should be returned based on the
+    // the samplerate and including final and initial samples.
+    var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
+
+    // Launch request
+    req = netStats.getNetworkStats({start:       startDate,
+                                    end:         endDate,
+                                    manifestURL: url});
+    req.onsuccess = function () {
+      ok(true, "Get app stats request ok");
+      ok(req.result.manifestURL == url, "manifestURL should be equals");
+      ok(req.result.connectionType == null, "connectionTypes should be null");
+      ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
+      ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
+      var data = req.result.data;
+      ok(Array.isArray(data) && data.length == samples,
+         "data is an array of length " + samples);
+      checkDataDates(data, startDate, endDate, sampleRate);
+      next();
+    };
+    req.onerror = function () {
+      ok(false, "Get app stats for all connectionTypes failure!");
+    }
+  },
+  function () {
+    ok(true, "Get system stats for a connectionType and dates not adapted to samplerate");
     // Prepare get params
     var type = netStats.connectionTypes[0];
     var diff = 2;
     // Get samplerate in millis
     var sampleRate = netStats.sampleRate * 1000;
     var endDate = new Date();
     var startDate = new Date(endDate.getTime() - (sampleRate * diff));
     // Calculate the number of samples that should be returned based on the
     // the samplerate, including final and initial samples and taking into
     // account that these will be filtered according to precision.
     var samples = (Math.floor(endDate.getTime() / (sampleRate)) * sampleRate -
                    Math.floor(startDate.getTime() / (sampleRate)) * sampleRate) / sampleRate + 1;
 
     // Launch request
     req = netStats.getNetworkStats({start: startDate, end: endDate, connectionType: type});
     req.onsuccess = function () {
-      ok(true, "Get stats request ok");
+      ok(true, "Get system stats request ok");
+      ok(req.result.manifestURL == null, "manifestURL should be null");
       ok(req.result.connectionType == type, "connectionTypes should be equals");
       ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
       ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
       var data = req.result.data;
       ok(Array.isArray(data) && data.length == samples,
          "data is an array of length " + samples);
       checkDataDates(data, startDate, endDate, sampleRate);
       next();
     };
     req.onerror = function () {
-      ok(false, "Get stats for a connectionType failure!");
+      ok(false, "Get system stats for a connectionType failure!");
     }
   },
   function () {
-    ok(true, "Get stats for all connectionTypes and dates not adapted to samplerate");
+    ok(true, "Get system stats for all connectionTypes and dates not adapted to samplerate");
     // Prepare get params
     var diff = 2;
     // Get samplerate in millis
     var sampleRate = netStats.sampleRate * 1000;
     // Get date with samplerate's precision
     var endDate = new Date();
     var startDate = new Date(endDate.getTime() - (sampleRate * diff));
     // Calculate the number of samples that should be returned based on the
@@ -253,27 +330,28 @@ var steps = [
     // account that these will be filtered according to precision.
     var samples = (Math.floor(endDate.getTime() / (sampleRate)) * sampleRate -
                    Math.floor(startDate.getTime() / (sampleRate)) * sampleRate) / sampleRate + 1;
 
     // Launch request
     req = netStats.getNetworkStats({start: startDate, end: endDate});
     req.onsuccess = function () {
       ok(true, "Get stats request ok");
+      ok(req.result.manifestURL == null, "manifestURL should be null");
       ok(req.result.connectionType == null, "connectionTypes should be null");
       ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
       ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
       var data = req.result.data;
       ok(Array.isArray(data) && data.length == samples,
          "data is an array of length " + samples);
       checkDataDates(data, startDate, endDate, sampleRate);
       next();
     };
     req.onerror = function () {
-      ok(false, "Get stats for all connectionType failure!");
+      ok(false, "Get system stats for all connectionType failure!");
     }
   },
   function () {
     ok(true, "all done!\n");
     SpecialPowers.removePermission("networkstats-manage", document);
     SimpleTest.finish();
     return;
   }
--- a/dom/network/tests/unit_stats/test_networkstats_db.js
+++ b/dom/network/tests/unit_stats/test_networkstats_db.js
@@ -1,9 +1,9 @@
-/* Any copyright is dedicated to the Public Domain.
+/* Any: copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
 
 const netStatsDb = new NetworkStatsDB(this);
 
@@ -91,31 +91,33 @@ add_test(function test_fillResultSamples
 add_test(function test_clear() {
   netStatsDb.clear(function (error, result) {
     do_check_eq(error, null);
     run_next_test();
   });
 });
 
 add_test(function test_internalSaveStats_singleSample() {
-  var stats = {connectionType: "wifi",
+  var stats = {appId:          0,
+               connectionType: "wifi",
                timestamp:      Date.now(),
                rxBytes:        0,
                txBytes:        0,
                rxTotalBytes:   1234,
                txTotalBytes:   1234};
 
   netStatsDb.dbNewTxn("readwrite", function(txn, store) {
     netStatsDb._saveStats(txn, store, stats);
   }, function(error, result) {
     do_check_eq(error, null);
 
     netStatsDb.logAllRecords(function(error, result) {
       do_check_eq(error, null);
       do_check_eq(result.length, 1);
+      do_check_eq(result[0].appId, stats.appId);
       do_check_eq(result[0].connectionType, stats.connectionType);
       do_check_eq(result[0].timestamp, stats.timestamp);
       do_check_eq(result[0].rxBytes, stats.rxBytes);
       do_check_eq(result[0].txBytes, stats.txBytes);
       do_check_eq(result[0].rxTotalBytes, stats.rxTotalBytes);
       do_check_eq(result[0].txTotalBytes, stats.txTotalBytes);
       run_next_test();
     });
@@ -124,17 +126,18 @@ add_test(function test_internalSaveStats
 
 add_test(function test_internalSaveStats_arraySamples() {
   netStatsDb.clear(function (error, result) {
     do_check_eq(error, null);
 
     var samples = 2;
     var stats = [];
     for (var i = 0; i < samples; i++) {
-      stats.push({connectionType: "wifi",
+      stats.push({appId:          0,
+                  connectionType: "wifi",
                   timestamp:      Date.now() + (10 * i),
                   rxBytes:        0,
                   txBytes:        0,
                   rxTotalBytes:   1234,
                   txTotalBytes:   1234});
     }
 
     netStatsDb.dbNewTxn("readwrite", function(txn, store) {
@@ -143,17 +146,18 @@ add_test(function test_internalSaveStats
       do_check_eq(error, null);
 
       netStatsDb.logAllRecords(function(error, result) {
         do_check_eq(error, null);
         do_check_eq(result.length, samples);
 
         var success = true;
         for (var i = 0; i < samples; i++) {
-          if (result[i].connectionType != stats[i].connectionType ||
+          if (result[i].appId != stats[i].appId ||
+              result[i].connectionType != stats[i].connectionType ||
               result[i].timestamp != stats[i].timestamp ||
               result[i].rxBytes != stats[i].rxBytes ||
               result[i].txBytes != stats[i].txBytes ||
               result[i].rxTotalBytes != stats[i].rxTotalBytes ||
               result[i].txTotalBytes != stats[i].txTotalBytes) {
             success = false;
             break;
           }
@@ -167,30 +171,32 @@ add_test(function test_internalSaveStats
 
 add_test(function test_internalRemoveOldStats() {
   netStatsDb.clear(function (error, result) {
     do_check_eq(error, null);
 
     var samples = 10;
     var stats = [];
     for (var i = 0; i < samples - 1; i++) {
-      stats.push({connectionType: "wifi", timestamp:    Date.now() + (10 * i),
+      stats.push({appId:               0,
+                  connectionType: "wifi", timestamp:    Date.now() + (10 * i),
                   rxBytes:             0, txBytes:      0,
                   rxTotalBytes:     1234, txTotalBytes: 1234});
     }
 
-    stats.push({connectionType: "wifi", timestamp:    Date.now() + (10 * samples),
-                  rxBytes:           0, txBytes:      0,
-                  rxTotalBytes:   1234, txTotalBytes: 1234});
+    stats.push({appId:               0,
+                connectionType: "wifi", timestamp:    Date.now() + (10 * samples),
+                rxBytes:             0, txBytes:      0,
+                rxTotalBytes:     1234, txTotalBytes: 1234});
 
     netStatsDb.dbNewTxn("readwrite", function(txn, store) {
       netStatsDb._saveStats(txn, store, stats);
       var date = stats[stats.length -1].timestamp
                  + (netStatsDb.sampleRate * netStatsDb.maxStorageSamples - 1) - 1;
-      netStatsDb._removeOldStats(txn, store, "wifi", date);
+      netStatsDb._removeOldStats(txn, store, 0, "wifi", date);
     }, function(error, result) {
       do_check_eq(error, null);
 
       netStatsDb.logAllRecords(function(error, result) {
         do_check_eq(error, null);
         do_check_eq(result.length, 1);
 
         run_next_test();
@@ -221,109 +227,148 @@ function processSamplesDiff(lastStat, ne
       });
     });
   });
 }
 
 add_test(function test_processSamplesDiffSameSample() {
   var sampleRate = netStatsDb.sampleRate;
   var date = filterTimestamp(new Date());
-  var lastStat = {connectionType: "wifi", timestamp:    date,
+  var lastStat = {appId:               0,
+                  connectionType: "wifi", timestamp:    date,
                   rxBytes:             0, txBytes:      0,
                   rxTotalBytes:     1234, txTotalBytes: 1234};
 
-  var newStat = {connectionType: "wifi", timestamp:    date,
+  var newStat = {appId:                 0,
+                 connectionType:   "wifi", timestamp:    date,
                  rxBytes:               0, txBytes:      0,
                  rxTotalBytes:       2234, txTotalBytes: 2234};
 
   processSamplesDiff(lastStat, newStat, function(result) {
     do_check_eq(result.length, 1);
+    do_check_eq(result[0].appId, newStat.appId);
     do_check_eq(result[0].connectionType, newStat.connectionType);
     do_check_eq(result[0].timestamp, newStat.timestamp);
     do_check_eq(result[0].rxBytes, newStat.rxTotalBytes - lastStat.rxTotalBytes);
     do_check_eq(result[0].txBytes, newStat.txTotalBytes - lastStat.txTotalBytes);
     do_check_eq(result[0].rxTotalBytes, newStat.rxTotalBytes);
     do_check_eq(result[0].txTotalBytes, newStat.txTotalBytes);
     run_next_test();
   });
 });
 
 add_test(function test_processSamplesDiffNextSample() {
   var sampleRate = netStatsDb.sampleRate;
   var date = filterTimestamp(new Date());
-  var lastStat = {connectionType: "wifi", timestamp:    date,
+  var lastStat = {appId:               0,
+                  connectionType: "wifi", timestamp:    date,
                   rxBytes:             0, txBytes:      0,
                   rxTotalBytes:     1234, txTotalBytes: 1234};
 
-  var newStat = {connectionType: "wifi", timestamp:    date + sampleRate,
+  var newStat = {appId:                 0,
+                 connectionType:   "wifi", timestamp:    date + sampleRate,
                  rxBytes:               0, txBytes:      0,
                  rxTotalBytes:        500, txTotalBytes: 500};
 
   processSamplesDiff(lastStat, newStat, function(result) {
     do_check_eq(result.length, 2);
+    do_check_eq(result[1].appId, newStat.appId);
     do_check_eq(result[1].connectionType, newStat.connectionType);
     do_check_eq(result[1].timestamp, newStat.timestamp);
     do_check_eq(result[1].rxBytes, newStat.rxTotalBytes);
     do_check_eq(result[1].txBytes, newStat.txTotalBytes);
     do_check_eq(result[1].rxTotalBytes, newStat.rxTotalBytes);
     do_check_eq(result[1].txTotalBytes, newStat.txTotalBytes);
     run_next_test();
   });
 });
 
 add_test(function test_processSamplesDiffSamplesLost() {
   var samples = 5;
   var sampleRate = netStatsDb.sampleRate;
   var date = filterTimestamp(new Date());
-  var lastStat = {connectionType: "wifi", timestamp:    date,
+  var lastStat = {appId:               0,
+                  connectionType: "wifi", timestamp:    date,
                   rxBytes:             0, txBytes:      0,
                   rxTotalBytes:     1234, txTotalBytes: 1234};
 
-  var newStat = {connectionType: "wifi", timestamp:    date + (sampleRate * samples),
-                 rxBytes:               0, txBytes:      0,
+  var newStat = {appId:                0,
+                 connectionType:  "wifi", timestamp:    date + (sampleRate * samples),
+                 rxBytes:              0, txBytes:      0,
                  rxTotalBytes:      2234, txTotalBytes: 2234};
 
   processSamplesDiff(lastStat, newStat, function(result) {
     do_check_eq(result.length, samples + 1);
+    do_check_eq(result[0].appId, newStat.appId);
     do_check_eq(result[samples].connectionType, newStat.connectionType);
     do_check_eq(result[samples].timestamp, newStat.timestamp);
     do_check_eq(result[samples].rxBytes, newStat.rxTotalBytes - lastStat.rxTotalBytes);
     do_check_eq(result[samples].txBytes, newStat.txTotalBytes - lastStat.txTotalBytes);
     do_check_eq(result[samples].rxTotalBytes, newStat.rxTotalBytes);
     do_check_eq(result[samples].txTotalBytes, newStat.txTotalBytes);
     run_next_test();
   });
 });
 
 add_test(function test_saveStats() {
-  var stats = { connectionType: "wifi",
-                date:           new Date(),
-                rxBytes:        2234,
-                txBytes:        2234};
+  var stats = {appId:          0,
+               connectionType: "wifi",
+               date:           new Date(),
+               rxBytes:        2234,
+               txBytes:        2234};
 
   netStatsDb.clear(function (error, result) {
     do_check_eq(error, null);
     netStatsDb.saveStats(stats, function(error, result) {
       do_check_eq(error, null);
       netStatsDb.logAllRecords(function(error, result) {
         do_check_eq(error, null);
         do_check_eq(result.length, 1);
+        do_check_eq(result[0].appId, stats.appId);
         do_check_eq(result[0].connectionType, stats.connectionType);
         let timestamp = filterTimestamp(stats.date);
         do_check_eq(result[0].timestamp, timestamp);
         do_check_eq(result[0].rxBytes, 0);
         do_check_eq(result[0].txBytes, 0);
         do_check_eq(result[0].rxTotalBytes, stats.rxBytes);
         do_check_eq(result[0].txTotalBytes, stats.txBytes);
         run_next_test();
       });
     });
   });
 });
 
+add_test(function test_saveAppStats() {
+  var stats = {appId:          1,
+               connectionType: "wifi",
+               date:           new Date(),
+               rxBytes:        2234,
+               txBytes:        2234};
+
+  netStatsDb.clear(function (error, result) {
+    do_check_eq(error, null);
+    netStatsDb.saveStats(stats, function(error, result) {
+      do_check_eq(error, null);
+      netStatsDb.logAllRecords(function(error, result) {
+        do_check_eq(error, null);
+        do_check_eq(result.length, 1);
+        do_check_eq(result[0].appId, stats.appId);
+        do_check_eq(result[0].connectionType, stats.connectionType);
+        let timestamp = filterTimestamp(stats.date);
+        do_check_eq(result[0].timestamp, timestamp);
+        do_check_eq(result[0].rxBytes, stats.rxBytes);
+        do_check_eq(result[0].txBytes, stats.txBytes);
+        do_check_eq(result[0].rxTotalBytes, 0);
+        do_check_eq(result[0].txTotalBytes, 0);
+        run_next_test();
+      });
+    });
+  });
+});
+
 function prepareFind(stats, callback) {
   netStatsDb.clear(function (error, result) {
     do_check_eq(error, null);
     netStatsDb.dbNewTxn("readwrite", function(txn, store) {
       netStatsDb._saveStats(txn, store, stats);
     }, function(error, result) {
         callback(error, result);
     });
@@ -333,24 +378,26 @@ function prepareFind(stats, callback) {
 add_test(function test_find () {
   var samples = 5;
   var sampleRate = netStatsDb.sampleRate;
   var start = Date.now();
   var saveDate = filterTimestamp(new Date());
   var end = new Date(start + (sampleRate * (samples - 1)));
   start = new Date(start - sampleRate);
   var stats = [];
-  for (var i = 0; i < samples; i++) {i
-    stats.push({connectionType: "wifi", timestamp:    saveDate + (sampleRate * i),
+  for (var i = 0; i < samples; i++) {
+    stats.push({appId:               0,
+                connectionType: "wifi", timestamp:    saveDate + (sampleRate * i),
                 rxBytes:             0, txBytes:      10,
                 rxTotalBytes:        0, txTotalBytes: 0});
 
-    stats.push({connectionType: "mobile", timestamp:  saveDate + (sampleRate * i),
-                rxBytes:             0, txBytes:      10,
-                rxTotalBytes:        0, txTotalBytes: 0});
+    stats.push({appId:                 0,
+                connectionType: "mobile", timestamp:    saveDate + (sampleRate * i),
+                rxBytes:               0, txBytes:      10,
+                rxTotalBytes:          0, txTotalBytes: 0});
   }
 
   prepareFind(stats, function(error, result) {
     do_check_eq(error, null);
     netStatsDb.find(function (error, result) {
       do_check_eq(error, null);
       do_check_eq(result.connectionType, "wifi");
       do_check_eq(result.start.getTime(), start.getTime());
@@ -366,18 +413,113 @@ add_test(function test_find () {
         do_check_eq(result.start.getTime(), start.getTime());
         do_check_eq(result.end.getTime(), end.getTime());
         do_check_eq(result.data.length, samples + 1);
         do_check_eq(result.data[0].rxBytes, null);
         do_check_eq(result.data[1].rxBytes, 0);
         do_check_eq(result.data[1].txBytes, 20);
         do_check_eq(result.data[samples].rxBytes, 0);
         run_next_test();
-      }, {start: start, end: end});
-    }, {start: start, end: end, connectionType: "wifi"});
+      }, {appId: 0, start: start, end: end});
+    }, {start: start, end: end, connectionType: "wifi", appId: 0});
+  });
+});
+
+add_test(function test_findAppStats () {
+  var samples = 5;
+  var sampleRate = netStatsDb.sampleRate;
+  var start = Date.now();
+  var saveDate = filterTimestamp(new Date());
+  var end = new Date(start + (sampleRate * (samples - 1)));
+  start = new Date(start - sampleRate);
+  var stats = [];
+  for (var i = 0; i < samples; i++) {
+    stats.push({appId:               1,
+                connectionType: "wifi", timestamp:    saveDate + (sampleRate * i),
+                rxBytes:             0, txBytes:      10,
+                rxTotalBytes:        0, txTotalBytes: 0});
+
+    stats.push({appId:                 1,
+                connectionType: "mobile", timestamp:    saveDate + (sampleRate * i),
+                rxBytes:               0, txBytes:      10,
+                rxTotalBytes:          0, txTotalBytes: 0});
+  }
+
+  prepareFind(stats, function(error, result) {
+    do_check_eq(error, null);
+    netStatsDb.find(function (error, result) {
+      do_check_eq(error, null);
+      do_check_eq(result.connectionType, "wifi");
+      do_check_eq(result.start.getTime(), start.getTime());
+      do_check_eq(result.end.getTime(), end.getTime());
+      do_check_eq(result.data.length, samples + 1);
+      do_check_eq(result.data[0].rxBytes, null);
+      do_check_eq(result.data[1].rxBytes, 0);
+      do_check_eq(result.data[samples].rxBytes, 0);
+
+      netStatsDb.findAll(function (error, result) {
+        do_check_eq(error, null);
+        do_check_eq(result.connectionType, null);
+        do_check_eq(result.start.getTime(), start.getTime());
+        do_check_eq(result.end.getTime(), end.getTime());
+        do_check_eq(result.data.length, samples + 1);
+        do_check_eq(result.data[0].rxBytes, null);
+        do_check_eq(result.data[1].rxBytes, 0);
+        do_check_eq(result.data[1].txBytes, 20);
+        do_check_eq(result.data[samples].rxBytes, 0);
+        run_next_test();
+      }, {start: start, end: end, appId: 1});
+    }, {start: start, end: end, connectionType: "wifi", appId: 1});
+  });
+});
+
+add_test(function test_saveMultipleAppStats () {
+  var saveDate = filterTimestamp(new Date());
+  var cached = Object.create(null);
+
+  cached['1wifi'] = {appId:                 1,
+                     connectionType:   "wifi", date:    new Date(),
+                     rxBytes:               0, txBytes:      10};
+
+  cached['1mobile'] = {appId:                 1,
+                       connectionType: "mobile", date:    new Date(),
+                       rxBytes:               0, txBytes:      10};
+
+  cached['2wifi'] = {appId:                 2,
+                     connectionType:   "wifi", date:    new Date(),
+                     rxBytes:               0, txBytes:      10};
+
+  cached['2mobile'] = {appId:                 2,
+                       connectionType: "mobile", date:    new Date(),
+                       rxBytes:               0, txBytes:      10};
+
+  let keys = Object.keys(cached);
+  let index = 0;
+
+  netStatsDb.clear(function (error, result) {
+    do_check_eq(error, null);
+    netStatsDb.saveStats(cached[keys[index]],
+      function callback(error, result) {
+        do_check_eq(error, null);
+
+        if (index == keys.length - 1) {
+          netStatsDb.logAllRecords(function(error, result) {
+          do_check_eq(error, null);
+          do_check_eq(result.length, 4);
+          do_check_eq(result[0].appId, 1);
+          do_check_eq(result[0].connectionType, 'mobile');
+          do_check_eq(result[0].rxBytes, 0);
+          do_check_eq(result[0].txBytes, 10);
+          run_next_test();
+          });
+        }
+
+        index += 1;
+        netStatsDb.saveStats(cached[keys[index]], callback);
+    });
   });
 });
 
 function run_test() {
   do_get_profile();
 
   var idbManager = Cc["@mozilla.org/dom/indexeddb/manager;1"].
                    getService(Ci.nsIIndexedDatabaseManager);
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/unit_stats/test_networkstats_service_proxy.js
@@ -0,0 +1,119 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "nssProxy",
+                                   "@mozilla.org/networkstatsServiceProxy;1",
+                                   "nsINetworkStatsServiceProxy");
+
+add_test(function test_saveAppStats() {
+  var cachedAppStats = NetworkStatsService.cachedAppStats;
+  var timestamp = NetworkStatsService.cachedAppStatsDate.getTime();
+  var samples = 5;
+
+  do_check_eq(Object.keys(cachedAppStats).length, 0);
+
+  for (var i = 0; i < samples; i++) {
+    nssProxy.saveAppStats(1, Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+                          timestamp, 10, 20);
+
+    nssProxy.saveAppStats(1, Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
+                          timestamp, 10, 20);
+  }
+
+  var key1 = 1 + 'wifi';
+  var key2 = 1 + 'mobile';
+
+  do_check_eq(Object.keys(cachedAppStats).length, 2);
+  do_check_eq(cachedAppStats[key1].appId, 1);
+  do_check_eq(cachedAppStats[key1].connectionType, 'wifi');
+  do_check_eq(new Date(cachedAppStats[key1].date).getTime() / 1000,
+              Math.floor(timestamp / 1000));
+  do_check_eq(cachedAppStats[key1].rxBytes, 50);
+  do_check_eq(cachedAppStats[key1].txBytes, 100);
+  do_check_eq(cachedAppStats[key2].appId, 1);
+  do_check_eq(cachedAppStats[key2].connectionType, 'mobile');
+  do_check_eq(new Date(cachedAppStats[key2].date).getTime() / 1000,
+              Math.floor(timestamp / 1000));
+  do_check_eq(cachedAppStats[key2].rxBytes, 50);
+  do_check_eq(cachedAppStats[key2].txBytes, 100);
+
+  run_next_test();
+});
+
+add_test(function test_saveAppStatsWithDifferentDates() {
+  var today = NetworkStatsService.cachedAppStatsDate;
+  var tomorrow = new Date(today.getTime() + (24 * 60 * 60 * 1000));
+  var key = 1 + 'wifi';
+
+  NetworkStatsService.updateCachedAppStats(
+    function (success, msg) {
+      do_check_eq(success, true);
+
+      do_check_eq(Object.keys(NetworkStatsService.cachedAppStats).length, 0);
+
+      nssProxy.saveAppStats(1, Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+                            today.getTime(), 10, 20);
+
+      nssProxy.saveAppStats(1, Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
+                            today.getTime(), 10, 20);
+
+      var saveAppStatsCb = {
+        notify: function notify(success, message) {
+          do_check_eq(success, true);
+
+          var cachedAppStats = NetworkStatsService.cachedAppStats;
+          var key = 2 + 'mobile';
+          do_check_eq(Object.keys(cachedAppStats).length, 1);
+          do_check_eq(cachedAppStats[key].appId, 2);
+          do_check_eq(cachedAppStats[key].connectionType, 'mobile');
+          do_check_eq(new Date(cachedAppStats[key].date).getTime() / 1000,
+                      Math.floor(tomorrow.getTime() / 1000));
+          do_check_eq(cachedAppStats[key].rxBytes, 30);
+          do_check_eq(cachedAppStats[key].txBytes, 40);
+
+          run_next_test();
+        }
+      };
+
+      nssProxy.saveAppStats(2, Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
+                            tomorrow.getTime(), 30, 40, saveAppStatsCb);
+    }
+  );
+});
+
+add_test(function test_saveAppStatsWithMaxCachedTraffic() {
+  var timestamp = NetworkStatsService.cachedAppStatsDate.getTime();
+  var maxtraffic = NetworkStatsService.maxCachedTraffic;
+
+  NetworkStatsService.updateCachedAppStats(
+    function (success, msg) {
+      do_check_eq(success, true);
+
+      var cachedAppStats = NetworkStatsService.cachedAppStats;
+      do_check_eq(Object.keys(cachedAppStats).length, 0);
+
+      nssProxy.saveAppStats(1, Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+                            timestamp, 10, 20);
+
+      do_check_eq(Object.keys(cachedAppStats).length, 1);
+
+      nssProxy.saveAppStats(1, Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+                            timestamp, maxtraffic, 20);
+
+      do_check_eq(Object.keys(cachedAppStats).length, 0);
+
+      run_next_test();
+  });
+});
+
+function run_test() {
+  do_get_profile();
+
+  Cu.import("resource://gre/modules/NetworkStatsService.jsm");
+
+  run_next_test();
+}
--- a/dom/network/tests/unit_stats/xpcshell.ini
+++ b/dom/network/tests/unit_stats/xpcshell.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 head =
 tail =
 
 [test_networkstats_service.js]
+[test_networkstats_service_proxy.js]
 [test_networkstats_db.js]
--- a/gfx/2d/BaseSize.h
+++ b/gfx/2d/BaseSize.h
@@ -1,31 +1,33 @@
 /* -*- Mode: C++; 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/. */
 
 #ifndef MOZILLA_GFX_BASESIZE_H_
 #define MOZILLA_GFX_BASESIZE_H_
 
+#include "mozilla/Attributes.h"
+
 namespace mozilla {
 namespace gfx {
 
 /**
  * Do not use this class directly. Subclass it, pass that subclass as the
  * Sub parameter, and only use that subclass. This allows methods to safely
  * cast 'this' to 'Sub*'.
  */
 template <class T, class Sub>
 struct BaseSize {
   T width, height;
 
   // Constructors
-  BaseSize() : width(0), height(0) {}
-  BaseSize(T aWidth, T aHeight) : width(aWidth), height(aHeight) {}
+  MOZ_CONSTEXPR BaseSize() : width(0), height(0) {}
+  MOZ_CONSTEXPR BaseSize(T aWidth, T aHeight) : width(aWidth), height(aHeight) {}
 
   void SizeTo(T aWidth, T aHeight) { width = aWidth; height = aHeight; }
 
   bool IsEmpty() const {
     return width == 0 || height == 0;
   }
 
   // Note that '=' isn't defined so we'll get the
--- a/gfx/2d/Point.h
+++ b/gfx/2d/Point.h
@@ -70,18 +70,18 @@ IntPointTyped<units> RoundedToInt(const 
 }
 
 template<class units>
 struct IntSizeTyped :
   public BaseSize< int32_t, IntSizeTyped<units> >,
   public units {
   typedef BaseSize< int32_t, IntSizeTyped<units> > Super;
 
-  IntSizeTyped() : Super() {}
-  IntSizeTyped(int32_t aWidth, int32_t aHeight) : Super(aWidth, aHeight) {}
+  MOZ_CONSTEXPR IntSizeTyped() : Super() {}
+  MOZ_CONSTEXPR IntSizeTyped(int32_t aWidth, int32_t aHeight) : Super(aWidth, aHeight) {}
 
   // XXX When all of the code is ported, the following functions to convert to and from
   // unknown types should be removed.
 
   static IntSizeTyped<units> FromUnknownSize(const IntSizeTyped<UnknownUnits>& aSize) {
     return IntSizeTyped<units>(aSize.width, aSize.height);
   }
 
--- a/gfx/2d/Rect.h
+++ b/gfx/2d/Rect.h
@@ -119,16 +119,22 @@ struct RectTyped :
 
     static RectTyped<units> FromUnknownRect(const RectTyped<UnknownUnits>& rect) {
         return RectTyped<units>(rect.x, rect.y, rect.width, rect.height);
     }
 
     RectTyped<UnknownUnits> ToUnknownRect() const {
         return RectTyped<UnknownUnits>(this->x, this->y, this->width, this->height);
     }
+
+    // This is here only to keep IPDL-generated code happy. DO NOT USE.
+    bool operator==(const RectTyped<units>& aRect) const
+    {
+      return RectTyped<units>::IsEqualEdges(aRect);
+    }
 };
 typedef RectTyped<UnknownUnits> Rect;
 
 template<class units>
 IntRectTyped<units> RoundedToInt(const RectTyped<units>& aRect)
 {
   return IntRectTyped<units>(int32_t(floorf(aRect.x + 0.5f)),
                              int32_t(floorf(aRect.y + 0.5f)),
--- a/gfx/2d/ScaleFactor.h
+++ b/gfx/2d/ScaleFactor.h
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 20; 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/. */
 
 #ifndef MOZILLA_GFX_SCALEFACTOR_H_
 #define MOZILLA_GFX_SCALEFACTOR_H_
 
+#include "mozilla/Attributes.h"
+
 #include "gfxPoint.h"
 
 namespace mozilla {
 namespace gfx {
 
 /*
  * This class represents a scaling factor between two different pixel unit
  * systems. This is effectively a type-safe float, intended to be used in
@@ -21,19 +23,19 @@ namespace gfx {
  * were always expected to be the same, so this class uses only one scale
  * factor for both axes. The two constructors that take two-axis scaling
  * factors check to ensure that this assertion holds.
  */
 template<class src, class dst>
 struct ScaleFactor {
   float scale;
 
-  ScaleFactor() : scale(1.0) {}
-  ScaleFactor(const ScaleFactor<src, dst>& aCopy) : scale(aCopy.scale) {}
-  explicit ScaleFactor(float aScale) : scale(aScale) {}
+  MOZ_CONSTEXPR ScaleFactor() : scale(1.0) {}
+  MOZ_CONSTEXPR ScaleFactor(const ScaleFactor<src, dst>& aCopy) : scale(aCopy.scale) {}
+  explicit MOZ_CONSTEXPR ScaleFactor(float aScale) : scale(aScale) {}
 
   explicit ScaleFactor(float aX, float aY) : scale(aX) {
     MOZ_ASSERT(fabs(aX - aY) < 1e-6);
   }
 
   explicit ScaleFactor(gfxSize aScale) : scale(aScale.width) {
     MOZ_ASSERT(fabs(aScale.width - aScale.height) < 1e-6);
   }
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -271,17 +271,17 @@ public:
         , mTemporaryEGLImageTexture(0)
     {
         // any EGL contexts will always be GLESv2
         SetProfileVersion(ContextProfile::OpenGLES, 200);
 
 #ifdef DEBUG
         printf_stderr("Initializing context %p surface %p on display %p\n", mContext, mSurface, EGL_DISPLAY());
 #endif
-#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION <= 15
+#if defined(MOZ_WIDGET_GONK)
         if (!mIsOffscreen) {
             mHwc = HwcComposer2D::GetInstance();
             MOZ_ASSERT(!mHwc->Initialized());
 
             if (mHwc->Init(EGL_DISPLAY(), mSurface)) {
                 NS_WARNING("HWComposer initialization failed!");
                 mHwc = nullptr;
             }
--- a/gfx/gl/Makefile.in
+++ b/gfx/gl/Makefile.in
@@ -6,16 +6,17 @@ EXPORT_LIBRARY   = 1
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
 ifdef MOZ_WEBGL
 DEFINES += -DMOZ_D3DCOMPILER_DLL=$(MOZ_D3DCOMPILER_DLL)
 endif
 endif
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),gonk)
 LOCAL_INCLUDES = -I$(topsrcdir)/widget/gonk
+LOCAL_INCLUDES += -I$(ANDROID_SOURCE)/hardware/libhardware/include
 endif
 
 ifdef MOZ_ANDROID_OMTC
 DEFINES += -DMOZ_ANDROID_OMTC
 endif
 
 include $(topsrcdir)/config/rules.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -181,16 +181,17 @@ Layer::Layer(LayerManager* aManager, voi
   mPostXScale(1.0f),
   mPostYScale(1.0f),
   mOpacity(1.0),
   mContentFlags(0),
   mUseClipRect(false),
   mUseTileSourceRect(false),
   mIsFixedPosition(false),
   mMargins(0, 0, 0, 0),
+  mStickyPositionData(nullptr),
   mDebugColorIndex(0),
   mAnimationGeneration(0)
 {}
 
 Layer::~Layer()
 {}
 
 Animation*
@@ -1275,16 +1276,24 @@ Layer::PrintInfo(nsACString& aTo, const 
     aTo += " [opaqueContent]";
   }
   if (GetContentFlags() & CONTENT_COMPONENT_ALPHA) {
     aTo += " [componentAlpha]";
   }
   if (GetIsFixedPosition()) {
     aTo.AppendPrintf(" [isFixedPosition anchor=%f,%f]", mAnchor.x, mAnchor.y);
   }
+  if (GetIsStickyPosition()) {
+    aTo.AppendPrintf(" [isStickyPosition scrollId=%d outer=%f,%f %fx%f "
+                     "inner=%f,%f %fx%f]", mStickyPositionData->mScrollId,
+                     mStickyPositionData->mOuter.x, mStickyPositionData->mOuter.y,
+                     mStickyPositionData->mOuter.width, mStickyPositionData->mOuter.height,
+                     mStickyPositionData->mInner.x, mStickyPositionData->mInner.y,
+                     mStickyPositionData->mInner.width, mStickyPositionData->mInner.height);
+  }
 
   return aTo;
 }
 
 nsACString&
 ThebesLayer::PrintInfo(nsACString& aTo, const char* aPrefix)
 {
   Layer::PrintInfo(aTo, aPrefix);
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -904,16 +904,42 @@ public:
   {
     if (mMargins != aMargins) {
       MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) FixedPositionMargins", this));
       mMargins = aMargins;
       Mutated();
     }
   }
 
+  /**
+   * CONSTRUCTION PHASE ONLY
+   * If a layer is "sticky position", |aScrollId| holds the scroll identifier
+   * of the scrollable content that contains it. The difference between the two
+   * rectangles |aOuter| and |aInner| is treated as two intervals in each
+   * dimension, with the current scroll position at the origin. For each
+   * dimension, while that component of the scroll position lies within either
+   * interval, the layer should not move relative to its scrolling container.
+   */
+  void SetStickyPositionData(FrameMetrics::ViewID aScrollId, LayerRect aOuter,
+                             LayerRect aInner)
+  {
+    if (!mStickyPositionData ||
+        !mStickyPositionData->mOuter.IsEqualEdges(aOuter) ||
+        !mStickyPositionData->mInner.IsEqualEdges(aInner)) {
+      MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) StickyPositionData", this));
+      if (!mStickyPositionData) {
+        mStickyPositionData = new StickyPositionData;
+      }
+      mStickyPositionData->mScrollId = aScrollId;
+      mStickyPositionData->mOuter = aOuter;
+      mStickyPositionData->mInner = aInner;
+      Mutated();
+    }
+  }
+
   // These getters can be used anytime.
   float GetOpacity() { return mOpacity; }
   const nsIntRect* GetClipRect() { return mUseClipRect ? &mClipRect : nullptr; }
   uint32_t GetContentFlags() { return mContentFlags; }
   const nsIntRegion& GetVisibleRegion() { return mVisibleRegion; }
   ContainerLayer* GetParent() { return mParent; }
   Layer* GetNextSibling() { return mNextSibling; }
   const Layer* GetNextSibling() const { return mNextSibling; }
@@ -921,18 +947,22 @@ public:
   const Layer* GetPrevSibling() const { return mPrevSibling; }
   virtual Layer* GetFirstChild() const { return nullptr; }
   virtual Layer* GetLastChild() const { return nullptr; }
   const gfx3DMatrix GetTransform() const;
   const gfx3DMatrix& GetBaseTransform() const { return mTransform; }
   float GetPostXScale() const { return mPostXScale; }
   float GetPostYScale() const { return mPostYScale; }
   bool GetIsFixedPosition() { return mIsFixedPosition; }
+  bool GetIsStickyPosition() { return mStickyPositionData; }
   LayerPoint GetFixedPositionAnchor() { return mAnchor; }
   const LayerMargin& GetFixedPositionMargins() { return mMargins; }
+  FrameMetrics::ViewID GetStickyScrollContainerId() { return mStickyPositionData->mScrollId; }
+  const LayerRect& GetStickyScrollRangeOuter() { return mStickyPositionData->mOuter; }
+  const LayerRect& GetStickyScrollRangeInner() { return mStickyPositionData->mInner; }
   Layer* GetMaskLayer() const { return mMaskLayer; }
 
   // Note that all lengths in animation data are either in CSS pixels or app
   // units and must be converted to device pixels by the compositor.
   AnimationArray& GetAnimations() { return mAnimations; }
   InfallibleTArray<AnimData>& GetAnimationData() { return mAnimationData; }
 
   uint64_t GetAnimationGeneration() { return mAnimationGeneration; }
@@ -1278,16 +1308,22 @@ protected:
   nsIntRect mTileSourceRect;
   nsIntRegion mInvalidRegion;
   uint32_t mContentFlags;
   bool mUseClipRect;
   bool mUseTileSourceRect;
   bool mIsFixedPosition;
   LayerPoint mAnchor;
   LayerMargin mMargins;
+  struct StickyPositionData {
+    FrameMetrics::ViewID mScrollId;
+    LayerRect mOuter;
+    LayerRect mInner;
+  };
+  nsAutoPtr<StickyPositionData> mStickyPositionData;
   DebugOnly<uint32_t> mDebugColorIndex;
   // If this layer is used for OMTA, then this counter is used to ensure we
   // stay in sync with the animation manager
   uint64_t mAnimationGeneration;
 };
 
 /**
  * A Layer which we can draw into using Thebes. It is a conceptually
--- a/gfx/layers/composite/APZCTreeManager.cpp
+++ b/gfx/layers/composite/APZCTreeManager.cpp
@@ -100,90 +100,103 @@ APZCTreeManager::UpdatePanZoomController
                                              Layer* aLayer, uint64_t aLayersId,
                                              gfx3DMatrix aTransform,
                                              AsyncPanZoomController* aParent,
                                              AsyncPanZoomController* aNextSibling,
                                              bool aIsFirstPaint, uint64_t aFirstPaintLayersId,
                                              nsTArray< nsRefPtr<AsyncPanZoomController> >* aApzcsToDestroy)
 {
   ContainerLayer* container = aLayer->AsContainerLayer();
-  AsyncPanZoomController* controller = nullptr;
+  AsyncPanZoomController* apzc = nullptr;
   if (container) {
     if (container->GetFrameMetrics().IsScrollable()) {
       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.
 
-        controller = container->GetAsyncPanZoomController();
-        if (!controller) {
-          controller = new AsyncPanZoomController(aLayersId, state->mController,
-                                                  AsyncPanZoomController::USE_GESTURE_DETECTOR);
-          controller->SetCompositorParent(aCompositor);
+        apzc = container->GetAsyncPanZoomController();
+
+        bool newApzc = (apzc == nullptr);
+        if (newApzc) {
+          apzc = new AsyncPanZoomController(aLayersId, state->mController,
+                                            AsyncPanZoomController::USE_GESTURE_DETECTOR);
+          apzc->SetCompositorParent(aCompositor);
         } else {
           // If there was already an APZC for the layer clear the tree pointers
           // so that it doesn't continue pointing to APZCs that should no longer
           // be in the tree. These pointers will get reset properly as we continue
           // building the tree. Also remove it from the set of APZCs that are going
           // to be destroyed, because it's going to remain active.
-          aApzcsToDestroy->RemoveElement(controller);
-          controller->SetPrevSibling(nullptr);
-          controller->SetLastChild(nullptr);
+          aApzcsToDestroy->RemoveElement(apzc);
+          apzc->SetPrevSibling(nullptr);
+          apzc->SetLastChild(nullptr);
         }
-        APZC_LOG("Using APZC %p for layer %p with identifiers %lld %lld\n", controller, aLayer, aLayersId, container->GetFrameMetrics().mScrollId);
+        APZC_LOG("Using APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer, aLayersId, container->GetFrameMetrics().mScrollId);
 
-        controller->NotifyLayersUpdated(container->GetFrameMetrics(),
+        apzc->NotifyLayersUpdated(container->GetFrameMetrics(),
                                         aIsFirstPaint && (aLayersId == aFirstPaintLayersId));
 
         LayerRect visible = ScreenRect(container->GetFrameMetrics().mCompositionBounds) * ScreenToLayerScale(1.0);
-        controller->SetLayerHitTestData(visible, aTransform, aLayer->GetTransform());
+        apzc->SetLayerHitTestData(visible, aTransform, aLayer->GetTransform());
         APZC_LOG("Setting rect(%f %f %f %f) as visible region for APZC %p\n", visible.x, visible.y,
                                                                               visible.width, visible.height,
-                                                                              controller);
+                                                                              apzc);
 
         // Bind the APZC instance into the tree of APZCs
         if (aNextSibling) {
-          aNextSibling->SetPrevSibling(controller);
+          aNextSibling->SetPrevSibling(apzc);
         } else if (aParent) {
-          aParent->SetLastChild(controller);
+          aParent->SetLastChild(apzc);
         } else {
-          mRootApzc = controller;
+          mRootApzc = apzc;
         }
 
-        // Let this controller be the parent of other controllers when we recurse downwards
-        aParent = controller;
+        // Let this apzc be the parent of other controllers when we recurse downwards
+        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)) {
+            apzc->UpdateZoomConstraints(allowZoom, minZoom, maxZoom);
+          }
+        }
       }
     }
 
-    container->SetAsyncPanZoomController(controller);
+    container->SetAsyncPanZoomController(apzc);
   }
 
   // Accumulate the CSS transform between layers that have an APZC, but exclude any
   // any layers that do have an APZC, and reset the accumulation at those layers.
-  if (controller) {
+  if (apzc) {
     aTransform = gfx3DMatrix();
   } else {
     // Multiply child layer transforms on the left so they get applied first
     aTransform = aLayer->GetTransform() * aTransform;
   }
 
   uint64_t childLayersId = (aLayer->AsRefLayer() ? aLayer->AsRefLayer()->GetReferentId() : aLayersId);
   AsyncPanZoomController* next = nullptr;
   for (Layer* child = aLayer->GetLastChild(); child; child = child->GetPrevSibling()) {
     next = UpdatePanZoomControllerTree(aCompositor, child, childLayersId, aTransform, aParent, next,
                                        aIsFirstPaint, aFirstPaintLayersId, aApzcsToDestroy);
   }
 
   // Return the APZC that should be the sibling of other APZCs as we continue
   // moving towards the first child at this depth in the layer tree.
-  // If this layer doesn't have a controller, we promote any APZCs in the subtree
+  // If this layer doesn't have an APZC, we promote any APZCs in the subtree
   // upwards. Otherwise we fall back to the aNextSibling that was passed in.
-  if (controller) {
-    return controller;
+  if (apzc) {
+    return apzc;
   }
   if (next) {
     return next;
   }
   return aNextSibling;
 }
 
 /*static*/ template<class T> void
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -209,24 +209,41 @@ GetLayerFixedMarginsOffset(Layer* aLayer
     } else {
       translation.y += aFixedLayerMargins.top - fixedMargins.top;
     }
   }
 
   return translation;
 }
 
+static gfxFloat
+IntervalOverlap(gfxFloat aTranslation, gfxFloat aMin, gfxFloat aMax)
+{
+  // Determine the amount of overlap between the 1D vector |aTranslation|
+  // and the interval [aMin, aMax].
+  if (aTranslation > 0) {
+    return std::max(0.0, std::min(aMax, aTranslation) - std::max(aMin, 0.0));
+  } else {
+    return std::min(0.0, std::max(aMin, aTranslation) - std::min(aMax, 0.0));
+  }
+}
+
 void
-AsyncCompositionManager::AlignFixedLayersForAnchorPoint(Layer* aLayer,
-                                                        Layer* aTransformedSubtreeRoot,
-                                                        const gfx3DMatrix& aPreviousTransformForRoot,
-                                                        const LayerMargin& aFixedLayerMargins)
+AsyncCompositionManager::AlignFixedAndStickyLayers(Layer* aLayer,
+                                                   Layer* aTransformedSubtreeRoot,
+                                                   const gfx3DMatrix& aPreviousTransformForRoot,
+                                                   const LayerMargin& aFixedLayerMargins)
 {
-  if (aLayer != aTransformedSubtreeRoot && aLayer->GetIsFixedPosition() &&
-      !aLayer->GetParent()->GetIsFixedPosition()) {
+  bool isRootFixed = aLayer->GetIsFixedPosition() &&
+    !aLayer->GetParent()->GetIsFixedPosition();
+  bool isStickyForSubtree = aLayer->GetIsStickyPosition() &&
+    aTransformedSubtreeRoot->AsContainerLayer() &&
+    aLayer->GetStickyScrollContainerId() ==
+      aTransformedSubtreeRoot->AsContainerLayer()->GetFrameMetrics().mScrollId;
+  if (aLayer != aTransformedSubtreeRoot && (isRootFixed || isStickyForSubtree)) {
     // Insert a translation so that the position of the anchor point is the same
     // before and after the change to the transform of aTransformedSubtreeRoot.
     // This currently only works for fixed layers with 2D transforms.
 
     // Accumulate the transforms between this layer and the subtree root layer.
     gfxMatrix ancestorTransform;
     if (!AccumulateLayerTransforms2D(aLayer->GetParent(), aTransformedSubtreeRoot,
                                      ancestorTransform)) {
@@ -281,28 +298,43 @@ AsyncCompositionManager::AlignFixedLayer
     // to the layer's parent, which is the same coordinate space as
     // locallyTransformedAnchor again, allowing us to subtract them and find
     // out the offset necessary to make sure the layer stays stationary.
     gfxPoint oldAnchorPositionInNewSpace =
       newCumulativeTransformInverse.Transform(
         oldCumulativeTransform.Transform(locallyTransformedOffsetAnchor));
     gfxPoint translation = oldAnchorPositionInNewSpace - locallyTransformedAnchor;
 
+    if (aLayer->GetIsStickyPosition()) {
+      // For sticky positioned layers, the difference between the two rectangles
+      // defines a pair of translation intervals in each dimension through which
+      // the layer should not move relative to the scroll container. To
+      // accomplish this, we limit each dimension of the |translation| to that
+      // part of it which overlaps those intervals.
+      const LayerRect& stickyOuter = aLayer->GetStickyScrollRangeOuter();
+      const LayerRect& stickyInner = aLayer->GetStickyScrollRangeInner();
+
+      translation.y = IntervalOverlap(translation.y, stickyOuter.y, stickyOuter.YMost()) -
+                      IntervalOverlap(translation.y, stickyInner.y, stickyInner.YMost());
+      translation.x = IntervalOverlap(translation.x, stickyOuter.x, stickyOuter.XMost()) -
+                      IntervalOverlap(translation.x, stickyInner.x, stickyInner.XMost());
+    }
+
     // Finally, apply the 2D translation to the layer transform.
     TranslateShadowLayer2D(aLayer, translation);
 
     // The transform has now been applied, so there's no need to iterate over
     // child layers.
     return;
   }
 
   for (Layer* child = aLayer->GetFirstChild();
        child; child = child->GetNextSibling()) {
-    AlignFixedLayersForAnchorPoint(child, aTransformedSubtreeRoot,
-                                   aPreviousTransformForRoot, aFixedLayerMargins);
+    AlignFixedAndStickyLayers(child, aTransformedSubtreeRoot,
+                              aPreviousTransformForRoot, aFixedLayerMargins);
   }
 }
 
 static void
 SampleValue(float aPortion, Animation& aAnimation, nsStyleAnimation::Value& aStart,
             nsStyleAnimation::Value& aEnd, Animatable* aValue)
 {
   nsStyleAnimation::Value interpolatedValue;
@@ -496,17 +528,17 @@ AsyncCompositionManager::ApplyAsyncConte
     // than the scrollable layer. See bug 732971. On non-Fennec we do the right thing.
     LayoutDeviceToLayerScale resolution(1.0 / rootTransform.GetXScale(),
                                         1.0 / rootTransform.GetYScale());
 #else
     LayoutDeviceToLayerScale resolution = metrics.mCumulativeResolution;
 #endif
     oldTransform.Scale(resolution.scale, resolution.scale, 1);
 
-    AlignFixedLayersForAnchorPoint(aLayer, aLayer, oldTransform, fixedLayerMargins);
+    AlignFixedAndStickyLayers(aLayer, aLayer, oldTransform, fixedLayerMargins);
 
     appliedTransform = true;
   }
 
   return appliedTransform;
 }
 
 void
@@ -602,17 +634,17 @@ AsyncCompositionManager::TransformScroll
   // Apply resolution scaling to the old transform - the layer tree as it is
   // doesn't have the necessary transform to display correctly.
   oldTransform.Scale(aResolution.scale, aResolution.scale, 1);
 
   // Make sure that overscroll and under-zoom are represented in the old
   // transform so that fixed position content moves and scales accordingly.
   // These calculations will effectively scale and offset fixed position layers
   // in screen space when the compensatory transform is performed in
-  // AlignFixedLayersForAnchorPoint.
+  // AlignFixedAndStickyLayers.
   ScreenRect contentScreenRect = mContentRect * userZoom;
   gfxPoint3D overscrollTranslation;
   if (userScroll.x < contentScreenRect.x) {
     overscrollTranslation.x = contentScreenRect.x - userScroll.x;
   } else if (userScroll.x + metrics.mCompositionBounds.width > contentScreenRect.XMost()) {
     overscrollTranslation.x = contentScreenRect.XMost() -
       (userScroll.x + metrics.mCompositionBounds.width);
   }
@@ -632,17 +664,17 @@ AsyncCompositionManager::TransformScroll
   if (mContentRect.height * userZoom.scale < metrics.mCompositionBounds.height) {
     underZoomScale.height = (mContentRect.height * userZoom.scale) /
       metrics.mCompositionBounds.height;
   }
   oldTransform.Scale(underZoomScale.width, underZoomScale.height, 1);
 
   // Make sure fixed position layers don't move away from their anchor points
   // when we're asynchronously panning or zooming
-  AlignFixedLayersForAnchorPoint(aLayer, aLayer, oldTransform, fixedLayerMargins);
+  AlignFixedAndStickyLayers(aLayer, aLayer, oldTransform, fixedLayerMargins);
 }
 
 bool
 AsyncCompositionManager::TransformShadowTree(TimeStamp aCurrentFrame)
 {
   PROFILER_LABEL("AsyncCompositionManager", "TransformShadowTree");
   Layer* root = mLayerManager->GetRoot();
   if (!root) {
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -141,29 +141,30 @@ private:
                         bool aLayersUpdated,
                         const CSSRect& aDisplayPort,
                         const CSSToLayerScale& aDisplayResolution,
                         bool aIsFirstPaint,
                         LayerMargin& aFixedLayerMargins,
                         ScreenPoint& aOffset);
 
   /**
-   * Adds a translation to the transform of any fixed-pos layer descendant of
-   * aTransformedSubtreeRoot whose parent layer is not fixed. The translation is
-   * chosen so that the layer's anchor point relative to aTransformedSubtreeRoot's
-   * parent layer is the same as it was when aTransformedSubtreeRoot's
-   * GetLocalTransform() was aPreviousTransformForRoot.
+   * Adds a translation to the transform of any fixed position (whose parent
+   * layer is not fixed) or sticky position layer descendant of
+   * aTransformedSubtreeRoot. The translation is chosen so that the layer's
+   * anchor point relative to aTransformedSubtreeRoot's parent layer is the same
+   * as it was when aTransformedSubtreeRoot's GetLocalTransform() was
+   * aPreviousTransformForRoot. For sticky position layers, the translation is
+   * further intersected with the layer's sticky scroll ranges.
    * This function will also adjust layers so that the given content document
    * fixed position margins will be respected during asynchronous panning and
    * zooming.
    */
-  void AlignFixedLayersForAnchorPoint(Layer* aLayer,
-                                      Layer* aTransformedSubtreeRoot,
-                                      const gfx3DMatrix& aPreviousTransformForRoot,
-                                      const LayerMargin& aFixedLayerMargins);
+  void AlignFixedAndStickyLayers(Layer* aLayer, Layer* aTransformedSubtreeRoot,
+                                 const gfx3DMatrix& aPreviousTransformForRoot,
+                                 const LayerMargin& aFixedLayerMargins);
 
   /**
    * DRAWING PHASE ONLY
    *
    * For reach RefLayer in our layer tree, look up its referent and connect it
    * to the layer tree, if found.
    */
   void ResolveRefLayers();
--- a/gfx/layers/composite/ThebesLayerComposite.cpp
+++ b/gfx/layers/composite/ThebesLayerComposite.cpp
@@ -126,17 +126,17 @@ ThebesLayerComposite::RenderLayer(const 
   TiledLayerProperties tiledLayerProps;
   if (mRequiresTiledProperties) {
     // calculating these things can be a little expensive, so don't
     // do them if we don't have to
     tiledLayerProps.mVisibleRegion = visibleRegion;
     tiledLayerProps.mDisplayPort = GetDisplayPort();
     tiledLayerProps.mEffectiveResolution = GetEffectiveResolution();
     tiledLayerProps.mCompositionBounds = GetCompositionBounds();
-    tiledLayerProps.mRetainTiles = !mIsFixedPosition;
+    tiledLayerProps.mRetainTiles = !(mIsFixedPosition || mStickyPositionData);
     tiledLayerProps.mValidRegion = mValidRegion;
   }
 
   mBuffer->SetPaintWillResample(MayResample());
 
   mBuffer->Composite(effectChain,
                      GetEffectiveOpacity(),
                      transform,
--- a/gfx/layers/ipc/GeckoContentController.h
+++ b/gfx/layers/ipc/GeckoContentController.h
@@ -59,16 +59,26 @@ public:
                                        const CSSSize &aScrollableSize) = 0;
 
   /**
    * 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.
+   */
+  virtual bool GetZoomConstraints(bool* aOutAllowZoom,
+                                  CSSToScreenScale* aOutMinZoom,
+                                  CSSToScreenScale* aOutMaxZoom)
+  {
+    return false;
+  }
 
   /**
    * Request any special actions be performed when panning starts
    */
   virtual void HandlePanBegin() {}
 
   /**
    * Request any special actions be performed when panning ends
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -267,16 +267,21 @@ LayerTransactionParent::RecvUpdate(const
       layer->SetContentFlags(common.contentFlags());
       layer->SetOpacity(common.opacity());
       layer->SetClipRect(common.useClipRect() ? &common.clipRect() : nullptr);
       layer->SetBaseTransform(common.transform().value());
       layer->SetPostScale(common.postXScale(), common.postYScale());
       layer->SetIsFixedPosition(common.isFixedPosition());
       layer->SetFixedPositionAnchor(common.fixedPositionAnchor());
       layer->SetFixedPositionMargins(common.fixedPositionMargin());
+      if (common.isStickyPosition()) {
+        layer->SetStickyPositionData(common.stickyScrollContainerId(),
+                                     common.stickyScrollRangeOuter(),
+                                     common.stickyScrollRangeInner());
+      }
       if (PLayerParent* maskLayer = common.maskLayerParent()) {
         layer->SetMaskLayer(cast(maskLayer)->AsLayer());
       } else {
         layer->SetMaskLayer(nullptr);
       }
       layer->SetAnimations(common.animations());
 
       typedef SpecificLayerAttributes Specific;
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -28,16 +28,17 @@ using nsPoint;
 using mozilla::TimeDuration;
 using mozilla::TimeStamp;
 using mozilla::ScreenRotation;
 using nsCSSProperty;
 using mozilla::dom::ScreenOrientation;
 using mozilla::layers::TextureInfo;
 using mozilla::LayerMargin;
 using mozilla::LayerPoint;
+using mozilla::LayerRect;
 using mozilla::layers::ScaleMode;
 using mozilla::layers::DiagnosticTypes;
 
 namespace mozilla {
 namespace layers {
 
 struct TargetConfig {
   nsIntRect naturalBounds;
@@ -187,16 +188,20 @@ struct CommonLayerAttributes {
   float postYScale;
   uint32_t contentFlags;
   float opacity;
   bool useClipRect;
   nsIntRect clipRect;
   bool isFixedPosition;
   LayerPoint fixedPositionAnchor;
   LayerMargin fixedPositionMargin;
+  bool isStickyPosition;
+  uint64_t stickyScrollContainerId;
+  LayerRect stickyScrollRangeOuter;
+  LayerRect stickyScrollRangeInner;
   nullable PLayer maskLayer;
   // Animated colors will only honored for ColorLayers.
   Animation[] animations;
  };
 
 struct ThebesLayerAttributes {
   nsIntRegion validRegion;
 };
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -491,16 +491,22 @@ ShadowLayerForwarder::EndTransaction(Inf
     common.contentFlags() = mutant->GetContentFlags();
     common.opacity() = mutant->GetOpacity();
     common.useClipRect() = !!mutant->GetClipRect();
     common.clipRect() = (common.useClipRect() ?
                          *mutant->GetClipRect() : nsIntRect());
     common.isFixedPosition() = mutant->GetIsFixedPosition();
     common.fixedPositionAnchor() = mutant->GetFixedPositionAnchor();
     common.fixedPositionMargin() = mutant->GetFixedPositionMargins();
+    common.isStickyPosition() = mutant->GetIsStickyPosition();
+    if (mutant->GetIsStickyPosition()) {
+      common.stickyScrollContainerId() = mutant->GetStickyScrollContainerId();
+      common.stickyScrollRangeOuter() = mutant->GetStickyScrollRangeOuter();
+      common.stickyScrollRangeInner() = mutant->GetStickyScrollRangeInner();
+    }
     if (Layer* maskLayer = mutant->GetMaskLayer()) {
       common.maskLayerChild() = Shadow(maskLayer->AsShadowableLayer());
     } else {
       common.maskLayerChild() = nullptr;
     }
     common.maskLayerParent() = nullptr;
     common.animations() = mutant->GetAnimations();
     attrs.specific() = null_t();
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -84,34 +84,84 @@ gfxCharacterMap::NotifyReleased()
 {
     gfxPlatformFontList *fontlist = gfxPlatformFontList::PlatformFontList();
     if (mShared) {
         fontlist->RemoveCmap(this);
     }
     delete this;
 }
 
+gfxFontEntry::gfxFontEntry() :
+    mItalic(false), mFixedPitch(false),
+    mIsProxy(false), mIsValid(true),
+    mIsBadUnderlineFont(false),
+    mIsUserFont(false),
+    mIsLocalUserFont(false),
+    mStandardFace(false),
+    mSymbolFont(false),
+    mIgnoreGDEF(false),
+    mIgnoreGSUB(false),
+    mSVGInitialized(false),
+    mHasSpaceFeaturesInitialized(false),
+    mHasSpaceFeatures(false),
+    mHasSpaceFeaturesKerning(false),
+    mHasSpaceFeaturesNonKerning(false),
+    mHasSpaceFeaturesSubDefault(false),
+    mCheckedForGraphiteTables(false),
+    mHasCmapTable(false),
+    mGrFaceInitialized(false),
+    mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
+    mUVSOffset(0), mUVSData(nullptr),
+    mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
+    mHBFace(nullptr),
+    mGrFace(nullptr),
+    mGrFaceRefCnt(0)
+{
+}
+
+gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) :
+    mName(aName), mItalic(false), mFixedPitch(false),
+    mIsProxy(false), mIsValid(true),
+    mIsBadUnderlineFont(false), mIsUserFont(false),
+    mIsLocalUserFont(false), mStandardFace(aIsStandardFace),
+    mSymbolFont(false),
+    mIgnoreGDEF(false),
+    mIgnoreGSUB(false),
+    mSVGInitialized(false),
+    mHasSpaceFeaturesInitialized(false),
+    mHasSpaceFeatures(false),
+    mHasSpaceFeaturesKerning(false),
+    mHasSpaceFeaturesNonKerning(false),
+    mHasSpaceFeaturesSubDefault(false),
+    mCheckedForGraphiteTables(false),
+    mHasCmapTable(false),
+    mGrFaceInitialized(false),
+    mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
+    mUVSOffset(0), mUVSData(nullptr),
+    mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
+    mHBFace(nullptr),
+    mGrFace(nullptr),
+    mGrFaceRefCnt(0)
+{
+    memset(&mHasSpaceFeaturesSub, 0, sizeof(mHasSpaceFeaturesSub));
+}
+
 gfxFontEntry::~gfxFontEntry()
 {
     // For downloaded fonts, we need to tell the user font cache that this
     // entry is being deleted.
     if (!mIsProxy && IsUserFont() && !IsLocalUserFont()) {
         gfxUserFontSet::UserFontCache::ForgetFont(this);
     }
 
     // By the time the entry is destroyed, all font instances that were
     // using it should already have been deleted, and so the HB and/or Gr
     // face objects should have been released.
     MOZ_ASSERT(!mHBFace);
     MOZ_ASSERT(!mGrFaceInitialized);
-
-    if (mSVGGlyphs) {
-        delete mSVGGlyphs;
-    }
-    delete mUserFontData;
 }
 
 bool gfxFontEntry::IsSymbolFont() 
 {
     return mSymbolFont;
 }
 
 bool gfxFontEntry::TestCharacterMap(uint32_t aCh)
@@ -243,17 +293,17 @@ gfxFontEntry::RenderSVGGlyph(gfxContext 
                              int aDrawMode, gfxTextContextPaint *aContextPaint)
 {
     NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first.");
     return mSVGGlyphs->RenderGlyph(aContext, aGlyphId, gfxFont::DrawMode(aDrawMode),
                                    aContextPaint);
 }
 
 bool
-gfxFontEntry::TryGetSVGData()
+gfxFontEntry::TryGetSVGData(gfxFont* aFont)
 {
     if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) {
         return false;
     }
 
     if (!mSVGInitialized) {
         mSVGInitialized = true;
 
@@ -261,22 +311,41 @@ gfxFontEntry::TryGetSVGData()
         // blobs to the gfxSVGGlyphs, once we've confirmed the tables exist
         hb_blob_t *svgTable = GetFontTable(TRUETYPE_TAG('S','V','G',' '));
         if (!svgTable) {
             return false;
         }
 
         // gfxSVGGlyphs will hb_blob_destroy() the table when it is finished
         // with it.
-        mSVGGlyphs = new gfxSVGGlyphs(svgTable);
+        mSVGGlyphs = new gfxSVGGlyphs(svgTable, this);
+    }
+
+    if (!mFontsUsingSVGGlyphs.Contains(aFont)) {
+        mFontsUsingSVGGlyphs.AppendElement(aFont);
     }
 
     return !!mSVGGlyphs;
 }
 
+void
+gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont)
+{
+    mFontsUsingSVGGlyphs.RemoveElement(aFont);
+}
+
+void
+gfxFontEntry::NotifyGlyphsChanged()
+{
+    for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) {
+        gfxFont* font = mFontsUsingSVGGlyphs[i];
+        font->NotifyGlyphsChanged();
+    }
+}
+
 /**
  * FontTableBlobData
  *
  * See FontTableHashEntry for the general strategy.
  */
 
 class gfxFontEntry::FontTableBlobData {
 public:
@@ -1699,25 +1768,39 @@ gfxFont::gfxFont(gfxFontEntry *aFontEntr
     mAntialiasOption(anAAOption)
 {
 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
     ++gFontCount;
 #endif
     mKerningSet = HasFeatureSet(HB_TAG('k','e','r','n'), mKerningEnabled);
 }
 
+static PLDHashOperator
+NotifyFontDestroyed(nsPtrHashKey<gfxFont::GlyphChangeObserver>* aKey,
+                    void* aClosure)
+{
+    aKey->GetKey()->ForgetFont();
+    return PL_DHASH_NEXT;
+}
+
 gfxFont::~gfxFont()
 {
     uint32_t i, count = mGlyphExtentsArray.Length();
     // We destroy the contents of mGlyphExtentsArray explicitly instead of
     // using nsAutoPtr because VC++ can't deal with nsTArrays of nsAutoPtrs
     // of classes that lack a proper copy constructor
     for (i = 0; i < count; ++i) {
         delete mGlyphExtentsArray[i];
     }
+
+    mFontEntry->NotifyFontDestroyed(this);
+
+    if (mGlyphChangeObservers) {
+        mGlyphChangeObservers->EnumerateEntries(NotifyFontDestroyed, nullptr);
+    }
 }
 
 /*static*/
 PLDHashOperator
 gfxFont::AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData)
 {
     if (!aEntry->mShapedWord) {
         NS_ASSERTION(aEntry->mShapedWord, "cache entry has no gfxShapedWord!");
@@ -2290,24 +2373,24 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
 
     const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
     const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
     const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit);
     bool isRTL = aTextRun->IsRightToLeft();
     double direction = aTextRun->GetDirection();
     gfxMatrix globalMatrix = aContext->CurrentMatrix();
 
-    bool haveSVGGlyphs = GetFontEntry()->TryGetSVGData();
+    bool haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
     nsAutoPtr<gfxTextContextPaint> contextPaint;
     if (haveSVGGlyphs && !aContextPaint) {
         // If no pattern is specified for fill, use the current pattern
         NS_ASSERTION((aDrawMode & GLYPH_STROKE) == 0, "no pattern supplied for stroking text");
         nsRefPtr<gfxPattern> fillPattern = aContext->GetPattern();
         contextPaint = new SimpleTextContextPaint(fillPattern, nullptr,
-                                                  aContext->CurrentMatrix());
+                                                 aContext->CurrentMatrix());
         aContextPaint = contextPaint;
     }
 
     // synthetic-bold strikes are each offset one device pixel in run direction
     // (these values are only needed if IsSyntheticBold() is true)
     double synBoldOnePixelOffset = 0;
     int32_t strikes = 1;
     if (IsSyntheticBold()) {
@@ -2930,16 +3013,38 @@ gfxFont::Measure(gfxTextRun *aTextRun,
     if (isRTL) {
         metrics.mBoundingBox -= gfxPoint(x, 0);
     }
 
     metrics.mAdvanceWidth = x*direction;
     return metrics;
 }
 
+static PLDHashOperator
+NotifyGlyphChangeObservers(nsPtrHashKey<gfxFont::GlyphChangeObserver>* aKey,
+                           void* aClosure)
+{
+    aKey->GetKey()->NotifyGlyphsChanged();
+    return PL_DHASH_NEXT;
+}
+
+void
+gfxFont::NotifyGlyphsChanged()
+{
+    uint32_t i, count = mGlyphExtentsArray.Length();
+    for (i = 0; i < count; ++i) {
+        // Flush cached extents array
+        mGlyphExtentsArray[i]->NotifyGlyphsChanged();
+    }
+
+    if (mGlyphChangeObservers) {
+        mGlyphChangeObservers->EnumerateEntries(NotifyGlyphChangeObservers, nullptr);
+    }
+}
+
 static bool
 IsBoundarySpace(PRUnichar aChar, PRUnichar aNextChar)
 {
     return (aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar);
 }
 
 static inline uint32_t
 HashMix(uint32_t aHash, PRUnichar aCh)
@@ -3384,17 +3489,17 @@ gfxFont::GetOrCreateGlyphExtents(int32_t
 void
 gfxFont::SetupGlyphExtents(gfxContext *aContext, uint32_t aGlyphID, bool aNeedTight,
                            gfxGlyphExtents *aExtents)
 {
     gfxContextMatrixAutoSaveRestore matrixRestore(aContext);
     aContext->IdentityMatrix();
 
     gfxRect svgBounds;
-    if (mFontEntry->TryGetSVGData() && mFontEntry->HasSVGGlyph(aGlyphID) &&
+    if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) &&
         mFontEntry->GetSVGGlyphExtents(aContext, aGlyphID, &svgBounds)) {
         gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
         aExtents->SetTightGlyphExtents(aGlyphID,
                                        gfxRect(svgBounds.x * d2a,
                                                svgBounds.y * d2a,
                                                svgBounds.width * d2a,
                                                svgBounds.height * d2a));
         return;
@@ -3720,16 +3825,33 @@ gfxFont::SizeOfExcludingThis(MallocSizeO
 void
 gfxFont::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
                              FontCacheSizes*   aSizes) const
 {
     aSizes->mFontInstances += aMallocSizeOf(this);
     SizeOfExcludingThis(aMallocSizeOf, aSizes);
 }
 
+void
+gfxFont::AddGlyphChangeObserver(GlyphChangeObserver *aObserver)
+{
+    if (!mGlyphChangeObservers) {
+        mGlyphChangeObservers = new nsTHashtable<nsPtrHashKey<GlyphChangeObserver> >;
+    }
+    mGlyphChangeObservers->PutEntry(aObserver);
+}
+
+void
+gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver *aObserver)
+{
+    NS_ASSERTION(mGlyphChangeObservers, "No observers registered");
+    NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver), "Observer not registered");
+    mGlyphChangeObservers->RemoveEntry(aObserver);
+}
+
 gfxGlyphExtents::~gfxGlyphExtents()
 {
 #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
     gGlyphExtentsWidthsTotalSize +=
         mContainedGlyphWidths.SizeOfExcludingThis(&FontCacheMallocSizeOf);
     gGlyphExtentsCount++;
 #endif
     MOZ_COUNT_DTOR(gfxGlyphExtents);
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -223,45 +223,17 @@ private:
     gfxCharacterMap(const gfxCharacterMap&);
     gfxCharacterMap& operator=(const gfxCharacterMap&);
 };
 
 class gfxFontEntry {
 public:
     NS_INLINE_DECL_REFCOUNTING(gfxFontEntry)
 
-    gfxFontEntry(const nsAString& aName, bool aIsStandardFace = false) :
-        mName(aName), mItalic(false), mFixedPitch(false),
-        mIsProxy(false), mIsValid(true), 
-        mIsBadUnderlineFont(false), mIsUserFont(false),
-        mIsLocalUserFont(false), mStandardFace(aIsStandardFace),
-        mSymbolFont(false),
-        mIgnoreGDEF(false),
-        mIgnoreGSUB(false),
-        mSVGInitialized(false),
-        mHasSpaceFeaturesInitialized(false),
-        mHasSpaceFeatures(false),
-        mHasSpaceFeaturesKerning(false),
-        mHasSpaceFeaturesNonKerning(false),
-        mHasSpaceFeaturesSubDefault(false),
-        mCheckedForGraphiteTables(false),
-        mHasCmapTable(false),
-        mGrFaceInitialized(false),
-        mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
-        mUVSOffset(0), mUVSData(nullptr),
-        mUserFontData(nullptr),
-        mSVGGlyphs(nullptr),
-        mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
-        mHBFace(nullptr),
-        mGrFace(nullptr),
-        mGrFaceRefCnt(0)
-    {
-        memset(&mHasSpaceFeaturesSub, 0, sizeof(mHasSpaceFeaturesSub));
-    }
-
+    gfxFontEntry(const nsAString& aName, bool aIsStandardFace = false);
     virtual ~gfxFontEntry();
 
     // unique name for the face, *not* the family; not necessarily the
     // "real" or user-friendly name, may be an internal identifier
     const nsString& Name() const { return mName; }
 
     // family name
     const nsString& FamilyName() const { return mFamilyName; }
@@ -319,22 +291,25 @@ public:
     // All concrete gfxFontEntry subclasses (except gfxProxyFontEntry) need
     // to override this, otherwise the font will never be used as it will
     // be considered to support no characters.
     // ReadCMAP() must *always* set the mCharacterMap pointer to a valid
     // gfxCharacterMap, even if empty, as other code assumes this pointer
     // can be safely dereferenced.
     virtual nsresult ReadCMAP();
 
-    bool TryGetSVGData();
+    bool TryGetSVGData(gfxFont* aFont);
     bool HasSVGGlyph(uint32_t aGlyphId);
     bool GetSVGGlyphExtents(gfxContext *aContext, uint32_t aGlyphId,
                             gfxRect *aResult);
     bool RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId, int aDrawMode,
                         gfxTextContextPaint *aContextPaint);
+    // Call this when glyph geometry or rendering has changed
+    // (e.g. animated SVG glyphs)
+    void NotifyGlyphsChanged();
 
     virtual bool MatchesGenericFamily(const nsACString& aGeneric) const {
         return true;
     }
     virtual bool SupportsLangGroup(nsIAtom *aLangGroup) const {
         return true;
     }
 
@@ -409,16 +384,20 @@ public:
     // and the font entry will be notified via ForgetHBFace.
     hb_face_t* GetHBFace();
     virtual void ForgetHBFace();
 
     // Get Graphite face corresponding to this font file.
     // Caller must call gfxFontEntry::ReleaseGrFace when finished with it.
     gr_face* GetGrFace();
     virtual void ReleaseGrFace(gr_face* aFace);
+
+    // Called to notify that aFont is being destroyed. Needed when we're tracking
+    // the fonts belonging to this font entry.
+    void NotifyFontDestroyed(gfxFont* aFont);
     
     // For memory reporting
     virtual void SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                      FontListSizes*    aSizes) const;
     virtual void SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                      FontListSizes*    aSizes) const;
 
     nsString         mName;
@@ -450,57 +429,31 @@ public:
     uint32_t         mHasSpaceFeaturesSub[(MOZ_NUM_SCRIPT_CODES + 31) / 32];
 
     uint16_t         mWeight;
     int16_t          mStretch;
 
     nsRefPtr<gfxCharacterMap> mCharacterMap;
     uint32_t         mUVSOffset;
     nsAutoArrayPtr<uint8_t> mUVSData;
-    gfxUserFontData* mUserFontData;
-    gfxSVGGlyphs    *mSVGGlyphs;
-
+    nsAutoPtr<gfxUserFontData> mUserFontData;
+    nsAutoPtr<gfxSVGGlyphs> mSVGGlyphs;
+    // list of gfxFonts that are using SVG glyphs
+    nsTArray<gfxFont*> mFontsUsingSVGGlyphs;
     nsTArray<gfxFontFeature> mFeatureSettings;
     uint32_t         mLanguageOverride;
 
 protected:
     friend class gfxPlatformFontList;
     friend class gfxMacPlatformFontList;
     friend class gfxUserFcFontEntry;
     friend class gfxFontFamily;
     friend class gfxSingleFaceMacFontFamily;
 
-    gfxFontEntry() :
-        mItalic(false), mFixedPitch(false),
-        mIsProxy(false), mIsValid(true), 
-        mIsBadUnderlineFont(false),
-        mIsUserFont(false),
-        mIsLocalUserFont(false),
-        mStandardFace(false),
-        mSymbolFont(false),
-        mIgnoreGDEF(false),
-        mIgnoreGSUB(false),
-        mSVGInitialized(false),
-        mHasSpaceFeaturesInitialized(false),
-        mHasSpaceFeatures(false),
-        mHasSpaceFeaturesKerning(false),
-        mHasSpaceFeaturesNonKerning(false),
-        mHasSpaceFeaturesSubDefault(false),
-        mCheckedForGraphiteTables(false),
-        mHasCmapTable(false),
-        mGrFaceInitialized(false),
-        mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
-        mUVSOffset(0), mUVSData(nullptr),
-        mUserFontData(nullptr),
-        mSVGGlyphs(nullptr),
-        mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
-        mHBFace(nullptr),
-        mGrFace(nullptr),
-        mGrFaceRefCnt(0)
-    { }
+    gfxFontEntry();
 
     virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) {
         NS_NOTREACHED("oops, somebody didn't override CreateFontInstance");
         return nullptr;
     }
 
     virtual void CheckForGraphiteTables();
 
@@ -1155,16 +1108,20 @@ public:
     gfxGlyphExtents(int32_t aAppUnitsPerDevUnit) :
         mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) {
         MOZ_COUNT_CTOR(gfxGlyphExtents);
     }
     ~gfxGlyphExtents();
 
     enum { INVALID_WIDTH = 0xFFFF };
 
+    void NotifyGlyphsChanged() {
+        mTightGlyphExtents.Clear();
+    }
+
     // returns INVALID_WIDTH => not a contained glyph
     // Otherwise the glyph has no before-bearing or vertical bearings,
     // and the result is its width measured from the baseline origin, in
     // appunits.
     uint16_t GetContainedGlyphWidthAppUnits(uint32_t aGlyphID) const {
         return mContainedGlyphWidths.Get(aGlyphID);
     }
 
@@ -1690,16 +1647,19 @@ public:
 
     // Discard all cached word records; called on memory-pressure notification.
     void ClearCachedWords() {
         if (mWordCache) {
             mWordCache->Clear();
         }
     }
 
+    // Glyph rendering/geometry has changed, so invalidate data as necessary.
+    void NotifyGlyphsChanged();
+
     virtual void SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                      FontCacheSizes*   aSizes) const;
     virtual void SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                      FontCacheSizes*   aSizes) const;
 
     typedef enum {
         FONT_TYPE_DWRITE,
         FONT_TYPE_GDI,
@@ -1713,17 +1673,49 @@ public:
 
     virtual mozilla::TemporaryRef<mozilla::gfx::ScaledFont> GetScaledFont(mozilla::gfx::DrawTarget *aTarget)
     { return gfxPlatform::GetPlatform()->GetScaledFontForFont(aTarget, this); }
 
     bool KerningDisabled() {
         return mKerningSet && !mKerningEnabled;
     }
 
+    /**
+     * Subclass this object to be notified of glyph changes. Delete the object
+     * when no longer needed.
+     */
+    class GlyphChangeObserver {
+    public:
+        virtual ~GlyphChangeObserver()
+        {
+            if (mFont) {
+                mFont->RemoveGlyphChangeObserver(this);
+            }
+        }
+        // This gets called when the gfxFont dies.
+        void ForgetFont() { mFont = nullptr; }
+        virtual void NotifyGlyphsChanged() = 0;
+    protected:
+        GlyphChangeObserver(gfxFont *aFont) : mFont(aFont)
+        {
+            mFont->AddGlyphChangeObserver(this);
+        }
+        gfxFont* mFont;
+    };
+    friend class GlyphChangeObserver;
+
+    bool GlyphsMayChange()
+    {
+        // Currently only fonts with SVG glyphs can have animated glyphs
+        return mFontEntry->TryGetSVGData(this);
+    }
+
 protected:
+    void AddGlyphChangeObserver(GlyphChangeObserver *aObserver);
+    void RemoveGlyphChangeObserver(GlyphChangeObserver *aObserver);
 
     bool HasSubstitutionRulesWithSpaceLookups(int32_t aRunScript) {
         NS_ASSERTION(GetFontEntry()->mHasSpaceFeaturesInitialized,
                      "need to initialize space lookup flags");
         NS_ASSERTION(aRunScript < MOZ_NUM_SCRIPT_CODES, "weird script code");
         if (aRunScript == MOZ_SCRIPT_INVALID ||
             aRunScript >= MOZ_NUM_SCRIPT_CODES) {
             return false;
@@ -1920,16 +1912,17 @@ protected:
     bool                       mApplySyntheticBold;
 
     bool                       mKerningSet;     // kerning explicitly set?
     bool                       mKerningEnabled; // if set, on or off?
 
     nsExpirationState          mExpirationState;
     gfxFontStyle               mStyle;
     nsAutoTArray<gfxGlyphExtents*,1> mGlyphExtentsArray;
+    nsAutoPtr<nsTHashtable<nsPtrHashKey<GlyphChangeObserver> > > mGlyphChangeObservers;
 
     gfxFloat                   mAdjustedSize;
 
     float                      mFUnitsConvFactor; // conversion factor from font units to dev units
 
     // the AA setting requested for this font - may affect glyph bounds
     AntialiasOption            mAntialiasOption;
 
--- a/gfx/thebes/gfxSVGGlyphs.cpp
+++ b/gfx/thebes/gfxSVGGlyphs.cpp
@@ -26,33 +26,37 @@
 #include "nsStringStream.h"
 #include "nsStreamUtils.h"
 #include "nsIPrincipal.h"
 #include "mozilla/dom/Element.h"
 #include "nsSVGUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsContentUtils.h"
+#include "gfxFont.h"
+#include "nsSMILAnimationController.h"
 #include "harfbuzz/hb.h"
 
 #define SVG_CONTENT_TYPE NS_LITERAL_CSTRING("image/svg+xml")
 #define UTF8_CHARSET NS_LITERAL_CSTRING("utf-8")
 
+using namespace mozilla;
+
 typedef mozilla::dom::Element Element;
 
 mozilla::gfx::UserDataKey gfxTextContextPaint::sUserDataKey;
 
 const float gfxSVGGlyphs::SVG_UNITS_PER_EM = 1000.0f;
 
 const gfxRGBA SimpleTextContextPaint::sZero = gfxRGBA(0.0f, 0.0f, 0.0f, 0.0f);
 
-gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t *aSVGTable)
+gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t *aSVGTable, gfxFontEntry *aFontEntry)
+    : mSVGData(aSVGTable)
+    , mFontEntry(aFontEntry)
 {
-    mSVGData = aSVGTable;
-
     unsigned int length;
     const char* svgData = hb_blob_get_data(mSVGData, &length);
     mHeader = reinterpret_cast<const Header*>(svgData);
     mDocIndex = nullptr;
 
     if (sizeof(Header) <= length && uint16_t(mHeader->mVersion) == 0 &&
         uint64_t(mHeader->mDocIndexOffset) + 2 <= length) {
         const DocIndex* docIndex = reinterpret_cast<const DocIndex*>
@@ -65,16 +69,22 @@ gfxSVGGlyphs::gfxSVGGlyphs(hb_blob_t *aS
     }
 }
 
 gfxSVGGlyphs::~gfxSVGGlyphs()
 {
     hb_blob_destroy(mSVGData);
 }
 
+void
+gfxSVGGlyphs::DidRefresh()
+{
+    mFontEntry->NotifyGlyphsChanged();
+}
+
 /*
  * Comparison operator for finding a range containing a given glyph ID. Simply
  *   checks whether |key| is less (greater) than every element of |range|, in
  *   which case return |key| < |range| (|key| > |range|). Otherwise |key| is in
  *   |range|, in which case return equality.
  * The total ordering here is guaranteed by
  *   (1) the index ranges being disjoint; and
  *   (2) the (sole) key always being a singleton, so intersection => containment
@@ -115,29 +125,27 @@ gfxSVGGlyphs::FindOrCreateGlyphsDocument
     gfxSVGGlyphsDocument *result = mGlyphDocs.Get(entry->mDocOffset);
 
     if (!result) {
         unsigned int length;
         const uint8_t *data = (const uint8_t*)hb_blob_get_data(mSVGData, &length);
         if (entry->mDocOffset > 0 &&
             uint64_t(mHeader->mDocIndexOffset) + entry->mDocOffset + entry->mDocLength <= length) {
             result = new gfxSVGGlyphsDocument(data + mHeader->mDocIndexOffset + entry->mDocOffset,
-                                              entry->mDocLength);
+                                              entry->mDocLength, this);
             mGlyphDocs.Put(entry->mDocOffset, result);
         }
     }
 
     return result;
 }
 
 nsresult
 gfxSVGGlyphsDocument::SetupPresentation()
 {
-    mDocument->SetIsBeingUsedAsImage();
-
     nsCOMPtr<nsICategoryManager> catMan = do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
     nsXPIDLCString contractId;
     nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "image/svg+xml", getter_Copies(contractId));
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = do_GetService(contractId);
     NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory");
 
@@ -149,32 +157,46 @@ gfxSVGGlyphsDocument::SetupPresentation(
     if (NS_SUCCEEDED(rv)) {
         rv = viewer->Open(nullptr, nullptr);
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     nsCOMPtr<nsIPresShell> presShell;
     rv = viewer->GetPresShell(getter_AddRefs(presShell));
     NS_ENSURE_SUCCESS(rv, rv);
-    presShell->GetPresContext()->SetIsGlyph(true);
+    nsPresContext* presContext = presShell->GetPresContext();
+    presContext->SetIsGlyph(true);
 
     if (!presShell->DidInitialize()) {
-        nsRect rect = presShell->GetPresContext()->GetVisibleArea();
+        nsRect rect = presContext->GetVisibleArea();
         rv = presShell->Initialize(rect.width, rect.height);
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     mDocument->FlushPendingNotifications(Flush_Layout);
 
+    nsSMILAnimationController* controller = mDocument->GetAnimationController();
+    if (controller) {
+      controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE);
+    }
+    mDocument->SetImagesNeedAnimating(true);
+
     mViewer = viewer;
     mPresShell = presShell;
+    mPresShell->AddPostRefreshObserver(this);
 
     return NS_OK;
 }
 
+void
+gfxSVGGlyphsDocument::DidRefresh()
+{
+    mOwner->DidRefresh();
+}
+
 /**
  * Walk the DOM tree to find all glyph elements and insert them into the lookup
  * table
  * @param aElem The element to search from
  */
 void
 gfxSVGGlyphsDocument::FindGlyphElements(Element *aElem)
 {
@@ -246,17 +268,20 @@ gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyp
 }
 
 Element *
 gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId)
 {
     return mGlyphIdMap.Get(aGlyphId);
 }
 
-gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t *aBuffer, uint32_t aBufLen)
+gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t *aBuffer,
+                                           uint32_t aBufLen,
+                                           gfxSVGGlyphs *aSVGGlyphs)
+    : mOwner(aSVGGlyphs)
 {
     ParseDocument(aBuffer, aBufLen);
     if (!mDocument) {
         NS_WARNING("Could not parse SVG glyphs document");
         return;
     }
 
     Element *root = mDocument->GetRootElement();
@@ -269,16 +294,32 @@ gfxSVGGlyphsDocument::gfxSVGGlyphsDocume
     if (NS_FAILED(rv)) {
         NS_WARNING("Couldn't setup presentation for SVG glyphs document");
         return;
     }
 
     FindGlyphElements(root);
 }
 
+gfxSVGGlyphsDocument::~gfxSVGGlyphsDocument()
+{
+    if (mDocument) {
+        nsSMILAnimationController* controller = mDocument->GetAnimationController();
+        if (controller) {
+            controller->Pause(nsSMILTimeContainer::PAUSE_PAGEHIDE);
+        }
+    }
+    if (mPresShell) {
+        mPresShell->RemovePostRefreshObserver(this);
+    }
+    if (mViewer) {
+        mViewer->Destroy();
+    }
+}
+
 static nsresult
 CreateBufferedStream(const uint8_t *aBuffer, uint32_t aBufLen,
                      nsCOMPtr<nsIInputStream> &aResult)
 {
     nsCOMPtr<nsIInputStream> stream;
     nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
                                         reinterpret_cast<const char *>(aBuffer),
                                         aBufLen, NS_ASSIGNMENT_DEPEND);
@@ -334,16 +375,18 @@ gfxSVGGlyphsDocument::ParseDocument(cons
 
     nsCOMPtr<nsIChannel> channel;
     rv = NS_NewInputStreamChannel(getter_AddRefs(channel), uri, nullptr /* stream */,
                                   SVG_CONTENT_TYPE, UTF8_CHARSET);
     NS_ENSURE_SUCCESS(rv, rv);
 
     channel->SetOwner(principal);
 
+    // Set this early because various decisions during page-load depend on it.
+    document->SetIsBeingUsedAsImage();
     document->SetReadyStateInternal(nsIDocument::READYSTATE_UNINITIALIZED);
 
     nsCOMPtr<nsIStreamListener> listener;
     rv = document->StartDocumentLoad("external-resource", channel,
                                      nullptr,    // aLoadGroup
                                      nullptr,    // aContainer
                                      getter_AddRefs(listener),
                                      true /* aReset */);
--- a/gfx/thebes/gfxSVGGlyphs.h
+++ b/gfx/thebes/gfxSVGGlyphs.h
@@ -12,51 +12,55 @@
 #include "nsIContentViewer.h"
 #include "nsIPresShell.h"
 #include "nsClassHashtable.h"
 #include "nsBaseHashtable.h"
 #include "nsHashKeys.h"
 #include "gfxPattern.h"
 #include "gfxFont.h"
 #include "mozilla/gfx/UserData.h"
+#include "nsRefreshDriver.h"
+ 
+class gfxSVGGlyphs;
 
 
 /**
  * Wraps an SVG document contained in the SVG table of an OpenType font.
  * There may be multiple SVG documents in an SVG table which we lazily parse
  *   so we have an instance of this class for every document in the SVG table
  *   which contains a glyph ID which has been used
  * Finds and looks up elements contained in the SVG document which have glyph
  *   mappings to be drawn by gfxSVGGlyphs
  */
-class gfxSVGGlyphsDocument
+class gfxSVGGlyphsDocument MOZ_FINAL : public nsAPostRefreshObserver
 {
     typedef mozilla::dom::Element Element;
     typedef gfxFont::DrawMode DrawMode;
 
 public:
-    gfxSVGGlyphsDocument(const uint8_t *aBuffer, uint32_t aBufLen);
+    gfxSVGGlyphsDocument(const uint8_t *aBuffer, uint32_t aBufLen,
+                         gfxSVGGlyphs *aSVGGlyphs);
 
     Element *GetGlyphElement(uint32_t aGlyphId);
 
-    ~gfxSVGGlyphsDocument() {
-        if (mViewer) {
-            mViewer->Destroy();
-        }
-    }
+    ~gfxSVGGlyphsDocument();
+
+    virtual void DidRefresh() MOZ_OVERRIDE;
 
 private:
     nsresult ParseDocument(const uint8_t *aBuffer, uint32_t aBufLen);
 
     nsresult SetupPresentation();
 
     void FindGlyphElements(Element *aElement);
 
     void InsertGlyphId(Element *aGlyphElement);
 
+    // Weak so as not to create a cycle. mOwner owns us so this can't dangle.
+    gfxSVGGlyphs* mOwner;
     nsCOMPtr<nsIDocument> mDocument;
     nsCOMPtr<nsIContentViewer> mViewer;
     nsCOMPtr<nsIPresShell> mPresShell;
 
     nsBaseHashtable<nsUint32HashKey, Element*, Element*> mGlyphIdMap;
 
     nsAutoCString mSVGGlyphsDocumentURI;
 };
@@ -78,24 +82,29 @@ public:
 
     /**
      * @param aSVGTable The SVG table from the OpenType font
      *
      * The gfxSVGGlyphs object takes over ownership of the blob references
      * that are passed in, and will hb_blob_destroy() them when finished;
      * the caller should -not- destroy these references.
      */
-    gfxSVGGlyphs(hb_blob_t *aSVGTable);
+    gfxSVGGlyphs(hb_blob_t *aSVGTable, gfxFontEntry *aFontEntry);
 
     /**
-     * Releases our references to the SVG table.
+     * Releases our references to the SVG table and cleans up everything else.
      */
     ~gfxSVGGlyphs();
 
     /**
+     * This is called when the refresh driver has ticked.
+     */
+    void DidRefresh();
+
+    /**
      * Find the |gfxSVGGlyphsDocument| containing an SVG glyph for |aGlyphId|.
      * If |aGlyphId| does not map to an SVG document, return null.
      * If a |gfxSVGGlyphsDocument| has not been created for the document, create one.
      */
     gfxSVGGlyphsDocument *FindOrCreateGlyphsDocument(uint32_t aGlyphId);
 
     /**
      * Return true iff there is an SVG glyph for |aGlyphId|
@@ -121,16 +130,17 @@ public:
 
 private:
     Element *GetGlyphElement(uint32_t aGlyphId);
 
     nsClassHashtable<nsUint32HashKey, gfxSVGGlyphsDocument> mGlyphDocs;
     nsBaseHashtable<nsUint32HashKey, Element*, Element*> mGlyphIdMap;
 
     hb_blob_t *mSVGData;
+    gfxFontEntry *mFontEntry;
 
     const struct Header {
         mozilla::AutoSwap_PRUint16 mVersion;
         mozilla::AutoSwap_PRUint32 mDocIndexOffset;
         mozilla::AutoSwap_PRUint32 mColorPalettesOffset;
     } *mHeader;
 
     struct IndexEntry {
--- a/gfx/thebes/gfxWindowsPlatform.h
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -241,17 +241,18 @@ public:
     // OS version in 16.16 major/minor form
     // based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx
     enum {
         kWindowsUnknown = 0,
         kWindowsXP = 0x50001,
         kWindowsServer2003 = 0x50002,
         kWindowsVista = 0x60000,
         kWindows7 = 0x60001,
-        kWindows8 = 0x60002
+        kWindows8 = 0x60002,
+        kWindows8_1 = 0x60003
     };
 
     static int32_t WindowsOSVersion(int32_t *aBuildNum = nullptr);
 
     static void GetDLLVersion(const PRUnichar *aDLLPath, nsAString& aVersion);
 
     // returns ClearType tuning information for each display
     static void GetCleartypeParams(nsTArray<ClearTypeParameterInfo>& aParams);
--- a/image/test/mochitest/test_animSVGImage.html
+++ b/image/test/mochitest/test_animSVGImage.html
@@ -49,56 +49,64 @@ function takeReferenceSnapshot() {
 
   // Re-hide reference div, and take another snapshot to be sure it's gone
   referenceDiv.style.display = "none";
   let blankSnapshot2 = snapshotWindow(window, false);
   ok(compareSnapshots(blankSnapshot, blankSnapshot2, true)[0],
      "reference div should disappear when it becomes display:none");
 }
 
-function myOnStopFrame() {
+function myOnStopFrame(aRequest) {
   gOnStopFrameCounter++;
   ok(true, "myOnStopFrame called");
   let currentSnapshot = snapshotWindow(window, false);
   if (compareSnapshots(currentSnapshot, gReferenceSnapshot, true)[0]) {
     // SUCCESS!
     ok(true, "Animated image looks correct, " +
              "at call #" + gOnStopFrameCounter + " to onStopFrame");
     cleanUpAndFinish();
   }
-  else
-    setTimeout(myOnStopFrame, 1);
+  setTimeout(function() { myOnStopFrame(0, 0); }, 1000);
 }
 
 function failTest() {
   ok(false, "timing out after " + FAILURE_TIMEOUT + "ms.  " +
             "Animated image still doesn't look correct, " +
             "after call #" + gOnStopFrameCounter + " to onStopFrame");
   cleanUpAndFinish();
 }
 
 function cleanUpAndFinish() {
   // On the off chance that failTest and myOnStopFrame are triggered
   // back-to-back, use a flag to prevent multiple calls to SimpleTest.finish.
   if (gIsTestFinished) {
     return;
   }
+  let imgLoadingContent = gImg.QueryInterface(Ci.nsIImageLoadingContent);
+  imgLoadingContent.removeObserver(gMyDecoderObserver);
   SimpleTest.finish();
   gIsTestFinished = true;
 }
 
 function main() {
   takeReferenceSnapshot();
 
+  // Create, customize & attach decoder observer
+  observer = new ImageDecoderObserverStub();
+  observer.frameComplete = myOnStopFrame;
+  gMyDecoderObserver =
+    Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+      .createScriptedObserver(observer);
+  let imgLoadingContent = gImg.QueryInterface(Ci.nsIImageLoadingContent);
+  imgLoadingContent.addObserver(gMyDecoderObserver);
+
   // We want to test the cold loading behavior, so clear cache in case an
   // earlier test got our image in there already.
   clearImageCache();
 
-  setTimeout(myOnStopFrame, 1);
-
   // kick off image-loading! myOnStopFrame handles the rest.
   gImg.setAttribute("src", "lime-anim-100x100.svg");
 
   // In case something goes wrong, fail earlier than mochitest timeout,
   // and with more information.
   setTimeout(failTest, FAILURE_TIMEOUT);
 }
 
--- a/ipc/chromium/src/chrome/common/ipc_message.h
+++ b/ipc/chromium/src/chrome/common/ipc_message.h
@@ -282,17 +282,16 @@ class Message : public Pickle {
     REPLY_ERROR_BIT = 0x0010,
     UNBLOCK_BIT     = 0x0020,
     PUMPING_MSGS_BIT= 0x0040,
     HAS_SENT_TIME_BIT = 0x0080,
     RPC_BIT         = 0x0100,
     COMPRESS_BIT    = 0x0200
   };
 
-#pragma pack(push, 2)
   struct Header : Pickle::Header {
     int32_t routing;  // ID of the view that this message is destined for
     msgid_t type;   // specifies the user-defined message type
     uint32_t flags;   // specifies control flags for the message
 #if defined(OS_POSIX)
     uint32_t num_fds; // the number of descriptors included with this message
 # if defined(OS_MACOSX)
     uint32_t cookie;  // cookie to ACK that the descriptors have been read.
@@ -300,17 +299,16 @@ class Message : public Pickle {
 #endif
     // For RPC messages, a guess at what the *other* side's stack depth is.
     uint32_t rpc_remote_stack_depth_guess;
     // The actual local stack depth.
     uint32_t rpc_local_stack_depth;
     // Sequence number
     int32_t seqno;
   };
-#pragma pack(pop)
 
   Header* header() {
     return headerT<Header>();
   }
   const Header* header() const {
     return headerT<Header>();
   }
 
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -395,66 +395,36 @@ js::DirectEvalFromIon(JSContext *cx,
     // Primitive 'this' values should have been filtered out by Ion. If boxed,
     // the calling frame cannot be updated to store the new object.
     JS_ASSERT(thisValue.isObject() || thisValue.isUndefined() || thisValue.isNull());
 
     return ExecuteKernel(cx, esg.script(), *scopeobj, thisValue, ExecuteType(DIRECT_EVAL),
                          NullFramePtr() /* evalInFrame */, vp.address());
 }
 
-// We once supported a second argument to eval to use as the scope chain
-// when evaluating the code string.  Warn when such uses are seen so that
-// authors will know that support for eval(s, o) has been removed.
-static inline bool
-WarnOnTooManyArgs(JSContext *cx, const CallArgs &args)
-{
-    if (args.length() > 1) {
-        Rooted<JSScript*> script(cx, cx->currentScript());
-        if (script && !script->warnedAboutTwoArgumentEval) {
-            static const char TWO_ARGUMENT_WARNING[] =
-                "Support for eval(code, scopeObject) has been removed. "
-                "Use |with (scopeObject) eval(code);| instead.";
-            if (!JS_ReportWarning(cx, TWO_ARGUMENT_WARNING))
-                return false;
-            script->warnedAboutTwoArgumentEval = true;
-        } else {
-            // In the case of an indirect call without a caller frame, avoid a
-            // potential warning-flood by doing nothing.
-        }
-    }
-
-    return true;
-}
-
 bool
 js::IndirectEval(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    if (!WarnOnTooManyArgs(cx, args))
-        return false;
-
     Rooted<GlobalObject*> global(cx, &args.callee().global());
     return EvalKernel(cx, args, INDIRECT_EVAL, NullFramePtr(), global, NULL);
 }
 
 bool
 js::DirectEval(JSContext *cx, const CallArgs &args)
 {
     // Direct eval can assume it was called from an interpreted or baseline frame.
     ScriptFrameIter iter(cx);
     AbstractFramePtr caller = iter.abstractFramePtr();
 
     JS_ASSERT(IsBuiltinEvalForScope(caller.scopeChain(), args.calleev()));
     JS_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL);
     JS_ASSERT_IF(caller.isFunctionFrame(),
                  caller.compartment() == caller.callee()->compartment());
 
-    if (!WarnOnTooManyArgs(cx, args))
-        return false;
-
     RootedObject scopeChain(cx, caller.scopeChain());
     return EvalKernel(cx, args, DIRECT_EVAL, caller, scopeChain, iter.pc());
 }
 
 bool
 js::IsBuiltinEvalForScope(JSObject *scopeChain, const Value &v)
 {
     return scopeChain->global().getOriginalEval() == v;
--- a/js/src/config/check_spidermonkey_style.py
+++ b/js/src/config/check_spidermonkey_style.py
@@ -212,17 +212,17 @@ class FileKind(object):
         if filename.endswith('.msg'):
             return FileKind.MSG
 
         error(filename, None, 'unknown file kind')
 
 
 def get_all_filenames():
     '''Get a list of all the files in the (Mercurial or Git) repository.'''
-    cmds = [['hg', 'manifest', '-q'], ['git', 'ls-files']]
+    cmds = [['hg', 'manifest', '-q'], ['git', 'ls-files', '--full-name', '../..']]
     for cmd in cmds:
         try:
             all_filenames = subprocess.check_output(cmd, universal_newlines=True,
                                                     stderr=subprocess.PIPE).split('\n')
             return all_filenames
         except:
             continue
     else:
--- a/js/src/jit-test/lib/asm.js
+++ b/js/src/jit-test/lib/asm.js
@@ -2,20 +2,20 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const ASM_OK_STRING = "successfully compiled asm.js code";
 const ASM_TYPE_FAIL_STRING = "asm.js type error:";
 const ASM_DIRECTIVE_FAIL_STRING = "\"use asm\" is only meaningful in the Directive Prologue of a function body";
 
 const USE_ASM = "'use asm';";
-const HEAP_IMPORTS = "var i8=new glob.Int8Array(b);var u8=new glob.Uint8Array(b);"+
-                     "var i16=new glob.Int16Array(b);var u16=new glob.Uint16Array(b);"+
-                     "var i32=new glob.Int32Array(b);var u32=new glob.Uint32Array(b);"+
-                     "var f32=new glob.Float32Array(b);var f64=new glob.Float64Array(b);";
+const HEAP_IMPORTS = "const i8=new glob.Int8Array(b);var u8=new glob.Uint8Array(b);"+
+                     "const i16=new glob.Int16Array(b);var u16=new glob.Uint16Array(b);"+
+                     "const i32=new glob.Int32Array(b);var u32=new glob.Uint32Array(b);"+
+                     "const f32=new glob.Float32Array(b);var f64=new glob.Float64Array(b);";
 const BUF_64KB = new ArrayBuffer(64 * 1024);
 
 function asmCompile()
 {
     var f = Function.apply(null, arguments);
     assertEq(!isAsmJSCompilationAvailable() || isAsmJSModule(f), true);
     return f;
 }
--- a/js/src/jit-test/tests/asm.js/testFFI.js
+++ b/js/src/jit-test/tests/asm.js/testFFI.js
@@ -43,18 +43,21 @@ assertEq(counter, 1);
 
 var f = asmLink(asmCompile('glob', 'imp', USE_ASM + 'var inc=imp.inc; function g() { return inc()|0 } return g'), null, imp);
 assertEq(f(), 1);
 assertEq(counter, 2);
 assertEq(f(), 2);
 assertEq(counter, 3);
 
 assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + 'var add1=imp.add1; function g(i) { i=i|0; return add1(i|0)|0 } return g'), null, imp)(9), 10);
+assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + 'const add1=imp.add1; function g(i) { i=i|0; return add1(i|0)|0 } return g'), null, imp)(9), 10);
 assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + 'var add3=imp.add3; function g() { var i=1,j=3,k=9; return add3(i|0,j|0,k|0)|0 } return g'), null, imp)(), 13);
+assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + 'const add3=imp.add3; function g() { var i=1,j=3,k=9; return add3(i|0,j|0,k|0)|0 } return g'), null, imp)(), 13);
 assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + 'var add3=imp.add3; function g() { var i=1.4,j=2.3,k=32.1; return +add3(i,j,k) } return g'), null, imp)(), 1.4+2.3+32.1);
+assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + 'const add3=imp.add3; function g() { var i=1.4,j=2.3,k=32.1; return +add3(i,j,k) } return g'), null, imp)(), 1.4+2.3+32.1);
 
 assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + 'var add3=imp.add3; function f(i,j,k) { i=i|0;j=+j;k=k|0; return add3(i|0,j,k|0)|0 } return f'), null, imp)(1, 2.5, 3), 6);
 assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + 'var addN=imp.addN; function f() { return +addN(1,2,3,4.1,5,6.1,7,8.1,9.1,10,11.1,12,13,14.1,15.1,16.1,17.1,18.1) } return f'), null, imp)(), 1+2+3+4.1+5+6.1+7+8.1+9.1+10+11.1+12+13+14.1+15.1+16.1+17.1+18.1);
 
 assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + 'var add2=imp.add2; function f(i,j) { i=i|0;j=+j; return +(+(add2(i|0,1)|0) + +add2(j,1) + +add2(+~~i,j)) } return f'), null, imp)(2, 5.5), 3+(5.5+1)+(2+5.5));
 assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + 'var addN=imp.addN; function f(i,j) { i=i|0;j=+j; return +(+addN(i|0,j,3,j,i|0) + +addN() + +addN(j,j,j)) } return f'), null, imp)(1, 2.2), (1+2.2+3+2.2+1)+(2.2+2.2+2.2));
 
 counter = 0;
--- a/js/src/jit-test/tests/asm.js/testFunctionPtr.js
+++ b/js/src/jit-test/tests/asm.js/testFunctionPtr.js
@@ -22,28 +22,52 @@ assertAsmTypeFail(USE_ASM + "function f(
 assertAsmTypeFail(USE_ASM + "function f() {return 42} function g(i) { i=i|0; return tbl[i&0]()|0 } var tbl=[f,f]; return g");
 assertAsmTypeFail(USE_ASM + "function f() {return 42} function g(i) { i=i|0; return tbl[i&3]()|0 } var tbl=[f,f]; return g");
 assertAsmTypeFail(USE_ASM + "function f() {return 42} function g(i,j) { i=i|0;j=+j; return tbl[j&1]()|0 } var tbl=[f,f]; return g");
 assertAsmTypeFail(USE_ASM + "function f() {return 42} function g(i) { i=i|0; return tbl[i&1](1)|0 } var tbl=[f,f]; return g");
 assertAsmTypeFail(USE_ASM + "function f(i) {i=i|0} function g(i) { i=i|0; return tbl[i&1]()|0 } var tbl=[f,f]; return g");
 assertAsmTypeFail(USE_ASM + "function f(i) {i=i|0} function g(i) { i=i|0; return tbl[i&1](3.0)|0 } var tbl=[f,f]; return g");
 assertAsmTypeFail(USE_ASM + "function f(d) {d=+d} function g(i) { i=i|0; return tbl[i&1](3)|0 } var tbl=[f,f]; return g");
 assertEq(asmLink(asmCompile(USE_ASM + "function f() {return 42} function g(i) { i=i|0; return tbl[i&1]()|0 } var tbl=[f,f]; return g"))(0), 42);
+assertEq(asmLink(asmCompile(USE_ASM + "function f() {return 42} function g(i) { i=i|0; return tbl[i&1]()|0 } const tbl=[f,f]; return g"))(0), 42);
 assertEq(asmLink(asmCompile(USE_ASM + "function f() {return 42} function g() {return 13} function h(i) { i=i|0; return tbl[i&1]()|0 } var tbl=[f,g]; return h"))(1), 13);
+assertEq(asmLink(asmCompile(USE_ASM + "function f() {return 42} function g() {return 13} function h(i) { i=i|0; return tbl[i&1]()|0 } const tbl=[f,g]; return h"))(1), 13);
 assertEq(asmLink(asmCompile(USE_ASM + "function f() {return 42} function g() {return 13} function h(i) { i=i|0; return tbl2[i&1]()|0 } var tbl1=[f,g]; var tbl2=[g,f]; return h"))(1), 42);
+assertEq(asmLink(asmCompile(USE_ASM + "function f() {return 42} function g() {return 13} function h(i) { i=i|0; return tbl2[i&1]()|0 } const tbl1=[f,g]; const tbl2=[g,f]; return h"))(1), 42);
 assertEq(asmLink(asmCompile(USE_ASM + "function f() {return 42} function g() {return 13} function h(i) { i=i|0; return tbl2[i&3]()|0 } var tbl1=[f,g]; var tbl2=[g,g,g,f]; return h"))(3), 42);
+assertEq(asmLink(asmCompile(USE_ASM + "function f() {return 42} function g() {return 13} function h(i) { i=i|0; return tbl2[i&3]()|0 } const tbl1=[f,g]; const tbl2=[g,g,g,f]; return h"))(3), 42);
 assertEq(asmLink(asmCompile(USE_ASM + "function f() {return 42} function g() {return 13} function h(i) { i=i|0; return tbl1[i&1]()|0 } var tbl1=[f,g]; var tbl2=[g,g,g,f]; return h"))(1), 13);
+assertEq(asmLink(asmCompile(USE_ASM + "function f() {return 42} function g() {return 13} function h(i) { i=i|0; return tbl1[i&1]()|0 } const tbl1=[f,g]; const tbl2=[g,g,g,f]; return h"))(1), 13);
 assertEq(asmLink(asmCompile(USE_ASM + "var i=0,j=0; function f() {return i|0} function g() {return j|0} function h(x) { x=x|0; i=5;j=10; return tbl2[x&3]()|0 } var tbl1=[f,g]; var tbl2=[g,g,g,f]; return h"))(3), 5);
+assertEq(asmLink(asmCompile(USE_ASM + "var i=0,j=0; function f() {return i|0} function g() {return j|0} function h(x) { x=x|0; i=5;j=10; return tbl2[x&3]()|0 } const tbl1=[f,g]; const tbl2=[g,g,g,f]; return h"))(3), 5);
 assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + "var ffi=imp.ffi; function f() {return ffi()|0} function g() {return 13} function h(x) { x=x|0; return tbl2[x&3]()|0 } var tbl2=[g,g,g,f]; return h"), null, {ffi:function(){return 20}})(3), 20);
-assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + "var ffi=imp.ffi; var i=0; function f() {return ((ffi()|0)+i)|0} function g() {return 13} function h(x) { x=x|0; i=2; return tbl2[x&3]()|0 } var tbl2=[g,g,g,f]; return h"), null, {ffi:function(){return 20}})(3), 22);
+assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + "const ffi=imp.ffi; function f() {return ffi()|0} function g() {return 13} function h(x) { x=x|0; return tbl2[x&3]()|0 } const tbl2=[g,g,g,f]; return h"), null, {ffi:function(){return 20}})(3), 20);
+assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + "var ffi=imp.ffi; var i=0; function f() {return ((ffi()|0)+i)|0} function g() {return 13} function h(x) { x=x|0; i=2; return tbl2[x&3]()|0 } var tbl2=[g,g,g,f]; return h"), null, {ffi:function(){return 20}})(3), 22)
+;assertEq(asmLink(asmCompile('glob', 'imp', USE_ASM + "const ffi=imp.ffi; var i=0; function f() {return ((ffi()|0)+i)|0} function g() {return 13} function h(x) { x=x|0; i=2; return tbl2[x&3]()|0 } const tbl2=[g,g,g,f]; return h"), null, {ffi:function(){return 20}})(3), 22);
 assertEq(asmLink(asmCompile(USE_ASM + "function f(i) {i=i|0; return +((i+1)|0)} function g(d) { d=+d; return +(d+2.5) } function h(i,j) { i=i|0;j=j|0; return +tbl2[i&1](+tbl1[i&1](j)) } var tbl1=[f,f]; var tbl2=[g,g]; return h"))(0,10), 11+2.5);
+assertEq(asmLink(asmCompile(USE_ASM + "function f(i) {i=i|0; return +((i+1)|0)} function g(d) { d=+d; return +(d+2.5) } function h(i,j) { i=i|0;j=j|0; return +tbl2[i&1](+tbl1[i&1](j)) } const tbl1=[f,f]; const tbl2=[g,g]; return h"))(0,10), 11+2.5);
 
 assertAsmTypeFail(USE_ASM + "function f() {return 42} function g() { return tbl[0]()|0 } var tbl=[f]; return g");
 assertEq(asmLink(asmCompile(USE_ASM + "function f() {return 42} function g() { return tbl[0&0]()|0 } var tbl=[f]; return g"))(), 42);
+assertEq(asmLink(asmCompile(USE_ASM + "function f() {return 42} function g() { return tbl[0&0]()|0 } const tbl=[f]; return g"))(), 42);
 assertEq(asmLink(asmCompile(USE_ASM + "function f1() {return 42} function f2() {return 13} function g() { return tbl[1&1]()|0 } var tbl=[f1,f2]; return g"))(), 13);
+assertEq(asmLink(asmCompile(USE_ASM + "function f1() {return 42} function f2() {return 13} function g() { return tbl[1&1]()|0 } const tbl=[f1,f2]; return g"))(), 13);
+
+// Test some literal constant paths.
+assertAsmTypeFail(USE_ASM + "function f() {return 42} function g() { return tbl[0&4294967295]()|0 } var tbl=[f]; return g");
+assertAsmTypeFail(USE_ASM + "const i=4294967295; function f() {return 42} function g() { return tbl[0&i]()|0 } var tbl=[f]; return g");
+assertAsmTypeFail(USE_ASM + "function f() {return 42} function g() { return tbl[0&-1]()|0 } var tbl=[f]; return g");
+assertAsmTypeFail(USE_ASM + "const i=-1; function f() {return 42} function g() { return tbl[0&i]()|0 } var tbl=[f]; return g");
+assertAsmTypeFail(USE_ASM + "function f() {return 42} function g() { return tbl[0&0x80000000]()|0 } var tbl=[f]; return g");
+assertAsmTypeFail(USE_ASM + "const i=0x80000000; function f() {return 42} function g() { return tbl[0&i]()|0 } var tbl=[f]; return g");
+assertAsmTypeFail(USE_ASM + "function f() {return 42} function g() { return tbl[0&-2147483648]()|0 } var tbl=[f]; return g");
+assertAsmTypeFail(USE_ASM + "const i=-2147483648; function f() {return 42} function g() { return tbl[0&i]()|0 } var tbl=[f]; return g");
+assertAsmTypeFail(USE_ASM + "const i=0; function f() {return 42} function g() { return tbl[0&i]()|0 } var tbl=[f]; return g");
+// Limited by the inability to test really large tables.
+assertAsmTypeFail(USE_ASM + "function f() {return 42} function g() { return tbl[0&0x7fffffff]()|0 } var tbl=[f]; return g");
 
 var f = asmLink(asmCompile(USE_ASM + "function f1(d) {d=+d; return +(d/2.0)} function f2(d) {d=+d; return +(d+10.0)} function g(i,j) { i=i|0;j=+j; return +tbl[i&1](+tbl[(i+1)&1](j)) } var tbl=[f1,f2]; return g"));
 assertEq(f(0,10.2), (10.2+10)/2);
 assertEq(f(1,10.2), (10.2/2)+10);
 
 var f = asmLink(asmCompile('glob','imp', USE_ASM + "var ffi=imp.ffi; function f(){return 13} function g(){return 42} function h(i) { i=i|0; var j=0; ffi(1); j=TBL[i&7]()|0; ffi(1.5); return j|0 } var TBL=[f,g,f,f,f,f,f,f]; return h"), null, {ffi:function(){}});
 for (var i = 0; i < 100; i++)
     assertEq(f(i), (i%8 == 1) ? 42 : 13);
--- a/js/src/jit-test/tests/asm.js/testGlobals.js
+++ b/js/src/jit-test/tests/asm.js/testGlobals.js
@@ -1,51 +1,77 @@
 load(libdir + "asm.js");
 
 assertAsmTypeFail(USE_ASM + "var i; function f(){} return f");
+assertAsmTypeFail(USE_ASM + "const i; function f(){} return f");
 assertEq(asmLink(asmCompile(USE_ASM + "var i=0; function f(){} return f"))(), undefined);
+assertEq(asmLink(asmCompile(USE_ASM + "const i=0; function f(){} return f"))(), undefined);
 assertEq(asmLink(asmCompile(USE_ASM + "var i=42; function f(){ return i|0 } return f"))(), 42);
+assertEq(asmLink(asmCompile(USE_ASM + "const i=42; function f(){ return i|0 } return f"))(), 42);
 assertEq(asmLink(asmCompile(USE_ASM + "var i=4.2; function f(){ return +i } return f"))(), 4.2);
+assertEq(asmLink(asmCompile(USE_ASM + "const i=4.2; function f(){ return +i } return f"))(), 4.2);
 assertAsmTypeFail(USE_ASM + "var i=42; function f(){ return +(i+.1) } return f");
+assertAsmTypeFail(USE_ASM + "const i=42; function f(){ return +(i+.1) } return f");
 assertAsmTypeFail(USE_ASM + "var i=1.2; function f(){ return i|0 } return f");
+assertAsmTypeFail(USE_ASM + "const i=1.2; function f(){ return i|0 } return f");
 assertAsmTypeFail(USE_ASM + "var i=0; function f(e){ e=+e; i=e } return f");
+assertAsmTypeFail(USE_ASM + "const i=0; function f(e){ e=+e; i=e } return f");
 assertAsmTypeFail(USE_ASM + "var d=0.1; function f(i){ i=i|0; d=i } return f");
+assertAsmTypeFail(USE_ASM + "const d=0.1; function f(i){ i=i|0; d=i } return f");
 assertEq(asmLink(asmCompile(USE_ASM + "var i=13; function f(j) { j=j|0; i=j; return i|0 } return f"))(42), 42);
+assertAsmTypeFail(USE_ASM + "const i=13; function f(j) { j=j|0; i=j; return i|0 } return f");
+assertAsmTypeFail(USE_ASM + "const c=0,i=13; function f(j) { j=j|0; i=j; return i|0 } return f");
 assertEq(asmLink(asmCompile(USE_ASM + "var d=.1; function f(e) { e=+e; d=e; return +e } return f"))(42.1), 42.1);
+assertAsmTypeFail(USE_ASM + "const d=.1; function f(e) { e=+e; d=e; return +e } return f");
+assertAsmTypeFail(USE_ASM + "const c=0, d=.1; function f(e) { e=+e; d=e; return +e } return f");
+assertEq(asmLink(asmCompile(USE_ASM + "var i=13; function f(i, j) { i=i|0; j=j|0; i=j; return i|0 } return f"))(42,43), 43);
+assertEq(asmLink(asmCompile(USE_ASM + "var i=13; function f(j) { j=j|0; var i=0; i=j; return i|0 } return f"))(42), 42);
 
 var f = asmLink(asmCompile(USE_ASM + "var i=13; function f(j) { j=j|0; if ((j|0) != -1) { i=j } else { return i|0 } return 0 } return f"));
 assertEq(f(-1), 13);
 assertEq(f(42), 0);
 assertEq(f(-1), 42);
 
 assertAsmTypeFail('global', USE_ASM + "var i=global; function f() { return i|0 } return f");
+assertAsmTypeFail('global', USE_ASM + "const i=global; function f() { return i|0 } return f");
 assertAsmTypeFail('global', USE_ASM + "var i=global|0; function f() { return i|0 } return f");
+assertAsmTypeFail('global', USE_ASM + "const i=global|0; function f() { return i|0 } return f");
 assertAsmTypeFail('global', USE_ASM + "var j=0;var i=j.i|0; function f() { return i|0 } return f");
 assertAsmTypeFail('global', USE_ASM + "var i=global.i|0; function f() { return i|0 } return f");
+assertAsmTypeFail('global', USE_ASM + "const i=global.i|0; function f() { return i|0 } return f");
 assertAsmTypeFail('global', USE_ASM + "var i=global.i|0; function f() { return i|0 } return f");
 assertAsmTypeFail('global', USE_ASM + 'var i=global.Infinity; function f() { i = 0.0 } return f');
+assertAsmTypeFail('global', USE_ASM + 'const i=global.Infinity; function f() { i = 0.0 } return f');
 assertAsmLinkAlwaysFail(asmCompile('global', USE_ASM + 'var i=global.Infinity; function f() { return +i } return f'), undefined);
+assertAsmLinkAlwaysFail(asmCompile('global', USE_ASM + 'const i=global.Infinity; function f() { return +i } return f'), undefined);
 assertAsmLinkAlwaysFail(asmCompile('global', USE_ASM + 'var i=global.Infinity; function f() { return +i } return f'), null);
+assertAsmLinkAlwaysFail(asmCompile('global', USE_ASM + 'const i=global.Infinity; function f() { return +i } return f'), null);
 assertAsmLinkFail(asmCompile('global', USE_ASM + 'var i=global.Infinity; function f() { return +i } return f'), 3);
 assertAsmLinkFail(asmCompile('global', USE_ASM + 'var i=global.Infinity; function f() { return +i } return f'), {});
 assertAsmLinkFail(asmCompile('global', USE_ASM + 'var i=global.Infinity; function f() { return +i } return f'), {Infinity:NaN});
 assertAsmLinkFail(asmCompile('global', USE_ASM + 'var i=global.Infinity; function f() { return +i } return f'), {Infinity:-Infinity});
 assertEq(asmLink(asmCompile('global', USE_ASM + 'var i=global.Infinity; function f() { return +i } return f'), {Infinity:Infinity})(), Infinity);
+assertEq(asmLink(asmCompile('global', USE_ASM + 'const i=global.Infinity; function f() { return +i } return f'), {Infinity:Infinity})(), Infinity);
 assertEq(asmLink(asmCompile('global', USE_ASM + 'var i=global.Infinity; function f() { return +i } return f'), this)(), Infinity);
+assertEq(asmLink(asmCompile('global', USE_ASM + 'const i=global.Infinity; function f() { return +i } return f'), this)(), Infinity);
 assertAsmLinkAlwaysFail(asmCompile('global', USE_ASM + 'var i=global.NaN; function f() { return +i } return f'), undefined);
 assertAsmLinkAlwaysFail(asmCompile('global', USE_ASM + 'var i=global.NaN; function f() { return +i } return f'), null);
 assertAsmLinkFail(asmCompile('global', USE_ASM + 'var i=global.NaN; function f() { return +i } return f'), 3);
 assertAsmLinkFail(asmCompile('global', USE_ASM + 'var i=global.NaN; function f() { return +i } return f'), {});
 assertAsmLinkFail(asmCompile('global', USE_ASM + 'var i=global.NaN; function f() { return +i } return f'), {Infinity:Infinity});
 assertAsmLinkFail(asmCompile('global', USE_ASM + 'var i=global.NaN; function f() { return +i } return f'), {Infinity:-Infinity});
 assertEq(asmLink(asmCompile('global', USE_ASM + 'var i=global.NaN; function f() { return +i } return f'), {NaN:NaN})(), NaN);
+assertEq(asmLink(asmCompile('global', USE_ASM + 'const i=global.NaN; function f() { return +i } return f'), {NaN:NaN})(), NaN);
 assertEq(asmLink(asmCompile('global', USE_ASM + 'var i=global.NaN; function f() { return +i } return f'), this)(), NaN);
+assertEq(asmLink(asmCompile('global', USE_ASM + 'const i=global.NaN; function f() { return +i } return f'), this)(), NaN);
 
 assertAsmLinkFail(asmCompile('glob','foreign','buf', USE_ASM + 'var t = new glob.Int32Array(buf); function f() {} return f'), {get Int32Array(){return Int32Array}}, null, new ArrayBuffer(4096))
+assertAsmLinkFail(asmCompile('glob','foreign','buf', USE_ASM + 'const t = new glob.Int32Array(buf); function f() {} return f'), {get Int32Array(){return Int32Array}}, null, new ArrayBuffer(4096))
 assertAsmLinkFail(asmCompile('glob','foreign','buf', USE_ASM + 'var t = new glob.Int32Array(buf); function f() {} return f'), new Proxy({}, {get:function() {return Int32Array}}), null, new ArrayBuffer(4096))
+assertAsmLinkFail(asmCompile('glob','foreign','buf', USE_ASM + 'const t = new glob.Int32Array(buf); function f() {} return f'), new Proxy({}, {get:function() {return Int32Array}}), null, new ArrayBuffer(4096))
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var t = glob.Math.sin; function f() {} return f'), {get Math(){return Math}});
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var t = glob.Math.sin; function f() {} return f'), new Proxy({}, {get:function(){return Math}}));
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var t = glob.Math.sin; function f() {} return f'), {Math:{get sin(){return Math.sin}}});
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var t = glob.Math.sin; function f() {} return f'), {Math:new Proxy({}, {get:function(){return Math.sin}})});
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var t = glob.Infinity; function f() {} return f'), {get Infinity(){return Infinity}});
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var t = glob.Math.sin; function f() {} return f'), new Proxy({}, {get:function(){return Infinity}}));
 assertAsmLinkFail(asmCompile('glob','foreign', USE_ASM + 'var i = foreign.x|0; function f() {} return f'), null, {get x(){return 42}})
 assertAsmLinkFail(asmCompile('glob','foreign', USE_ASM + 'var i = +foreign.x; function f() {} return f'), null, {get x(){return 42}})
@@ -56,19 +82,23 @@ assertAsmLinkFail(asmCompile('glob','for
 assertAsmTypeFail('global', 'imp', USE_ASM + "var i=imp; function f() { return i|0 } return f");
 assertAsmTypeFail('global', 'imp', USE_ASM + "var j=0;var i=j.i|0; function f() { return i|0 } return f");
 assertAsmLinkAlwaysFail(asmCompile('global','imp', USE_ASM + "var i=imp.i|0; function f() { return i|0 } return f"), null, undefined);
 assertAsmLinkAlwaysFail(asmCompile('global','imp', USE_ASM + "var i=imp.i|0; function f() { return i|0 } return f"), this, undefined);
 assertAsmLinkAlwaysFail(asmCompile('global', 'imp', USE_ASM + "var i=imp.i|0; function f() { return i|0 } return f"), null, null);
 assertAsmLinkAlwaysFail(asmCompile('global', 'imp', USE_ASM + "var i=imp.i|0; function f() { return i|0 } return f"), this, null);
 assertAsmLinkFail(asmCompile('global', 'imp', USE_ASM + "var i=imp.i|0; function f() { return i|0 } return f"), this, 42);
 assertEq(asmLink(asmCompile('global', 'imp', USE_ASM + "var i=imp.i|0; function f() { return i|0 } return f")(null, {i:42})), 42);
+assertEq(asmLink(asmCompile('global', 'imp', USE_ASM + "const i=imp.i|0; function f() { return i|0 } return f")(null, {i:42})), 42);
 assertEq(asmLink(asmCompile('global', 'imp', USE_ASM + "var i=imp.i|0; function f() { return i|0 } return f")(null, {i:1.4})), 1);
+assertEq(asmLink(asmCompile('global', 'imp', USE_ASM + "const i=imp.i|0; function f() { return i|0 } return f")(null, {i:1.4})), 1);
 assertEq(asmLink(asmCompile('global', 'imp', USE_ASM + "var i=+imp.i; function f() { return +i } return f")(null, {i:42})), 42);
+assertEq(asmLink(asmCompile('global', 'imp', USE_ASM + "const i=+imp.i; function f() { return +i } return f")(null, {i:42})), 42);
 assertEq(asmLink(asmCompile('global', 'imp', USE_ASM + "var i=+imp.i; function f() { return +i } return f")(this, {i:1.4})), 1.4);
+assertEq(asmLink(asmCompile('global', 'imp', USE_ASM + "const i=+imp.i; function f() { return +i } return f")(this, {i:1.4})), 1.4);
 assertEq(asmLink(asmCompile(USE_ASM + "var g=0; function f() { var i=42; while (1) { break; } g = i; return g|0 } return f"))(), 42);
 
 var f1 = asmCompile('global', 'foreign', 'heap', USE_ASM + 'var i32 = new global.Int32Array(heap); function g() { return i32[4]|0 } return g');
 var global = this;
 var ab = new ArrayBuffer(4096);
 var p = new Proxy(global,
                   {has:function(name) { f1(global, null, ab); return true},
                    getOwnPropertyDescriptor:function(name) { return {value:Int32Array}}});
--- a/js/src/jit-test/tests/asm.js/testHeapAccess.js
+++ b/js/src/jit-test/tests/asm.js/testHeapAccess.js
@@ -448,8 +448,86 @@ new Int32Array(buf)[0] = 0x55aa5a5a;
 assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i32[(0&0)>>2]|0; } return f'), this, null, buf)(),0x55aa5a5a);
 assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i32[(4&0)>>2]|0; } return f'), this, null, buf)(),0x55aa5a5a);
 assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f1() { i32[0] = 1; return 8; }; function f() { return i32[((f1()|0)&0)>>2]|0; } return f'), this, null, buf)(),1);
 assertEq(new Int32Array(buf)[0], 1);
 
 
 // Bug 882012
 assertEq(asmLink(asmCompile('stdlib', 'foreign', 'heap', USE_ASM + "var id=foreign.id;var doubles=new stdlib.Float64Array(heap);function g(){doubles[0]=+id(2.0);return +doubles[0];}return g"), this, {id: function(x){return x;}}, BUF_64KB)(), 2.0);
+
+
+// Some literal constant paths.
+
+var buf = new ArrayBuffer(8192);
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[0>>4294967295]|0; } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[0>>-1]|0; } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[0>>0x80000000]|0; } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[0>>-2147483648]|0; } return f');
+
+new Uint32Array(buf)[0] = 0xAA;
+new Uint32Array(buf)[0x5A>>2] = 0xA5;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[(0x5A&4294967295)>>2]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=4294967295; function f() { return u32[(0x5A&i)>>2]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[(0x5A&-1)>>2]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-1; function f() { return u32[(0x5A&i)>>2]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[(0x5A&0x80000000)>>2]|0; } return f'), this, null, buf)(),0xAA);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=0x80000000; function f() { return u32[(0x5A&i)>>2]|0; } return f'), this, null, buf)(),0xAA);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[(0x5A&-2147483648)>>2]|0; } return f'), this, null, buf)(),0xAA);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-2147483648; function f() { return u32[(0x5A&i)>>2]|0; } return f'), this, null, buf)(),0xAA);
+
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[(4294967295&0x5A)>>2]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=4294967295; function f() { return u32[(i&0x5A)>>2]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[(-1&0x5A)>>2]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-1; function f() { return u32[(i&0x5A)>>2]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[(0x80000000&0x5A)>>2]|0; } return f'), this, null, buf)(),0xAA);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=0x80000000; function f() { return u32[(i&0x5A)>>2]|0; } return f'), this, null, buf)(),0xAA);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[(-2147483648&0x5A)>>2]|0; } return f'), this, null, buf)(),0xAA);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-2147483648; function f() { return u32[(-2147483648&0x5A)>>2]|0; } return f'), this, null, buf)(),0xAA);
+
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[4294967295>>2]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=4294967295; function f() { return u32[i>>2]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[-1>>2]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-1; function f() { return u32[i>>2]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[0x80000000>>2]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=0x80000000; function f() { return u32[i>>2]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[-2147483648>>2]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-2147483648; function f() { return u32[-2147483648>>2]|0; } return f'), this, null, buf)(),0);
+
+var buf = new ArrayBuffer(8192);
+new Uint8Array(buf)[0] = 0xAA;
+new Uint8Array(buf)[0x5A] = 0xA5;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[0x5A&4294967295]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=4294967295; function f() { return u8[0x5A&i]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[0x5A&-1]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-1; function f() { return u8[0x5A&i]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[0x5A&0x80000000]|0; } return f'), this, null, buf)(),0xAA);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=0x80000000; function f() { return u8[0x5A&i]|0; } return f'), this, null, buf)(),0xAA);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[0x5A&-2147483648]|0; } return f'), this, null, buf)(),0xAA);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-2147483648; function f() { return u8[0x5A&i]|0; } return f'), this, null, buf)(),0xAA);
+
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[4294967295&0x5A]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=4294967295; function f() { return u8[i&0x5A]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[-1&0x5A]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-1; function f() { return u8[i&0x5A]|0; } return f'), this, null, buf)(),0xA5);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[0x80000000&0x5A]|0; } return f'), this, null, buf)(),0xAA);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=0x80000000; function f() { return u8[i&0x5A]|0; } return f'), this, null, buf)(),0xAA);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[-2147483648&0x5A]|0; } return f'), this, null, buf)(),0xAA);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-2147483648; function f() { return u8[i&0x5A]|0; } return f'), this, null, buf)(),0xAA);
+
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[4294967295]|0; } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=4294967295; function f() { return u8[i]|0; } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[-1]|0; } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-1; function f() { return u8[i]|0; } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[0x80000000]|0; } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=0x80000000; function f() { return u8[i]|0; } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[-2147483648]|0; } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-2147483648; function f() { return u8[i]|0; } return f');
+
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[4294967295>>0]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=4294967295; function f() { return u8[i>>0]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[-1>>0]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-1; function f() { return u8[i>>0]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[0x80000000>>0]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=0x80000000; function f() { return u8[i>>0]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[-2147483648>>0]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'const i=-2147483648; function f() { return u8[i>>0]|0; } return f'), this, null, buf)(),0);
+
--- a/js/src/jit-test/tests/asm.js/testLiterals.js
+++ b/js/src/jit-test/tests/asm.js/testLiterals.js
@@ -36,11 +36,15 @@ assertAsmTypeFail(USE_ASM + 'function f(
 assertEq(asmLink(asmCompile(USE_ASM + 'function f() { var i=-2147483648; return i|0 } return f'))(), -2147483648);
 assertEq(asmLink(asmCompile(USE_ASM + 'function f() { var i=4294967295; return i|0 } return f'))(), 4294967295|0);
 assertAsmTypeFail(USE_ASM + 'function f(i) { i=i|0; return (i+-2147483649)|0 } return f');
 assertAsmTypeFail(USE_ASM + 'function f(i) { i=i|0; return (i+4294967296)|0 } return f');
 assertEq(asmLink(asmCompile(USE_ASM + 'function f(i) { i=i|0; return (i+-2147483648)|0 } return f'))(0), -2147483648);
 assertEq(asmLink(asmCompile(USE_ASM + 'function f(i) { i=i|0; return (i+4294967295)|0 } return f'))(0), 4294967295|0);
 
 assertAsmTypeFail(USE_ASM + 'var i=-2147483649; function f() { return i|0 } return f');
+assertAsmTypeFail(USE_ASM + 'const i=-2147483649; function f() { return i|0 } return f');
 assertAsmTypeFail(USE_ASM + 'var i=4294967296; function f() { return i|0 } return f');
+assertAsmTypeFail(USE_ASM + 'const i=4294967296; function f() { return i|0 } return f');
 assertEq(asmLink(asmCompile(USE_ASM + 'var i=-2147483648; function f() { return i|0 } return f'))(), -2147483648);
+assertEq(asmLink(asmCompile(USE_ASM + 'const i=-2147483648; function f() { return i|0 } return f'))(), -2147483648);
 assertEq(asmLink(asmCompile(USE_ASM + 'var i=4294967295; function f() { return i|0 } return f'))(), 4294967295|0);
+assertEq(asmLink(asmCompile(USE_ASM + 'const i=4294967295; function f() { return i|0 } return f'))(), 4294967295|0);
--- a/js/src/jit-test/tests/asm.js/testMathLib.js
+++ b/js/src/jit-test/tests/asm.js/testMathLib.js
@@ -4,16 +4,17 @@ function testUnary(f, g) {
     var numbers = [NaN, Infinity, -Infinity, -10000, -3.4, -0, 0, 3.4, 10000];
     for (n of numbers)
         assertEq(f(n), g(n));
 }
 
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var sq=glob.Math.sin; function f(d) { d=+d; return +sq(d) } return f'), {Math:{sin:Math.sqrt}});
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var sq=glob.Math.sin; function f(d) { d=+d; return +sq(d) } return f'), {Math:{sin:null}});
 testUnary(asmLink(asmCompile('glob', USE_ASM + 'var sq=glob.Math.sin; function f(d) { d=+d; return +sq(d) } return f'), {Math:{sin:Math.sin}}), Math.sin);
+testUnary(asmLink(asmCompile('glob', USE_ASM + 'const sq=glob.Math.sin; function f(d) { d=+d; return +sq(d) } return f'), {Math:{sin:Math.sin}}), Math.sin);
 
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var co=glob.Math.cos; function f(d) { d=+d; return +co(d) } return f'), {Math:{cos:Math.sqrt}});
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var co=glob.Math.cos; function f(d) { d=+d; return +co(d) } return f'), {Math:{cos:null}});
 testUnary(asmLink(asmCompile('glob', USE_ASM + 'var co=glob.Math.cos; function f(d) { d=+d; return +co(d) } return f'), {Math:{cos:Math.cos}}), Math.cos);
 
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var ta=glob.Math.tan; function f(d) { d=+d; return +ta(d) } return f'), {Math:{tan:Math.sqrt}});
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var ta=glob.Math.tan; function f(d) { d=+d; return +ta(d) } return f'), {Math:{tan:null}});
 testUnary(asmLink(asmCompile('glob', USE_ASM + 'var ta=glob.Math.tan; function f(d) { d=+d; return +ta(d) } return f'), {Math:{tan:Math.tan}}), Math.tan);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/baseline/bug916039.js
@@ -0,0 +1,4 @@
+(function() {
+    "use strict";
+    assertEq(eval("this"), undefined);
+})();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/proxy/bug897403.js
@@ -0,0 +1,3 @@
+var f = (function () {}).bind({});
+var p = new Proxy(f, {});
+Object.defineProperty(p, "caller", {get: function(){}});
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -141,17 +141,17 @@ CallArgList(ParseNode *pn)
 {
     JS_ASSERT(pn->isKind(PNK_CALL));
     return NextNode(ListHead(pn));
 }
 
 static inline ParseNode *
 VarListHead(ParseNode *pn)
 {
-    JS_ASSERT(pn->isKind(PNK_VAR));
+    JS_ASSERT(pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST));
     return ListHead(pn);
 }
 
 static inline ParseNode *
 CaseExpr(ParseNode *pn)
 {
     JS_ASSERT(pn->isKind(PNK_CASE) || pn->isKind(PNK_DEFAULT));
     return BinaryLeft(pn);
@@ -365,28 +365,29 @@ PeekToken(AsmJSParser &parser)
 {
     TokenStream &ts = parser.tokenStream;
     while (ts.peekToken(TokenStream::Operand) == TOK_SEMI)
         ts.consumeKnownToken(TOK_SEMI);
     return ts.peekToken(TokenStream::Operand);
 }
 
 static bool
-ParseVarStatement(AsmJSParser &parser, ParseNode **var)
-{
-    if (PeekToken(parser) != TOK_VAR) {
+ParseVarOrConstStatement(AsmJSParser &parser, ParseNode **var)
+{
+    TokenKind tk = PeekToken(parser);
+    if (tk != TOK_VAR && tk != TOK_CONST) {
         *var = NULL;
         return true;
     }
 
     *var = parser.statement();
     if (!*var)
         return false;
 
-    JS_ASSERT((*var)->isKind(PNK_VAR));
+    JS_ASSERT((*var)->isKind(PNK_VAR) || (*var)->isKind(PNK_CONST));
     return true;
 }
 
 /*****************************************************************************/
 
 namespace {
 
 // Respresents the type of a general asm.js expression.
@@ -818,28 +819,28 @@ ExtractNumericLiteral(ParseNode *pn)
         JS_ASSERT(i64 <= UINT32_MAX);
         return NumLit(NumLit::BigUnsigned, Int32Value(uint32_t(i64)));
     }
     JS_ASSERT(i64 >= INT32_MIN);
     return NumLit(NumLit::NegativeInt, Int32Value(i64));
 }
 
 static inline bool
-IsLiteralUint32(ParseNode *pn, uint32_t *u32)
+IsLiteralInt(ParseNode *pn, uint32_t *u32)
 {
     if (!IsNumericLiteral(pn))
         return false;
 
     NumLit literal = ExtractNumericLiteral(pn);
     switch (literal.which()) {
       case NumLit::Fixnum:
       case NumLit::BigUnsigned:
+      case NumLit::NegativeInt:
         *u32 = uint32_t(literal.toInt32());
         return true;
-      case NumLit::NegativeInt:
       case NumLit::Double:
       case NumLit::OutOfRangeInt:
         return false;
     }
 
     MOZ_ASSUME_UNREACHABLE("Bad literal type");
 }
 
@@ -1014,16 +1015,19 @@ class MOZ_STACK_CLASS ModuleCompiler
         enum Which { Variable, Function, FuncPtrTable, FFI, ArrayView, MathBuiltin, Constant };
 
       private:
         Which which_;
         union {
             struct {
                 uint32_t index_;
                 VarType::Which type_;
+                bool isConst_;
+                bool isLitConst_;
+                Value litConstValue_;
             } var;
             uint32_t funcIndex_;
             uint32_t funcPtrTableIndex_;
             uint32_t ffiIndex_;
             ArrayBufferView::ViewType viewType_;
             AsmJSMathBuiltin mathBuiltin_;
             double constant_;
         } u;
@@ -1040,16 +1044,29 @@ class MOZ_STACK_CLASS ModuleCompiler
         VarType varType() const {
             JS_ASSERT(which_ == Variable);
             return VarType(u.var.type_);
         }
         uint32_t varIndex() const {
             JS_ASSERT(which_ == Variable);
             return u.var.index_;
         }
+        bool varIsConstant() const {
+            JS_ASSERT(which_ == Variable);
+            return u.var.isConst_;
+        }
+        bool varIsLitConstant() const {
+            JS_ASSERT(which_ == Variable);
+            return u.var.isLitConst_;
+        }
+        const Value &litConstValue() const {
+            JS_ASSERT(which_ == Variable);
+            JS_ASSERT(u.var.isLitConst_);
+            return u.var.litConstValue_;
+        }
         uint32_t funcIndex() const {
             JS_ASSERT(which_ == Function);
             return u.funcIndex_;
         }
         uint32_t funcPtrTableIndex() const {
             JS_ASSERT(which_ == FuncPtrTable);
             return u.funcPtrTableIndex_;
         }
@@ -1356,36 +1373,44 @@ class MOZ_STACK_CLASS ModuleCompiler
     /***************************************************** Mutable interface */
 
     void initModuleFunctionName(PropertyName *name) { moduleFunctionName_ = name; }
 
     void initGlobalArgumentName(PropertyName *n) { module_->initGlobalArgumentName(n); }
     void initImportArgumentName(PropertyName *n) { module_->initImportArgumentName(n); }
     void initBufferArgumentName(PropertyName *n) { module_->initBufferArgumentName(n); }
 
-    bool addGlobalVarInitConstant(PropertyName *varName, VarType type, const Value &v) {
+    bool addGlobalVarInitConstant(PropertyName *varName, VarType type, const Value &v,
+                                  bool isConst) {
         uint32_t index;
         if (!module_->addGlobalVarInitConstant(v, &index))
             return false;
         Global *global = moduleLifo_.new_<Global>(Global::Variable);
         if (!global)
             return false;
         global->u.var.index_ = index;
         global->u.var.type_ = type.which();
+        global->u.var.isConst_ = isConst;
+        global->u.var.isLitConst_ = isConst;
+        if (isConst)
+            global->u.var.litConstValue_ = v;
         return globals_.putNew(varName, global);
     }
-    bool addGlobalVarImport(PropertyName *varName, PropertyName *fieldName, AsmJSCoercion coercion) {
+    bool addGlobalVarImport(PropertyName *varName, PropertyName *fieldName, AsmJSCoercion coercion,
+                            bool isConst) {
         uint32_t index;
         if (!module_->addGlobalVarImport(fieldName, coercion, &index))
             return false;
         Global *global = moduleLifo_.new_<Global>(Global::Variable);
         if (!global)
             return false;
         global->u.var.index_ = index;
         global->u.var.type_ = VarType(coercion).which();
+        global->u.var.isConst_ = isConst;
+        global->u.var.isLitConst_ = false;
         return globals_.putNew(varName, global);
     }
     bool addFunction(PropertyName *name, MoveRef<Signature> sig, Func **func) {
         JS_ASSERT(!finishedFunctionBodies_);
         Global *global = moduleLifo_.new_<Global>(Global::Function);
         if (!global)
             return false;
         global->u.funcIndex_ = functions_.length();
@@ -1985,19 +2010,26 @@ class FunctionCompiler
         if (chk == NO_BOUNDS_CHECK)
             store->setSkipBoundsCheck(true);
     }
 
     MDefinition *loadGlobalVar(const ModuleCompiler::Global &global)
     {
         if (!curBlock_)
             return NULL;
+        if (global.varIsLitConstant()) {
+            JS_ASSERT(global.litConstValue().isNumber());
+            MConstant *constant = MConstant::New(global.litConstValue());
+            curBlock_->add(constant);
+            return constant;
+        }
         MIRType type = global.varType().toMIRType();
         unsigned globalDataOffset = module().globalVarIndexToGlobalDataOffset(global.varIndex());
-        MAsmJSLoadGlobalVar *load = MAsmJSLoadGlobalVar::New(type, globalDataOffset);
+        MAsmJSLoadGlobalVar *load = MAsmJSLoadGlobalVar::New(type, globalDataOffset,
+                                                             global.varIsConstant());
         curBlock_->add(load);
         return load;
     }
 
     void storeGlobalVar(const ModuleCompiler::Global &global, MDefinition *v)
     {
         if (!curBlock_)
             return;
@@ -2687,33 +2719,34 @@ CheckPrecedingStatements(ModuleCompiler 
 
     if (ListLength(stmtList) != 0)
         return m.fail(ListHead(stmtList), "invalid asm.js statement");
 
     return true;
 }
 
 static bool
-CheckGlobalVariableInitConstant(ModuleCompiler &m, PropertyName *varName, ParseNode *initNode)
+CheckGlobalVariableInitConstant(ModuleCompiler &m, PropertyName *varName, ParseNode *initNode,
+                                bool isConst)
 {
     NumLit literal = ExtractNumericLiteral(initNode);
     VarType type;
     switch (literal.which()) {
       case NumLit::Fixnum:
       case NumLit::NegativeInt:
       case NumLit::BigUnsigned:
         type = VarType::Int;
         break;
       case NumLit::Double:
         type = VarType::Double;
         break;
       case NumLit::OutOfRangeInt:
         return m.fail(initNode, "global initializer is out of representable integer range");
     }
-    return m.addGlobalVarInitConstant(varName, type, literal.value());
+    return m.addGlobalVarInitConstant(varName, type, literal.value(), isConst);
 }
 
 static bool
 CheckTypeAnnotation(ModuleCompiler &m, ParseNode *coercionNode, AsmJSCoercion *coercion,
                     ParseNode **coercedExpr = NULL)
 {
     switch (coercionNode->getKind()) {
       case PNK_BITOR: {
@@ -2739,17 +2772,18 @@ CheckTypeAnnotation(ModuleCompiler &m, P
       }
       default:;
     }
 
     return m.fail(coercionNode, "in coercion expression, the expression must be of the form +x or x|0");
 }
 
 static bool
-CheckGlobalVariableInitImport(ModuleCompiler &m, PropertyName *varName, ParseNode *initNode)
+CheckGlobalVariableInitImport(ModuleCompiler &m, PropertyName *varName, ParseNode *initNode,
+                              bool isConst)
 {
     AsmJSCoercion coercion;
     ParseNode *coercedExpr;
     if (!CheckTypeAnnotation(m, initNode, &coercion, &coercedExpr))
         return false;
 
     if (!coercedExpr->isKind(PNK_DOT))
         return m.failName(coercedExpr, "invalid import expression for global '%s'", varName);
@@ -2758,17 +2792,17 @@ CheckGlobalVariableInitImport(ModuleComp
     PropertyName *field = DotMember(coercedExpr);
 
     PropertyName *importName = m.module().importArgumentName();
     if (!importName)
         return m.fail(coercedExpr, "cannot import without an asm.js foreign parameter");
     if (!IsUseOfName(base, importName))
         return m.failName(coercedExpr, "base of import expression must be '%s'", importName);
 
-    return m.addGlobalVarImport(varName, field, coercion);
+    return m.addGlobalVarImport(varName, field, coercion, isConst);
 }
 
 static bool
 CheckNewArrayView(ModuleCompiler &m, PropertyName *varName, ParseNode *newExpr)
 {
     ParseNode *ctorExpr = ListHead(newExpr);
     if (!ctorExpr->isKind(PNK_DOT))
         return m.fail(ctorExpr, "only valid 'new' import is 'new global.*Array(buf)'");
@@ -2845,54 +2879,54 @@ CheckGlobalDotImport(ModuleCompiler &m, 
 
     if (IsUseOfName(base, m.module().importArgumentName()))
         return m.addFFI(varName, field);
 
     return m.fail(initNode, "expecting c.y where c is either the global or foreign parameter");
 }
 
 static bool
-CheckModuleGlobal(ModuleCompiler &m, ParseNode *var)
+CheckModuleGlobal(ModuleCompiler &m, ParseNode *var, bool isConst)
 {
     if (!IsDefinition(var))
         return m.fail(var, "import variable names must be unique");
 
     if (!CheckModuleLevelName(m, var, var->name()))
         return false;
 
     ParseNode *initNode = MaybeDefinitionInitializer(var);
     if (!initNode)
         return m.fail(var, "module import needs initializer");
 
     if (IsNumericLiteral(initNode))
-        return CheckGlobalVariableInitConstant(m, var->name(), initNode);
+        return CheckGlobalVariableInitConstant(m, var->name(), initNode, isConst);
 
     if (initNode->isKind(PNK_BITOR) || initNode->isKind(PNK_POS))
-        return CheckGlobalVariableInitImport(m, var->name(), initNode);
+        return CheckGlobalVariableInitImport(m, var->name(), initNode, isConst);
 
     if (initNode->isKind(PNK_NEW))
         return CheckNewArrayView(m, var->name(), initNode);
 
     if (initNode->isKind(PNK_DOT))
         return CheckGlobalDotImport(m, var->name(), initNode);
 
     return m.fail(initNode, "unsupported import expression");
 }
 
 static bool
 CheckModuleGlobals(ModuleCompiler &m)
 {
     while (true) {
         ParseNode *varStmt;
-        if (!ParseVarStatement(m.parser(), &varStmt))
+        if (!ParseVarOrConstStatement(m.parser(), &varStmt))
             return false;
         if (!varStmt)
             break;
         for (ParseNode *var = VarListHead(varStmt); var; var = NextNode(var)) {
-            if (!CheckModuleGlobal(m, var))
+            if (!CheckModuleGlobal(m, var, varStmt->isKind(PNK_CONST)))
                 return false;
         }
     }
 
     return true;
 }
 
 static bool
@@ -3101,25 +3135,49 @@ CheckVarRef(FunctionCompiler &f, ParseNo
             return f.failName(varRef, "'%s' may not be accessed by ordinary expressions", name);
         }
         return true;
     }
 
     return f.failName(varRef, "'%s' not found in local or asm.js module scope", name);
 }
 
+static inline bool
+IsLiteralOrConstInt(FunctionCompiler &f, ParseNode *pn, uint32_t *u32)
+{
+    if (IsLiteralInt(pn, u32))
+        return true;
+
+    if (pn->getKind() != PNK_NAME)
+        return false;
+
+    PropertyName *name = pn->name();
+    const ModuleCompiler::Global *global = f.lookupGlobal(name);
+    if (!global || global->which() != ModuleCompiler::Global::Variable ||
+        !global->varIsLitConstant()) {
+        return false;
+    }
+
+    const Value &v = global->litConstValue();
+    if (!v.isInt32())
+        return false;
+
+    *u32 = (uint32_t) v.toInt32();
+    return true;
+}
+
 static bool
 FoldMaskedArrayIndex(FunctionCompiler &f, ParseNode **indexExpr, int32_t *mask,
                      NeedsBoundsCheck *needsBoundsCheck)
 {
     ParseNode *indexNode = BinaryLeft(*indexExpr);
     ParseNode *maskNode = BinaryRight(*indexExpr);
 
     uint32_t mask2;
-    if (IsLiteralUint32(maskNode, &mask2)) {
+    if (IsLiteralOrConstInt(f, maskNode, &mask2)) {
         // Flag the access to skip the bounds check if the mask ensures that an 'out of
         // bounds' access can not occur based on the current heap length constraint.
         if (mask2 == 0 ||
             CountLeadingZeroes32(f.m().minHeapLength() - 1) <= CountLeadingZeroes32(mask2)) {
             *needsBoundsCheck = NO_BOUNDS_CHECK;
         }
         *mask &= mask2;
         *indexExpr = indexNode;
@@ -3142,17 +3200,17 @@ CheckArrayAccess(FunctionCompiler &f, Pa
 
     const ModuleCompiler::Global *global = f.lookupGlobal(viewName->name());
     if (!global || global->which() != ModuleCompiler::Global::ArrayView)
         return f.fail(viewName, "base of array access must be a typed array view name");
 
     *viewType = global->viewType();
 
     uint32_t pointer;
-    if (IsLiteralUint32(indexExpr, &pointer)) {
+    if (IsLiteralOrConstInt(f, indexExpr, &pointer)) {
         if (pointer > (uint32_t(INT32_MAX) >> TypedArrayShift(*viewType)))
             return f.fail(indexExpr, "constant index out of range");
         pointer <<= TypedArrayShift(*viewType);
         // It is adequate to note pointer+1 rather than rounding up to the next
         // access-size boundary because access is always aligned and the constraint
         // will be rounded up to a larger alignment later.
         f.m().requireHeapLengthToBeAtLeast(uint32_t(pointer) + 1);
         *needsBoundsCheck = NO_BOUNDS_CHECK;
@@ -3166,30 +3224,30 @@ CheckArrayAccess(FunctionCompiler &f, Pa
     int32_t mask = ~((uint32_t(1) << TypedArrayShift(*viewType)) - 1);
 
     MDefinition *pointerDef;
     if (indexExpr->isKind(PNK_RSH)) {
         ParseNode *shiftNode = BinaryRight(indexExpr);
         ParseNode *pointerNode = BinaryLeft(indexExpr);
 
         uint32_t shift;
-        if (!IsLiteralUint32(shiftNode, &shift))
+        if (!IsLiteralInt(shiftNode, &shift))
             return f.failf(shiftNode, "shift amount must be constant");
 
         unsigned requiredShift = TypedArrayShift(*viewType);
         if (shift != requiredShift)
             return f.failf(shiftNode, "shift amount must be %u", requiredShift);
 
         if (pointerNode->isKind(PNK_BITAND))
             FoldMaskedArrayIndex(f, &pointerNode, &mask, needsBoundsCheck);
 
         // Fold a 'literal constant right shifted' now, and skip the bounds check if
         // currently possible. This handles the optimization of many of these uses without
         // the need for range analysis, and saves the generation of a MBitAnd op.
-        if (IsLiteralUint32(pointerNode, &pointer) && pointer <= uint32_t(INT32_MAX)) {
+        if (IsLiteralOrConstInt(f, pointerNode, &pointer) && pointer <= uint32_t(INT32_MAX)) {
             // Cases: b[c>>n], and b[(c&m)>>n]
             pointer &= mask;
             if (pointer < f.m().minHeapLength())
                 *needsBoundsCheck = NO_BOUNDS_CHECK;
             *def = f.constant(Int32Value(pointer));
             return true;
         }
 
@@ -3292,16 +3350,18 @@ CheckAssignName(FunctionCompiler &f, Par
         if (!(rhsType <= lhsVar->type)) {
             return f.failf(lhs, "%s is not a subtype of %s",
                            rhsType.toChars(), lhsVar->type.toType().toChars());
         }
         f.assign(*lhsVar, rhsDef);
     } else if (const ModuleCompiler::Global *global = f.lookupGlobal(name)) {
         if (global->which() != ModuleCompiler::Global::Variable)
             return f.failName(lhs, "'%s' is not a mutable variable", name);
+        if (global->varIsConstant())
+            return f.failName(lhs, "'%s' is a constant variable and not mutable", name);
         if (!(rhsType <= global->varType())) {
             return f.failf(lhs, "%s is not a subtype of %s",
                            rhsType.toChars(), global->varType().toType().toChars());
         }
         f.storeGlobalVar(*global, rhsDef);
     } else {
         return f.failName(lhs, "'%s' not found in local or asm.js module scope", name);
     }
@@ -3555,17 +3615,17 @@ CheckFuncPtrCall(FunctionCompiler &f, Pa
 
     if (!indexExpr->isKind(PNK_BITAND))
         return f.fail(indexExpr, "function-pointer table index expression needs & mask");
 
     ParseNode *indexNode = BinaryLeft(indexExpr);
     ParseNode *maskNode = BinaryRight(indexExpr);
 
     uint32_t mask;
-    if (!IsLiteralUint32(maskNode, &mask) || mask == UINT32_MAX || !IsPowerOfTwo(mask + 1))
+    if (!IsLiteralInt(maskNode, &mask) || mask == UINT32_MAX || !IsPowerOfTwo(mask + 1))
         return f.fail(maskNode, "function-pointer table index mask value must be a power of two");
 
     MDefinition *indexDef;
     Type indexType;
     if (!CheckExpr(f, indexNode, &indexDef, &indexType))
         return false;
 
     if (!indexType.isIntish())
@@ -5182,17 +5242,17 @@ CheckFuncPtrTable(ModuleCompiler &m, Par
     return true;
 }
 
 static bool
 CheckFuncPtrTables(ModuleCompiler &m)
 {
     while (true) {
         ParseNode *varStmt;
-        if (!ParseVarStatement(m.parser(), &varStmt))
+        if (!ParseVarOrConstStatement(m.parser(), &varStmt))
             return false;
         if (!varStmt)
             break;
         for (ParseNode *var = VarListHead(varStmt); var; var = NextNode(var)) {
             if (!CheckFuncPtrTable(m, var))
                 return false;
         }
     }
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -64,16 +64,19 @@ BaselineCompiler::addPCMappingEntry(bool
 }
 
 MethodStatus
 BaselineCompiler::compile()
 {
     IonSpew(IonSpew_BaselineScripts, "Baseline compiling script %s:%d (%p)",
             script->filename(), script->lineno, script.get());
 
+    IonSpew(IonSpew_Codegen, "# Emitting baseline code for script %s:%d",
+            script->filename(), script->lineno);
+
     if (cx->typeInferenceEnabled() && !script->ensureHasBytecodeTypeMap(cx))
         return Method_Error;
 
     // Only need to analyze scripts which are marked |argumensHasVarBinding|, to
     // compute |needsArgsObj| flag.
     if (script->argumentsHasVarBinding()) {
         if (!script->ensureRanAnalysis(cx))
             return Method_Error;
@@ -963,18 +966,18 @@ BaselineCompiler::emit_JSOP_NULL()
 }
 
 bool
 BaselineCompiler::emit_JSOP_THIS()
 {
     // Keep this value in R0
     frame.pushThis();
 
-    // In strict mode function or self-hosted function, |this| is left alone.
-    if (function() && (function()->strict() || function()->isSelfHostedBuiltin()))
+    // In strict mode code or self-hosted functions, |this| is left alone.
+    if (script->strict || (function() && function()->isSelfHostedBuiltin()))
         return true;
 
     Label skipIC;
     // Keep |thisv| in R0
     frame.popRegsAndSync(1);
     // If |this| is already an object, skip the IC.
     masm.branchTestObject(Assembler::Equal, R0, &skipIC);
 
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -5507,16 +5507,18 @@ CodeGenerator::visitRestPar(LRestPar *li
         return false;
 
     return emitRest(lir, temp2, numActuals, temp0, temp1, numFormals, templateObject);
 }
 
 bool
 CodeGenerator::generateAsmJS()
 {
+    IonSpew(IonSpew_Codegen, "# Emitting asm.js code");
+
     // The caller (either another asm.js function or the external-entry
     // trampoline) has placed all arguments in registers and on the stack
     // according to the system ABI. The MAsmJSParameters which represent these
     // parameters have been useFixed()ed to these ABI-specified positions.
     // Thus, there is nothing special to do in the prologue except (possibly)
     // bump the stack.
     if (!generatePrologue())
         return false;
@@ -5547,16 +5549,20 @@ CodeGenerator::generateAsmJS()
     JS_ASSERT(cacheList_.empty());
     JS_ASSERT(safepoints_.size() == 0);
     return true;
 }
 
 bool
 CodeGenerator::generate()
 {
+    IonSpew(IonSpew_Codegen, "# Emitting code for script %s:%d",
+            gen->info().script()->filename(),
+            gen->info().script()->lineno);
+
     if (!safepoints_.init(graph.totalSlotCount()))
         return false;
 
 #if JS_TRACE_LOGGING
     masm.tracelogStart(gen->info().script());
     masm.tracelogLog(TraceLogging::INFO_ENGINE_IONMONKEY);
 #endif
 
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -222,74 +222,87 @@ IonRuntime::initialize(JSContext *cx)
 
     if (!cx->compartment()->ensureIonCompartmentExists(cx))
         return false;
 
     functionWrappers_ = cx->new_<VMWrapperMap>(cx);
     if (!functionWrappers_ || !functionWrappers_->init())
         return false;
 
+    IonSpew(IonSpew_Codegen, "# Emitting exception tail stub");
     exceptionTail_ = generateExceptionTailStub(cx);
     if (!exceptionTail_)
         return false;
 
+    IonSpew(IonSpew_Codegen, "# Emitting bailout tail stub");
     bailoutTail_ = generateBailoutTailStub(cx);
     if (!bailoutTail_)
         return false;
 
     if (cx->runtime()->jitSupportsFloatingPoint) {
+        IonSpew(IonSpew_Codegen, "# Emitting bailout tables");
+
         // Initialize some Ion-only stubs that require floating-point support.
         if (!bailoutTables_.reserve(FrameSizeClass::ClassLimit().classId()))
             return false;
 
         for (uint32_t id = 0;; id++) {
             FrameSizeClass class_ = FrameSizeClass::FromClass(id);
             if (class_ == FrameSizeClass::ClassLimit())
                 break;
             bailoutTables_.infallibleAppend((IonCode *)NULL);
             bailoutTables_[id] = generateBailoutTable(cx, id);
             if (!bailoutTables_[id])
                 return false;
         }
 
+        IonSpew(IonSpew_Codegen, "# Emitting bailout handler");
         bailoutHandler_ = generateBailoutHandler(cx);
         if (!bailoutHandler_)
             return false;
 
+        IonSpew(IonSpew_Codegen, "# Emitting invalidator");
         invalidator_ = generateInvalidator(cx);
         if (!invalidator_)
             return false;
     }
 
+    IonSpew(IonSpew_Codegen, "# Emitting sequential arguments rectifier");
     argumentsRectifier_ = generateArgumentsRectifier(cx, SequentialExecution, &argumentsRectifierReturnAddr_);
     if (!argumentsRectifier_)
         return false;
 
 #ifdef JS_THREADSAFE
+    IonSpew(IonSpew_Codegen, "# Emitting parallel arguments rectifier");
     parallelArgumentsRectifier_ = generateArgumentsRectifier(cx, ParallelExecution, NULL);
     if (!parallelArgumentsRectifier_)
         return false;
 #endif
 
+    IonSpew(IonSpew_Codegen, "# Emitting EnterJIT sequence");
     enterJIT_ = generateEnterJIT(cx, EnterJitOptimized);
     if (!enterJIT_)
         return false;
 
+    IonSpew(IonSpew_Codegen, "# Emitting EnterBaselineJIT sequence");
     enterBaselineJIT_ = generateEnterJIT(cx, EnterJitBaseline);
     if (!enterBaselineJIT_)
         return false;
 
+    IonSpew(IonSpew_Codegen, "# Emitting Pre Barrier for Value");
     valuePreBarrier_ = generatePreBarrier(cx, MIRType_Value);
     if (!valuePreBarrier_)
         return false;
 
+    IonSpew(IonSpew_Codegen, "# Emitting Pre Barrier for Shape");
     shapePreBarrier_ = generatePreBarrier(cx, MIRType_Shape);
     if (!shapePreBarrier_)
         return false;
 
+    IonSpew(IonSpew_Codegen, "# Emitting VM function wrappers");
     for (VMFunction *fun = VMFunction::functions; fun; fun = fun->next) {
         if (!generateVMWrapper(cx, *fun))
             return false;
     }
 
     return true;
 }
 
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -3743,18 +3743,16 @@ IonBuilder::inlineScriptedCall(CallInfo 
 
     // Improve type information of |this| when not set.
     if (callInfo.constructing() && !callInfo.thisArg()->resultTypeSet()) {
         types::StackTypeSet *types = types::TypeScript::ThisTypes(calleeScript);
         if (!types->unknown()) {
             MTypeBarrier *barrier = MTypeBarrier::New(callInfo.thisArg(), cloneTypeSet(types), Bailout_Normal);
             current->add(barrier);
             callInfo.setThis(barrier);
-            // object or missing
-            JS_ASSERT(barrier->type() == MIRType_Object || barrier->type() == MIRType_Value);
         }
     }
 
     // Start inlining.
     LifoAlloc *alloc = GetIonContext()->temp->lifoAlloc();
     CompileInfo *info = alloc->new_<CompileInfo>(calleeScript.get(), target,
                                                  (jsbytecode *)NULL, callInfo.constructing(),
                                                  this->info().executionMode());
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -8429,33 +8429,43 @@ class MAsmJSStoreHeap : public MBinaryIn
     }
 
     MDefinition *ptr() const { return getOperand(0); }
     MDefinition *value() const { return getOperand(1); }
 };
 
 class MAsmJSLoadGlobalVar : public MNullaryInstruction
 {
-    MAsmJSLoadGlobalVar(MIRType type, unsigned globalDataOffset)
-      : globalDataOffset_(globalDataOffset)
+    MAsmJSLoadGlobalVar(MIRType type, unsigned globalDataOffset, bool isConstant)
+      : globalDataOffset_(globalDataOffset), isConstant_(isConstant)
     {
         JS_ASSERT(type == MIRType_Int32 || type == MIRType_Double);
         setResultType(type);
+        if (isConstant)
+            setMovable();
     }
 
     unsigned globalDataOffset_;
+    bool isConstant_;
 
   public:
     INSTRUCTION_HEADER(AsmJSLoadGlobalVar);
 
-    static MAsmJSLoadGlobalVar *New(MIRType type, unsigned globalDataOffset) {
-        return new MAsmJSLoadGlobalVar(type, globalDataOffset);
+    static MAsmJSLoadGlobalVar *New(MIRType type, unsigned globalDataOffset, bool isConstant) {
+        return new MAsmJSLoadGlobalVar(type, globalDataOffset, isConstant);
     }
 
     unsigned globalDataOffset() const { return globalDataOffset_; }
+
+    AliasSet getAliasSet() const {
+        if (isConstant_)
+            return AliasSet::None();
+        else
+            return AliasSet::Store(AliasSet::Any);
+    }
 };
 
 class MAsmJSStoreGlobalVar : public MUnaryInstruction
 {
     MAsmJSStoreGlobalVar(unsigned globalDataOffset, MDefinition *v)
       : MUnaryInstruction(v), globalDataOffset_(globalDataOffset)
     {}
 
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -302,17 +302,17 @@ js::fun_resolve(JSContext *cx, HandleObj
     for (unsigned i = 0; i < ArrayLength(poisonPillProps); i++) {
         const uint16_t offset = poisonPillProps[i];
 
         if (JSID_IS_ATOM(id, OFFSET_TO_NAME(cx->runtime(), offset))) {
             JS_ASSERT(!IsInternalFunctionObject(fun));
 
             PropertyOp getter;
             StrictPropertyOp setter;
-            unsigned attrs = JSPROP_PERMANENT;
+            unsigned attrs = JSPROP_PERMANENT | JSPROP_SHARED;
             if (fun->isInterpretedLazy() && !fun->getOrCreateScript(cx))
                 return false;
             if (fun->isInterpreted() ? fun->strict() : fun->isBoundFunction()) {
                 JSObject *throwTypeError = fun->global().getThrowTypeError();
 
                 getter = CastAsPropertyOp(throwTypeError);
                 setter = CastAsStrictPropertyOp(throwTypeError);
                 attrs |= JSPROP_GETTER | JSPROP_SETTER;
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -564,19 +564,16 @@ class JSScript : public js::gc::Cell
     bool            strict:1; /* code is in strict mode */
     bool            explicitUseStrict:1; /* code has "use strict"; explicitly */
     bool            compileAndGo:1;   /* see Parser::compileAndGo */
     bool            selfHosted:1;     /* see Parser::selfHostingMode */
     bool            bindingsAccessedDynamically:1; /* see FunctionContextFlags */
     bool            funHasExtensibleScope:1;       /* see FunctionContextFlags */
     bool            funNeedsDeclEnvObject:1;       /* see FunctionContextFlags */
     bool            funHasAnyAliasedFormal:1;      /* true if any formalIsAliased(i) */
-    bool            warnedAboutTwoArgumentEval:1; /* have warned about use of
-                                                     obsolete eval(s, o) in
-                                                     this script */
     bool            warnedAboutUndefinedProp:1; /* have warned about uses of
                                                    undefined properties in this
                                                    script */
     bool            hasSingletons:1;  /* script has singleton objects */
     bool            treatAsRunOnce:1; /* script is a lambda to treat as running once. */
     bool            hasRunOnce:1;     /* if treatAsRunOnce, whether script has executed. */
     bool            hasBeenCloned:1;  /* script has been reused for a clone. */
     bool            isActiveEval:1;   /* script came from eval(), and is still active */
--- a/js/src/tests/ecma_6/Math/acosh-approx.js
+++ b/js/src/tests/ecma_6/Math/acosh-approx.js
@@ -257,25 +257,27 @@ var cosh_data = [
     [48862560256, 25.305424481799395],
     [113763549184, 26.150535181949436],
     [161334755328, 26.499894449532565],
     [321933279232, 27.19075733422632],
     [715734122496, 27.989721778208146],
     [1875817529344, 28.953212876533797]
 ];
 
+var sloppy_tolerance = 1000;  // FIXME
+
 for (var [x, y] of cosh_data)
-    assertNear(Math.acosh(x), y);
+    assertNear(Math.acosh(x), y, sloppy_tolerance);
+
+assertNear(Math.acosh(1e300), 691.4686750787737, sloppy_tolerance);
+assertNear(Math.acosh(1.0000000001), 0.000014142136208675862, sloppy_tolerance);
 
 for (var i = 0; i <= 100; i++) {
     var x = (i - 50) / 5;
     var y = Math.cosh(x);
     var z = Math.acosh(y);
-    assertNear(z, Math.abs(x));
+    assertNear(z, Math.abs(x), sloppy_tolerance);
 }
 
 for (var i = 1; i < 20; i++)
-    assertNear(Math.acosh(Math.cosh(i)), i);
-
-assertNear(Math.acosh(1e300), 691.4686750787737);
-assertNear(Math.acosh(1.0000000001), 0.000014142136208675862);
+    assertNear(Math.acosh(Math.cosh(i)), i, sloppy_tolerance);
 
 reportCompare(0, 0, "ok");
--- a/js/src/tests/ecma_6/Math/asinh-approx.js
+++ b/js/src/tests/ecma_6/Math/asinh-approx.js
@@ -277,29 +277,29 @@ var sinh_data = [
     [60601991168, 25.520740767599584],
     [134018236416, 26.31438890085422],
     [204864946176, 26.73876398039979],
     [284346286080, 27.06660583008718],
     [914576637952, 28.234874284944635],
     [1581915832320, 28.78280496108106]
 ];
 
+var sloppy_tolerance = 1000;  // FIXME
+
 for (var [x, y] of sinh_data)
-    assertNear(Math.asinh(x), y);
+    assertNear(Math.asinh(x), y, sloppy_tolerance);
+
+assertNear(Math.asinh(1e300), 691.4686750787737, sloppy_tolerance);
+assertNear(Math.asinh(1e-300), 1e-300, sloppy_tolerance);
+assertNear(Math.asinh(1e-5), 0.000009999999999833334, sloppy_tolerance);
+assertNear(Math.asinh(0.3), 0.29567304756342244, sloppy_tolerance);
+assertNear(Math.asinh(1), 0.881373587019543, sloppy_tolerance);
 
 for (var i = 0; i <= 80; i++) {
     var x = (i - 40) / 4;
-    var y = Math.sinh(x);
-    var z = Math.asinh(y);
-    assertNear(z, x);
+    assertNear(Math.asinh(Math.sinh(x)), x, sloppy_tolerance);
 }
 
 for (var i = -20; i < 20; i++)
-  assertNear(Math.asinh(Math.sinh(i)), i);
-
-assertNear(Math.asinh(1e300), 691.4686750787737);
-assertNear(Math.asinh(1e-300), 1e-300);
-assertNear(Math.asinh(1e-5), 0.000009999999999833334);
-assertNear(Math.asinh(0.3), 0.29567304756342244);
-assertNear(Math.asinh(1), 0.881373587019543);
+    assertNear(Math.asinh(Math.sinh(i)), i, sloppy_tolerance);
 
 reportCompare(0, 0, "ok");
 
--- a/js/src/tests/ecma_6/Math/atanh-approx.js
+++ b/js/src/tests/ecma_6/Math/atanh-approx.js
@@ -1,11 +1,8 @@
-for (var i = -1; i < 1; i += 0.05)
-    assertNear(Math.atanh(Math.tanh(i)), i);
-
 var tanh_data = [
     [-0.9999983310699463, -6.998237084679027],
     [-0.9999978542327881, -6.87257975132917],
     [-0.999992847442627, -6.2705920974657525],
     [-0.9999861717224121, -5.940967614084813],
     [-0.9999828338623047, -5.832855225378502],
     [-0.9999399185180664, -5.20646301208756],
     [-0.9998834133148193, -4.8749821841810785],
@@ -264,15 +261,20 @@ var tanh_data = [
     [0.9928233623504639, 2.8132383539094192],
     [1e-300, 1e-300],
     [0.00001, 0.000010000000000333334],
     [0.3, 0.3095196042031117],
     [1e-30, 1e-30],
     [1e-10, 1e-10],
 ];
 
+var sloppy_tolerance = 10;  // FIXME
+
 for (var [x, y] of tanh_data)
-    assertNear(Math.atanh(x), y);
+    assertNear(Math.atanh(x), y, sloppy_tolerance);
 
-assertNear(Math.atanh(+3 / 5), +Math.log(2));
-assertNear(Math.atanh(-3 / 5), -Math.log(2));
+assertNear(Math.atanh(+3 / 5), +Math.log(2), sloppy_tolerance);
+assertNear(Math.atanh(-3 / 5), -Math.log(2), sloppy_tolerance);
+
+for (var i = -1; i < 1; i += 0.05)
+    assertNear(Math.atanh(Math.tanh(i)), i, sloppy_tolerance);
 
 reportCompare(0, 0, "ok");
--- a/js/src/tests/ecma_6/Math/cbrt-approx.js
+++ b/js/src/tests/ecma_6/Math/cbrt-approx.js
@@ -1,18 +1,19 @@
 assertEq(Math.cbrt(1), 1);
 assertEq(Math.cbrt(-1), -1);
 
-assertNear(Math.cbrt(1e-300), 1e-100);
-assertNear(Math.cbrt(-1e-300), -1e-100);
+var sloppy_tolerance = 200;  // FIXME
+
+assertNear(Math.cbrt(1e-300), 1e-100, sloppy_tolerance);
+assertNear(Math.cbrt(-1e-300), -1e-100, sloppy_tolerance);
 
 var cbrt_data = [
     [ Math.E, 1.3956124250860895 ], 
     [ Math.PI, 1.4645918875615231 ], 
     [ Math.LN2, 0.8849970445005177 ], 
     [ Math.SQRT2, 1.1224620483093728 ]
 ];
 
 for (var [x, y] of cbrt_data)
-    assertNear(Math.cbrt(x), y);
+    assertNear(Math.cbrt(x), y, sloppy_tolerance);
 
 reportCompare(0, 0, "ok");
-
--- a/js/src/tests/ecma_6/Math/cosh-approx.js
+++ b/js/src/tests/ecma_6/Math/cosh-approx.js
@@ -1,14 +1,14 @@
-for (var i = -20; i < 20; i++)
-    assertNear(Math.cosh(i), (Math.exp(i) + Math.exp(-i)) / 2);
+var sloppy_tolerance = 100;
 
-assertNear(Math.cosh(1e5), Infinity);
-assertNear(Math.cosh(1e-30), 1);
-assertNear(Math.cosh(1e-10), 1);
+assertEq(Math.cosh(1000), Infinity);
+assertEq(Math.cosh(Number.MAX_VALUE), Infinity);
+assertNear(Math.cosh(1e-30), 1, sloppy_tolerance);
+assertNear(Math.cosh(1e-10), 1, sloppy_tolerance);
 
 var cosh_data = [
     [0.0016914556651292944, 1.0000014305114746],
     [0.001953124689559275, 1.0000019073486328],
     [0.003782208044661295, 1.000007152557373],
     [0.005258943946801101, 1.000013828277588],
     [0.005859366618129203, 1.0000171661376953],
     [0.010961831992188852, 1.0000600814819336],
@@ -265,11 +265,14 @@ var cosh_data = [
     [26.150535181949436, 113763549183.99998],
     [26.499894449532565, 161334755328.00018],
     [27.19075733422632,  321933279232.0004],
     [27.989721778208146, 715734122496],
     [28.953212876533797, 1875817529343.9976],
 ];
 
 for (var [x, y] of cosh_data)
-    assertNear(Math.cosh(x), y);
+    assertNear(Math.cosh(x), y, sloppy_tolerance);
+
+for (var i = -20; i < 20; i++)
+    assertNear(Math.cosh(i), (Math.exp(i) + Math.exp(-i)) / 2, sloppy_tolerance);
 
 reportCompare(0, 0, "ok");
--- a/js/src/tests/ecma_6/Math/hypot-approx.js
+++ b/js/src/tests/ecma_6/Math/hypot-approx.js
@@ -1,17 +1,16 @@
 // |reftest| skip
 // Math.hypot is disabled pending the resolution of spec issues (bug 896264).
 
-for (var i = -20; i < 20; i++)
+for (var i = -20; i < 20; i++) {
     assertEq(Math.hypot(+0, i), Math.abs(i));
-
-for (var i = -20; i < 20; i++)
     assertEq(Math.hypot(-0, i), Math.abs(i));
-
-for (var i = 1, j = 1; i < 2; i += 0.05, j += 0.05)
-    assertNear(Math.hypot(i, j), Math.sqrt(i * i + j * j));
+}
 
 assertNear(Math.hypot(1e300, 1e300), 1.4142135623730952e+300);
 assertNear(Math.hypot(1e-300, 1e-300), 1.414213562373095e-300);
 assertNear(Math.hypot(1e3, 1e-3), 1000.0000000005);
 
+for (var i = 1, j = 1; i < 2; i += 0.05, j += 0.05)
+    assertNear(Math.hypot(i, j), Math.sqrt(i * i + j * j));
+
 reportCompare(0, 0, "ok");
--- a/js/src/tests/ecma_6/Math/log10-approx.js
+++ b/js/src/tests/ecma_6/Math/log10-approx.js
@@ -1,9 +1,9 @@
-for (var i = -10; i < 10; i++)
-    assertNear(Math.log10(Math.pow(10, i)), i);
-
 assertNear(Math.log10(2), 0.3010299956639812);
 assertNear(Math.log10(7), 0.8450980400142568);
 assertNear(Math.log10(Math.E), Math.LOG10E);
 
+for (var i = -10; i < 10; i++)
+    assertNear(Math.log10(Math.pow(10, i)), i);
+
 reportCompare(0, 0, 'ok');
 
--- a/js/src/tests/ecma_6/Math/shell.js
+++ b/js/src/tests/ecma_6/Math/shell.js
@@ -1,11 +1,74 @@
 // The nearest representable values to +1.0.
 const ONE_PLUS_EPSILON = 1 + Math.pow(2, -52);  // 0.9999999999999999
 const ONE_MINUS_EPSILON = 1 - Math.pow(2, -53);  // 1.0000000000000002
 
-function assertNear(actual, expected) {
-    var error = Math.abs(actual - expected);
+{
+    var fail = function (msg) {
+        var exc = new Error(msg);
+        try {
+            // Try to improve on exc.fileName and .lineNumber; leave exc.stack
+            // alone. We skip two frames: fail() and its caller, an assertX()
+            // function.
+            var frames = exc.stack.trim().split("\n");
+            if (frames.length > 2) {
+                var m = /@([^@:]*):([0-9]+)$/.exec(frames[2]);
+                if (m) {
+                    exc.fileName = m[1];
+                    exc.lineNumber = +m[2];
+                }
+            }
+        } catch (ignore) { throw ignore;}
+        throw exc;
+    };
+
+    var ENDIAN;  // 0 for little-endian, 1 for big-endian.
 
-    if (error > 1e-300 && error > Math.abs(actual) * 1e-12)
-        throw 'Assertion failed: got "' + actual + '", expected "' + expected + '" (rel error = ' + (error / Math.abs(actual)) + ')';
+    // Return the difference between the IEEE 754 bit-patterns for a and b.
+    //
+    // This is meaningful when a and b are both finite and have the same
+    // sign. Then the following hold:
+    //
+    //   * If a === b, then diff(a, b) === 0.
+    //
+    //   * If a !== b, then diff(a, b) === 1 + the number of representable values
+    //                                         between a and b.
+    //
+    var f = new Float64Array([0, 0]);
+    var u = new Uint32Array(f.buffer);
+    var diff = function (a, b) {
+        f[0] = a;
+        f[1] = b;
+        //print(u[1].toString(16) + u[0].toString(16) + " " + u[3].toString(16) + u[2].toString(16));
+        return Math.abs((u[3-ENDIAN] - u[1-ENDIAN]) * 0x100000000 + u[2+ENDIAN] - u[0+ENDIAN]);
+    };
+
+    // Set ENDIAN to the platform's endianness.
+    ENDIAN = 0;  // try little-endian first
+    if (diff(2, 4) === 0x100000)  // exact wrong answer we'll get on a big-endian platform
+        ENDIAN = 1;
+    assertEq(diff(2,4), 0x10000000000000);
+    assertEq(diff(0, Number.MIN_VALUE), 1);
+    assertEq(diff(1, ONE_PLUS_EPSILON), 1);
+    assertEq(diff(1, ONE_MINUS_EPSILON), 1);
+
+    var assertNear = function assertNear(a, b, tolerance=1) {
+        if (!Number.isFinite(b)) {
+            fail("second argument to assertNear (expected value) must be a finite number");
+        } else if (Number.isNaN(a)) {
+            fail("got NaN, expected a number near " + b);
+        } else if (!Number.isFinite(a)) {
+            if (b * Math.sign(a) < Number.MAX_VALUE)
+                fail("got " + a + ", expected a number near " + b);
+        } else {
+            // When the two arguments do not have the same sign bit, diff()
+            // returns some huge number. So if b is positive or negative 0,
+            // make target the zero that has the same sign bit as a.
+            var target = b === 0 ? a * 0 : b;
+            var err = diff(a, target);
+            if (err > tolerance) {
+                fail("got " + a + ", expected a number near " + b +
+                     " (relative error: " + err + ")");
+            }
+        }
+    };
 }
-
--- a/js/src/tests/ecma_6/Math/sinh-approx.js
+++ b/js/src/tests/ecma_6/Math/sinh-approx.js
@@ -1,14 +1,16 @@
+var sloppy_tolerance = 100;
 for (var i = -20; i < 20; i++)
-    assertNear(Math.sinh(i), (Math.exp(i) - Math.exp(-i)) / 2);
+    assertNear(Math.sinh(i), (Math.exp(i) - Math.exp(-i)) / 2, sloppy_tolerance);
 
-assertNear(Math.sinh(1e5), Infinity);
-assertNear(Math.sinh(1e-30), 1e-30);
-assertNear(Math.sinh(1e-10), 1e-10);
+assertEq(Math.sinh(1000), Infinity);
+assertEq(Math.sinh(Number.MAX_VALUE), Infinity);
+assertNear(Math.sinh(1e-30), 1e-30, sloppy_tolerance);
+assertNear(Math.sinh(1e-10), 1e-10, sloppy_tolerance);
 
 var sinh_data = [
     [-6.902103625349695, -497.1816406250001],
     [-6.898143347143859, -495.21655273437517],
     [-6.883664481302669, -488.0980224609375],
     [-6.880304842490273, -486.46093750000006],
     [-6.871561546509046, -482.2261962890624],
     [-6.841973895837549, -468.167236328125],
@@ -285,12 +287,12 @@ var sinh_data = [
     [26.31438890085422,  134018236416.00002],
     [26.73876398039979,  204864946175.99973],
     [27.06660583008718,  284346286080.00024],
     [28.234874284944635, 914576637951.9989],
     [28.78280496108106,  1581915832319.9973]
 ];
 
 for (var [x, y] of sinh_data)
-    assertNear(Math.sinh(x), y);
+    assertNear(Math.sinh(x), y, sloppy_tolerance);
 
 reportCompare(0, 0, "ok");
 
--- a/js/src/tests/ecma_6/Math/tanh-approx.js
+++ b/js/src/tests/ecma_6/Math/tanh-approx.js
@@ -1,10 +1,15 @@
-for (var i = -20; i < 20; i++)
-    assertNear(Math.tanh(i), (Math.exp(i) - Math.exp(-i)) / (Math.exp(i) + Math.exp(-i)));
+var sloppy_tolerance = 4;
+
+for (var i = -20; i < 20; i++) {
+    assertNear(Math.tanh(i),
+               (Math.exp(i) - Math.exp(-i)) / (Math.exp(i) + Math.exp(-i)),
+               sloppy_tolerance);
+}
 
 assertEq(Math.tanh(1e300), 1);
 
 var tanh_data = [
     [-0.9999983310699463, -6.998237084679027],
     [-0.9999978542327881, -6.87257975132917],
     [-0.999992847442627, -6.2705920974657525],
     [-0.9999861717224121, -5.940967614084813],
@@ -267,11 +272,11 @@ var tanh_data = [
     [1e-300, 1e-300],
     [0.00001, 0.000010000000000333334],
     [0.3, 0.3095196042031117],
     [1e-30, 1e-30],
     [1e-10, 1e-10],
 ];
 
 for (var [x, y] of tanh_data)
-    assertNear(Math.tanh(y), x);
+    assertNear(Math.tanh(y), x, sloppy_tolerance);
 
 reportCompare(0, 0, "ok");
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -39,16 +39,17 @@
 #include "nsSVGClipPathFrame.h"
 #include "GeckoProfiler.h"
 #include "nsAnimationManager.h"
 #include "nsTransitionManager.h"
 #include "nsViewManager.h"
 #include "ImageLayers.h"
 #include "ImageContainer.h"
 #include "nsCanvasFrame.h"
+#include "StickyScrollContainer.h"
 #include "mozilla/LookAndFeel.h"
 
 #include <stdint.h>
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::css;
 using namespace mozilla::layers;
@@ -609,16 +610,25 @@ static void UnmarkFrameForDisplay(nsIFra
   for (nsIFrame* f = aFrame; f;
        f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) {
     if (!(f->GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO))
       return;
     f->RemoveStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO);
   }
 }
 
+static void AdjustForScrollBars(ScreenIntRect& aToAdjust, nsIScrollableFrame* aScrollableFrame) {
+  if (aScrollableFrame && !LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars)) {
+    nsMargin sizes = aScrollableFrame->GetActualScrollbarSizes();
+    // Scrollbars are not subject to scaling, so CSS pixels = screen pixels for them.
+    ScreenIntMargin boundMargins = RoundedToInt(CSSMargin::FromAppUnits(sizes) * CSSToScreenScale(1.0f));
+    aToAdjust.Deflate(boundMargins);
+  }
+}
+
 static void RecordFrameMetrics(nsIFrame* aForFrame,
                                nsIFrame* aScrollFrame,
                                const nsIFrame* aReferenceFrame,
                                ContainerLayer* aRoot,
                                const nsRect& aVisibleRect,
                                const nsRect& aViewport,
                                nsRect* aDisplayPort,
                                nsRect* aCriticalDisplayPort,
@@ -707,22 +717,40 @@ static void RecordFrameMetrics(nsIFrame*
   // so it's a xul document. In this case, use the size of the viewport frame.
   nsIFrame* frameForCompositionBoundsCalculation = aScrollFrame ? aScrollFrame : aForFrame;
   nsRect compositionBounds(frameForCompositionBoundsCalculation->GetOffsetToCrossDoc(aReferenceFrame),
                            frameForCompositionBoundsCalculation->GetSize());
   metrics.mCompositionBounds = RoundedToInt(LayoutDeviceRect::FromAppUnits(compositionBounds, auPerDevPixel)
                              * metrics.mCumulativeResolution
                              * layerToScreenScale);
 
+  // For the root scroll frame of the root content document, clamp the
+  // composition bounds to the widget bounds. This is necessary because, if
+  // the page is zoomed in, the frame's size might be larger than the widget
+  // bounds, but we don't want the composition bounds to be.
+  bool useWidgetBounds = false;
+  bool isRootContentDocRootScrollFrame = aForFrame->GetParent() == nullptr
+                                      && presContext->IsRootContentDocument();
+  if (isRootContentDocRootScrollFrame) {
+    if (nsIWidget* widget = aForFrame->GetNearestWidget()) {
+      nsIntRect bounds;
+      widget->GetBounds(bounds);
+      ScreenIntRect screenBounds = ScreenIntRect::FromUnknownRect(mozilla::gfx::IntRect(
+          bounds.x, bounds.y, bounds.width, bounds.height));
+      AdjustForScrollBars(screenBounds, scrollableFrame);
+      metrics.mCompositionBounds = screenBounds.ClampRect(metrics.mCompositionBounds);
+      useWidgetBounds = true;
+    }
+  }
+
   // Adjust composition bounds for the size of scroll bars.
-  if (scrollableFrame && !LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars)) {
-    nsMargin sizes = scrollableFrame->GetActualScrollbarSizes();
-    // Scrollbars are not subject to scaling, so CSS pixels = screen pixels for them.
-    ScreenIntMargin boundMargins = RoundedToInt(CSSMargin::FromAppUnits(sizes) * CSSToScreenScale(1.0f));
-    metrics.mCompositionBounds.Deflate(boundMargins);
+  // If the widget bounds were used to clamp the composition bounds,
+  // this adjustment was already made to the widget bounds.
+  if (!useWidgetBounds) {
+    AdjustForScrollBars(metrics.mCompositionBounds, scrollableFrame);
   }
 
   metrics.mPresShellId = presShell->GetPresShellId();
 
   aRoot->SetFrameMetrics(metrics);
 }
 
 nsDisplayListBuilder::~nsDisplayListBuilder() {
@@ -3128,65 +3156,52 @@ nsDisplayFixedPosition::nsDisplayFixedPo
 }
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayFixedPosition::~nsDisplayFixedPosition() {
   MOZ_COUNT_DTOR(nsDisplayFixedPosition);
 }
 #endif
 
-already_AddRefed<Layer>
-nsDisplayFixedPosition::BuildLayer(nsDisplayListBuilder* aBuilder,
-                                   LayerManager* aManager,
-                                   const ContainerParameters& aContainerParameters) {
-  nsRefPtr<Layer> layer =
-    nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, aContainerParameters);
+void nsDisplayFixedPosition::SetFixedPositionLayerData(Layer* const aLayer,
+                                                       nsIFrame* aViewportFrame,
+                                                       nsSize aViewportSize,
+                                                       nsPresContext* aPresContext,
+                                                       const ContainerParameters& aContainerParameters) {
+  // Find out the rect of the viewport frame relative to the reference frame.
+  // This, in conjunction with the container scale, will correspond to the
+  // coordinate-space of the built layer.
+  float factor = aPresContext->AppUnitsPerDevPixel();
+  nsPoint origin = aViewportFrame->GetOffsetToCrossDoc(ReferenceFrame());
+  LayerRect anchorRect(NSAppUnitsToFloatPixels(origin.x, factor) *
+                         aContainerParameters.mXScale,
+                       NSAppUnitsToFloatPixels(origin.y, factor) *
+                         aContainerParameters.mYScale,
+                       NSAppUnitsToFloatPixels(aViewportSize.width, factor) *
+                         aContainerParameters.mXScale,
+                       NSAppUnitsToFloatPixels(aViewportSize.height, factor) *
+                         aContainerParameters.mYScale);
 
   // Work out the anchor point for this fixed position layer. We assume that
   // any positioning set (left/top/right/bottom) indicates that the
   // corresponding side of its container should be the anchor point,
   // defaulting to top-left.
-  nsIFrame* viewportFrame = mFixedPosFrame->GetParent();
-  nsPresContext *presContext = viewportFrame->PresContext();
-
-  // Fixed position frames are reflowed into the scroll-port size if one has
-  // been set.
-  nsSize containingBlockSize = viewportFrame->GetSize();
-  if (presContext->PresShell()->IsScrollPositionClampingScrollPortSizeSet()) {
-    containingBlockSize = presContext->PresShell()->
-      GetScrollPositionClampingScrollPortSize();
-  }
-
-  // Find out the rect of the viewport frame relative to the reference frame.
-  // This, in conjunction with the container scale, will correspond to the
-  // coordinate-space of the built layer.
-  float factor = presContext->AppUnitsPerDevPixel();
-  nsPoint origin = viewportFrame->GetOffsetToCrossDoc(ReferenceFrame());
-  LayerRect anchorRect(NSAppUnitsToFloatPixels(origin.x, factor) *
-                         aContainerParameters.mXScale,
-                       NSAppUnitsToFloatPixels(origin.y, factor) *
-                         aContainerParameters.mYScale,
-                       NSAppUnitsToFloatPixels(containingBlockSize.width, factor) *
-                         aContainerParameters.mXScale,
-                       NSAppUnitsToFloatPixels(containingBlockSize.height, factor) *
-                         aContainerParameters.mYScale);
-
   LayerPoint anchor = anchorRect.TopLeft();
 
   const nsStylePosition* position = mFixedPosFrame->StylePosition();
   if (position->mOffset.GetRightUnit() != eStyleUnit_Auto)
     anchor.x = anchorRect.XMost();
   if (position->mOffset.GetBottomUnit() != eStyleUnit_Auto)
     anchor.y = anchorRect.YMost();
 
-  layer->SetFixedPositionAnchor(anchor);
+  aLayer->SetFixedPositionAnchor(anchor);
 
   // Also make sure the layer is aware of any fixed position margins that have
   // been set.
-  nsMargin fixedMargins = presContext->PresShell()->GetContentDocumentFixedPositionMargins();
+  nsMargin fixedMargins = aPresContext->PresShell()->GetContentDocumentFixedPositionMargins();
   LayerMargin fixedLayerMargins(NSAppUnitsToFloatPixels(fixedMargins.top, factor) *
                                   aContainerParameters.mYScale,
                                 NSAppUnitsToFloatPixels(fixedMargins.right, factor) *
                                   aContainerParameters.mXScale,
                                 NSAppUnitsToFloatPixels(fixedMargins.bottom, factor) *
                                   aContainerParameters.mYScale,
                                 NSAppUnitsToFloatPixels(fixedMargins.left, factor) *
                                   aContainerParameters.mXScale);
@@ -3198,17 +3213,39 @@ nsDisplayFixedPosition::BuildLayer(nsDis
       position->mOffset.GetRightUnit() == eStyleUnit_Auto) {
     fixedLayerMargins.left = -1;
   }
   if (position->mOffset.GetTopUnit() == eStyleUnit_Auto &&
       position->mOffset.GetBottomUnit() == eStyleUnit_Auto) {
     fixedLayerMargins.top = -1;
   }
 
-  layer->SetFixedPositionMargins(fixedLayerMargins);
+  aLayer->SetFixedPositionMargins(fixedLayerMargins);
+}
+
+already_AddRefed<Layer>
+nsDisplayFixedPosition::BuildLayer(nsDisplayListBuilder* aBuilder,
+                                   LayerManager* aManager,
+                                   const ContainerParameters& aContainerParameters) {
+  nsRefPtr<Layer> layer =
+    nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, aContainerParameters);
+
+  nsIFrame* viewportFrame = mFixedPosFrame->GetParent();
+  nsPresContext *presContext = viewportFrame->PresContext();
+
+  // Fixed position frames are reflowed into the scroll-port size if one has
+  // been set.
+  nsSize viewportSize = viewportFrame->GetSize();
+  if (presContext->PresShell()->IsScrollPositionClampingScrollPortSizeSet()) {
+    viewportSize = presContext->PresShell()->
+      GetScrollPositionClampingScrollPortSize();
+  }
+
+  SetFixedPositionLayerData(layer, viewportFrame, viewportSize, presContext,
+                            aContainerParameters);
 
   return layer.forget();
 }
 
 bool nsDisplayFixedPosition::TryMerge(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) {
   if (aItem->GetType() != TYPE_FIXED_POSITION)
     return false;
   // Items with the same fixed position frame can be merged.
@@ -3216,16 +3253,86 @@ bool nsDisplayFixedPosition::TryMerge(ns
   if (other->mFixedPosFrame != mFixedPosFrame)
     return false;
   if (aItem->GetClip() != GetClip())
     return false;
   MergeFromTrackingMergedFrames(other);
   return true;
 }
 
+nsDisplayStickyPosition::nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder,
+                                                 nsIFrame* aFrame,
+                                                 nsIFrame* aStickyPosFrame,
+                                                 nsDisplayList* aList)
+    : nsDisplayFixedPosition(aBuilder, aFrame, aStickyPosFrame, aList) {
+  MOZ_COUNT_CTOR(nsDisplayStickyPosition);
+}
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+nsDisplayStickyPosition::~nsDisplayStickyPosition() {
+  MOZ_COUNT_DTOR(nsDisplayStickyPosition);
+}
+#endif
+
+already_AddRefed<Layer>
+nsDisplayStickyPosition::BuildLayer(nsDisplayListBuilder* aBuilder,
+                                    LayerManager* aManager,
+                                    const ContainerParameters& aContainerParameters) {
+  nsRefPtr<Layer> layer =
+    nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, aContainerParameters);
+
+  StickyScrollContainer* stickyScrollContainer = StickyScrollContainer::
+    GetStickyScrollContainerForFrame(mFrame);
+  if (!stickyScrollContainer) {
+    return layer.forget();
+  }
+
+  nsIFrame* scrollFrame = do_QueryFrame(stickyScrollContainer->ScrollFrame());
+  nsPresContext* presContext = scrollFrame->PresContext();
+
+  // Sticky position frames whose scroll frame is the root scroll frame are
+  // reflowed into the scroll-port size if one has been set.
+  nsSize scrollFrameSize = scrollFrame->GetSize();
+  if (scrollFrame == presContext->PresShell()->GetRootScrollFrame() &&
+      presContext->PresShell()->IsScrollPositionClampingScrollPortSizeSet()) {
+    scrollFrameSize = presContext->PresShell()->
+      GetScrollPositionClampingScrollPortSize();
+  }
+
+  SetFixedPositionLayerData(layer, scrollFrame, scrollFrameSize, presContext,
+                            aContainerParameters);
+
+  ViewID scrollId = nsLayoutUtils::FindOrCreateIDFor(
+    stickyScrollContainer->ScrollFrame()->GetScrolledFrame()->GetContent());
+
+  float factor = presContext->AppUnitsPerDevPixel();
+  nsRect outer;
+  nsRect inner;
+  stickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner);
+  LayerRect stickyOuter(NSAppUnitsToFloatPixels(outer.x, factor) *
+                          aContainerParameters.mXScale,
+                        NSAppUnitsToFloatPixels(outer.y, factor) *
+                          aContainerParameters.mYScale,
+                        NSAppUnitsToFloatPixels(outer.width, factor) *
+                          aContainerParameters.mXScale,
+                        NSAppUnitsToFloatPixels(outer.height, factor) *
+                          aContainerParameters.mYScale);
+  LayerRect stickyInner(NSAppUnitsToFloatPixels(inner.x, factor) *
+                          aContainerParameters.mXScale,
+                        NSAppUnitsToFloatPixels(inner.y, factor) *
+                          aContainerParameters.mYScale,
+                        NSAppUnitsToFloatPixels(inner.width, factor) *
+                          aContainerParameters.mXScale,
+                        NSAppUnitsToFloatPixels(inner.height, factor) *
+                          aContainerParameters.mYScale);
+  layer->SetStickyPositionData(scrollId, stickyOuter, stickyInner);
+
+  return layer.forget();
+}
+
 nsDisplayScrollLayer::nsDisplayScrollLayer(nsDisplayListBuilder* aBuilder,
                                            nsDisplayList* aList,
                                            nsIFrame* aForFrame,
                                            nsIFrame* aScrolledFrame,
                                            nsIFrame* aScrollFrame)
   : nsDisplayWrapList(aBuilder, aForFrame, aList)
   , mScrollFrame(aScrollFrame)
   , mScrolledFrame(aScrolledFrame)
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -2564,20 +2564,41 @@ public:
   {
     return mozilla::LAYER_ACTIVE;
   }
   virtual bool TryMerge(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) MOZ_OVERRIDE;
 
   NS_DISPLAY_DECL_NAME("FixedPosition", TYPE_FIXED_POSITION)
 
 protected:
+  void SetFixedPositionLayerData(Layer* const aLayer, nsIFrame* aViewportFrame,
+                                 nsSize aViewportSize, nsPresContext* aPresContext,
+                                 const ContainerParameters& aContainerParameters);
   nsIFrame* mFixedPosFrame;
 };
 
 /**
+ * A display item used to represent sticky position elements. The contents
+ * gets its own layer and creates a stacking context, and the layer will have
+ * position-related metadata set on it.
+ */
+class nsDisplayStickyPosition : public nsDisplayFixedPosition {
+public:
+  nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+                          nsIFrame* aStickyPosFrame, nsDisplayList* aList);
+#ifdef NS_BUILD_REFCNT_LOGGING
+  virtual ~nsDisplayStickyPosition();
+#endif
+
+  virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+                                             LayerManager* aManager,
+                                             const ContainerParameters& aContainerParameters) MOZ_OVERRIDE;
+};
+
+/**
  * This potentially creates a layer for the given list of items, whose
  * visibility is determined by the displayport for the given frame instead of
  * what is passed in to ComputeVisibility.
  *
  * Here in content, we can use this to render more content than is actually
  * visible. Then, the compositing process can manipulate the generated layer
  * through transformations so that asynchronous scrolling can be implemented.
  *
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -72,16 +72,17 @@ class nsDisplayListBuilder;
 class nsPIDOMWindow;
 struct nsPoint;
 struct nsIntPoint;
 struct nsIntRect;
 struct nsRect;
 class nsRegion;
 class nsRefreshDriver;
 class nsARefreshObserver;
+class nsAPostRefreshObserver;
 #ifdef ACCESSIBILITY
 class nsAccessibilityService;
 namespace mozilla {
 namespace a11y {
 class DocAccessible;
 } // namespace a11y
 } // namespace mozilla
 #endif
@@ -1358,50 +1359,53 @@ public:
   // Ensures the image is in the list of visible images.
   virtual void EnsureImageInVisibleList(nsIImageLoadingContent* aImage) = 0;
 
   /**
    * Refresh observer management.
    */
 protected:
   virtual bool AddRefreshObserverExternal(nsARefreshObserver* aObserver,
-                                            mozFlushType aFlushType);
+                                          mozFlushType aFlushType);
   bool AddRefreshObserverInternal(nsARefreshObserver* aObserver,
-                                    mozFlushType aFlushType);
+                                  mozFlushType aFlushType);
   virtual bool RemoveRefreshObserverExternal(nsARefreshObserver* aObserver,
-                                               mozFlushType aFlushType);
+                                             mozFlushType aFlushType);
   bool RemoveRefreshObserverInternal(nsARefreshObserver* aObserver,
-                                       mozFlushType aFlushType);
+                                     mozFlushType aFlushType);
 
   /**
    * Do computations necessary to determine if font size inflation is enabled.
    * This value is cached after computation, as the computation is somewhat
    * expensive.
    */
   void RecomputeFontSizeInflationEnabled();
 
 public:
   bool AddRefreshObserver(nsARefreshObserver* aObserver,
-                            mozFlushType aFlushType) {
+                          mozFlushType aFlushType) {
 #ifdef MOZILLA_INTERNAL_API
     return AddRefreshObserverInternal(aObserver, aFlushType);
 #else
     return AddRefreshObserverExternal(aObserver, aFlushType);
 #endif
   }
 
   bool RemoveRefreshObserver(nsARefreshObserver* aObserver,
-                               mozFlushType aFlushType) {
+                             mozFlushType aFlushType) {
 #ifdef MOZILLA_INTERNAL_API
     return RemoveRefreshObserverInternal(aObserver, aFlushType);
 #else
     return RemoveRefreshObserverExternal(aObserver, aFlushType);
 #endif
   }
 
+  virtual bool AddPostRefreshObserver(nsAPostRefreshObserver* aObserver);
+  virtual bool RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver);
+
   /**
    * Initialize and shut down static variables.
    */
   static void InitializeStatics();
   static void ReleaseStatics();
 
   // If a frame in the subtree rooted at aFrame is capturing the mouse then
   // clears that capture.
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -8216,43 +8216,65 @@ PresShell::Observe(nsISupports* aSubject
   return NS_ERROR_FAILURE;
 }
 
 bool
 nsIPresShell::AddRefreshObserverInternal(nsARefreshObserver* aObserver,
                                          mozFlushType aFlushType)
 {
   nsPresContext* presContext = GetPresContext();
-  return presContext ? presContext->RefreshDriver()->
-    AddRefreshObserver(aObserver, aFlushType) : false;
+  return presContext &&
+      presContext->RefreshDriver()->AddRefreshObserver(aObserver, aFlushType);
 }
 
 /* virtual */ bool
 nsIPresShell::AddRefreshObserverExternal(nsARefreshObserver* aObserver,
                                          mozFlushType aFlushType)
 {
   return AddRefreshObserverInternal(aObserver, aFlushType);
 }
 
 bool
 nsIPresShell::RemoveRefreshObserverInternal(nsARefreshObserver* aObserver,
                                             mozFlushType aFlushType)
 {
   nsPresContext* presContext = GetPresContext();
-  return presContext ? presContext->RefreshDriver()->
-    RemoveRefreshObserver(aObserver, aFlushType) : false;
+  return presContext &&
+      presContext->RefreshDriver()->RemoveRefreshObserver(aObserver, aFlushType);
 }
 
 /* virtual */ bool
 nsIPresShell::RemoveRefreshObserverExternal(nsARefreshObserver* aObserver,
                                             mozFlushType aFlushType)
 {
   return RemoveRefreshObserverInternal(aObserver, aFlushType);
 }
 
+/* virtual */ bool
+nsIPresShell::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver)
+{
+  nsPresContext* presContext = GetPresContext();
+  if (!presContext) {
+    return false;
+  }
+  presContext->RefreshDriver()->AddPostRefreshObserver(aObserver);
+  return true;
+}
+
+/* virtual */ bool
+nsIPresShell::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver)
+{
+  nsPresContext* presContext = GetPresContext();
+  if (!presContext) {
+    return false;
+  }
+  presContext->RefreshDriver()->RemovePostRefreshObserver(aObserver);
+  return true;
+}
+
 //------------------------------------------------------
 // End of protected and private methods on the PresShell
 //------------------------------------------------------
 
 // Start of DEBUG only code
 
 #ifdef DEBUG
 
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -318,17 +318,16 @@ public:
                            size_t *aPresShellSize,
                            size_t *aStyleSetsSize,
                            size_t *aTextRunsSize,
                            size_t *aPresContextSize) MOZ_OVERRIDE;
   size_t SizeOfTextRuns(mozilla::MallocSizeOf aMallocSizeOf) const;
 
   virtual void AddInvalidateHiddenPresShellObserver(nsRefreshDriver *aDriver) MOZ_OVERRIDE;
 
-
   // This data is stored as a content property (nsGkAtoms::scrolling) on
   // mContentToScrollTo when we have a pending ScrollIntoView.
   struct ScrollIntoViewData {
     ScrollAxis mContentScrollVAxis;
     ScrollAxis mContentScrollHAxis;
     uint32_t   mContentToScrollToFlags;
   };
 
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -748,30 +748,40 @@ nsRefreshDriver::MostRecentRefreshEpochT
 }
 
 bool
 nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver,
                                     mozFlushType aFlushType)
 {
   ObserverArray& array = ArrayFor(aFlushType);
   bool success = array.AppendElement(aObserver) != nullptr;
-
   EnsureTimerStarted(false);
-
   return success;
 }
 
 bool
 nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
                                        mozFlushType aFlushType)
 {
   ObserverArray& array = ArrayFor(aFlushType);
   return array.RemoveElement(aObserver);
 }
 
+void
+nsRefreshDriver::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver)
+{
+  mPostRefreshObservers.AppendElement(aObserver);
+}
+
+void
+nsRefreshDriver::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver)
+{
+  mPostRefreshObservers.RemoveElement(aObserver);
+}
+
 bool
 nsRefreshDriver::AddImageRequest(imgIRequest* aRequest)
 {
   uint32_t delay = GetFirstFrameDelay(aRequest);
   if (delay == 0) {
     if (!mRequests.PutEntry(aRequest)) {
       return false;
     }
@@ -1193,16 +1203,20 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
     nsRefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager();
     vm->ProcessPendingUpdates();
 #ifdef MOZ_DUMP_PAINTING
     if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
       printf("Ending ProcessPendingUpdates\n");
     }
 #endif
   }
+
+  for (uint32_t i = 0; i < mPostRefreshObservers.Length(); ++i) {
+    mPostRefreshObservers[i]->DidRefresh();
+  }
 }
 
 /* static */ PLDHashOperator
 nsRefreshDriver::ImageRequestEnumerator(nsISupportsHashKey* aEntry,
                                         void* aUserArg)
 {
   nsCOMArray<imgIContainer>* imagesToRefresh =
     static_cast<nsCOMArray<imgIContainer>*> (aUserArg);
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -21,40 +21,51 @@
 #include "nsHashKeys.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
 
 class nsPresContext;
 class nsIPresShell;
 class nsIDocument;
 class imgIRequest;
+class nsIRunnable;
+
+namespace mozilla {
+class RefreshDriverTimer;
+}
 
 /**
  * An abstract base class to be implemented by callers wanting to be
  * notified at refresh times.  When nothing needs to be painted, callers
  * may not be notified.
  */
-namespace mozilla {
-    class RefreshDriverTimer;
-}
-
 class nsARefreshObserver {
 public:
   // AddRef and Release signatures that match nsISupports.  Implementors
   // must implement reference counting, and those that do implement
   // nsISupports will already have methods with the correct signature.
   //
   // The refresh driver does NOT hold references to refresh observers
   // except while it is notifying them.
   NS_IMETHOD_(nsrefcnt) AddRef(void) = 0;
   NS_IMETHOD_(nsrefcnt) Release(void) = 0;
 
   virtual void WillRefresh(mozilla::TimeStamp aTime) = 0;
 };
 
+/**
+ * An abstract base class to be implemented by callers wanting to be notified
+ * that a refresh has occurred. Callers must ensure an observer is removed
+ * before it is destroyed.
+ */
+class nsAPostRefreshObserver {
+public:
+  virtual void DidRefresh() = 0;
+};
+
 class nsRefreshDriver MOZ_FINAL : public nsISupports {
 public:
   nsRefreshDriver(nsPresContext *aPresContext);
   ~nsRefreshDriver();
 
   static void InitializeStatics();
   static void Shutdown();
 
@@ -96,21 +107,31 @@ public:
    *   + (in the future) which observers are suppressed when the display
    *     doesn't require current position data or isn't currently
    *     painting, and, correspondingly, which get notified when there
    *     is a flush during such suppression
    * and it must be either Flush_Style, Flush_Layout, or Flush_Display.
    *
    * The refresh driver does NOT own a reference to these observers;
    * they must remove themselves before they are destroyed.
+   *
+   * The observer will be called even if there is no other activity.
    */
   bool AddRefreshObserver(nsARefreshObserver *aObserver,
-                            mozFlushType aFlushType);
+                          mozFlushType aFlushType);
   bool RemoveRefreshObserver(nsARefreshObserver *aObserver,
-                               mozFlushType aFlushType);
+                             mozFlushType aFlushType);
+
+  /**
+   * Add an observer that will be called after each refresh. The caller
+   * must remove the observer before it is deleted. This does not trigger
+   * refresh driver ticks.
+   */
+  void AddPostRefreshObserver(nsAPostRefreshObserver *aObserver);
+  void RemovePostRefreshObserver(nsAPostRefreshObserver *aObserver);
 
   /**
    * Add/Remove imgIRequest versions of observers.
    *
    * These are used for hooking into the refresh driver for
    * controlling animated images.
    *
    * @note The refresh driver owns a reference to these listeners.
@@ -290,16 +311,17 @@ private:
   RequestTable mRequests;
   ImageStartTable mStartTable;
 
   nsAutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
   nsAutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
   nsAutoTArray<nsIPresShell*, 16> mPresShellsToInvalidateIfHidden;
   // nsTArray on purpose, because we want to be able to swap.
   nsTArray<nsIDocument*> mFrameRequestCallbackDocs;
+  nsTArray<nsAPostRefreshObserver*> mPostRefreshObservers;
 
   // Helper struct for processing image requests
   struct ImageRequestParameters {
     mozilla::TimeStamp mCurrent;
     mozilla::TimeStamp mPrevious;
     RequestTable* mRequests;
     mozilla::TimeStamp mDesired;
   };
--- a/layout/generic/StickyScrollContainer.cpp
+++ b/layout/generic/StickyScrollContainer.cpp
@@ -215,21 +215,62 @@ StickyScrollContainer::ComputePosition(n
   position.y = std::min(position.y, std::max(stick.YMost(), contain.y));
   position.x = std::max(position.x, std::min(stick.x, contain.XMost()));
   position.x = std::min(position.x, std::max(stick.XMost(), contain.x));
 
   return position;
 }
 
 void
+StickyScrollContainer::GetScrollRanges(nsIFrame* aFrame, nsRect* aOuter,
+                                       nsRect* aInner) const
+{
+  nsRect stick;
+  nsRect contain;
+  ComputeStickyLimits(aFrame, &stick, &contain);
+
+  aOuter->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
+  aInner->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
+
+  const nsPoint normalPosition = aFrame->GetNormalPosition();
+
+  // Bottom and top
+  if (stick.YMost() != nscoord_MAX/2) {
+    aOuter->SetTopEdge(contain.y - stick.YMost());
+    aInner->SetTopEdge(normalPosition.y - stick.YMost());
+  }
+
+  if (stick.y != nscoord_MIN/2) {
+    aInner->SetBottomEdge(normalPosition.y - stick.y);
+    aOuter->SetBottomEdge(contain.YMost() - stick.y);
+  }
+
+  // Right and left
+  if (stick.XMost() != nscoord_MAX/2) {
+    aOuter->SetLeftEdge(contain.x - stick.XMost());
+    aInner->SetLeftEdge(normalPosition.x - stick.XMost());
+  }
+
+  if (stick.x != nscoord_MIN/2) {
+    aInner->SetRightEdge(normalPosition.x - stick.x);
+    aOuter->SetRightEdge(contain.XMost() - stick.x);
+  }
+}
+
+void
 StickyScrollContainer::UpdatePositions(nsPoint aScrollPosition,
                                        nsIFrame* aSubtreeRoot)
 {
-  NS_ASSERTION(!aSubtreeRoot || aSubtreeRoot == do_QueryFrame(mScrollFrame),
-    "If reflowing, should be reflowing the scroll frame");
+#ifdef DEBUG
+  {
+    nsIFrame* scrollFrameAsFrame = do_QueryFrame(mScrollFrame);
+    NS_ASSERTION(!aSubtreeRoot || aSubtreeRoot == scrollFrameAsFrame,
+                 "If reflowing, should be reflowing the scroll frame");
+  }
+#endif
   mScrollPosition = aScrollPosition;
 
   OverflowChangedTracker oct;
   oct.SetSubtreeRoot(aSubtreeRoot);
   for (nsTArray<nsIFrame*>::size_type i = 0; i < mFrames.Length(); i++) {
     nsIFrame* f = mFrames[i];
     if (aSubtreeRoot) {
       // Reflowing the scroll frame, so recompute offsets.
--- a/layout/generic/StickyScrollContainer.h
+++ b/layout/generic/StickyScrollContainer.h
@@ -39,26 +39,36 @@ public:
 
   void AddFrame(nsIFrame* aFrame) {
     mFrames.AppendElement(aFrame);
   }
   void RemoveFrame(nsIFrame* aFrame) {
     mFrames.RemoveElement(aFrame);
   }
 
+  nsIScrollableFrame* ScrollFrame() const {
+    return mScrollFrame;
+  }
+
   // Compute the offsets for a sticky position element
   static void ComputeStickyOffsets(nsIFrame* aFrame);
 
   /**
    * Compute the position of a sticky positioned frame, based on information
    * stored in its properties along with our scroll frame and scroll position.
    */
   nsPoint ComputePosition(nsIFrame* aFrame) const;
 
   /**
+   * Compute where a frame should not scroll with the page, represented by the
+   * difference of two rectangles.
+   */
+  void GetScrollRanges(nsIFrame* aFrame, nsRect* aOuter, nsRect* aInner) const;
+
+  /**
    * Compute and set the position of all sticky frames, given the current
    * scroll position of the scroll frame. If not in reflow, aSubtreeRoot should
    * be null; otherwise, overflow-area updates will be limited to not affect
    * aSubtreeRoot or its ancestors.
    */
   void UpdatePositions(nsPoint aScrollPosition, nsIFrame* aSubtreeRoot);
 
   // nsIScrollPositionListener
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1731,16 +1731,22 @@ WrapPreserve3DListInternal(nsIFrame* aFr
  
     if (NS_FAILED(rv) || !item || aIndex > nsDisplayTransform::INDEX_MAX)
       return rv;
   }
     
   return NS_OK;
 }
 
+static bool
+IsScrollFrameActive(nsIScrollableFrame* aScrollableFrame)
+{
+  return aScrollableFrame && aScrollableFrame->IsScrollingActive();
+}
+
 static nsresult
 WrapPreserve3DList(nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, nsDisplayList *aList)
 {
   uint32_t index = 0;
   nsDisplayList temp;
   nsDisplayList output;
   nsresult rv = WrapPreserve3DListInternal(aFrame, aBuilder, aList, &output, index, &temp);
 
@@ -1808,20 +1814,24 @@ nsIFrame::BuildDisplayListForStackingCon
         return;
       }
     }
     inTransform = true;
   }
 
   bool useOpacity = HasOpacity() && !nsSVGUtils::CanOptimizeOpacity(this);
   bool usingSVGEffects = nsSVGIntegrationUtils::UsingEffectsForFrame(this);
+  bool useStickyPosition = disp->mPosition == NS_STYLE_POSITION_STICKY &&
+    IsScrollFrameActive(nsLayoutUtils::GetNearestScrollableFrame(GetParent(),
+                        nsLayoutUtils::SCROLLABLE_SAME_DOC |
+                        nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN));
 
   DisplayListClipState::AutoSaveRestore clipState(aBuilder);
 
-  if (isTransformed || useOpacity || usingSVGEffects) {
+  if (isTransformed || useOpacity || usingSVGEffects || useStickyPosition) {
     // We don't need to pass ancestor clipping down to our children;
     // everything goes inside a display item's child list, and the display
     // item itself will be clipped.
     // For transforms we also need to clear ancestor clipping because it's
     // relative to the wrong display item reference frame anyway.
     clipState.Clear();
   }
 
@@ -1930,16 +1940,23 @@ nsIFrame::BuildDisplayListForStackingCon
   }
   /* Else, if the list is non-empty and there is CSS group opacity without SVG
    * effects, wrap it up in an opacity item.
    */
   else if (useOpacity && !resultList.IsEmpty()) {
     resultList.AppendNewToTop(
         new (aBuilder) nsDisplayOpacity(aBuilder, this, &resultList));
   }
+  /* If we have sticky positioning, wrap it in a sticky position item.
+   */
+  if (useStickyPosition) {
+    resultList.AppendNewToTop(
+        new (aBuilder) nsDisplayStickyPosition(aBuilder, this, this,
+                                               &resultList));
+  }
 
   /* If we're going to apply a transformation and don't have preserve-3d set, wrap 
    * everything in an nsDisplayTransform. If there's nothing in the list, don't add 
    * anything.
    *
    * For the preserve-3d case we want to individually wrap every child in the list with
    * a separate nsDisplayTransform instead. When the child is already an nsDisplayTransform,
    * we can skip this step, as the computed transform will already include our own.
@@ -1957,23 +1974,16 @@ nsIFrame::BuildDisplayListForStackingCon
       resultList.AppendNewToTop(
         new (aBuilder) nsDisplayTransform(aBuilder, this, &resultList));
     }
   }
 
   aList->AppendToTop(&resultList);
 }
 
-static bool
-IsRootScrollFrameActive(nsIPresShell* aPresShell)
-{
-  nsIScrollableFrame* sf = aPresShell->GetRootScrollFrameAsScrollable();
-  return sf && sf->IsScrollingActive();
-}
-
 static nsDisplayItem*
 WrapInWrapList(nsDisplayListBuilder* aBuilder,
                nsIFrame* aFrame, nsDisplayList* aList)
 {
   nsDisplayItem* item = aList->GetBottom();
   if (!item || item->GetAbove() || item->Frame() != aFrame) {
     return new (aBuilder) nsDisplayWrapList(aBuilder, aFrame, aList);
   }
@@ -2122,17 +2132,18 @@ nsIFrame::BuildDisplayListForChild(nsDis
   // This controls later whether we build an nsDisplayWrapList or an
   // nsDisplayFixedPosition. We check if we're already building a fixed-pos
   // item and disallow nesting, to prevent the situation of bug #769541
   // occurring.
   // Don't build an nsDisplayFixedPosition if our root scroll frame is not
   // active, that's pointless and the extra layer(s) created may be wasteful.
   bool buildFixedPositionItem = disp->mPosition == NS_STYLE_POSITION_FIXED &&
     !child->GetParent()->GetParent() && !aBuilder->IsInFixedPosition() &&
-    IsRootScrollFrameActive(PresContext()->PresShell()) && !isSVG;
+    IsScrollFrameActive(PresContext()->PresShell()->GetRootScrollFrameAsScrollable()) &&
+    !isSVG;
 
   nsDisplayListBuilder::AutoBuildingDisplayList
     buildingForChild(aBuilder, child, pseudoStackingContext, buildFixedPositionItem);
   DisplayListClipState::AutoClipMultiple clipState(aBuilder);
 
   if (savedOutOfFlowData) {
     clipState.SetClipForContainingBlockDescendants(
       &savedOutOfFlowData->mContainingBlockClip);
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -143,16 +143,38 @@ NS_DECLARE_FRAME_PROPERTY(TabWidthProper
 
 NS_DECLARE_FRAME_PROPERTY(OffsetToFrameProperty, nullptr)
 
 // text runs are destroyed by the text run cache
 NS_DECLARE_FRAME_PROPERTY(UninflatedTextRunProperty, nullptr)
 
 NS_DECLARE_FRAME_PROPERTY(FontSizeInflationProperty, nullptr)
 
+class GlyphObserver : public gfxFont::GlyphChangeObserver {
+public:
+  GlyphObserver(gfxFont* aFont, nsTextFrame* aFrame)
+    : gfxFont::GlyphChangeObserver(aFont), mFrame(aFrame) {}
+  virtual void NotifyGlyphsChanged() MOZ_OVERRIDE;
+private:
+  nsTextFrame* mFrame;
+};
+
+static void DestroyGlyphObserverList(void* aPropertyValue)
+{
+  delete static_cast<nsTArray<nsAutoPtr<GlyphObserver> >*>(aPropertyValue);
+}
+
+/**
+ * This property is set on text frames with TEXT_IN_TEXTRUN_USER_DATA set that
+ * have potentially-animated glyphs.
+ * The only reason this list is in a property is to automatically destroy the
+ * list when the frame is deleted, unregistering the observers.
+ */
+NS_DECLARE_FRAME_PROPERTY(TextFrameGlyphObservers, DestroyGlyphObserverList);
+
 // The following flags are set during reflow
 
 // This bit is set on the first frame in a continuation indicating
 // that it was chopped short because of :first-letter style.
 #define TEXT_FIRST_LETTER    NS_FRAME_STATE_BIT(20)
 // This bit is set on frames that are logically adjacent to the start of the
 // line (i.e. no prior frame on line with actual displayed in-flow content).
 #define TEXT_START_OF_LINE   NS_FRAME_STATE_BIT(21)
@@ -501,16 +523,36 @@ UnhookTextRunFromFrames(gfxTextRun* aTex
       userData->mMappedFlowCount = uint32_t(destroyFromIndex);
       if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
         userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
       }
     }
   }
 }
 
+void
+GlyphObserver::NotifyGlyphsChanged()
+{
+  nsIPresShell* shell = mFrame->PresContext()->PresShell();
+  for (nsIFrame* f = mFrame; f;
+       f = nsLayoutUtils::GetNextContinuationOrSpecialSibling(f)) {
+    if (f != mFrame && f->HasAnyStateBits(TEXT_IN_TEXTRUN_USER_DATA)) {
+      // f will have its own GlyphObserver (if needed) so we can stop here.
+      break;
+    }
+    f->InvalidateFrame();
+    // Theoretically we could just update overflow areas, perhaps using
+    // OverflowChangedTracker, but that would do a bunch of work eagerly that
+    // we should probably do lazily here since there could be a lot
+    // of text frames affected and we'd like to coalesce the work. So that's
+    // not easy to do well.
+    shell->FrameNeedsReflow(f, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+  }
+}
+
 class FrameTextRunCache;
 
 static FrameTextRunCache *gTextRuns = nullptr;
 
 /*
  * Cache textruns and expire them after 3*10 seconds of no use.
  */
 class FrameTextRunCache MOZ_FINAL : public nsExpirationTracker<gfxTextRun,3> {
@@ -767,16 +809,73 @@ IsAllNewlines(const nsTextFragment* aFra
   for (int32_t i = 0; i < len; ++i) {
     char ch = str[i];
     if (ch != '\n')
       return false;
   }
   return true;
 }
 
+static void
+CreateObserverForAnimatedGlyphs(nsTextFrame* aFrame, const nsTArray<gfxFont*>& aFonts)
+{
+  if (!(aFrame->GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA)) {
+    // Maybe the textrun was created for uninflated text.
+    return;
+  }
+
+  nsTArray<nsAutoPtr<GlyphObserver> >* observers =
+    new nsTArray<nsAutoPtr<GlyphObserver> >();
+  for (uint32_t i = 0, count = aFonts.Length(); i < count; ++i) {
+    observers->AppendElement(new GlyphObserver(aFonts[i], aFrame));
+  }
+  aFrame->Properties().Set(TextFrameGlyphObservers(), observers);
+  // We are lazy and don't try to remove a property value that might be
+  // obsolete due to style changes or font selection changes. That is
+  // likely to be rarely needed, and we don't want to eat the overhead of
+  // doing it for the overwhelmingly common case of no property existing.
+  // (And we're out of state bits to conveniently use for a fast property
+  // existence check.) The only downside is that in some rare cases we might
+  // keep fonts alive for longer than necessary, or unnecessarily invalidate
+  // frames.
+}
+
+static void
+CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun)
+{
+  if (!aTextRun->GetUserData()) {
+    return;
+  }
+  nsTArray<gfxFont*> fontsWithAnimatedGlyphs;
+  uint32_t numGlyphRuns;
+  const gfxTextRun::GlyphRun* glyphRuns =
+    aTextRun->GetGlyphRuns(&numGlyphRuns);
+  for (uint32_t i = 0; i < numGlyphRuns; ++i) {
+    gfxFont* font = glyphRuns[i].mFont;
+    if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
+      fontsWithAnimatedGlyphs.AppendElement(font);
+    }
+  }
+  if (fontsWithAnimatedGlyphs.IsEmpty()) {
+    return;
+  }
+
+  if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
+    CreateObserverForAnimatedGlyphs(static_cast<nsTextFrame*>(
+      static_cast<nsIFrame*>(aTextRun->GetUserData())), fontsWithAnimatedGlyphs);
+  } else {
+    TextRunUserData* userData =
+      static_cast<TextRunUserData*>(aTextRun->GetUserData());
+    for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
+      CreateObserverForAnimatedGlyphs(userData->mMappedFlows[i].mStartFrame,
+                                      fontsWithAnimatedGlyphs);
+    }
+  }
+}
+
 /**
  * This class accumulates state as we scan a paragraph of text. It detects
  * textrun boundaries (changes from text to non-text, hard
  * line breaks, and font changes) and builds a gfxTextRun at each boundary.
  * It also detects linebreaker run boundaries (changes from text to non-text,
  * and hard line breaks) and at each boundary runs the linebreaker to compute
  * potential line breaks. It also records actual line breaks to store them in
  * the textruns.
@@ -922,16 +1021,20 @@ public:
                      (gfxTextRunFactory::TEXT_UNUSED_FLAGS |
                       nsTextFrameUtils::TEXT_UNUSED_FLAG)),
                    "Flag set that should never be set! (memory safety error?)");
       if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED) {
         nsTransformedTextRun* transformedTextRun =
           static_cast<nsTransformedTextRun*>(mTextRun);
         transformedTextRun->FinishSettingProperties(mContext);
       }
+      // The way nsTransformedTextRun is implemented, its glyph runs aren't
+      // available until after nsTransformedTextRun::FinishSettingProperties()
+      // is called. So that's why we defer checking for animated glyphs to here.
+      CreateObserversForAnimatedGlyphs(mTextRun);
     }
 
     gfxTextRun*  mTextRun;
     gfxContext*  mContext;
     uint32_t     mOffsetIntoTextRun;
     bool mChangedBreaks;
     bool mExistingTextRun;
   };
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -482,16 +482,18 @@ GetFrom(nsFrameLoader* aFrameLoader)
   return nsContentUtils::LayerManagerForDocument(doc);
 }
 
 class RemoteContentController : public GeckoContentController {
 public:
   RemoteContentController(RenderFrameParent* aRenderFrame)
     : mUILoop(MessageLoop::current())
     , mRenderFrame(aRenderFrame)
+    , mHaveZoomConstraints(false)
+    , mAllowZoom(true)
   { }
 
   virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) MOZ_OVERRIDE
   {
     // We always need to post requests into the "UI thread" otherwise the
     // requests may get processed out of order.
     mUILoop->PostTask(
       FROM_HERE,
@@ -571,27 +573,54 @@ public:
     }
   }
 
   virtual void PostDelayedTask(Task* aTask, int aDelayMs) MOZ_OVERRIDE
   {
     MessageLoop::current()->PostDelayedTask(FROM_HERE, aTask, aDelayMs);
   }
 
+  void SaveZoomConstraints(bool aAllowZoom,
+                           const CSSToScreenScale& aMinZoom,
+                           const CSSToScreenScale& aMaxZoom)
+  {
+    mHaveZoomConstraints = true;
+    mAllowZoom = aAllowZoom;
+    mMinZoom = aMinZoom;
+    mMaxZoom = aMaxZoom;
+  }
+
+  virtual bool GetZoomConstraints(bool* aOutAllowZoom,
+                                  CSSToScreenScale* aOutMinZoom,
+                                  CSSToScreenScale* aOutMaxZoom)
+  {
+    if (mHaveZoomConstraints) {
+      *aOutAllowZoom = mAllowZoom;
+      *aOutMinZoom = mMinZoom;
+      *aOutMaxZoom = mMaxZoom;
+    }
+    return mHaveZoomConstraints;
+  }
+
 private:
   void DoRequestContentRepaint(const FrameMetrics& aFrameMetrics)
   {
     if (mRenderFrame) {
       TabParent* browser = static_cast<TabParent*>(mRenderFrame->Manager());
       browser->UpdateFrame(aFrameMetrics);
     }
   }
 
   MessageLoop* mUILoop;
   RenderFrameParent* mRenderFrame;
+
+  bool mHaveZoomConstraints;
+  bool mAllowZoom;
+  CSSToScreenScale mMinZoom;
+  CSSToScreenScale mMaxZoom;
 };
 
 RenderFrameParent::RenderFrameParent(nsFrameLoader* aFrameLoader,
                                      ScrollingBehavior aScrollingBehavior,
                                      TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                      uint64_t* aId)
   : mLayersId(0)
   , mFrameLoader(aFrameLoader)
@@ -1007,16 +1036,19 @@ RenderFrameParent::ContentReceivedTouch(
   }
 }
 
 void
 RenderFrameParent::UpdateZoomConstraints(bool aAllowZoom,
                                          const CSSToScreenScale& aMinZoom,
                                          const CSSToScreenScale& aMaxZoom)
 {
+  if (mContentController) {
+    mContentController->SaveZoomConstraints(aAllowZoom, aMinZoom, aMaxZoom);
+  }
   if (GetApzcTreeManager()) {
     GetApzcTreeManager()->UpdateZoomConstraints(ScrollableLayerGuid(mLayersId),
                                                 aAllowZoom, aMinZoom, aMaxZoom);
   }
 }
 
 void
 RenderFrameParent::UpdateScrollOffset(uint32_t aPresShellId, ViewID aViewId, const CSSIntPoint& aScrollOffset)
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-placeholder/input/css-text-align.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+  <link rel='stylesheet' type='text/css' href='style.css'>
+  <style>
+    input::-moz-placeholder {
+      text-align: center;
+    }
+  </style>
+  <body>
+    <input placeholder='foo'>
+  </body>
+</html>
--- a/layout/reftests/css-placeholder/input/reftest.list
+++ b/layout/reftests/css-placeholder/input/reftest.list
@@ -19,8 +19,9 @@ needs-focus == placeholder-blur.html pla
 == placeholder-value-unset.html placeholder-simple-ref.html
 == placeholder-value-reset.html placeholder-simple-ref.html
 == placeholder-type-change-1.html placeholder-simple-ref.html
 == placeholder-type-change-2.html placeholder-button-ref.html
 == css-display.html placeholder-simple-ref.html
 # We can't check except by verifying that the output is different.
 # Same reasons as focus issues explained above.
 != css-opacity.html placeholder-simple-ref.html
+!= css-text-align.html placeholder-simple-ref.html
--- a/layout/reftests/position-sticky/reftest.list
+++ b/layout/reftests/position-sticky/reftest.list
@@ -25,19 +25,19 @@ fuzzy-if(Android,2,4) == right-3.html ri
 == padding-2.html padding-2-ref.html
 == padding-3.html padding-3-ref.html
 == overcontain-1.html overcontain-1-ref.html
 == initial-1.html initial-1-ref.html
 == initial-scroll-1.html initial-scroll-1-ref.html
 == scrollframe-reflow-1.html scrollframe-reflow-1-ref.html
 == scrollframe-reflow-2.html scrollframe-reflow-2-ref.html
 == scrollframe-auto-1.html scrollframe-auto-1-ref.html
-== stacking-context-1.html stacking-context-1-ref.html
+fuzzy-if(Android,2,3) == stacking-context-1.html stacking-context-1-ref.html
 == top-bottom-1.html top-bottom-1-ref.html
 == top-bottom-2.html top-bottom-2-ref.html
 == top-bottom-3.html top-bottom-3-ref.html
 == left-right-1.html left-right-1-ref.html
 == left-right-2.html left-right-2-ref.html
 == left-right-3.html left-right-3-ref.html
-== containing-block-1.html containing-block-1-ref.html
+fuzzy-if(Android,4,1) == containing-block-1.html containing-block-1-ref.html
 == overconstrained-1.html overconstrained-1-ref.html
 == overconstrained-2.html overconstrained-2-ref.html
 == overconstrained-3.html overconstrained-3-ref.html
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -2717,17 +2717,17 @@ CSS_PROP_TABLE(
     VARIANT_HK,
     kTableLayoutKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 CSS_PROP_TEXT(
     text-align,
     text_align,
     TextAlign,
-    CSS_PROPERTY_PARSE_VALUE,
+    CSS_PROPERTY_PARSE_VALUE | CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
     "",
     // When we support aligning on a string, we can parse text-align
     // as a string....
     VARIANT_HK /* | VARIANT_STRING */,
     kTextAlignKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 CSS_PROP_TEXT(
--- a/layout/style/test/test_bug798843_pref.html
+++ b/layout/style/test/test_bug798843_pref.html
@@ -23,31 +23,34 @@ var props = {
 };
 
 function testDisabled() {
   for (var p in props) {
     document.body.style[p] = props[p];
     is(document.body.style[p], "", p + " not settable to " + props[p]);
     document.body.style[p] = "";
   }
+  SimpleTest.finish();
 }
 
 function testEnabled() {
   for (var p in props) {
     document.body.style[p] = props[p];
     is(document.body.style[p], props[p], p + " settable to " + props[p]);
     document.body.style[p] = "";
   }
 
   SpecialPowers.pushPrefEnv(
     {'set': [['gfx.font_rendering.opentype_svg.enabled', false]]},
     testDisabled
   );
 }
 
+SimpleTest.waitForExplicitFinish();
+
 SpecialPowers.pushPrefEnv(
   {'set': [['gfx.font_rendering.opentype_svg.enabled', true]]},
   testEnabled
 );
 
 </script>
 
 </body>
--- a/media/webrtc/trunk/build/build_config.h
+++ b/media/webrtc/trunk/build/build_config.h
@@ -108,16 +108,53 @@
 #define ARCH_CPU_LITTLE_ENDIAN 1
 #elif defined(__pnacl__)
 #define ARCH_CPU_32_BITS 1
 #elif defined(__MIPSEL__)
 #define ARCH_CPU_MIPS_FAMILY 1
 #define ARCH_CPU_MIPSEL 1
 #define ARCH_CPU_32_BITS 1
 #define ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(__powerpc64__)
+#define ARCH_CPU_PPC_FAMILY 1
+#define ARCH_CPU_PPC64 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_BIG_ENDIAN 1
+#elif defined(__ppc__) || defined(__powerpc__)
+#define ARCH_CPU_PPC_FAMILY 1
+#define ARCH_CPU_PPC 1
+#define ARCH_CPU_32_BITS 1
+#define ARCH_CPU_BIG_ENDIAN 1
+#elif defined(__sparc64__)
+#define ARCH_CPU_SPARC_FAMILY 1
+#define ARCH_CPU_SPARC 1
+#define ARCH_CPU_64_BITS 1
+#elif defined(__sparc__)
+#define ARCH_CPU_SPARC_FAMILY 1
+#define ARCH_CPU_SPARC 1
+#define ARCH_CPU_32_BITS 1
+#elif defined(__mips__)
+#define ARCH_CPU_MIPS_FAMILY 1
+#define ARCH_CPU_MIPS 1
+#define ARCH_CPU_32_BITS 1
+#elif defined(__hppa__)
+#define ARCH_CPU_HPPA 1
+#define ARCH_CPU_32_BITS 1
+#elif defined(__ia64__)
+#define ARCH_CPU_IA64 1
+#define ARCH_CPU_64_BITS 1
+#elif defined(__s390x__)
+#define ARCH_CPU_S390X 1
+#define ARCH_CPU_64_BITS 1
+#elif defined(__s390__)
+#define ARCH_CPU_S390 1
+#define ARCH_CPU_32_BITS 1
+#elif defined(__alpha__)
+#define ARCH_CPU_ALPHA 1
+#define ARCH_CPU_64_BITS 1
 #else
 #error Please add support for your architecture in build/build_config.h
 #endif
 
 // Type detection for wchar_t.
 #if defined(OS_WIN)
 #define WCHAR_T_IS_UTF16
 #elif defined(OS_POSIX) && defined(COMPILER_GCC) && \
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -62,17 +62,20 @@ public class GeckoEvent {
         COMPOSITOR_PAUSE(29),
         COMPOSITOR_RESUME(30),
         NATIVE_GESTURE_EVENT(31),
         IME_KEY_EVENT(32),
         CALL_OBSERVER(33),
         REMOVE_OBSERVER(34),
         LOW_MEMORY(35),
         NETWORK_LINK_CHANGE(36),
-        TELEMETRY_HISTOGRAM_ADD(37);
+        TELEMETRY_HISTOGRAM_ADD(37),
+        PREFERENCES_OBSERVE(39),
+        PREFERENCES_GET(40),
+        PREFERENCES_REMOVE_OBSERVERS(41);
 
         public final int value;
 
         private NativeGeckoEvent(int value) {
             this.value = value;
          }
     }
 
@@ -181,16 +184,18 @@ public class GeckoEvent {
 
     private short mScreenOrientation;
 
     private ByteBuffer mBuffer;
 
     private int mWidth;
     private int mHeight;
 
+    private String[] mPrefNames;
+
     private GeckoEvent(NativeGeckoEvent event) {
         mType = event.value;
     }
 
     public static GeckoEvent createAppBackgroundingEvent() {
         return new GeckoEvent(NativeGeckoEvent.APP_BACKGROUNDING);
     }
 
@@ -684,16 +689,36 @@ public class GeckoEvent {
     }
 
     public static GeckoEvent createRemoveObserverEvent(String observerKey) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.REMOVE_OBSERVER);
         event.mCharacters = observerKey;
         return event;
     }
 
+    public static GeckoEvent createPreferencesObserveEvent(int requestId, String[] prefNames) {
+        GeckoEvent event = new GeckoEvent(NativeGeckoEvent.PREFERENCES_OBSERVE);
+        event.mCount = requestId;
+        event.mPrefNames = prefNames;
+        return event;
+    }
+
+    public static GeckoEvent createPreferencesGetEvent(int requestId, String[] prefNames) {
+        GeckoEvent event = new GeckoEvent(NativeGeckoEvent.PREFERENCES_GET);
+        event.mCount = requestId;
+        event.mPrefNames = prefNames;
+        return event;
+    }
+
+    public static GeckoEvent createPreferencesRemoveObserversEvent(int requestId) {
+        GeckoEvent event = new GeckoEvent(NativeGeckoEvent.PREFERENCES_REMOVE_OBSERVERS);
+        event.mCount = requestId;
+        return event;
+    }
+
     public static GeckoEvent createLowMemoryEvent(int level) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.LOW_MEMORY);
         event.mMetaState = level;
         return event;
     }
 
     public static GeckoEvent createNetworkLinkChangeEvent(String status) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.NETWORK_LINK_CHANGE);
--- a/mobile/android/base/GeckoPreferences.java
+++ b/mobile/android/base/GeckoPreferences.java
@@ -671,19 +671,17 @@ public class GeckoPreferences
                 return null;
         }
 
         return dialog;
     }
 
     // Initialize preferences by requesting the preference values from Gecko
     private int getGeckoPreferences(final PreferenceGroup screen, ArrayList<String> prefs) {
-        JSONArray jsonPrefs = new JSONArray(prefs);
-
-        return PrefsHelper.getPrefs(jsonPrefs, new PrefsHelper.PrefHandlerBase() {
+        return PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
             private Preference getField(String prefName) {
                 return screen.findPreference(prefName);
             }
 
             // Handle v14 TwoStatePreference with backwards compatibility.
             class CheckBoxPrefSetter {
                 public void setBooleanPref(Preference preference, boolean value) {
                     if ((preference instanceof CheckBoxPreference) &&
--- a/mobile/android/base/PrefsHelper.java
+++ b/mobile/android/base/PrefsHelper.java
@@ -8,72 +8,58 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.util.Log;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
 
 /**
  * Helper class to get/set gecko prefs.
  */
 public final class PrefsHelper {
     private static final String LOGTAG = "GeckoPrefsHelper";
 
     private static boolean sRegistered = false;
     private static final Map<Integer, PrefHandler> sCallbacks = new HashMap<Integer, PrefHandler>();
     private static int sUniqueRequestId = 1;
 
     public static int getPref(String prefName, PrefHandler callback) {
-        JSONArray prefs = new JSONArray();
-        prefs.put(prefName);
-        return getPrefs(prefs, callback);
+        return getPrefsInternal(new String[] { prefName }, callback);
     }
 
     public static int getPrefs(String[] prefNames, PrefHandler callback) {
-        JSONArray prefs = new JSONArray();
-        for (String p : prefNames) {
-            prefs.put(p);
-        }
-        return getPrefs(prefs, callback);
+        return getPrefsInternal(prefNames, callback);
     }
 
-    public static int getPrefs(JSONArray prefNames, PrefHandler callback) {
+    public static int getPrefs(ArrayList<String> prefNames, PrefHandler callback) {
+        return getPrefsInternal(prefNames.toArray(new String[prefNames.size()]), callback);
+    }
+
+    private static int getPrefsInternal(String[] prefNames, PrefHandler callback) {
         int requestId;
         synchronized (PrefsHelper.class) {
             ensureRegistered();
 
             requestId = sUniqueRequestId++;
             sCallbacks.put(requestId, callback);
         }
 
         GeckoEvent event;
-        try {
-            JSONObject message = new JSONObject();
-            message.put("requestId", Integer.toString(requestId));
-            message.put("preferences", prefNames);
-            event = GeckoEvent.createBroadcastEvent(callback.isObserver() ?
-                "Preferences:Observe" : "Preferences:Get", message.toString());
-            GeckoAppShell.sendEventToGecko(event);
-        } catch (Exception e) {
-            Log.e(LOGTAG, "Error while composing Preferences:" +
-                  (callback.isObserver() ? "Observe" : "Get") + " message", e);
-
-            // if we failed to send the message, drop our reference to the callback because
-            // otherwise it will leak since we will never get the response
-            synchronized (PrefsHelper.class) {
-                sCallbacks.remove(requestId);
-            }
-
-            return -1;
+        if (callback.isObserver()) {
+            event = GeckoEvent.createPreferencesObserveEvent(requestId, prefNames);
+        } else {
+            event = GeckoEvent.createPreferencesGetEvent(requestId, prefNames);
         }
+        GeckoAppShell.sendEventToGecko(event);
 
         return requestId;
     }
 
     private static void ensureRegistered() {
         if (sRegistered) {
             return;
         }
--- a/mobile/android/base/RobocopAPI.java
+++ b/mobile/android/base/RobocopAPI.java
@@ -30,16 +30,28 @@ public class RobocopAPI {
     public void unregisterEventListener(String event, GeckoEventListener listener) {
         GeckoAppShell.unregisterEventListener(event, listener);
     }
 
     public void broadcastEvent(String subject, String data) {
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(subject, data));
     }
 
+    public void preferencesGetEvent(int requestId, String[] prefNames) {
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesGetEvent(requestId, prefNames));
+    }
+
+    public void preferencesObserveEvent(int requestId, String[] prefNames) {
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesObserveEvent(requestId, prefNames));
+    }
+
+    public void preferencesRemoveObserversEvent(int requestId) {
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesRemoveObserversEvent(requestId));
+    }
+
     public void setDrawListener(GeckoLayerClient.DrawListener listener) {
         GeckoAppShell.getLayerView().getLayerClient().setDrawListener(listener);
     }
 
     public Cursor querySql(String dbPath, String query) {
         GeckoLoader.loadSQLiteLibs(mGeckoApp, mGeckoApp.getApplication().getPackageResourcePath());
         return new SQLiteBridge(dbPath).rawQuery(query, null);
     }
--- a/mobile/android/base/gfx/Axis.java
+++ b/mobile/android/base/gfx/Axis.java
@@ -59,23 +59,22 @@ abstract class Axis {
     }
 
     private static int getIntPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
         Integer value = (prefs == null ? null : prefs.get(prefName));
         return (value == null || value < 0 ? defaultValue : value);
     }
 
     static void initPrefs() {
-        JSONArray prefs = new JSONArray();
-        prefs.put(PREF_SCROLLING_FRICTION_FAST);
-        prefs.put(PREF_SCROLLING_FRICTION_SLOW);
-        prefs.put(PREF_SCROLLING_MAX_EVENT_ACCELERATION);
-        prefs.put(PREF_SCROLLING_OVERSCROLL_DECEL_RATE);
-        prefs.put(PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT);
-        prefs.put(PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE);
+        final String[] prefs = { PREF_SCROLLING_FRICTION_FAST,
+                                 PREF_SCROLLING_FRICTION_SLOW,
+                                 PREF_SCROLLING_MAX_EVENT_ACCELERATION,
+                                 PREF_SCROLLING_OVERSCROLL_DECEL_RATE,
+                                 PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT,
+                                 PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE };
 
         PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
             Map<String, Integer> mPrefs = new HashMap<String, Integer>();
 
             @Override public void prefValue(String name, int value) {
                 mPrefs.put(name, value);
             }
 
--- a/mobile/android/base/gfx/DisplayPortCalculator.java
+++ b/mobile/android/base/gfx/DisplayPortCalculator.java
@@ -56,29 +56,28 @@ final class DisplayPortCalculator {
         return sStrategy.drawTimeUpdate(millis, pixels);
     }
 
     static void resetPageState() {
         sStrategy.resetPageState();
     }
 
     static void initPrefs() {
-        JSONArray prefs = new JSONArray();
-        prefs.put(PREF_DISPLAYPORT_STRATEGY);
-        prefs.put(PREF_DISPLAYPORT_FM_MULTIPLIER);
-        prefs.put(PREF_DISPLAYPORT_FM_DANGER_X);
-        prefs.put(PREF_DISPLAYPORT_FM_DANGER_Y);
-        prefs.put(PREF_DISPLAYPORT_VB_MULTIPLIER);
-        prefs.put(PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD);
-        prefs.put(PREF_DISPLAYPORT_VB_REVERSE_BUFFER);
-        prefs.put(PREF_DISPLAYPORT_VB_DANGER_X_BASE);
-        prefs.put(PREF_DISPLAYPORT_VB_DANGER_Y_BASE);
-        prefs.put(PREF_DISPLAYPORT_VB_DANGER_X_INCR);
-        prefs.put(PREF_DISPLAYPORT_VB_DANGER_Y_INCR);
-        prefs.put(PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD);
+        final String[] prefs = { PREF_DISPLAYPORT_STRATEGY,
+                                 PREF_DISPLAYPORT_FM_MULTIPLIER,
+                                 PREF_DISPLAYPORT_FM_DANGER_X,
+                                 PREF_DISPLAYPORT_FM_DANGER_Y,
+                                 PREF_DISPLAYPORT_VB_MULTIPLIER,
+                                 PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD,
+                                 PREF_DISPLAYPORT_VB_REVERSE_BUFFER,
+                                 PREF_DISPLAYPORT_VB_DANGER_X_BASE,
+                                 PREF_DISPLAYPORT_VB_DANGER_Y_BASE,
+                                 PREF_DISPLAYPORT_VB_DANGER_X_INCR,
+                                 PREF_DISPLAYPORT_VB_DANGER_Y_INCR,
+                                 PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD };
 
         PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
             private Map<String, Integer> mValues = new HashMap<String, Integer>();
 
             @Override public void prefValue(String pref, int value) {
                 mValues.put(pref, value);
             }
 
--- a/mobile/android/base/tests/testAddonManager.java.in
+++ b/mobile/android/base/tests/testAddonManager.java.in
@@ -54,31 +54,28 @@ public class testAddonManager extends Pi
         JSONObject jsonPref = new JSONObject();
         try {
             jsonPref.put("name", "extensions.getAddons.browseAddons");
             jsonPref.put("type", "string");
             jsonPref.put("value", getAbsoluteUrl("/robocop/robocop_blank_01.html"));
             mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
 
             // Wait for confirmation of the pref change before proceeding with the test.
-            JSONArray getPrefData = new JSONArray();
-            getPrefData.put("extensions.getAddons.browseAddons");
-            JSONObject message = new JSONObject();
-            message.put("requestId", "testAddonManager");
-            message.put("preferences", getPrefData);
+            final String[] prefNames = { "extensions.getAddons.browseAddons" };
+            final int ourRequestId = 0x7357;
             Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendGeckoEvent("Preferences:Get", message.toString());
+            mActions.sendPreferencesGetEvent(ourRequestId, prefNames);
 
             JSONObject data = null;
-            String requestId = "";
+            int requestId = -1;
 
             // Wait until we get the correct "Preferences:Data" event
-            while (!requestId.equals("testAddonManager")) {
+            while (requestId != ourRequestId) {
                 data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getString("requestId");
+                requestId = data.getInt("requestId");
             }
             eventExpecter.unregisterListener();
 
         } catch (Exception ex) { 
             mAsserter.ok(false, "exception in testAddonManager", ex.toString());
         }
 
         // Load AMO page by clicking the AMO icon
--- a/mobile/android/base/tests/testDistribution.java.in
+++ b/mobile/android/base/tests/testDistribution.java.in
@@ -24,17 +24,17 @@ import org.json.JSONObject;
  *     preferences.json
  *     bookmarks.json
  *     searchplugins/
  *       common/
  *         engine.xml
  */
 public class testDistribution extends ContentProviderTest {
     private static final String MOCK_PACKAGE = "mock-package.zip";
-    private static final String PREF_REQUEST_ID = "testDistribution";
+    private static final int PREF_REQUEST_ID = 0x7357;
 
     private Activity mActivity;
 
     @Override
     protected int getTestType() {
         return TEST_MOCHITEST;
     }
 
@@ -81,38 +81,33 @@ public class testDistribution extends Co
         String prefID = "distribution.id";
         String prefAbout = "distribution.about";
         String prefVersion = "distribution.version";
         String prefTestBoolean = "distribution.test.boolean";
         String prefTestString = "distribution.test.string";
         String prefTestInt = "distribution.test.int";
 
         try {
-            JSONArray getPrefData = new JSONArray();
-            getPrefData.put(prefID);
-            getPrefData.put(prefAbout);
-            getPrefData.put(prefVersion);
-            getPrefData.put(prefTestBoolean);
-            getPrefData.put(prefTestString);
-            getPrefData.put(prefTestInt);
-
-            JSONObject message = new JSONObject();
-            message.put("requestId", PREF_REQUEST_ID);
-            message.put("preferences", getPrefData);
+            final String[] prefNames = { prefID,
+                                         prefAbout,
+                                         prefVersion,
+                                         prefTestBoolean,
+                                         prefTestString,
+                                         prefTestInt };
 
             Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendGeckoEvent("Preferences:Get", message.toString());
+            mActions.sendPreferencesGetEvent(PREF_REQUEST_ID, prefNames);
 
             JSONObject data = null;
-            String requestId = "";
+            int requestId = -1;
 
             // Wait until we get the correct "Preferences:Data" event
-            while (!requestId.equals(PREF_REQUEST_ID)) {
+            while (requestId != PREF_REQUEST_ID) {
                 data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getString("requestId");
+                requestId = data.getInt("requestId");
             }
             eventExpecter.unregisterListener();
 
             JSONArray preferences = data.getJSONArray("preferences");
             for (int i = 0; i < preferences.length(); i++) {
                 JSONObject pref = (JSONObject) preferences.get(i);
                 String name = pref.getString("name");
 
@@ -167,67 +162,55 @@ public class testDistribution extends Co
         try {
             // Request the pref change to the locale.
             jsonPref.put("name", prefUseragentLocale);
             jsonPref.put("type", "string");
             jsonPref.put("value", aLocale);
             mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
 
             // Wait for confirmation of the pref change.
-            JSONArray getPrefData = new JSONArray();
-            getPrefData.put(prefUseragentLocale);
-
-            JSONObject message = new JSONObject();
-            message.put("requestId", PREF_REQUEST_ID);
-            message.put("preferences", getPrefData);
+            final String[] prefNames = { prefUseragentLocale };
 
             Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendGeckoEvent("Preferences:Get", message.toString());
+            mActions.sendPreferencesGetEvent(PREF_REQUEST_ID, prefNames);
 
             JSONObject data = null;
-            String requestId = "";
+            int requestId = -1;
 
             // Wait until we get the correct "Preferences:Data" event
-            while (!requestId.equals(PREF_REQUEST_ID)) {
+            while (requestId != PREF_REQUEST_ID) {
                 data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getString("requestId");
+                requestId = data.getInt("requestId");
             }
             eventExpecter.unregisterListener();
 
         } catch (Exception e) {
             mAsserter.ok(false, "exception setting test locale", e.toString());
         }
     }
 
     // Test localized distribution and preferences values stored in preferences.json
     private void checkLocalizedPreferences(String aLocale) {
         String prefAbout = "distribution.about";
         String prefLocalizeable = "distribution.test.localizeable";
         String prefLocalizeableOverride = "distribution.test.localizeable-override";
 
         try {
-            JSONArray getPrefData = new JSONArray();
-            getPrefData.put(prefAbout);
-            getPrefData.put(prefLocalizeable);
-            getPrefData.put(prefLocalizeableOverride);
-
-            JSONObject message = new JSONObject();
-            message.put("requestId", PREF_REQUEST_ID);
-            message.put("preferences", getPrefData);
+            final String[] prefNames = { prefAbout, prefLocalizeable, prefLocalizeableOverride };
 
             Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendGeckoEvent("Preferences:Get", message.toString());
+            mActions.sendPreferencesGetEvent(PREF_REQUEST_ID, prefNames);
 
             JSONObject data = null;
-            String requestId = "";
+            int requestId = -1;
 
             // Wait until we get the correct "Preferences:Data" event
-            while (!requestId.equals(PREF_REQUEST_ID)) {
+            while (requestId != PREF_REQUEST_ID) {
                 data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getString("requestId");
+                requestId = data.getInt("requestId");
             }
             eventExpecter.unregisterListener();
 
             JSONArray preferences = data.getJSONArray("preferences");
             for (int i = 0; i < preferences.length(); i++) {
                 JSONObject pref = (JSONObject) preferences.get(i);
                 String name = pref.getString("name");
 
--- a/mobile/android/base/tests/testDoorHanger.java.in
+++ b/mobile/android/base/tests/testDoorHanger.java.in
@@ -76,32 +76,29 @@ public class testDoorHanger extends Base
         // Make sure doorhanger is hidden
         mAsserter.is(mSolo.searchText(GEO_MESSAGE), false, "Geolocation doorhanger notification is hidden when opening a new tab");
         */
 
 
         boolean offlineAllowedByDefault = true;
         try {
             // Save offline-allow-by-default preferences first
-            JSONArray getPrefData = new JSONArray();
-            getPrefData.put("offline-apps.allow_by_default");
-            JSONObject message = new JSONObject();
-            message.put("requestId", "testDoorHanger");
-            message.put("preferences", getPrefData);
+            final String[] prefNames = { "offline-apps.allow_by_default" };
+            final int ourRequestId = 0x7357;
 
             Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendGeckoEvent("Preferences:Get", message.toString());
+            mActions.sendPreferencesGetEvent(ourRequestId, prefNames);
 
             JSONObject data = null;
-            String requestId = "";
+            int requestId = -1;
 
             // Wait until we get the correct "Preferences:Data" event
-            while (!requestId.equals("testDoorHanger")) {
+            while (requestId != ourRequestId) {
                 data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getString("requestId");
+                requestId = data.getInt("requestId");
             }
             eventExpecter.unregisterListener();
 
             JSONArray preferences = data.getJSONArray("preferences");
             if (preferences.length() > 0) {
                 JSONObject pref = (JSONObject) preferences.get(0);
                 offlineAllowedByDefault = pref.getBoolean("value");
             }
--- a/mobile/android/base/tests/testPasswordEncrypt.java.in
+++ b/mobile/android/base/tests/testPasswordEncrypt.java.in
@@ -121,31 +121,28 @@ public class testPasswordEncrypt extends
         JSONObject jsonPref = new JSONObject();
         try {
             jsonPref.put("name", "privacy.masterpassword.enabled");
             jsonPref.put("type", "string");
             jsonPref.put("value", passwd);
             mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
 
             // Wait for confirmation of the pref change before proceeding with the test.
-            JSONArray getPrefData = new JSONArray();
-            getPrefData.put("privacy.masterpassword.enabled");
-            JSONObject message = new JSONObject();
-            message.put("requestId", "testPasswordEncrypt");
-            message.put("preferences", getPrefData);
+            final String[] prefNames = { "privacy.masterpassword.enabled" };
+            final int ourRequestId = 0x73577;
             Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendGeckoEvent("Preferences:Get", message.toString());
+            mActions.sendPreferencesGetEvent(ourRequestId, prefNames);
 
             JSONObject data = null;
-            String requestId = "";
+            int requestId = -1;
 
             // Wait until we get the correct "Preferences:Data" event
-            while (!requestId.equals("testPasswordEncrypt")) {
+            while (requestId != ourRequestId) {
                 data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getString("requestId");
+                requestId = data.getInt("requestId");
             }
         } catch (Exception ex) { 
             mAsserter.ok(false, "exception in toggleMasterPassword", ex.toString());
         }
     }
 
     @Override
     public void tearDown() throws Exception {
--- a/mobile/android/base/tests/testPrefsObserver.java.in
+++ b/mobile/android/base/tests/testPrefsObserver.java.in
@@ -11,17 +11,17 @@ import org.json.JSONObject;
 /**
  * Basic test to check bounce-back from overscroll.
  * - Load the page and verify it draws
  * - Drag page downwards by 100 pixels into overscroll, verify it snaps back.
  * - Drag page rightwards by 100 pixels into overscroll, verify it snaps back.
  */
 public class testPrefsObserver extends BaseTest {
     private static final String PREF_TEST_PREF = "robocop.tests.dummy";
-    private static final String PREF_REQUEST_ID = "testPrefsObserver";
+    private static final int PREF_OBSERVE_REQUEST_ID = 0x7357;
     private static final long PREF_TIMEOUT = 10000;
 
     private Actions.RepeatedEventExpecter mExpecter;
 
     @Override
     protected int getTestType() {
         return TEST_MOCHITEST;
     }
@@ -35,69 +35,64 @@ public class testPrefsObserver extends B
         jsonPref.put("value", value);
         mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
     }
 
     public void waitAndCheckPref(boolean value) throws JSONException {
         mAsserter.dumpLog("Waiting to check pref");
 
         JSONObject data = null;
-        String requestId = "";
+        int requestId = -1;
 
-        while (!requestId.equals(PREF_REQUEST_ID)) {
+        while (requestId != PREF_OBSERVE_REQUEST_ID) {
             data = new JSONObject(mExpecter.blockForEventData());
             if (!mExpecter.eventReceived()) {
                 mAsserter.ok(false, "Checking pref is correct value", "Didn't receive pref");
                 return;
             }
-            requestId = data.getString("requestId");
+            requestId = data.getInt("requestId");
         }
 
         JSONObject pref = data.getJSONArray("preferences").getJSONObject(0);
         mAsserter.is(pref.getString("name"), PREF_TEST_PREF, "Pref name is correct");
         mAsserter.is(pref.getString("type"), "bool", "Pref type is correct");
         mAsserter.is(pref.getBoolean("value"), value, "Pref value is correct");
     }
 
     public void verifyDisconnect() throws JSONException {
         mAsserter.dumpLog("Checking pref observer is removed");
 
         JSONObject pref = null;
-        String requestId = "";
+        int requestId = -1;
 
-        while (!requestId.equals(PREF_REQUEST_ID)) {
+        while (requestId != PREF_OBSERVE_REQUEST_ID) {
             String data = mExpecter.blockForEventDataWithTimeout(PREF_TIMEOUT);
             if (data == null) {
                 mAsserter.ok(true, "Verifying pref is unobserved", "Didn't get unobserved pref");
                 return;
             }
             pref = new JSONObject(data);
-            requestId = pref.getString("requestId");
+            requestId = pref.getInt("requestId");
         }
 
         mAsserter.ok(false, "Received unobserved pref change", "");
     }
 
     public void observePref() throws JSONException {
         mAsserter.dumpLog("Setting up pref observer");
 
         // Setup the pref observer
-        JSONArray getPrefData = new JSONArray();
-        getPrefData.put(PREF_TEST_PREF);
-        JSONObject message = new JSONObject();
-        message.put("requestId", PREF_REQUEST_ID);
-        message.put("preferences", getPrefData);
         mExpecter = mActions.expectGeckoEvent("Preferences:Data");
-        mActions.sendGeckoEvent("Preferences:Observe", message.toString());
+        mActions.sendPreferencesObserveEvent(PREF_OBSERVE_REQUEST_ID, new String[] { PREF_TEST_PREF });
     }
 
     public void removePrefObserver() {
         mAsserter.dumpLog("Removing pref observer");
 
-        mActions.sendGeckoEvent("Preferences:RemoveObservers", PREF_REQUEST_ID);
+        mActions.sendPreferencesRemoveObserversEvent(PREF_OBSERVE_REQUEST_ID);
     }
 
     public void testPrefsObserver() {
         blockForGeckoReady();
 
         try {
             setPref(false);
             observePref();
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -274,20 +274,17 @@ var BrowserApp = {
     Services.obs.addObserver(this, "Tab:Closed", false);
     Services.obs.addObserver(this, "Session:Back", false);
     Services.obs.addObserver(this, "Session:ShowHistory", false);
     Services.obs.addObserver(this, "Session:Forward", false);
     Services.obs.addObserver(this, "Session:Reload", false);
     Services.obs.addObserver(this, "Session:Stop", false);
     Services.obs.addObserver(this, "SaveAs:PDF", false);
     Services.obs.addObserver(this, "Browser:Quit", false);
-    Services.obs.addObserver(this, "Preferences:Get", false);
     Services.obs.addObserver(this, "Preferences:Set", false);
-    Services.obs.addObserver(this, "Preferences:Observe", false);
-    Services.obs.addObserver(this, "Preferences:RemoveObservers", false);
     Services.obs.addObserver(this, "ScrollTo:FocusedInput", false);
     Services.obs.addObserver(this, "Sanitize:ClearData", false);
     Services.obs.addObserver(this, "FullScreen:Exit", false);
     Services.obs.addObserver(this, "Viewport:Change", false);
     Services.obs.addObserver(this, "Viewport:Flush", false);
     Services.obs.addObserver(this, "Viewport:FixedMarginsChanged", false);
     Services.obs.addObserver(this, "Passwords:Init", false);
     Services.obs.addObserver(this, "FormHistory:Init", false);
@@ -917,17 +914,17 @@ var BrowserApp = {
   },
 
   quit: function quit() {
     // Figure out if there's at least one other browser window around.
     let lastBrowser = true;
     let e = Services.wm.getEnumerator("navigator:browser");
     while (e.hasMoreElements() && lastBrowser) {
       let win = e.getNext();
-      if (win != window)
+      if (!win.closed && win != window)
         lastBrowser = false;
     }
 
     if (lastBrowser) {
       // Let everyone know we are closing the last browser window
       let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
       Services.obs.notifyObservers(closingCanceled, "browser-lastwindow-close-requested", null);
       if (closingCanceled.data)
@@ -988,37 +985,38 @@ var BrowserApp = {
                                   Services.io.newFileURI(file), "", mimeInfo,
                                   Date.now() * 1000, null, cancelable, isPrivate);
 
     webBrowserPrint.print(printSettings, download);
   },
 
   notifyPrefObservers: function(aPref) {
     this._prefObservers[aPref].forEach(function(aRequestId) {
-      let request = { requestId : aRequestId,
-                      preferences : [aPref] };
-      this.getPreferences(request);
+      this.getPreferences(aRequestId, [aPref], 1);
     }, this);
   },
 
-  getPreferences: function getPreferences(aPrefsRequest, aListen) {
+  handlePreferencesRequest: function handlePreferencesRequest(aRequestId,
+                                                              aPrefNames,
+                                                              aListen) {
+
     let prefs = [];
 
-    for (let prefName of aPrefsRequest.preferences) {
+    for (let prefName of aPrefNames) {
       let pref = {
         name: prefName,
         type: "",
         value: null
       };
 
       if (aListen) {
         if (this._prefObservers[prefName])
-          this._prefObservers[prefName].push(aPrefsRequest.requestId);
+          this._prefObservers[prefName].push(aRequestId);
         else
-          this._prefObservers[prefName] = [ aPrefsRequest.requestId ];
+          this._prefObservers[prefName] = [ aRequestId ];
         Services.prefs.addObserver(prefName, this, false);
       }
 
       // These pref names are not "real" pref names.
       // They are used in the setting menu,
       // and these are passed when initializing the setting menu.
       switch (prefName) {
         // The plugin pref is actually two separate prefs, so
@@ -1115,41 +1113,21 @@ var BrowserApp = {
           break;
       }
 
       prefs.push(pref);
     }
 
     sendMessageToJava({
       type: "Preferences:Data",
-      requestId: aPrefsRequest.requestId,    // opaque request identifier, can be any string/int/whatever
+      requestId: aRequestId,    // opaque request identifier, can be any string/int/whatever
       preferences: prefs
     });
   },
 
-  removePreferenceObservers: function removePreferenceObservers(aRequestId) {
-    let newPrefObservers = [];
-    for (let prefName in this._prefObservers) {
-      let requestIds = this._prefObservers[prefName];
-      // Remove the requestID from the preference handlers
-      let i = requestIds.indexOf(aRequestId);
-      if (i >= 0) {
-        requestIds.splice(i, 1);
-      }
-
-      // If there are no more request IDs, remove the observer
-      if (requestIds.length == 0) {
-        Services.prefs.removeObserver(prefName, this);
-      } else {
-        newPrefObservers[prefName] = requestIds;
-      }
-    }
-    this._prefObservers = newPrefObservers;
-  },
-
   setPreferences: function setPreferences(aPref) {
     let json = JSON.parse(aPref);
 
     switch (json.name) {
       // The plugin pref is actually two separate prefs, so
       // we need to handle it differently
       case "plugin.enable":
         PluginHelper.setPluginPreference(json.value);
@@ -1438,32 +1416,20 @@ var BrowserApp = {
       case "Browser:Quit":
         this.quit();
         break;
 
       case "SaveAs:PDF":
         this.saveAsPDF(browser);
         break;
 
-      case "Preferences:Get":
-        this.getPreferences(JSON.parse(aData));
-        break;
-
       case "Preferences:Set":
         this.setPreferences(aData);
         break;
 
-      case "Preferences:Observe":
-        this.getPreferences(JSON.parse(aData), true);
-        break;
-
-      case "Preferences:RemoveObservers":
-        this.removePreferenceObservers(aData);
-        break;
-
       case "ScrollTo:FocusedInput":
         // these messages come from a change in the viewable area and not user interaction
         // we allow scrolling to the selected input, but not zooming the page
         this.scrollToFocusedInput(browser, false);
         break;
 
       case "Sanitize:ClearData":
         this.sanitize(aData);
@@ -1527,16 +1493,44 @@ var BrowserApp = {
     return this.defaultBrowserWidth = width;
   },
 
   // nsIAndroidBrowserApp
   getBrowserTab: function(tabId) {
     return this.getTabForId(tabId);
   },
 
+  getPreferences: function getPreferences(requestId, prefNames, count) {
+    this.handlePreferencesRequest(requestId, prefNames, false);
+  },
+
+  observePreferences: function observePreferences(requestId, prefNames, count) {
+    this.handlePreferencesRequest(requestId, prefNames, true);
+  },
+
+  removePreferenceObservers: function removePreferenceObservers(aRequestId) {
+    let newPrefObservers = [];
+    for (let prefName in this._prefObservers) {
+      let requestIds = this._prefObservers[prefName];
+      // Remove the requestID from the preference handlers
+      let i = requestIds.indexOf(aRequestId);
+      if (i >= 0) {
+        requestIds.splice(i, 1);
+      }
+
+      // If there are no more request IDs, remove the observer
+      if (requestIds.length == 0) {
+        Services.prefs.removeObserver(prefName, this);
+      } else {
+        newPrefObservers[prefName] = requestIds;
+      }
+    }
+    this._prefObservers = newPrefObservers;
+  },
+
   // This method will print a list from fromIndex to toIndex, optionally
   // selecting selIndex(if fromIndex<=selIndex<=toIndex)
   showHistory: function(fromIndex, toIndex, selIndex) {
     let browser = this.selectedBrowser;
     let hist = browser.sessionHistory;
     let listitems = [];
     for (let i = toIndex; i >= fromIndex; i--) {
       let entry = hist.getEntryAtIndex(i, false);
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -476,16 +476,18 @@ pref("devtools.debugger.log", false);
 pref("devtools.debugger.remote-enabled", false);
 pref("devtools.debugger.remote-port", 6000);
 // Force debugger server binding on the loopback interface
 pref("devtools.debugger.force-local", true);
 // Display a prompt when a new connection starts to accept/reject it
 pref("devtools.debugger.prompt-connection", true);
 // Temporary setting to enable webapps actors
 pref("devtools.debugger.enable-content-actors", true);
+// Block tools from seeing / interacting with certified apps
+pref("devtools.debugger.forbid-certified-apps", true);
 
 // view source
 pref("view_source.syntax_highlight", true);
 pref("view_source.wrap_long_lines", false);
 pref("view_source.editor.external", false);
 pref("view_source.editor.path", "");
 // allows to add further arguments to the editor; use the %LINE% placeholder
 // for jumping to a specific line (e.g. "/line:%LINE%" or "--goto %LINE%")
--- a/netwerk/protocol/app/AppProtocolHandler.js
+++ b/netwerk/protocol/app/AppProtocolHandler.js
@@ -65,20 +65,20 @@ AppProtocolHandler.prototype = {
       // downstream user get a 404 error.
       dump("!! got no appInfo for " + appId + "\n");
       return new DummyChannel();
     }
 
     let uri;
     if (this._runningInParent || appInfo.isCoreApp) {
       // In-parent and CoreApps can directly access files, so use jar:file://
-      uri = "jar:file://" + appInfo.basePath + appId + "/application.zip!" + fileSpec;
+      uri = "jar:file://" + appInfo.path + "/application.zip!" + fileSpec;
     } else {
       // non-CoreApps in child need to ask parent for file handle, use jar:ipcfile://
-      uri = "jar:remoteopenfile://" + appInfo.basePath + appId + "/application.zip!" + fileSpec;
+      uri = "jar:remoteopenfile://" + appInfo.path + "/application.zip!" + fileSpec;
     }
     let channel = Services.io.newChannel(uri, null, null);
     channel.QueryInterface(Ci.nsIJARChannel).setAppURI(aURI);
     channel.QueryInterface(Ci.nsIChannel).originalURI = aURI;
 
     return channel;
   },
 
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -163,39 +163,29 @@ class TreeMetadataEmitter(LoggingMixin):
         if exports:
             yield Exports(sandbox, exports,
                 dist_install=not sandbox.get('NO_DIST_INSTALL', False))
 
         program = sandbox.get('PROGRAM')
         if program:
             yield Program(sandbox, program, sandbox['CONFIG']['BIN_SUFFIX'])
 
-        for manifest in sandbox.get('XPCSHELL_TESTS_MANIFESTS', []):
-            yield XpcshellManifests(sandbox, manifest)
-
-        for ipdl in sandbox.get('IPDL_SOURCES', []):
-            yield IPDLFile(sandbox, ipdl)
-
-        for local_include in sandbox.get('LOCAL_INCLUDES', []):
-            yield LocalInclude(sandbox, local_include)
-
-        for webidl in sandbox.get('WEBIDL_FILES', []):
-            yield WebIDLFile(sandbox, webidl)
-
-        for webidl in sandbox.get('GENERATED_EVENTS_WEBIDL_FILES', []):
-            yield GeneratedEventWebIDLFile(sandbox, webidl)
-
-        for webidl in sandbox.get('TEST_WEBIDL_FILES', []):
-            yield TestWebIDLFile(sandbox, webidl)
-
-        for webidl in sandbox.get('PREPROCESSED_WEBIDL_FILES', []):
-            yield PreprocessedWebIDLFile(sandbox, webidl)
-
-        for webidl in sandbox.get('GENERATED_WEBIDL_FILES', []):
-            yield GeneratedWebIDLFile(sandbox, webidl)
+        simple_lists = [
+            ('GENERATED_EVENTS_WEBIDL_FILES', GeneratedEventWebIDLFile),
<