Merge mozilla-central and inbound
authorEd Morley <emorley@mozilla.com>
Fri, 13 Sep 2013 15:13:00 +0100
changeset 159932 017efee4dc037efbd0fae00e85fdad790fd4aeb8
parent 159907 87c4a21ed1587e23e166db2a122d204ef7ed2112 (current diff)
parent 159931 3c7913e853b86dbe2503819145830689747ab59b (diff)
child 159933 9a8916b16e6e1071de5b97cb875d313f0657dceb
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 mozilla-central and inbound
--- 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/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/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/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/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/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/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/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/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,17 +2373,17 @@ 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());
         aContextPaint = contextPaint;
@@ -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/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/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);
--- 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/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/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;
@@ -3128,65 +3129,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 +3186,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 +3226,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,16 +215,52 @@ 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");
   mScrollPosition = aScrollPosition;
 
   OverflowChangedTracker oct;
--- 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/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,1,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/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),
+            ('GENERATED_WEBIDL_FILES', GeneratedWebIDLFile),
+            ('IPDL_SOURCES', IPDLFile),
+            ('LOCAL_INCLUDES', LocalInclude),
+            ('PREPROCESSED_WEBIDL_FILES', PreprocessedWebIDLFile),
+            ('TEST_WEBIDL_FILES', TestWebIDLFile),
+            ('WEBIDL_FILES', WebIDLFile),
+            ('XPCSHELL_TESTS_MANIFESTS', XpcshellManifests),
+        ]
+        for sandbox_var, klass in simple_lists:
+            for name in sandbox.get(sandbox_var, []):
+                yield klass(sandbox, name)
 
     def _emit_directory_traversal_from_sandbox(self, sandbox):
         o = DirectoryTraversal(sandbox)
         o.dirs = sandbox.get('DIRS', [])
         o.parallel_dirs = sandbox.get('PARALLEL_DIRS', [])
         o.tool_dirs = sandbox.get('TOOL_DIRS', [])
         o.test_dirs = sandbox.get('TEST_DIRS', [])
         o.test_tool_dirs = sandbox.get('TEST_TOOL_DIRS', [])
--- a/security/manager/ssl/src/nsNSSComponent.cpp
+++ b/security/manager/ssl/src/nsNSSComponent.cpp
@@ -11,17 +11,17 @@
 #include "nsNSSComponent.h"
 
 #include "CertVerifier.h"
 #include "nsCertVerificationThread.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsComponentManagerUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsICertOverrideService.h"
-#include "nsIPrefService.h"
+#include "mozilla/Preferences.h"
 
 #ifndef MOZ_DISABLE_CRYPTOLEGACY
 #include "nsIDOMNode.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMWindow.h"
 #include "nsIDOMWindowCollection.h"
 #include "nsIDocument.h"
@@ -866,89 +866,72 @@ static CipherPref CipherPrefs[] = {
  {"security.ssl3.rsa_aes_128_sha", TLS_RSA_WITH_AES_128_CBC_SHA}, // 128-bit AES encryption with RSA and a SHA1 MAC
  {"security.ssl3.dhe_rsa_des_ede3_sha", SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA}, // 168-bit Triple DES with RSA, DHE, and a SHA1 MAC
  {"security.ssl3.dhe_dss_des_ede3_sha", SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA}, // 168-bit Triple DES with DSA, DHE, and a SHA1 MAC
  {"security.ssl3.rsa_seed_sha", TLS_RSA_WITH_SEED_CBC_SHA}, // SEED encryption with RSA and a SHA1 MAC
  {nullptr, 0} /* end marker */
 };
 
 static void
-setNonPkixOcspEnabled(int32_t ocspEnabled, nsIPrefBranch * pref)
+setNonPkixOcspEnabled(int32_t ocspEnabled)
 {
-  // Note: this preference is numeric vs bolean because previously we
+  // Note: this preference is numeric vs boolean because previously we
   // supported more than two options.
   if (!ocspEnabled) {
     CERT_DisableOCSPChecking(CERT_GetDefaultCertDB());
     CERT_DisableOCSPDefaultResponder(CERT_GetDefaultCertDB());
   } else {
     CERT_EnableOCSPChecking(CERT_GetDefaultCertDB());
     CERT_DisableOCSPDefaultResponder(CERT_GetDefaultCertDB());
   }
 }
 
 #define CRL_DOWNLOAD_DEFAULT false
 #define OCSP_ENABLED_DEFAULT 1
-#define OCSP_REQUIRED_DEFAULT 0
+#define OCSP_REQUIRED_DEFAULT false
 #define FRESH_REVOCATION_REQUIRED_DEFAULT false
 #define MISSING_CERT_DOWNLOAD_DEFAULT false
 #define FIRST_REVO_METHOD_DEFAULT "ocsp"
 #define USE_NSS_LIBPKIX_DEFAULT false
 #define OCSP_STAPLING_ENABLED_DEFAULT true
 
 // Caller must hold a lock on nsNSSComponent::mutex when calling this function
-void nsNSSComponent::setValidationOptions(nsIPrefBranch * pref)
+void nsNSSComponent::setValidationOptions()
 {
   nsNSSShutDownPreventionLock locker;
-  nsresult rv;
 
-  bool crlDownloading;
-  rv = pref->GetBoolPref("security.CRL_download.enabled", &crlDownloading);
-  if (NS_FAILED(rv))
-    crlDownloading = CRL_DOWNLOAD_DEFAULT;
-  
-  int32_t ocspEnabled;
-  rv = pref->GetIntPref("security.OCSP.enabled", &ocspEnabled);
-  // 0 = disabled, 1 = enabled, 
-  // 2 = enabled with given default responder
-  if (NS_FAILED(rv))
-    ocspEnabled = OCSP_ENABLED_DEFAULT;
-
-  bool ocspRequired;
-  rv = pref->GetBoolPref("security.OCSP.require", &ocspRequired);
-  if (NS_FAILED(rv))
-    ocspRequired = OCSP_REQUIRED_DEFAULT;
+  bool crlDownloading = Preferences::GetBool("security.CRL_download.enabled",
+                                             CRL_DOWNLOAD_DEFAULT);
+  // 0 = disabled, 1 = enabled
+  int32_t ocspEnabled = Preferences::GetInt("security.OCSP.enabled",
+                                            OCSP_ENABLED_DEFAULT);
 
-  bool anyFreshRequired;
-  rv = pref->GetBoolPref("security.fresh_revocation_info.require", &anyFreshRequired);
-  if (NS_FAILED(rv))
-    anyFreshRequired = FRESH_REVOCATION_REQUIRED_DEFAULT;
-  
-  bool aiaDownloadEnabled;
-  rv = pref->GetBoolPref("security.missing_cert_download.enabled", &aiaDownloadEnabled);
-  if (NS_FAILED(rv))
-    aiaDownloadEnabled = MISSING_CERT_DOWNLOAD_DEFAULT;
+  bool ocspRequired = Preferences::GetBool("security.OCSP.require",
+                                           OCSP_REQUIRED_DEFAULT);
+  bool anyFreshRequired = Preferences::GetBool("security.fresh_revocation_info.require",
+                                               FRESH_REVOCATION_REQUIRED_DEFAULT);
+  bool aiaDownloadEnabled = Preferences::GetBool("security.missing_cert_download.enabled",
+                                                 MISSING_CERT_DOWNLOAD_DEFAULT);
 
-  nsCString firstNetworkRevo;
-  rv = pref->GetCharPref("security.first_network_revocation_method", getter_Copies(firstNetworkRevo));
-  if (NS_FAILED(rv))
+  nsCString firstNetworkRevo =
+    Preferences::GetCString("security.first_network_revocation_method");
+  if (firstNetworkRevo.IsEmpty()) {
     firstNetworkRevo = FIRST_REVO_METHOD_DEFAULT;
+  }
 
-  bool ocspStaplingEnabled;
-  rv = pref->GetBoolPref("security.ssl.enable_ocsp_stapling", &ocspStaplingEnabled);
-  if (NS_FAILED(rv)) {
-    ocspStaplingEnabled = OCSP_STAPLING_ENABLED_DEFAULT;
-  }
+  bool ocspStaplingEnabled = Preferences::GetBool("security.ssl.enable_ocsp_stapling",
+                                                  OCSP_STAPLING_ENABLED_DEFAULT);
   if (!ocspEnabled) {
     ocspStaplingEnabled = false;
   }
   PublicSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);
   PrivateSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);
-  
-  setNonPkixOcspEnabled(ocspEnabled, pref);
-  
+
+  setNonPkixOcspEnabled(ocspEnabled);
+
   CERT_SetOCSPFailureMode( ocspRequired ?
                            ocspMode_FailureIsVerificationFailure
                            : ocspMode_FailureIsNotAVerificationFailure);
 
   mDefaultCertVerifier = new CertVerifier(
       aiaDownloadEnabled ? 
         CertVerifier::missing_cert_download_on : CertVerifier::missing_cert_download_off,
       crlDownloading ?
@@ -966,26 +949,26 @@ void nsNSSComponent::setValidationOption
     * let's not reuse them.
     */
   SSL_ClearSessionCache();
 }
 
 // Enable the TLS versions given in the prefs, defaulting to SSL 3.0 and
 // TLS 1.0 when the prefs aren't set or when they are set to invalid values.
 nsresult
-nsNSSComponent::setEnabledTLSVersions(nsIPrefBranch * prefBranch)
+nsNSSComponent::setEnabledTLSVersions()
 {
   // keep these values in sync with security-prefs.js and firefox.js
   static const int32_t PSM_DEFAULT_MIN_TLS_VERSION = 0;
   static const int32_t PSM_DEFAULT_MAX_TLS_VERSION = 1;
 
-  int32_t minVersion = PSM_DEFAULT_MIN_TLS_VERSION;
-  int32_t maxVersion = PSM_DEFAULT_MAX_TLS_VERSION;
-  mPrefBranch->GetIntPref("security.tls.version.min", &minVersion);
-  mPrefBranch->GetIntPref("security.tls.version.max", &maxVersion);
+  int32_t minVersion = Preferences::GetInt("security.tls.version.min",
+                                           PSM_DEFAULT_MIN_TLS_VERSION);
+  int32_t maxVersion = Preferences::GetInt("security.tls.version.max",
+                                           PSM_DEFAULT_MAX_TLS_VERSION);
 
   // 0 means SSL 3.0, 1 means TLS 1.0, 2 means TLS 1.1, etc.
   minVersion += SSL_LIBRARY_VERSION_3_0;
   maxVersion += SSL_LIBRARY_VERSION_3_0;
 
   SSLVersionRange range = { (uint16_t) minVersion, (uint16_t) maxVersion };
 
   if (minVersion != (int32_t) range.min || // prevent truncation
@@ -1011,23 +994,21 @@ nsNSSComponent::SkipOcsp()
   SECStatus rv = CERT_DisableOCSPChecking(certdb);
   return (rv == SECSuccess) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsNSSComponent::SkipOcspOff()
 {
   nsNSSShutDownPreventionLock locker;
-  int32_t ocspEnabled;
-  if (NS_FAILED(mPrefBranch->GetIntPref("security.OCSP.enabled", &ocspEnabled)))
-    ocspEnabled = OCSP_ENABLED_DEFAULT;
-  // 0 = disabled, 1 = enabled, 
-  // 2 = enabled with given default responder
-  
-  setNonPkixOcspEnabled(ocspEnabled, mPrefBranch);
+  // 0 = disabled, 1 = enabled
+  int32_t ocspEnabled = Preferences::GetInt("security.OCSP.enabled",
+                                            OCSP_ENABLED_DEFAULT);
+
+  setNonPkixOcspEnabled(ocspEnabled);
 
   if (ocspEnabled)
     SSL_ClearSessionCache();
 
   return NS_OK;
 }
 
 static void configureMD5(bool enabled)
@@ -1045,16 +1026,24 @@ static void configureMD5(bool enabled)
         0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
     NSS_SetAlgorithmPolicy(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION,
         0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
     NSS_SetAlgorithmPolicy(SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC,
         0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
   }
 }
 
+static const bool SUPPRESS_WARNING_PREF_DEFAULT = false;
+static const bool MD5_ENABLED_DEFAULT = false;
+static const bool TLS_SESSION_TICKETS_ENABLED_DEFAULT = true;
+static const bool REQUIRE_SAFE_NEGOTIATION_DEFAULT = false;
+static const bool ALLOW_UNRESTRICTED_RENEGO_DEFAULT = false;
+static const bool FALSE_START_ENABLED_DEFAULT = true;
+static const bool CIPHER_ENABLED_DEFAULT = false;
+
 nsresult
 nsNSSComponent::InitializeNSS(bool showWarningBox)
 {
   // Can be called both during init and profile change.
   // Needs mutex protection.
 
   PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("nsNSSComponent::InitializeNSS\n"));
 
@@ -1114,27 +1103,23 @@ nsNSSComponent::InitializeNSS(bool showW
   #endif
       if (NS_FAILED(rv)) {
         nsPSMInitPanic::SetPanic();
         return rv;
       }
     }
 
 #ifndef NSS_NO_LIBPKIX
-    rv = mPrefBranch->GetBoolPref("security.use_libpkix_verification", &globalConstFlagUsePKIXVerification);
-    if (NS_FAILED(rv))
-      globalConstFlagUsePKIXVerification = USE_NSS_LIBPKIX_DEFAULT;
+    globalConstFlagUsePKIXVerification =
+      Preferences::GetBool("security.use_libpkix_verification", USE_NSS_LIBPKIX_DEFAULT);
 #endif
 
-    bool supress_warning_preference = false;
-    rv = mPrefBranch->GetBoolPref("security.suppress_nss_rw_impossible_warning", &supress_warning_preference);
-
-    if (NS_FAILED(rv)) {
-      supress_warning_preference = false;
-    }
+    bool suppressWarningPref =
+      Preferences::GetBool("security.suppress_nss_rw_impossible_warning",
+                           SUPPRESS_WARNING_PREF_DEFAULT);
 
     // init phase 2, init calls to NSS library
 
     PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("NSS Initialization beginning\n"));
 
     // The call to ConfigureInternalPKCS11Token needs to be done before NSS is initialized, 
     // but affects only static data.
     // If we could assume i18n will not change between profiles, one call per application
@@ -1150,17 +1135,17 @@ nsNSSComponent::InitializeNSS(bool showW
     // "/usr/lib/nss/libnssckbi.so".
     uint32_t init_flags = NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE;
     SECStatus init_rv = ::NSS_Initialize(profileStr.get(), "", "",
                                          SECMOD_DB, init_flags);
 
     if (init_rv != SECSuccess) {
       PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("can not init NSS r/w in %s\n", profileStr.get()));
 
-      if (supress_warning_preference) {
+      if (suppressWarningPref) {
         which_nss_problem = problem_none;
       }
       else {
         which_nss_problem = problem_no_rw;
       }
 
       // try to init r/o
       init_flags |= NSS_INIT_READONLY;
@@ -1188,78 +1173,82 @@ nsNSSComponent::InitializeNSS(bool showW
 
       ::NSS_SetDomesticPolicy();
 
       PK11_SetPasswordFunc(PK11PasswordPrompt);
 
       SharedSSLState::GlobalInit();
 
       // Register an observer so we can inform NSS when these prefs change
-      mPrefBranch->AddObserver("security.", this, false);
+      Preferences::AddStrongObserver(this, "security.");
 
       SSL_OptionSetDefault(SSL_ENABLE_SSL2, false);
       SSL_OptionSetDefault(SSL_V2_COMPATIBLE_HELLO, false);
 
-      rv = setEnabledTLSVersions(mPrefBranch);
+      rv = setEnabledTLSVersions();
       if (NS_FAILED(rv)) {
         nsPSMInitPanic::SetPanic();
         return NS_ERROR_UNEXPECTED;
       }
 
-      bool enabled = true; // XXX: see bug 733644
-
-      mPrefBranch->GetBoolPref("security.enable_md5_signatures", &enabled);
-      configureMD5(enabled);
+      bool md5Enabled = Preferences::GetBool("security.enable_md5_signatures",
+                                             MD5_ENABLED_DEFAULT);
+      configureMD5(md5Enabled);
 
       // Configure TLS session tickets
-      mPrefBranch->GetBoolPref("security.enable_tls_session_tickets", &enabled);
-      SSL_OptionSetDefault(SSL_ENABLE_SESSION_TICKETS, enabled);
-
-      mPrefBranch->GetBoolPref("security.ssl.require_safe_negotiation", &enabled);
-      SSL_OptionSetDefault(SSL_REQUIRE_SAFE_NEGOTIATION, enabled);
+      bool tlsSessionTicketsEnabled =
+        Preferences::GetBool("security.enable_tls_session_tickets",
+                             TLS_SESSION_TICKETS_ENABLED_DEFAULT);
+      SSL_OptionSetDefault(SSL_ENABLE_SESSION_TICKETS, tlsSessionTicketsEnabled);
 
-      mPrefBranch->GetBoolPref(
-        "security.ssl.allow_unrestricted_renego_everywhere__temporarily_available_pref", 
-        &enabled);
-      SSL_OptionSetDefault(SSL_ENABLE_RENEGOTIATION, 
-        enabled ? SSL_RENEGOTIATE_UNRESTRICTED : SSL_RENEGOTIATE_REQUIRES_XTN);
+      bool requireSafeNegotiation =
+        Preferences::GetBool("security.ssl.require_safe_negotiation",
+                             REQUIRE_SAFE_NEGOTIATION_DEFAULT);
+      SSL_OptionSetDefault(SSL_REQUIRE_SAFE_NEGOTIATION, requireSafeNegotiation);
+
+      bool allowUnrestrictedRenego =
+        Preferences::GetBool("security.ssl.allow_unrestricted_renego_everywhere__temporarily_available_pref",
+                             ALLOW_UNRESTRICTED_RENEGO_DEFAULT);
+      SSL_OptionSetDefault(SSL_ENABLE_RENEGOTIATION,
+                           allowUnrestrictedRenego ?
+                             SSL_RENEGOTIATE_UNRESTRICTED :
+                             SSL_RENEGOTIATE_REQUIRES_XTN);
 
 #ifdef SSL_ENABLE_FALSE_START // Requires NSS 3.12.8
-      mPrefBranch->GetBoolPref("security.ssl.enable_false_start", &enabled);
-      SSL_OptionSetDefault(SSL_ENABLE_FALSE_START, enabled);
+      bool falseStartEnabled = Preferences::GetBool("security.ssl.enable_false_start",
+                                                    FALSE_START_ENABLED_DEFAULT);
+      SSL_OptionSetDefault(SSL_ENABLE_FALSE_START, falseStartEnabled);
 #endif
 
       // Disable any ciphers that NSS might have enabled by default
       for (uint16_t i = 0; i < SSL_NumImplementedCiphers; ++i)
       {
         uint16_t cipher_id = SSL_ImplementedCiphers[i];
         SSL_CipherPrefSetDefault(cipher_id, false);
       }
 
+      bool cipherEnabled;
       // Now only set SSL/TLS ciphers we knew about at compile time
       for (CipherPref* cp = CipherPrefs; cp->pref; ++cp) {
-        rv = mPrefBranch->GetBoolPref(cp->pref, &enabled);
-        if (NS_FAILED(rv))
-          enabled = false;
-
-        SSL_CipherPrefSetDefault(cp->id, enabled);
+        cipherEnabled = Preferences::GetBool(cp->pref, CIPHER_ENABLED_DEFAULT);
+        SSL_CipherPrefSetDefault(cp->id, cipherEnabled);
       }
 
       // Enable ciphers for PKCS#12
       SEC_PKCS12EnableCipher(PKCS12_RC4_40, 1);
       SEC_PKCS12EnableCipher(PKCS12_RC4_128, 1);
       SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_40, 1);
       SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_128, 1);
       SEC_PKCS12EnableCipher(PKCS12_DES_56, 1);
       SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1);
       SEC_PKCS12SetPreferredCipher(PKCS12_DES_EDE3_168, 1);
       PORT_SetUCS2_ASCIIConversionFunction(pip_ucs2_ascii_conversion_fn);
 
       // dynamic options from prefs
-      setValidationOptions(mPrefBranch);
+      setValidationOptions();
 
       mHttpForNSS.initTable();
       mHttpForNSS.registerHttpClient();
 
       InstallLoadableRoots();
 
 #ifndef MOZ_DISABLE_CRYPTOLEGACY
       LaunchSmartCardThreads();
@@ -1295,19 +1284,17 @@ nsNSSComponent::ShutdownNSS()
   MutexAutoLock lock(mutex);
 
   if (mNSSInitialized) {
     mNSSInitialized = false;
 
     PK11_SetPasswordFunc((PK11PasswordFunc)nullptr);
     mHttpForNSS.unregisterHttpClient();
 
-    if (mPrefBranch) {
-      mPrefBranch->RemoveObserver("security.", this);
-    }
+    Preferences::RemoveObserver(this, "security.");
 
 #ifndef MOZ_DISABLE_CRYPTOLEGACY
     ShutdownSmartCardThreads();
 #endif
     SSL_ClearSessionCache();
     UnloadLoadableRoots();
 #ifndef NSS_NO_LIBPKIX
     CleanupIdentityInfo();
@@ -1318,17 +1305,19 @@ nsNSSComponent::ShutdownNSS()
     if (SECSuccess != ::NSS_Shutdown()) {
       PR_LOG(gPIPNSSLog, PR_LOG_ALWAYS, ("NSS SHUTDOWN FAILURE\n"));
     }
     else {
       PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("NSS shutdown =====>> OK <<=====\n"));
     }
   }
 }
- 
+
+static const bool SEND_LM_DEFAULT = false;
+
 NS_IMETHODIMP
 nsNSSComponent::Init()
 {
   // No mutex protection.
   // Assume Init happens before any concurrency on "this" can start.
 
   nsresult rv = NS_OK;
 
@@ -1354,23 +1343,18 @@ nsNSSComponent::Init()
     NS_NAMED_LITERAL_STRING(dummy_name, "dummy");
     nsXPIDLString result;
     mPIPNSSBundle->GetStringFromName(dummy_name.get(),
                                      getter_Copies(result));
     mNSSErrorsBundle->GetStringFromName(dummy_name.get(),
                                         getter_Copies(result));
   }
 
-  if (!mPrefBranch) {
-    mPrefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
-    NS_ASSERTION(mPrefBranch, "Unable to get pref service");
-  }
-
-  bool sendLM = false;
-  mPrefBranch->GetBoolPref("network.ntlm.send-lm-response", &sendLM);
+  bool sendLM = Preferences::GetBool("network.ntlm.send-lm-response",
+                                     SEND_LM_DEFAULT);
   nsNTLMAuthModule::SetSendLM(sendLM);
 
   // Do that before NSS init, to make sure we won't get unloaded.
   RegisterObservers();
 
   rv = InitializeNSS(true); // ok to show a warning box on failure
   if (NS_FAILED(rv)) {
     PR_LOG(gPIPNSSLog, PR_LOG_ERROR, ("Unable to Initialize NSS.\n"));
@@ -1630,61 +1614,71 @@ nsNSSComponent::Observe(nsISupports *aSu
       if (bec) {
         bec->DontForward();
       }
     }
   }
   else if (nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) { 
     nsNSSShutDownPreventionLock locker;
     bool clearSessionCache = false;
-    bool enabled;
     NS_ConvertUTF16toUTF8  prefName(someData);
 
     if (prefName.Equals("security.tls.version.min") ||
         prefName.Equals("security.tls.version.max")) {
-      (void) setEnabledTLSVersions(mPrefBranch);
+      (void) setEnabledTLSVersions();
       clearSessionCache = true;
     } else if (prefName.Equals("security.enable_md5_signatures")) {
-      mPrefBranch->GetBoolPref("security.enable_md5_signatures", &enabled);
-      configureMD5(enabled);
+      bool md5Enabled = Preferences::GetBool("security.enable_md5_signatures",
+                                             MD5_ENABLED_DEFAULT);
+      configureMD5(md5Enabled);
       clearSessionCache = true;
     } else if (prefName.Equals("security.enable_tls_session_tickets")) {
-      mPrefBranch->GetBoolPref("security.enable_tls_session_tickets", &enabled);
-      SSL_OptionSetDefault(SSL_ENABLE_SESSION_TICKETS, enabled);
+      bool tlsSessionTicketsEnabled =
+        Preferences::GetBool("security.enable_tls_session_tickets",
+                             TLS_SESSION_TICKETS_ENABLED_DEFAULT);
+      SSL_OptionSetDefault(SSL_ENABLE_SESSION_TICKETS, tlsSessionTicketsEnabled);
     } else if (prefName.Equals("security.ssl.require_safe_negotiation")) {
-      mPrefBranch->GetBoolPref("security.ssl.require_safe_negotiation", &enabled);
-      SSL_OptionSetDefault(SSL_REQUIRE_SAFE_NEGOTIATION, enabled);
+      bool requireSafeNegotiation =
+        Preferences::GetBool("security.ssl.require_safe_negotiation",
+                             REQUIRE_SAFE_NEGOTIATION_DEFAULT);
+      SSL_OptionSetDefault(SSL_REQUIRE_SAFE_NEGOTIATION, requireSafeNegotiation);
     } else if (prefName.Equals("security.ssl.allow_unrestricted_renego_everywhere__temporarily_available_pref")) {
-      mPrefBranch->GetBoolPref("security.ssl.allow_unrestricted_renego_everywhere__temporarily_available_pref", &enabled);
-      SSL_OptionSetDefault(SSL_ENABLE_RENEGOTIATION, 
-        enabled ? SSL_RENEGOTIATE_UNRESTRICTED : SSL_RENEGOTIATE_REQUIRES_XTN);
+      bool allowUnrestrictedRenego =
+        Preferences::GetBool("security.ssl.allow_unrestricted_renego_everywhere__temporarily_available_pref",
+                             ALLOW_UNRESTRICTED_RENEGO_DEFAULT);
+      SSL_OptionSetDefault(SSL_ENABLE_RENEGOTIATION,
+                           allowUnrestrictedRenego ?
+                             SSL_RENEGOTIATE_UNRESTRICTED :
+                             SSL_RENEGOTIATE_REQUIRES_XTN);
 #ifdef SSL_ENABLE_FALSE_START // Requires NSS 3.12.8
     } else if (prefName.Equals("security.ssl.enable_false_start")) {
-      mPrefBranch->GetBoolPref("security.ssl.enable_false_start", &enabled);
-      SSL_OptionSetDefault(SSL_ENABLE_FALSE_START, enabled);
+      bool falseStartEnabled = Preferences::GetBool("security.ssl.enable_false_start",
+                                                    FALSE_START_ENABLED_DEFAULT);
+      SSL_OptionSetDefault(SSL_ENABLE_FALSE_START, falseStartEnabled);
 #endif
     } else if (prefName.Equals("security.OCSP.enabled")
                || prefName.Equals("security.CRL_download.enabled")
                || prefName.Equals("security.fresh_revocation_info.require")
                || prefName.Equals("security.missing_cert_download.enabled")
                || prefName.Equals("security.first_network_revocation_method")
                || prefName.Equals("security.OCSP.require")
                || prefName.Equals("security.ssl.enable_ocsp_stapling")) {
       MutexAutoLock lock(mutex);
-      setValidationOptions(mPrefBranch);
+      setValidationOptions();
     } else if (prefName.Equals("network.ntlm.send-lm-response")) {
-      bool sendLM = false;
-      mPrefBranch->GetBoolPref("network.ntlm.send-lm-response", &sendLM);
+      bool sendLM = Preferences::GetBool("network.ntlm.send-lm-response",
+                                         SEND_LM_DEFAULT);
       nsNTLMAuthModule::SetSendLM(sendLM);
     } else {
       /* Look through the cipher table and set according to pref setting */
+      bool cipherEnabled;
       for (CipherPref* cp = CipherPrefs; cp->pref; ++cp) {
         if (prefName.Equals(cp->pref)) {
-          mPrefBranch->GetBoolPref(cp->pref, &enabled);
-          SSL_CipherPrefSetDefault(cp->id, enabled);
+          cipherEnabled = Preferences::GetBool(cp->pref, CIPHER_ENABLED_DEFAULT);
+          SSL_CipherPrefSetDefault(cp->id, cipherEnabled);
           clearSessionCache = true;
           break;
         }
       }
     }
     if (clearSessionCache)
       SSL_ClearSessionCache();
   }
--- a/security/manager/ssl/src/nsNSSComponent.h
+++ b/security/manager/ssl/src/nsNSSComponent.h
@@ -8,17 +8,16 @@
 #define _nsNSSComponent_h_
 
 #include "mozilla/Mutex.h"
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsISignatureVerifier.h"
 #include "nsIEntropyCollector.h"
 #include "nsIStringBundle.h"
-#include "nsIPrefBranch.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #ifndef MOZ_DISABLE_CRYPTOLEGACY
 #endif
 #include "nsINSSErrorsService.h"
 #include "nsNSSCallbacks.h"
 #include "ScopedNSSTypes.h"
 #include "nsNSSHelper.h"
@@ -180,35 +179,34 @@ public:
 private:
 
   nsresult InitializeNSS(bool showWarningBox);
   void ShutdownNSS();
 
   void InstallLoadableRoots();
   void UnloadLoadableRoots();
   void CleanupIdentityInfo();
-  void setValidationOptions(nsIPrefBranch * pref);
-  nsresult setEnabledTLSVersions(nsIPrefBranch * pref);
+  void setValidationOptions();
+  nsresult setEnabledTLSVersions();
   nsresult InitializePIPNSSBundle();
   nsresult ConfigureInternalPKCS11Token();
   nsresult RegisterObservers();
   nsresult DeregisterObservers();
 
   // Methods that we use to handle the profile change notifications (and to
   // synthesize a full profile change when we're just doing a profile startup):
   void DoProfileChangeNetTeardown();
   void DoProfileChangeTeardown(nsISupports* aSubject);
   void DoProfileBeforeChange(nsISupports* aSubject);
   void DoProfileChangeNetRestore();
   
   Mutex mutex;
   
   nsCOMPtr<nsIStringBundle> mPIPNSSBundle;
   nsCOMPtr<nsIStringBundle> mNSSErrorsBundle;
-  nsCOMPtr<nsIPrefBranch> mPrefBranch;
   bool mNSSInitialized;
   bool mObserversRegistered;
   static int mInstanceCount;
   nsNSSShutDownList *mShutdownObjectList;
 #ifndef MOZ_DISABLE_CRYPTOLEGACY
   SmartCardThreadList *mThreadList;
 #endif
   bool mIsNetworkDown;
--- a/testing/xpcshell/mach_commands.py
+++ b/testing/xpcshell/mach_commands.py
@@ -137,16 +137,18 @@ class XPCShellRunner(MozbuildObject):
             'profileName': 'firefox',
             'verbose': test_path is not None,
             'xunitFilename': os.path.join(self.statedir, 'xpchsell.xunit.xml'),
             'xunitName': 'xpcshell',
             'pluginsPath': os.path.join(self.distdir, 'plugins'),
             'debugger': debugger,
             'debuggerArgs': debuggerArgs,
             'debuggerInteractive': debuggerInteractive,
+            'on_message': (lambda obj, msg: xpcshell.log.info(msg)) \
+                            if test_path is not None else None,
         }
 
         if manifest is not None:
             args['manifest'] = manifest
         elif test_dirs is not None:
             if isinstance(test_dirs, list):
                 args['testdirs'] = test_dirs
             else:
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -109,16 +109,17 @@ class XPCShellTestThread(Thread):
         self.profileName = kwargs.get('profileName')
         self.singleFile = kwargs.get('singleFile')
         self.env = copy.deepcopy(kwargs.get('env'))
         self.symbolsPath = kwargs.get('symbolsPath')
         self.logfiles = kwargs.get('logfiles')
         self.xpcshell = kwargs.get('xpcshell')
         self.xpcsRunArgs = kwargs.get('xpcsRunArgs')
         self.failureManifest = kwargs.get('failureManifest')
+        self.on_message = kwargs.get('on_message')
 
         self.tests_root_dir = tests_root_dir
         self.app_dir_key = app_dir_key
         self.interactive = interactive
         self.verbose = verbose
         self.pStdout = pStdout
         self.pStderr = pStderr
         self.keep_going = keep_going
@@ -127,16 +128,18 @@ class XPCShellTestThread(Thread):
         # only one of these will be set to 1. adding them to the totals in
         # the harness
         self.passCount = 0
         self.todoCount = 0
         self.failCount = 0
 
         self.output_lines = []
         self.has_failure_output = False
+        self.saw_proc_start = False
+        self.saw_proc_end = False
 
         # event from main thread to signal work done
         self.event = event
         self.done = False # explicitly set flag so we don't rely on thread.isAlive
 
     def run(self):
         try:
             self.run_test()
@@ -188,16 +191,28 @@ class XPCShellTestThread(Thread):
         """
         return proc.returncode
 
     def communicate(self, proc):
         """
           Simple wrapper to communicate with a process.
           On a remote system, this is overloaded to handle remote process communication.
         """
+        # Processing of incremental output put here to
+        # sidestep issues on remote platforms, where what we know
+        # as proc is a file pulled off of a device.
+        while True:
+            line = proc.stdout.readline()
+            if not line:
+                break
+            self.process_line(line)
+
+        if self.saw_proc_start and not self.saw_proc_end:
+            self.has_failure_output = True
+
         return proc.communicate()
 
     def launchProcess(self, cmd, stdout, stderr, env, cwd):
         """
           Simple wrapper to launch a process.
           On a remote system, this is more complex and we need to overload this function.
         """
         if HAVE_PSUTIL:
@@ -388,78 +403,88 @@ class XPCShellTestThread(Thread):
         if self.profileDir and not self.interactive and not self.singleFile:
             self.cleanupDir(self.profileDir, name, self.xunit_result)
 
         self.cleanupDir(self.tempDir, name, self.xunit_result)
 
         if self.pluginsDir:
             self.cleanupDir(self.pluginsDir, name, self.xunit_result)
 
-    def append_message_from_line(self, line):
-        """Given a line of raw output, convert to message and append to
-        output buffer."""
+    def message_from_line(self, line):
+        """ Given a line of raw output, convert to a string message. """
         if isinstance(line, basestring):
             # This function has received unstructured output.
             if line:
-                self.output_lines.append(line)
                 if 'TEST-UNEXPECTED-' in line:
                     self.has_failure_output = True
-            return
+            return line
 
         msg = ['%s: ' % line['process'] if 'process' in line else '']
 
         # Each call to the logger in head.js either specified '_message'
         # or both 'source_file' and 'diagnostic'. If either of these are
         # missing, they ended up being undefined as a result of the way
         # the test was run.
         if '_message' in line:
             msg.append(line['_message'])
         else:
             msg.append('%s | %s | %s' % (ACTION_STRINGS[line['action']],
                                          line.get('source_file', 'undefined'),
                                          line.get('diagnostic', 'undefined')))
 
         msg.append('\n%s' % line['stack'] if 'stack' in line else '')
-        self.output_lines.append(''.join(msg))
+        return ''.join(msg)
 
     def parse_output(self, output):
         """Parses process output for structured messages and saves output as it is
         read. Sets self.has_failure_output in case of evidence of a failure"""
-        seen_proc_start = False
-        seen_proc_end = False
-        self.output_lines = []
         for line_string in output.splitlines():
-            try:
-                line_object = json.loads(line_string)
-                if not isinstance(line_object, dict):
-                    self.append_message_from_line(line_string)
-                    continue
-            except ValueError:
-                self.append_message_from_line(line_string)
-                continue
+            self.process_line(line_string)
+
+        if self.saw_proc_start and not self.saw_proc_end:
+            self.has_failure_output = True
+
+    def report_message(self, line):
+        """ Reports a message to a consumer, both as a strucutured and
+        human-readable log message. """
+        message = self.message_from_line(line).strip()
+
+        if self.on_message:
+            self.on_message(line, message)
+        else:
+            self.output_lines.append(message)
 
-            if 'action' not in line_object:
-                # In case a test outputs something that happens to be valid
-                # JSON object.
-                self.append_message_from_line(line_string)
-                continue
-
-            action = line_object['action']
-            self.append_message_from_line(line_object)
+    def process_line(self, line_string):
+        """ Parses a single line of output, determining its significance and
+        reporting a message.
+        """
+        try:
+            line_object = json.loads(line_string)
+            if not isinstance(line_object, dict):
+                self.report_message(line_string)
+                return
+        except ValueError:
+            self.report_message(line_string)
+            return
 
-            if action in FAILURE_ACTIONS:
-                self.has_failure_output = True
+        if 'action' not in line_object:
+            # In case a test outputs something that happens to be valid
+            # JSON.
+            self.report_message(line_string)
+            return
 
-            elif action == 'child_test_start':
-                seen_proc_start = True
-            elif action == 'child_test_end':
-                seen_proc_end = True
+        action = line_object['action']
+        self.report_message(line_object)
 
-        if seen_proc_start and not seen_proc_end:
+        if action in FAILURE_ACTIONS:
             self.has_failure_output = True
+        elif action == 'child_test_start':
+            self.saw_proc_start = True
+        elif action == 'child_test_end':
+            self.saw_proc_end = True
 
     def log_output(self, output):
         """Prints given output line-by-line to avoid overflowing buffers."""
         self.log.info(">>>>>>>")
         if output:
             if isinstance(output, basestring):
                 output = output.splitlines()
             for part in output:
@@ -574,18 +599,20 @@ class XPCShellTestThread(Thread):
                           (self.getReturnCode(proc) != 0))
 
             if result != expected:
                 if self.retry:
                     self.clean_temp_dirs(name, stdout)
                     return
 
                 failureType = "TEST-UNEXPECTED-%s" % ("FAIL" if expected else "PASS")
-                message = "%s | %s | test failed (with xpcshell return code: %d), see following log:" % (
+                message = "%s | %s | test failed (with xpcshell return code: %d)" % (
                               failureType, name, self.getReturnCode(proc))
+                if self.output_lines:
+                    message += ", see following log:"
 
                 with LOG_MUTEX:
                     self.log.error(message)
                     self.log_output(self.output_lines)
 
                 self.failCount += 1
                 self.xunit_result["passed"] = False
 
@@ -1087,17 +1114,17 @@ class XPCShellTests(object):
                  manifest=None, testdirs=None, testPath=None, mobileArgs=None,
                  interactive=False, verbose=False, keepGoing=False, logfiles=True,
                  thisChunk=1, totalChunks=1, debugger=None,
                  debuggerArgs=None, debuggerInteractive=False,
                  profileName=None, mozInfo=None, sequential=False, shuffle=False,
                  testsRootDir=None, xunitFilename=None, xunitName=None,
                  testingModulesDir=None, autolog=False, pluginsPath=None,
                  testClass=XPCShellTestThread, failureManifest=None,
-                 **otherOptions):
+                 on_message=None, **otherOptions):
         """Run xpcshell tests.
 
         |xpcshell|, is the xpcshell executable to use to run the tests.
         |xrePath|, if provided, is the path to the XRE to use.
         |appPath|, if provided, is the path to an application directory.
         |symbolsPath|, if provided is the path to a directory containing
           breakpad symbols for processing crashes in tests.
         |manifest|, if provided, is a file containing a list of
@@ -1176,16 +1203,17 @@ class XPCShellTests(object):
         self.symbolsPath = symbolsPath
         self.manifest = manifest
         self.testdirs = testdirs
         self.testPath = testPath
         self.interactive = interactive
         self.verbose = verbose
         self.keepGoing = keepGoing
         self.logfiles = logfiles
+        self.on_message = on_message
         self.totalChunks = totalChunks
         self.thisChunk = thisChunk
         self.debuggerInfo = getDebuggerInfo(self.oldcwd, debugger, debuggerArgs, debuggerInteractive)
         self.profileName = profileName or "xpcshell"
         self.mozInfo = mozInfo
         self.testingModulesDir = testingModulesDir
         self.pluginsPath = pluginsPath
         self.sequential = sequential
@@ -1252,17 +1280,18 @@ class XPCShellTests(object):
             'testharnessdir': self.testharnessdir,
             'profileName': self.profileName,
             'singleFile': self.singleFile,
             'env': self.env, # making a copy of this in the testthreads
             'symbolsPath': self.symbolsPath,
             'logfiles': self.logfiles,
             'xpcshell': self.xpcshell,
             'xpcsRunArgs': self.xpcsRunArgs,
-            'failureManifest': failureManifest
+            'failureManifest': failureManifest,
+            'on_message': self.on_message,
         }
 
         if self.sequential:
             # Allow user to kill hung xpcshell subprocess with SIGINT
             # when we are only running tests sequentially.
             signal.signal(signal.SIGINT, markGotSIGINT)
 
         if self.debuggerInfo: