Merge m-c to autoland a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Fri, 08 Jul 2016 15:10:57 -0700
changeset 304256 7a7f440d578febfd991a3f657ae8f57815d27909
parent 304255 751186781016d52db0b7a957af1ffe0358574012 (current diff)
parent 304242 bbb29a9b88dd680dbb59577cbe4dc6e58d117100 (diff)
child 304257 d5d91aa3a4305a769d4ae057353de3e56c4c8765
push id19932
push userphilringnalda@gmail.com
push dateSat, 09 Jul 2016 16:00:45 +0000
treeherderfx-team@679118259e91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.0a1
Merge m-c to autoland a=merge CLOSED TREE
browser/themes/linux/tabbrowser/connecting.png
browser/themes/linux/tabbrowser/connecting@2x.png
browser/themes/osx/tabbrowser/connecting.png
browser/themes/osx/tabbrowser/connecting@2x.png
browser/themes/windows/tabbrowser/connecting.png
browser/themes/windows/tabbrowser/connecting@2x.png
build/mozconfig.cache
--- a/accessible/base/EventTree.cpp
+++ b/accessible/base/EventTree.cpp
@@ -7,16 +7,18 @@
 
 #include "Accessible-inl.h"
 #include "nsEventShell.h"
 #include "DocAccessible.h"
 #ifdef A11Y_LOG
 #include "Logging.h"
 #endif
 
+#include "mozilla/UniquePtr.h"
+
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // TreeMutation class
 
 EventTree* const TreeMutation::kNoEventTree = reinterpret_cast<EventTree*>(-1);
 
@@ -157,17 +159,17 @@ EventTree::Process(const RefPtr<DocAcces
   while (mFirst) {
     // Skip a node and its subtree if its container is not in the document.
     if (mFirst->mContainer->IsInDocument()) {
       mFirst->Process(aDeathGrip);
       if (aDeathGrip->IsDefunct()) {
         return;
       }
     }
-    mFirst = mFirst->mNext.forget();
+    mFirst = Move(mFirst->mNext);
   }
 
   MOZ_ASSERT(mContainer || mDependentEvents.IsEmpty(),
              "No container, no events");
   MOZ_ASSERT(!mContainer || !mContainer->IsDefunct(),
              "Processing events for defunct container");
   MOZ_ASSERT(!mFireReorder || mContainer, "No target for reorder event");
 
@@ -224,21 +226,22 @@ EventTree::Process(const RefPtr<DocAcces
 
   mDependentEvents.Clear();
 }
 
 EventTree*
 EventTree::FindOrInsert(Accessible* aContainer)
 {
   if (!mFirst) {
-    return mFirst = new EventTree(aContainer, true);
+    mFirst.reset(new EventTree(aContainer, true));
+    return mFirst.get();
   }
 
   EventTree* prevNode = nullptr;
-  EventTree* node = mFirst;
+  EventTree* node = mFirst.get();
   do {
     MOZ_ASSERT(!node->mContainer->IsApplication(),
                "No event for application accessible is expected here");
     MOZ_ASSERT(!node->mContainer->IsDefunct(), "An event target has to be alive");
 
     // Case of same target.
     if (node->mContainer == aContainer) {
       return node;
@@ -272,67 +275,68 @@ EventTree::FindOrInsert(Accessible* aCon
       if (curParent->Parent() != aContainer) {
         curParent = curParent->Parent();
         continue;
       }
 
       // Insert the tail node into the hierarchy between the current node and
       // its parent.
       node->mFireReorder = false;
-      nsAutoPtr<EventTree>& nodeOwnerRef = prevNode ? prevNode->mNext : mFirst;
-      nsAutoPtr<EventTree> newNode(new EventTree(aContainer, mDependentEvents.IsEmpty()));
+      UniquePtr<EventTree>& nodeOwnerRef = prevNode ? prevNode->mNext : mFirst;
+      UniquePtr<EventTree> newNode(new EventTree(aContainer, mDependentEvents.IsEmpty()));
       newNode->mFirst = Move(nodeOwnerRef);
       nodeOwnerRef = Move(newNode);
       nodeOwnerRef->mNext = Move(node->mNext);
 
       // Check if a next node is contained by the given node too, and move them
       // under the given node if so.
-      prevNode = nodeOwnerRef;
-      node = nodeOwnerRef->mNext;
-      nsAutoPtr<EventTree>* nodeRef = &nodeOwnerRef->mNext;
-      EventTree* insNode = nodeOwnerRef->mFirst;
+      prevNode = nodeOwnerRef.get();
+      node = nodeOwnerRef->mNext.get();
+      UniquePtr<EventTree>* nodeRef = &nodeOwnerRef->mNext;
+      EventTree* insNode = nodeOwnerRef->mFirst.get();
       while (node) {
         Accessible* curParent = node->mContainer;
         while (curParent && !curParent->IsDoc()) {
           if (curParent->Parent() != aContainer) {
             curParent = curParent->Parent();
             continue;
           }
 
           MOZ_ASSERT(!insNode->mNext);
 
           node->mFireReorder = false;
           insNode->mNext = Move(*nodeRef);
-          insNode = insNode->mNext;
+          insNode = insNode->mNext.get();
 
           prevNode->mNext = Move(node->mNext);
           node = prevNode;
           break;
         }
 
         prevNode = node;
         nodeRef = &node->mNext;
-        node = node->mNext;
+        node = node->mNext.get();
       }
 
-      return nodeOwnerRef;
+      return nodeOwnerRef.get();
     }
 
     prevNode = node;
-  } while ((node = node->mNext));
+  } while ((node = node->mNext.get()));
 
   MOZ_ASSERT(prevNode, "Nowhere to insert");
   MOZ_ASSERT(!prevNode->mNext, "Taken by another node");
 
   // If 'this' node contains the given container accessible, then
   //   do not emit a reorder event for the container
   //   if a dependent show event target contains the given container then do not
   //   emit show / hide events (see Process() method)
 
-  return prevNode->mNext = new EventTree(aContainer, mDependentEvents.IsEmpty());
+  prevNode->mNext.reset(new EventTree(aContainer, mDependentEvents.IsEmpty()));
+  return prevNode->mNext.get();
 }
 
 void
 EventTree::Clear()
 {
   mFirst = nullptr;
   mNext = nullptr;
   mContainer = nullptr;
@@ -352,24 +356,24 @@ EventTree::Find(const Accessible* aConta
 {
   const EventTree* et = this;
   while (et) {
     if (et->mContainer == aContainer) {
       return et;
     }
 
     if (et->mFirst) {
-      et = et->mFirst;
+      et = et->mFirst.get();
       const EventTree* cet = et->Find(aContainer);
       if (cet) {
         return cet;
       }
     }
 
-    et = et->mNext;
+    et = et->mNext.get();
     const EventTree* cet = et->Find(aContainer);
     if (cet) {
       return cet;
     }
   }
 
   return nullptr;
 }
@@ -416,17 +420,17 @@ EventTree::Log(uint32_t aLevel) const
 }
 #endif
 
 void
 EventTree::Mutated(AccMutationEvent* aEv)
 {
   // If shown or hidden node is a root of previously mutated subtree, then
   // discard those subtree mutations as we are no longer interested in them.
-  nsAutoPtr<EventTree>* node = &mFirst;
+  UniquePtr<EventTree>* node = &mFirst;
   while (*node) {
     if ((*node)->mContainer == aEv->mAccessible) {
       *node = Move((*node)->mNext);
       break;
     }
     node = &(*node)->mNext;
   }
 
--- a/accessible/base/EventTree.h
+++ b/accessible/base/EventTree.h
@@ -5,16 +5,17 @@
 
 #ifndef mozilla_a11y_EventTree_h_
 #define mozilla_a11y_EventTree_h_
 
 #include "AccEvent.h"
 #include "Accessible.h"
 
 #include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
 
 namespace mozilla {
 namespace a11y {
 
 /**
  * This class makes sure required tasks are done before and after tree
  * mutations. Currently this only includes group info invalidation. You must
  * have an object of this class on the stack when calling methods that mutate
@@ -96,18 +97,18 @@ private:
   /**
    * Return an event subtree for the given accessible.
    */
   EventTree* FindOrInsert(Accessible* aContainer);
 
   void Mutated(AccMutationEvent* aEv);
   void Clear();
 
-  nsAutoPtr<EventTree> mFirst;
-  nsAutoPtr<EventTree> mNext;
+  UniquePtr<EventTree> mFirst;
+  UniquePtr<EventTree> mNext;
 
   Accessible* mContainer;
   nsTArray<RefPtr<AccMutationEvent>> mDependentEvents;
   bool mFireReorder;
 
   friend class NotificationController;
 };
 
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -2206,43 +2206,43 @@ Accessible::IndexInParent() const
   return mIndexInParent;
 }
 
 uint32_t
 Accessible::EmbeddedChildCount()
 {
   if (mStateFlags & eHasTextKids) {
     if (!mEmbeddedObjCollector)
-      mEmbeddedObjCollector = new EmbeddedObjCollector(this);
+      mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
     return mEmbeddedObjCollector->Count();
   }
 
   return ChildCount();
 }
 
 Accessible*
 Accessible::GetEmbeddedChildAt(uint32_t aIndex)
 {
   if (mStateFlags & eHasTextKids) {
     if (!mEmbeddedObjCollector)
-      mEmbeddedObjCollector = new EmbeddedObjCollector(this);
-    return mEmbeddedObjCollector ?
+      mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
+    return mEmbeddedObjCollector.get() ?
       mEmbeddedObjCollector->GetAccessibleAt(aIndex) : nullptr;
   }
 
   return GetChildAt(aIndex);
 }
 
 int32_t
 Accessible::GetIndexOfEmbeddedChild(Accessible* aChild)
 {
   if (mStateFlags & eHasTextKids) {
     if (!mEmbeddedObjCollector)
-      mEmbeddedObjCollector = new EmbeddedObjCollector(this);
-    return mEmbeddedObjCollector ?
+      mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
+    return mEmbeddedObjCollector.get() ?
       mEmbeddedObjCollector->GetIndexAt(aChild) : -1;
   }
 
   return GetIndexOf(aChild);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // HyperLinkAccessible methods
--- a/accessible/generic/Accessible.h
+++ b/accessible/generic/Accessible.h
@@ -6,17 +6,18 @@
 #ifndef _Accessible_H_
 #define _Accessible_H_
 
 #include "mozilla/a11y/AccTypes.h"
 #include "mozilla/a11y/RelationType.h"
 #include "mozilla/a11y/Role.h"
 #include "mozilla/a11y/States.h"
 
-#include "nsAutoPtr.h"
+#include "mozilla/UniquePtr.h"
+
 #include "nsIContent.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsRefPtrHashtable.h"
 #include "nsRect.h"
 
 struct nsRoleMapEntry;
 
@@ -1133,17 +1134,17 @@ protected:
                             DocAccessible* aDoc,
                             logging::GetTreePrefix aPrefixFunc,
                             void* aGetTreePrefixData);
 #endif
   friend class DocAccessible;
   friend class xpcAccessible;
   friend class TreeMutation;
 
-  nsAutoPtr<mozilla::a11y::EmbeddedObjCollector> mEmbeddedObjCollector;
+  UniquePtr<mozilla::a11y::EmbeddedObjCollector> mEmbeddedObjCollector;
   union {
     int32_t mIndexOfEmbeddedChild;
     uint32_t mProxyInterfaces;
   } mInt;
 
   friend class EmbeddedObjCollector;
 
   union
--- a/browser/components/contextualidentity/test/browser/browser.ini
+++ b/browser/components/contextualidentity/test/browser/browser.ini
@@ -1,19 +1,21 @@
 [DEFAULT]
 skip-if = buildapp == "mulet"
 support-files =
   empty_file.html
   file_reflect_cookie_into_title.html
   favicon-normal32.png
+  file_set_storages.html
   serviceworker.html
   worker.js
 
 [browser_aboutURLs.js]
 [browser_favicon.js]
+[browser_forgetaboutsite.js]
 [browser_usercontext.js]
 [browser_usercontextid_tabdrop.js]
 skip-if = os == "mac" || os == "win" # Intermittent failure - bug 1268276
 [browser_windowName.js]
 tags = openwindow
 [browser_windowOpen.js]
 tags = openwindow
 [browser_serviceworkers.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js
@@ -0,0 +1,351 @@
+/*
+ * Bug 1238183 - Test cases for forgetAboutSite with userContextId.
+ */
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/ForgetAboutSite.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+let LoadContextInfo = Cc["@mozilla.org/load-context-info-factory;1"]
+                      .getService(Ci.nsILoadContextInfoFactory);
+let css = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+           .getService(Ci.nsICacheStorageService);
+
+const USER_CONTEXTS = [
+  "default",
+  "personal",
+  "work",
+];
+const TEST_HOST = "example.com";
+const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/";
+const COOKIE_NAME = "userContextId";
+
+// Counter for image load hits.
+let gHits = 0;
+
+let gHttpServer = null;
+
+function imageHandler(metadata, response) {
+  // A 1x1 PNG image.
+  // Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+  const IMAGE = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+                     "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=");
+  gHits++;
+  response.setHeader("Cache-Control", "max-age=10000", false);
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "image/png", false);
+  response.write(IMAGE);
+}
+
+function loadImagePageHandler(metadata, response) {
+  response.setHeader("Cache-Control", "max-age=10000", false);
+  response.setStatusLine(metadata.httpVersion, 200, "Ok");
+  response.setHeader("Content-Type", "text/html", false);
+  let body = "<!DOCTYPE HTML>\
+              <html>\
+                <head>\
+                  <meta charset='utf-8'>\
+                  <title>Load Image</title>\
+                </head>\
+                <body>\
+                <img src='image.png'>\
+                </body>\
+              </html>";
+  response.bodyOutputStream.write(body, body.length);
+}
+
+function* openTabInUserContext(uri, userContextId) {
+  // Open the tab in the correct userContextId.
+  let tab = gBrowser.addTab(uri, {userContextId});
+
+  // Select tab and make sure its browser is focused.
+  gBrowser.selectedTab = tab;
+  tab.ownerDocument.defaultView.focus();
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  yield BrowserTestUtils.browserLoaded(browser);
+  return {tab, browser};
+}
+
+function getCookiesForOA(host, userContextId) {
+  return Services.cookies.getCookiesFromHost(host, {userContextId});
+}
+
+function createURI(uri)
+{
+  let ioServ = Cc["@mozilla.org/network/io-service;1"]
+                  .getService(Components.interfaces.nsIIOService);
+  return ioServ.newURI(uri, null, null);
+}
+
+function getCacheStorage(where, lci, appcache)
+{
+  if (!lci) lci = LoadContextInfo.default;
+  switch (where) {
+    case "disk": return css.diskCacheStorage(lci, false);
+    case "memory": return css.memoryCacheStorage(lci);
+    case "appcache": return css.appCacheStorage(lci, appcache);
+    case "pin": return css.pinningCacheStorage(lci);
+  }
+  return null;
+}
+
+function OpenCacheEntry(key, where, flags, lci)
+{
+  return new Promise(resolve => {
+    key = createURI(key);
+    function CacheListener() { }
+    CacheListener.prototype = {
+      _appCache: null,
+
+      QueryInterface: function (iid) {
+        if (iid.equals(Components.interfaces.nsICacheEntryOpenCallback) ||
+            iid.equals(Components.interfaces.nsISupports))
+          return this;
+        throw Components.results.NS_ERROR_NO_INTERFACE;
+      },
+
+      onCacheEntryCheck: function(entry, appCache) {
+        return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+      },
+
+      onCacheEntryAvailable: function (entry, isnew, appCache, status) {
+        resolve();
+      },
+
+      run: function () {
+        let storage = getCacheStorage(where, lci, this._appCache);
+        storage.asyncOpenURI(key, "", flags, this);
+      }
+    };
+
+    (new CacheListener()).run();
+  });
+}
+
+//
+// Test functions.
+//
+
+// Cookies
+function* test_cookie_cleared() {
+  let tabs = [];
+
+  for (let userContextId of Object.keys(USER_CONTEXTS)) {
+    // Load the page in 3 different contexts and set a cookie
+    // which should only be visible in that context.
+    let value = USER_CONTEXTS[userContextId];
+
+    // Open our tab in the given user context.
+    tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "file_reflect_cookie_into_title.html?" + value, userContextId);
+
+    // Close this tab.
+    yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
+  }
+  // Check that cookies have been set properly.
+  for (let userContextId of Object.keys(USER_CONTEXTS)) {
+    let enumerator = getCookiesForOA(TEST_HOST, userContextId);
+    ok(enumerator.hasMoreElements(), "Cookies available");
+
+    let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+    Assert.equal(foundCookie["name"], COOKIE_NAME, "Check cookie name");
+    Assert.equal(foundCookie["value"], USER_CONTEXTS[userContextId], "Check cookie value");
+  }
+
+  // Forget the site.
+  ForgetAboutSite.removeDataFromDomain(TEST_HOST);
+
+  // Check that whether cookies has been cleared or not.
+  for (let userContextId of Object.keys(USER_CONTEXTS)) {
+    let enumerator = getCookiesForOA(TEST_HOST, userContextId);
+    ok(!enumerator.hasMoreElements(), "No Cookie should be here");
+  }
+}
+
+// Cache
+function* test_cache_cleared() {
+  // First, add some caches.
+  for (let userContextId of Object.keys(USER_CONTEXTS)) {
+    yield OpenCacheEntry("http://" + TEST_HOST + "/",
+                         "disk",
+                         Ci.nsICacheStorage.OPEN_NORMALLY,
+                         LoadContextInfo.custom(false, false, {userContextId}));
+
+    yield OpenCacheEntry("http://" + TEST_HOST + "/",
+                         "memory",
+                         Ci.nsICacheStorage.OPEN_NORMALLY,
+                         LoadContextInfo.custom(false, false, {userContextId}));
+  }
+
+
+  // Check that caches have been set correctly.
+  for (let userContextId of Object.keys(USER_CONTEXTS)) {
+    let mem = getCacheStorage("memory");
+    let disk = getCacheStorage("disk");
+
+    Assert.ok(mem.exists(createURI("http://" + TEST_HOST + "/"), ""), "The memory cache has been set correctly");
+    Assert.ok(disk.exists(createURI("http://" + TEST_HOST + "/"), ""), "The disk cache has been set correctly");
+  }
+
+  // Forget the site.
+  ForgetAboutSite.removeDataFromDomain(TEST_HOST);
+
+  // Check that do caches be removed or not?
+  for (let userContextId of Object.keys(USER_CONTEXTS)) {
+    let mem = getCacheStorage("memory");
+    let disk = getCacheStorage("disk");
+
+    Assert.ok(!mem.exists(createURI("http://" + TEST_HOST + "/"), ""), "The memory cache is cleared");
+    Assert.ok(!disk.exists(createURI("http://" + TEST_HOST + "/"), ""), "The disk cache is cleared");
+  }
+}
+
+// Image Cache
+function* test_image_cache_cleared() {
+  let tabs = [];
+
+  for (let userContextId of Object.keys(USER_CONTEXTS)) {
+    // Open our tab in the given user context to cache image.
+    tabs[userContextId] = yield* openTabInUserContext('http://localhost:' + gHttpServer.identity.primaryPort + '/loadImage.html',
+                                                      userContextId);
+    yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
+  }
+
+  // Check that image cache works with the userContextId.
+  todo_is(gHits, 3, "The image should be loaded three times. This test should be enabled after the bug 1270680 landed");
+
+  // Reset the cache count.
+  gHits = 0;
+
+  // Forget the site.
+  ForgetAboutSite.removeDataFromDomain("localhost:" + gHttpServer.identity.primaryPort + "/");
+
+  // Load again.
+  for (let userContextId of Object.keys(USER_CONTEXTS)) {
+    // Open our tab in the given user context to cache image.
+    tabs[userContextId] = yield* openTabInUserContext('http://localhost:' + gHttpServer.identity.primaryPort + '/loadImage.html',
+                                                      userContextId);
+    yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
+  }
+
+  // Check that image cache was cleared and the server gets another three hits.
+  todo_is(gHits, 3, "The image should be loaded three times. This test should be enabled after the bug 1270680 landed");
+}
+
+// Offline Storage
+function* test_storage_cleared() {
+  for (let userContextId of Object.keys(USER_CONTEXTS)) {
+    // Load the page in 3 different contexts and set the local storage
+    // which should only be visible in that context.
+    let value = USER_CONTEXTS[userContextId];
+
+    // Open our tab in the given user context.
+    let tabInfo = yield* openTabInUserContext(TEST_URL+ "file_set_storages.html?" + value, userContextId);
+
+    // Check that the local storage has been set correctly.
+    let win = tabInfo.browser.contentWindow;
+    Assert.equal(win.localStorage.getItem("userContext"), USER_CONTEXTS[userContextId], "Check the local storage value");
+
+    // Check that the session storage has been set correctly.
+    Assert.equal(win.sessionStorage.getItem("userContext"), USER_CONTEXTS[userContextId], "Check the session storage value");
+
+    // Check that the indexedDB has been set correctly.
+    yield ContentTask.spawn(tabInfo.browser, { userContext: USER_CONTEXTS[userContextId] }, function* (arg) {
+      let request = content.indexedDB.open("idb", 1);
+
+      let db = yield new Promise(done => {
+        request.onsuccess = event => {
+          done(event.target.result);
+        };
+      });
+
+      let transaction = db.transaction(["obj"], "readonly");
+      let store = transaction.objectStore("obj");
+      let storeRequest = store.get(1);
+
+      yield new Promise(done => {
+        storeRequest.onsuccess = event => {
+          let res = storeRequest.result;
+          Assert.equal(res.userContext, arg.userContext, "Check the indexedDB value");
+          done();
+        };
+      });
+    });
+
+    // Close this tab.
+    yield BrowserTestUtils.removeTab(tabInfo.tab);
+  }
+
+  // Forget the site.
+  ForgetAboutSite.removeDataFromDomain(TEST_HOST);
+
+  // Open the tab again without setting the localStorage and check that the
+  // local storage has been cleared or not.
+  for (let userContextId of Object.keys(USER_CONTEXTS)) {
+    // Open our tab in the given user context without setting local storage.
+    let tabInfo = yield* openTabInUserContext(TEST_URL+ "file_set_storages.html", userContextId);
+    let win = tabInfo.browser.contentWindow;
+
+    // Check that does the local storage be cleared or not.
+    Assert.ok(!win.localStorage.getItem("userContext"), "The local storage has been cleared");
+
+    // Check that does the session storage be cleared or not.
+    Assert.ok(!win.sessionStorage.getItem("userContext"), "The session storage has been cleared");
+
+    // Check that does the indexedDB be cleared or not.
+    yield ContentTask.spawn(tabInfo.browser, null, function* () {
+      let request = content.indexedDB.open("idb", 1);
+
+      let db = yield new Promise(done => {
+        request.onsuccess = event => {
+          done(event.target.result);
+        };
+      });
+      try {
+        let transaction = db.transaction(["obj"], "readonly");
+        Assert.ok(false, "The indexedDB should not exist");
+      } catch (e) {
+        Assert.equal(e.name, "NotFoundError", "The indexedDB does not exist as expected");
+      }
+    });
+
+    // Close the tab.
+    yield BrowserTestUtils.removeTab(tabInfo.tab);
+  }
+}
+
+add_task(function* setup() {
+  // Make sure userContext is enabled.
+  yield new Promise(resolve => {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["privacy.userContext.enabled", true]
+    ]}, resolve);
+  });
+
+  // Create a http server for the image cache test.
+  if (!gHttpServer) {
+    gHttpServer = new HttpServer();
+    gHttpServer.registerPathHandler('/image.png', imageHandler);
+    gHttpServer.registerPathHandler('/loadImage.html', loadImagePageHandler);
+    gHttpServer.start(-1);
+  }
+});
+
+let tests = [
+  test_cookie_cleared,
+  test_cache_cleared,
+  test_image_cache_cleared,
+  test_storage_cleared,
+];
+
+add_task(function* test() {
+  for (let i = 0; i < tests.length; i++)
+    add_task(tests[i]);
+});
+
+registerCleanupFunction(() => {
+  gHttpServer.stop(() => {
+    gHttpServer = null;
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/file_set_storages.html
@@ -0,0 +1,41 @@
+<html>
+  <head>
+    <meta charset="UTF-8">
+    <title>Bug 1238183</title>
+  </head>
+  <body>
+    <script type="application/javascript;version=1.7">
+      "use strict";
+
+      // if we have a query string, use it to set storages
+      if (window.location.search.length > 0) {
+        let context_name = window.location.search.substr(1);
+        localStorage.setItem("userContext", context_name);
+        sessionStorage.setItem("userContext", context_name);
+
+        let request = indexedDB.open("idb", 1);
+
+        request.onerror = function() {
+          throw new Error("error opening db connection");
+        };
+
+        request.onupgradeneeded = event => {
+          let db = event.target.result;
+          let store = db.createObjectStore("obj", { keyPath: "id" });
+          store.createIndex("userContext", "userContext", { unique: false });
+        };
+
+        request.onsuccess = event => {
+          let db = request.result;
+          let transaction = db.transaction(["obj"], "readwrite");
+          let store = transaction.objectStore("obj");
+          store.add({id: 1, userContext: context_name});
+
+          transaction.oncomplete = () => {
+            db.close();
+          };
+        };
+      }
+    </script>
+  </body>
+</html>
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -449,26 +449,26 @@ MenuItem.prototype = {
     return true;
   },
 };
 
 var gExtensionCount = 0;
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("startup", (type, extension) => {
   gContextMenuMap.set(extension, new Map());
-  gRootItems.delete(extension);
   if (++gExtensionCount == 1) {
     Services.obs.addObserver(contextMenuObserver,
                              "on-build-contextmenu",
                              false);
   }
 });
 
 extensions.on("shutdown", (type, extension) => {
   gContextMenuMap.delete(extension);
+  gRootItems.delete(extension);
   if (--gExtensionCount == 0) {
     Services.obs.removeObserver(contextMenuObserver,
                                 "on-build-contextmenu");
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 extensions.registerSchemaAPI("contextMenus", (extension, context) => {
--- a/browser/components/extensions/test/browser/.eslintrc
+++ b/browser/components/extensions/test/browser/.eslintrc
@@ -14,13 +14,17 @@
     "PanelUI": false,
 
     // Test harness globals
     "ExtensionTestUtils": false,
     "TestUtils": false,
 
     "clickBrowserAction": true,
     "clickPageAction": true,
-    "CustomizableUI": true,
+    "closeContextMenu": true,
+    "closeExtensionContextMenu": true,
     "focusWindow": true,
     "makeWidgetId": true,
+    "openContextMenu": true,
+    "openExtensionContextMenu": true,
+    "CustomizableUI": true,
   }
 }
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -23,16 +23,20 @@ support-files =
 [browser_ext_browserAction_popup.js]
 [browser_ext_browserAction_popup_resize.js]
 [browser_ext_browserAction_simple.js]
 [browser_ext_commands_execute_page_action.js]
 [browser_ext_commands_getAll.js]
 [browser_ext_commands_onCommand.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_contextMenus.js]
+[browser_ext_contextMenus_checkboxes.js]
+[browser_ext_contextMenus_icons.js]
+[browser_ext_contextMenus_radioGroups.js]
+[browser_ext_contextMenus_uninstall.js]
 [browser_ext_currentWindow.js]
 [browser_ext_getViews.js]
 [browser_ext_history.js]
 [browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
 [browser_ext_optionsPage_privileges.js]
 [browser_ext_pageAction_context.js]
 [browser_ext_pageAction_popup.js]
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js
@@ -22,46 +22,25 @@ add_task(function* () {
       });
       browser.test.notifyPass();
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish();
 
-  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-  let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
-  yield BrowserTestUtils.synthesizeMouseAtCenter("#img1", {
-    type: "contextmenu",
-    button: 2,
-  }, gBrowser.selectedBrowser);
-  yield popupShownPromise;
-
+  let contentAreaContextMenu = yield openContextMenu("#img1");
   let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
   is(item.length, 1, "contextMenu item for image was found");
-
-  let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
-  EventUtils.synthesizeMouseAtCenter(item[0], {});
-  yield popupHiddenPromise;
+  yield closeContextMenu();
 
-  contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-  popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
-  yield BrowserTestUtils.synthesizeMouseAtCenter("body", {
-    type: "contextmenu",
-    button: 2,
-  }, gBrowser.selectedBrowser);
-  yield popupShownPromise;
-
+  contentAreaContextMenu = yield openContextMenu("body");
   item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
   is(item.length, 0, "no contextMenu item for image was found");
-
-  // click something to close the context menu
-  popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
-  EventUtils.synthesizeMouseAtCenter(document.getElementById("context-selectall"), {});
-  yield popupHiddenPromise;
+  yield closeContextMenu();
 
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab1);
 });
 
 /* globals content */
 /* eslint-disable mozilla/no-cpows-in-tests */
@@ -137,260 +116,128 @@ add_task(function* () {
       browser.contextMenus.create({
         title: "child2",
         parentId: parentToDel,
         onclick: genericOnClick,
       });
       browser.contextMenus.remove(parentToDel);
 
       browser.contextMenus.create({
-        title: "radio-group-1",
-        type: "radio",
-        checked: true,
-        onclick: genericOnClick,
-      });
-
-      browser.contextMenus.create({
-        title: "Checkbox",
-        type: "checkbox",
-        onclick: genericOnClick,
-      });
-
-      browser.contextMenus.create({
-        title: "radio-group-2",
-        type: "radio",
-        onclick: genericOnClick,
-      });
-
-      browser.contextMenus.create({
-        title: "radio-group-2",
-        type: "radio",
-        onclick: genericOnClick,
-      });
-
-      browser.contextMenus.create({
-        type: "separator",
-      });
-
-      browser.contextMenus.create({
-        title: "Checkbox",
-        type: "checkbox",
-        checked: true,
-        onclick: genericOnClick,
-      });
-
-      browser.contextMenus.create({
-        title: "Checkbox",
-        type: "checkbox",
-        onclick: genericOnClick,
-      });
-
-      browser.contextMenus.create({
         title: "Without onclick property",
         id: "ext-without-onclick",
       });
 
       browser.contextMenus.update(parent, {parentId: child2}).then(
         () => {
-          browser.test.notifyFail();
+          browser.test.notifyFail("contextmenus");
         },
         () => {
-          browser.test.notifyPass();
+          browser.test.notifyPass("contextmenus");
         }
       );
     },
   });
 
   yield extension.startup();
-  yield extension.awaitFinish();
-
-  let contentAreaContextMenu;
-
-  function getTop() {
-    contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-    let items = contentAreaContextMenu.getElementsByAttribute("ext-type", "top-level-menu");
-    is(items.length, 1, "top level item was found (context=selection)");
-    let topItem = items[0];
-    return topItem.childNodes[0];
-  }
+  yield extension.awaitFinish("contextmenus");
 
-  function* openExtensionMenu() {
-    contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-    let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
-    yield BrowserTestUtils.synthesizeMouseAtCenter("#img1", {
-      type: "contextmenu",
-      button: 2,
-    }, gBrowser.selectedBrowser);
-    yield popupShownPromise;
+  let expectedClickInfo = {
+    menuItemId: "ext-image",
+    mediaType: "image",
+    srcUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/ctxmenu-image.png",
+    pageUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html",
+  };
 
-    popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
-    EventUtils.synthesizeMouseAtCenter(getTop(), {});
-    yield popupShownPromise;
+  function checkClickInfo(result) {
+    for (let i of Object.keys(expectedClickInfo)) {
+      is(result.info[i], expectedClickInfo[i],
+         "click info " + i + " expected to be: " + expectedClickInfo[i] + " but was: " + info[i]);
+    }
+    is(expectedClickInfo.pageSrc, result.tab.url);
   }
 
-  function* closeContextMenu(itemToSelect, expectedClickInfo, hasOnclickProperty = true) {
-    function checkClickInfo(info, tab) {
-      for (let i of Object.keys(expectedClickInfo)) {
-        is(info[i], expectedClickInfo[i],
-           "click info " + i + " expected to be: " + expectedClickInfo[i] + " but was: " + info[i]);
-      }
-      is(expectedClickInfo.pageSrc, tab.url);
-    }
-    let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
-    EventUtils.synthesizeMouseAtCenter(itemToSelect, {});
-
-    if (hasOnclickProperty) {
-      let {info, tab} = yield extension.awaitMessage("onclick");
-      if (expectedClickInfo) {
-        checkClickInfo(info, tab);
-      }
-    }
-
-    let {info, tab} = yield extension.awaitMessage("browser.contextMenus.onClicked");
-    if (expectedClickInfo) {
-      checkClickInfo(info, tab);
-    }
-
-    yield popupHiddenPromise;
-  }
-
-  function confirmRadioGroupStates(expectedStates) {
-    let top = getTop();
-
-    let radioItems = top.getElementsByAttribute("type", "radio");
-    let radioGroup1 = top.getElementsByAttribute("label", "radio-group-1");
-    let radioGroup2 = top.getElementsByAttribute("label", "radio-group-2");
-
-    is(radioItems.length, 3, "there should be 3 radio items in the context menu");
-    is(radioGroup1.length, 1, "the first radio group should only have 1 radio item");
-    is(radioGroup2.length, 2, "the second radio group should only have 2 radio items");
-
-    is(radioGroup1[0].hasAttribute("checked"), expectedStates[0], `radio item 1 has state (checked=${expectedStates[0]})`);
-    is(radioGroup2[0].hasAttribute("checked"), expectedStates[1], `radio item 2 has state (checked=${expectedStates[1]})`);
-    is(radioGroup2[1].hasAttribute("checked"), expectedStates[2], `radio item 3 has state (checked=${expectedStates[2]})`);
-  }
-
-  function confirmCheckboxStates(expectedStates) {
-    let checkboxItems = getTop().getElementsByAttribute("type", "checkbox");
-
-    is(checkboxItems.length, 3, "there should be 3 checkbox items in the context menu");
-
-    is(checkboxItems[0].hasAttribute("checked"), expectedStates[0], `checkbox item 1 has state (checked=${expectedStates[0]})`);
-    is(checkboxItems[1].hasAttribute("checked"), expectedStates[1], `checkbox item 2 has state (checked=${expectedStates[1]})`);
-    is(checkboxItems[2].hasAttribute("checked"), expectedStates[2], `checkbox item 3 has state (checked=${expectedStates[2]})`);
-  }
-
-  yield openExtensionMenu();
+  let extensionMenuRoot = yield openExtensionContextMenu();
 
   // Check some menu items
-  let top = getTop();
-  let items = top.getElementsByAttribute("label", "image");
+  let items = extensionMenuRoot.getElementsByAttribute("label", "image");
   is(items.length, 1, "contextMenu item for image was found (context=image)");
   let image = items[0];
 
-  items = top.getElementsByAttribute("label", "selection-edited");
+  items = extensionMenuRoot.getElementsByAttribute("label", "selection-edited");
   is(items.length, 0, "contextMenu item for selection was not found (context=image)");
 
-  items = top.getElementsByAttribute("label", "parentToDel");
+  items = extensionMenuRoot.getElementsByAttribute("label", "parentToDel");
   is(items.length, 0, "contextMenu item for removed parent was not found (context=image)");
 
-  items = top.getElementsByAttribute("label", "parent");
+  items = extensionMenuRoot.getElementsByAttribute("label", "parent");
   is(items.length, 1, "contextMenu item for parent was found (context=image)");
 
   is(items[0].childNodes[0].childNodes.length, 2, "child items for parent were found (context=image)");
 
   // Click on ext-image item and check the click results
-  yield closeContextMenu(image, {
-    menuItemId: "ext-image",
-    mediaType: "image",
-    srcUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/ctxmenu-image.png",
-    pageUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html",
-  });
-
-  // Test radio groups
-  yield openExtensionMenu();
-  confirmRadioGroupStates([true, false, false]);
-  items = getTop().getElementsByAttribute("type", "radio");
-  yield closeContextMenu(items[1]);
-
-  yield openExtensionMenu();
-  confirmRadioGroupStates([true, true, false]);
-  items = getTop().getElementsByAttribute("type", "radio");
-  yield closeContextMenu(items[2]);
-
-  yield openExtensionMenu();
-  confirmRadioGroupStates([true, false, true]);
-  items = getTop().getElementsByAttribute("type", "radio");
-  yield closeContextMenu(items[0]);
+  yield closeExtensionContextMenu(image);
 
-  yield openExtensionMenu();
-  confirmRadioGroupStates([true, false, true]);
-
-  // Test checkboxes
-  items = getTop().getElementsByAttribute("type", "checkbox");
-  confirmCheckboxStates([false, true, false]);
-  yield closeContextMenu(items[0]);
-
-  yield openExtensionMenu();
-  confirmCheckboxStates([true, true, false]);
-  items = getTop().getElementsByAttribute("type", "checkbox");
-  yield closeContextMenu(items[2]);
-
-  yield openExtensionMenu();
-  confirmCheckboxStates([true, true, true]);
-  items = getTop().getElementsByAttribute("type", "checkbox");
-  yield closeContextMenu(items[0]);
-
-  yield openExtensionMenu();
-  confirmCheckboxStates([false, true, true]);
-  items = getTop().getElementsByAttribute("type", "checkbox");
-  yield closeContextMenu(items[2]);
+  let result = yield extension.awaitMessage("onclick");
+  checkClickInfo(result);
+  result = yield extension.awaitMessage("browser.contextMenus.onClicked");
+  checkClickInfo(result);
 
   // Select some text
   yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
     let doc = content.document;
     let range = doc.createRange();
     let selection = content.getSelection();
     selection.removeAllRanges();
     let textNode = doc.getElementById("img1").previousSibling;
     range.setStart(textNode, 0);
     range.setEnd(textNode, 100);
     selection.addRange(range);
   });
 
   // Bring up context menu again
-  yield openExtensionMenu();
+  extensionMenuRoot = yield openExtensionContextMenu();
 
   // Check some menu items
-  top = getTop();
-  items = top.getElementsByAttribute("label", "Without onclick property");
+  items = extensionMenuRoot.getElementsByAttribute("label", "Without onclick property");
   is(items.length, 1, "contextMenu item was found (context=page)");
 
-  yield closeContextMenu(items[0], {
+  yield closeExtensionContextMenu(items[0]);
+
+  expectedClickInfo = {
     menuItemId: "ext-without-onclick",
     pageUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html",
-  }, false /* hasOnclickProperty */);
+  };
+
+  result = yield extension.awaitMessage("browser.contextMenus.onClicked");
+  checkClickInfo(result);
 
   // Bring up context menu again
-  yield openExtensionMenu();
+  extensionMenuRoot = yield openExtensionContextMenu();
 
   // Check some menu items
-  top = getTop();
-  items = top.getElementsByAttribute("label", "selection is: 'just some text 123456789012345678901234567890...'");
+  items = extensionMenuRoot.getElementsByAttribute("label", "selection is: 'just some text 123456789012345678901234567890...'");
   is(items.length, 1, "contextMenu item for selection was found (context=selection)");
   let selectionItem = items[0];
 
-  items = top.getElementsByAttribute("label", "selection");
+  items = extensionMenuRoot.getElementsByAttribute("label", "selection");
   is(items.length, 0, "contextMenu item label update worked (context=selection)");
 
-  yield closeContextMenu(selectionItem, {
+  yield closeExtensionContextMenu(selectionItem);
+
+  expectedClickInfo = {
     menuItemId: "ext-selection",
     pageUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html",
     selectionText: "just some text 1234567890123456789012345678901234567890123456789012345678901234567890123456789012",
-  });
+  };
 
+  result = yield extension.awaitMessage("onclick");
+  checkClickInfo(result);
+  result = yield extension.awaitMessage("browser.contextMenus.onClicked");
+  checkClickInfo(result);
+
+  let contentAreaContextMenu = yield openContextMenu("#img1");
   items = contentAreaContextMenu.getElementsByAttribute("ext-type", "top-level-menu");
   is(items.length, 0, "top level item was not found (after removeAll()");
+  yield closeContextMenu();
 
   yield extension.unload();
-
   yield BrowserTestUtils.removeTab(tab1);
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_checkboxes.js
@@ -0,0 +1,74 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+    "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+  gBrowser.selectedTab = tab1;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["contextMenus"],
+    },
+
+    background: function() {
+      browser.contextMenus.create({
+        title: "Checkbox",
+        type: "checkbox",
+      });
+
+      browser.contextMenus.create({
+        type: "separator",
+      });
+
+      browser.contextMenus.create({
+        title: "Checkbox",
+        type: "checkbox",
+        checked: true,
+      });
+
+      browser.contextMenus.create({
+        title: "Checkbox",
+        type: "checkbox",
+      });
+
+      browser.test.notifyPass("contextmenus-checkboxes");
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("contextmenus-checkboxes");
+
+  function confirmCheckboxStates(extensionMenuRoot, expectedStates) {
+    let checkboxItems = extensionMenuRoot.getElementsByAttribute("type", "checkbox");
+
+    is(checkboxItems.length, 3, "there should be 3 checkbox items in the context menu");
+
+    is(checkboxItems[0].hasAttribute("checked"), expectedStates[0], `checkbox item 1 has state (checked=${expectedStates[0]})`);
+    is(checkboxItems[1].hasAttribute("checked"), expectedStates[1], `checkbox item 2 has state (checked=${expectedStates[1]})`);
+    is(checkboxItems[2].hasAttribute("checked"), expectedStates[2], `checkbox item 3 has state (checked=${expectedStates[2]})`);
+
+    return extensionMenuRoot.getElementsByAttribute("type", "checkbox");
+  }
+
+  let extensionMenuRoot = yield openExtensionContextMenu();
+  let items = confirmCheckboxStates(extensionMenuRoot, [false, true, false]);
+  yield closeExtensionContextMenu(items[0]);
+
+  extensionMenuRoot = yield openExtensionContextMenu();
+  items = confirmCheckboxStates(extensionMenuRoot, [true, true, false]);
+  yield closeExtensionContextMenu(items[2]);
+
+  extensionMenuRoot = yield openExtensionContextMenu();
+  items = confirmCheckboxStates(extensionMenuRoot, [true, true, true]);
+  yield closeExtensionContextMenu(items[0]);
+
+  extensionMenuRoot = yield openExtensionContextMenu();
+  items = confirmCheckboxStates(extensionMenuRoot, [false, true, true]);
+  yield closeExtensionContextMenu(items[2]);
+
+  yield extension.unload();
+  yield BrowserTestUtils.removeTab(tab1);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
@@ -0,0 +1,69 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+    "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+  let encodedImageData = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
+  let decodedImageData = atob(encodedImageData);
+  const IMAGE_ARRAYBUFFER = Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["contextMenus"],
+      "icons": {
+        "18": "extension.png",
+      },
+    },
+
+    files: {
+      "extension.png": IMAGE_ARRAYBUFFER,
+    },
+
+    background: function() {
+      let menuitemId = browser.contextMenus.create({
+        title: "child-to-delete",
+        onclick: () => {
+          browser.contextMenus.remove(menuitemId);
+        },
+      });
+
+      browser.contextMenus.create({
+        title: "child",
+      });
+
+      browser.test.notifyPass("contextmenus-icons");
+    },
+  });
+
+  let confirmContextMenuIcon = (rootElement) => {
+    let expectedURL = new RegExp(String.raw`^moz-extension://[^/]+/extension\.png$`);
+    let imageUrl = rootElement.getAttribute("image");
+    ok(expectedURL.test(imageUrl), "The context menu should display the extension icon next to the root element");
+  };
+
+  yield extension.startup();
+  yield extension.awaitFinish("contextmenus-icons");
+
+  let extensionMenu = yield openExtensionContextMenu();
+
+  let contextMenu = document.getElementById("contentAreaContextMenu");
+  let topLevelMenuItem = contextMenu.getElementsByAttribute("ext-type", "top-level-menu")[0];
+  confirmContextMenuIcon(topLevelMenuItem);
+
+  let childToDelete = extensionMenu.getElementsByAttribute("label", "child-to-delete")[0];
+  yield closeExtensionContextMenu(childToDelete);
+
+  yield openExtensionContextMenu();
+
+  contextMenu = document.getElementById("contentAreaContextMenu");
+  topLevelMenuItem = contextMenu.getElementsByAttribute("label", "child")[0];
+
+  confirmContextMenuIcon(topLevelMenuItem);
+  yield closeContextMenu();
+
+  yield extension.unload();
+  yield BrowserTestUtils.removeTab(tab1);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_radioGroups.js
@@ -0,0 +1,78 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+    "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+  gBrowser.selectedTab = tab1;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["contextMenus"],
+    },
+
+    background: function() {
+      browser.contextMenus.create({
+        title: "radio-group-1",
+        type: "radio",
+        checked: true,
+      });
+
+      browser.contextMenus.create({
+        type: "separator",
+      });
+
+      browser.contextMenus.create({
+        title: "radio-group-2",
+        type: "radio",
+      });
+
+      browser.contextMenus.create({
+        title: "radio-group-2",
+        type: "radio",
+      });
+
+      browser.test.notifyPass("contextmenus-radio-groups");
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("contextmenus-radio-groups");
+
+  function confirmRadioGroupStates(extensionMenuRoot, expectedStates) {
+    let radioItems = extensionMenuRoot.getElementsByAttribute("type", "radio");
+    let radioGroup1 = extensionMenuRoot.getElementsByAttribute("label", "radio-group-1");
+    let radioGroup2 = extensionMenuRoot.getElementsByAttribute("label", "radio-group-2");
+
+    is(radioItems.length, 3, "there should be 3 radio items in the context menu");
+    is(radioGroup1.length, 1, "the first radio group should only have 1 radio item");
+    is(radioGroup2.length, 2, "the second radio group should only have 2 radio items");
+
+    is(radioGroup1[0].hasAttribute("checked"), expectedStates[0], `radio item 1 has state (checked=${expectedStates[0]})`);
+    is(radioGroup2[0].hasAttribute("checked"), expectedStates[1], `radio item 2 has state (checked=${expectedStates[1]})`);
+    is(radioGroup2[1].hasAttribute("checked"), expectedStates[2], `radio item 3 has state (checked=${expectedStates[2]})`);
+
+    return extensionMenuRoot.getElementsByAttribute("type", "radio");
+  }
+
+  let extensionMenuRoot = yield openExtensionContextMenu();
+  let items = confirmRadioGroupStates(extensionMenuRoot, [true, false, false]);
+  yield closeExtensionContextMenu(items[1]);
+
+  extensionMenuRoot = yield openExtensionContextMenu();
+  items = confirmRadioGroupStates(extensionMenuRoot, [true, true, false]);
+  yield closeExtensionContextMenu(items[2]);
+
+  extensionMenuRoot = yield openExtensionContextMenu();
+  items = confirmRadioGroupStates(extensionMenuRoot, [true, false, true]);
+  yield closeExtensionContextMenu(items[0]);
+
+  extensionMenuRoot = yield openExtensionContextMenu();
+  items = confirmRadioGroupStates(extensionMenuRoot, [true, false, true]);
+  yield closeExtensionContextMenu(items[0]);
+
+  yield extension.unload();
+  yield BrowserTestUtils.removeTab(tab1);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_uninstall.js
@@ -0,0 +1,84 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+    "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+  // Install an extension.
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["contextMenus"],
+    },
+
+    background: function() {
+      browser.contextMenus.create({title: "a"});
+      browser.contextMenus.create({title: "b"});
+      browser.test.notifyPass("contextmenus-icons");
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("contextmenus-icons");
+
+  // Open the context menu.
+  let contextMenu = yield openContextMenu("#img1");
+
+  // Confirm that the extension menu item exists.
+  let topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+  is(topLevelExtensionMenuItems.length, 1, "the top level extension menu item exists");
+
+  yield closeContextMenu();
+
+  // Uninstall the extension.
+  yield extension.unload();
+
+  // Open the context menu.
+  contextMenu = yield openContextMenu("#img1");
+
+  // Confirm that the extension menu item has been removed.
+  topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+  is(topLevelExtensionMenuItems.length, 0, "no top level extension menu items should exist");
+
+  yield closeContextMenu();
+
+  // Install a new extension.
+  extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["contextMenus"],
+    },
+    background: function() {
+      browser.contextMenus.create({title: "c"});
+      browser.contextMenus.create({title: "d"});
+      browser.test.notifyPass("contextmenus-uninstall-second-extension");
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("contextmenus-uninstall-second-extension");
+
+  // Open the context menu.
+  contextMenu = yield openContextMenu("#img1");
+
+  // Confirm that only the new extension menu item is in the context menu.
+  topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+  is(topLevelExtensionMenuItems.length, 1, "only one top level extension menu item should exist");
+
+  // Close the context menu.
+  yield closeContextMenu();
+
+  // Uninstall the extension.
+  yield extension.unload();
+
+  // Open the context menu.
+  contextMenu = yield openContextMenu("#img1");
+
+  // Confirm that no extension menu items exist.
+  topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+  is(topLevelExtensionMenuItems.length, 0, "no top level extension menu items should exist");
+
+  yield closeContextMenu();
+
+  yield BrowserTestUtils.removeTab(tab1);
+});
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -3,16 +3,18 @@
 "use strict";
 
 /* exported CustomizableUI makeWidgetId focusWindow forceGC
  *          getBrowserActionWidget
  *          clickBrowserAction clickPageAction
  *          getBrowserActionPopup getPageActionPopup
  *          closeBrowserAction closePageAction
  *          promisePopupShown promisePopupHidden
+ *          openContextMenu closeContextMenu
+ *          openExtensionContextMenu closeExtensionContextMenu
  */
 
 var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
 var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
 
 // Bug 1239884: Our tests occasionally hit a long GC pause at unpredictable
 // times in debug builds, which results in intermittent timeouts. Until we have
 // a better solution, we force a GC after certain strategic tests, which tend to
@@ -99,16 +101,54 @@ function closeBrowserAction(extension, w
   let group = getBrowserActionWidget(extension);
 
   let node = win.document.getElementById(group.viewId);
   CustomizableUI.hidePanelForNode(node);
 
   return Promise.resolve();
 }
 
+function* openContextMenu(id) {
+  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+  let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+  yield BrowserTestUtils.synthesizeMouseAtCenter(id, {type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
+  yield popupShownPromise;
+  return contentAreaContextMenu;
+}
+
+function* closeContextMenu() {
+  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+  let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+  contentAreaContextMenu.hidePopup();
+  yield popupHiddenPromise;
+}
+
+function* openExtensionContextMenu() {
+  let contextMenu = yield openContextMenu("#img1");
+  let topLevelMenu = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+
+  // Return null if the extension only has one item and therefore no extension menu.
+  if (topLevelMenu.length == 0) {
+    return null;
+  }
+
+  let extensionMenu = topLevelMenu[0].childNodes[0];
+  let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(extensionMenu, {});
+  yield popupShownPromise;
+  return extensionMenu;
+}
+
+function* closeExtensionContextMenu(itemToSelect) {
+  let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+  let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+  EventUtils.synthesizeMouseAtCenter(itemToSelect, {});
+  yield popupHiddenPromise;
+}
+
 function getPageActionPopup(extension, win = window) {
   let panelId = makeWidgetId(extension.id) + "-panel";
   return win.document.getElementById(panelId);
 }
 
 function clickPageAction(extension, win = window) {
   // This would normally be set automatically on navigation, and cleared
   // when the user types a value into the URL bar, to show and hide page
--- a/browser/config/mozconfigs/linux32/l10n-mozconfig
+++ b/browser/config/mozconfigs/linux32/l10n-mozconfig
@@ -8,9 +8,13 @@ ac_add_options --with-branding=browser/b
 
 export MOZILLA_OFFICIAL=1
 
 # Enable Telemetry
 export MOZ_TELEMETRY_REPORTING=1
 
 ac_add_options --disable-stdcxx-compat
 
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux64/l10n-mozconfig
+++ b/browser/config/mozconfigs/linux64/l10n-mozconfig
@@ -8,9 +8,13 @@ ac_add_options --with-branding=browser/b
 
 export MOZILLA_OFFICIAL=1
 
 # Enable Telemetry
 export MOZ_TELEMETRY_REPORTING=1
 
 ac_add_options --disable-stdcxx-compat
 
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/macosx-universal/l10n-mozconfig
+++ b/browser/config/mozconfigs/macosx-universal/l10n-mozconfig
@@ -9,10 +9,14 @@ if test "${MOZ_UPDATE_CHANNEL}" = "night
 ac_add_options --with-macbundlename-prefix=Firefox
 fi
 
 export MOZILLA_OFFICIAL=1
 
 # Enable Telemetry
 export MOZ_TELEMETRY_REPORTING=1
 
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
 . "$topsrcdir/build/mozconfig.common.override"
 . "$topsrcdir/build/mozconfig.cache"
--- a/browser/config/mozconfigs/macosx64/l10n-mozconfig
+++ b/browser/config/mozconfigs/macosx64/l10n-mozconfig
@@ -1,8 +1,12 @@
 . "$topsrcdir/browser/config/mozconfigs/common"
 
 ac_add_options --with-l10n-base=../../l10n
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --with-branding=browser/branding/nightly
 
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
 . "$topsrcdir/build/mozconfig.common.override"
 . "$topsrcdir/build/mozconfig.cache"
--- a/browser/config/mozconfigs/win32/l10n-mozconfig
+++ b/browser/config/mozconfigs/win32/l10n-mozconfig
@@ -5,11 +5,15 @@ ac_add_options --with-l10n-base=../../l1
 ac_add_options --with-windows-version=603
 ac_add_options --with-branding=browser/branding/nightly
 
 export MOZILLA_OFFICIAL=1
 
 # Enable Telemetry
 export MOZ_TELEMETRY_REPORTING=1
 
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
 . $topsrcdir/build/win32/mozconfig.vs-latest
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win64/l10n-mozconfig
+++ b/browser/config/mozconfigs/win64/l10n-mozconfig
@@ -6,11 +6,15 @@ ac_add_options --with-l10n-base=../../l1
 ac_add_options --with-windows-version=603
 ac_add_options --with-branding=browser/branding/nightly
 
 export MOZILLA_OFFICIAL=1
 
 # Enable Telemetry
 export MOZ_TELEMETRY_REPORTING=1
 
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
 . $topsrcdir/build/win64/mozconfig.vs-latest
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -89,18 +89,16 @@ browser.jar:
 * skin/classic/browser/preferences/in-content/dialog.css      (preferences/in-content/dialog.css)
   skin/classic/browser/preferences/applications.css   (preferences/applications.css)
   skin/classic/browser/social/services-16.png         (social/services-16.png)
   skin/classic/browser/social/services-64.png         (social/services-64.png)
   skin/classic/browser/social/share-button.png        (social/share-button.png)
   skin/classic/browser/social/share-button-active.png (social/share-button-active.png)
   skin/classic/browser/tabbrowser/alltabs.png         (tabbrowser/alltabs.png)
   skin/classic/browser/tabbrowser/alltabs-inverted.png (tabbrowser/alltabs-inverted.png)
-  skin/classic/browser/tabbrowser/connecting.png      (tabbrowser/connecting.png)
-  skin/classic/browser/tabbrowser/connecting@2x.png   (tabbrowser/connecting@2x.png)
   skin/classic/browser/tabbrowser/newtab.svg                (tabbrowser/newtab.svg)
   skin/classic/browser/tabbrowser/newtab-inverted.svg       (tabbrowser/newtab-inverted.svg)
   skin/classic/browser/tabbrowser/tab-active-middle.png     (tabbrowser/tab-active-middle.png)
   skin/classic/browser/tabbrowser/tab-active-middle@2x.png  (tabbrowser/tab-active-middle@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-left.png        (tabbrowser/tab-arrow-left.png)
   skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
   skin/classic/browser/tabbrowser/tab-background-end.png    (tabbrowser/tab-background-end.png)
   skin/classic/browser/tabbrowser/tab-background-end@2x.png (tabbrowser/tab-background-end@2x.png)
deleted file mode 100644
index e564fb5708f3d9da4c623bb3bdfccb960566e327..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 97e2b2eb67ecc58e98dd839187143ab6870699e5..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -140,18 +140,16 @@ browser.jar:
   skin/classic/browser/social/services-64@2x.png            (social/services-64@2x.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon.png             (tabbrowser/alltabs-box-bkgnd-icon.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted.png    (tabbrowser/alltabs-box-bkgnd-icon-inverted.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png (tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png)
   skin/classic/browser/tabbrowser/newtab.png                             (tabbrowser/newtab.png)
   skin/classic/browser/tabbrowser/newtab@2x.png                          (tabbrowser/newtab@2x.png)
   skin/classic/browser/tabbrowser/newtab-inverted.png                    (tabbrowser/newtab-inverted.png)
   skin/classic/browser/tabbrowser/newtab-inverted@2x.png                 (tabbrowser/newtab-inverted@2x.png)
-  skin/classic/browser/tabbrowser/connecting.png                         (tabbrowser/connecting.png)
-  skin/classic/browser/tabbrowser/connecting@2x.png                      (tabbrowser/connecting@2x.png)
   skin/classic/browser/tabbrowser/tab-active-middle.png                  (tabbrowser/tab-active-middle.png)
   skin/classic/browser/tabbrowser/tab-active-middle@2x.png               (tabbrowser/tab-active-middle@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-left.png                     (tabbrowser/tab-arrow-left.png)
   skin/classic/browser/tabbrowser/tab-arrow-left@2x.png                  (tabbrowser/tab-arrow-left@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png            (tabbrowser/tab-arrow-left-inverted.png)
   skin/classic/browser/tabbrowser/tab-arrow-left-inverted@2x.png         (tabbrowser/tab-arrow-left-inverted@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-right.png                    (tabbrowser/tab-arrow-right.png)
   skin/classic/browser/tabbrowser/tab-arrow-right@2x.png                 (tabbrowser/tab-arrow-right@2x.png)
deleted file mode 100644
index e564fb5708f3d9da4c623bb3bdfccb960566e327..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 97e2b2eb67ecc58e98dd839187143ab6870699e5..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -95,16 +95,18 @@
   skin/classic/browser/search-indicator-badge-add.png          (../shared/search/search-indicator-badge-add.png)
   skin/classic/browser/search-indicator-badge-add@2x.png       (../shared/search/search-indicator-badge-add@2x.png)
   skin/classic/browser/search-history-icon.svg                 (../shared/search/history-icon.svg)
   skin/classic/browser/search-indicator-magnifying-glass.svg   (../shared/search/search-indicator-magnifying-glass.svg)
   skin/classic/browser/search-arrow-go.svg                     (../shared/search/search-arrow-go.svg)
   skin/classic/browser/social/chat-icons.svg                   (../shared/social/chat-icons.svg)
   skin/classic/browser/social/gear_default.png                 (../shared/social/gear_default.png)
   skin/classic/browser/social/gear_clicked.png                 (../shared/social/gear_clicked.png)
+  skin/classic/browser/tabbrowser/connecting.png               (../shared/tabbrowser/connecting.png)
+  skin/classic/browser/tabbrowser/connecting@2x.png            (../shared/tabbrowser/connecting@2x.png)
   skin/classic/browser/tabbrowser/crashed.svg                  (../shared/tabbrowser/crashed.svg)
   skin/classic/browser/tabbrowser/pendingpaint.png             (../shared/tabbrowser/pendingpaint.png)
 * skin/classic/browser/tabbrowser/tab-audio.svg                (../shared/tabbrowser/tab-audio.svg)
   skin/classic/browser/tabbrowser/tab-audio-small.svg          (../shared/tabbrowser/tab-audio-small.svg)
   skin/classic/browser/tabbrowser/tab-overflow-indicator.png   (../shared/tabbrowser/tab-overflow-indicator.png)
   skin/classic/browser/theme-switcher-icon.png                 (../shared/theme-switcher-icon.png)
   skin/classic/browser/theme-switcher-icon@2x.png              (../shared/theme-switcher-icon@2x.png)
   skin/classic/browser/translating-16.png                      (../shared/translation/translating-16.png)
rename from browser/themes/windows/tabbrowser/connecting.png
rename to browser/themes/shared/tabbrowser/connecting.png
rename from browser/themes/windows/tabbrowser/connecting@2x.png
rename to browser/themes/shared/tabbrowser/connecting@2x.png
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -148,18 +148,16 @@ browser.jar:
 * skin/classic/browser/preferences/in-content/dialog.css       (preferences/in-content/dialog.css)
   skin/classic/browser/preferences/applications.css            (preferences/applications.css)
   skin/classic/browser/social/services-16.png                  (social/services-16.png)
   skin/classic/browser/social/services-64.png                  (social/services-64.png)
   skin/classic/browser/tabbrowser/newtab.svg                   (tabbrowser/newtab.svg)
   skin/classic/browser/tabbrowser/newtab-XPVista7.svg          (tabbrowser/newtab-XPVista7.svg)
   skin/classic/browser/tabbrowser/newtab-inverted.svg          (tabbrowser/newtab-inverted.svg)
   skin/classic/browser/tabbrowser/newtab-inverted-XPVista7.svg (tabbrowser/newtab-inverted-XPVista7.svg)
-  skin/classic/browser/tabbrowser/connecting.png               (tabbrowser/connecting.png)
-  skin/classic/browser/tabbrowser/connecting@2x.png            (tabbrowser/connecting@2x.png)
   skin/classic/browser/tabbrowser/tab-active-middle.png        (tabbrowser/tab-active-middle.png)
   skin/classic/browser/tabbrowser/tab-active-middle@2x.png     (tabbrowser/tab-active-middle@2x.png)
   skin/classic/browser/tabbrowser/tab-arrow-left.svg           (tabbrowser/tab-arrow-left.svg)
   skin/classic/browser/tabbrowser/tab-arrow-left-XPVista7.svg  (tabbrowser/tab-arrow-left-XPVista7.svg)
   skin/classic/browser/tabbrowser/tab-arrow-left-inverted.svg  (tabbrowser/tab-arrow-left-inverted.svg)
   skin/classic/browser/tabbrowser/tab-background-start.png     (tabbrowser/tab-background-start.png)
   skin/classic/browser/tabbrowser/tab-background-start@2x.png  (tabbrowser/tab-background-start@2x.png)
   skin/classic/browser/tabbrowser/tab-background-middle.png    (tabbrowser/tab-background-middle.png)
--- a/build/mozconfig.cache
+++ b/build/mozconfig.cache
@@ -2,18 +2,16 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # Setup for build cache
 
 # Avoid duplication if the file happens to be included twice.
 if test -z "$bucket" -a -z "$NO_CACHE"; then
 
-# buildbot (or builders that use buildprops.json):
-if [ -f $topsrcdir/../buildprops.json ]; then
 read branch platform master <<EOF
 $(python2.7 -c 'import json; p = json.loads(open("'"$topsrcdir"'/../buildprops.json").read())["properties"]; print p["branch"], p["platform"], p["master"]' 2> /dev/null)
 EOF
 
 bucket=
 if test -z "$SCCACHE_DISABLE" -a -z "$no_sccache" -a -z "$MOZ_PGO_IS_SET" -a -z "$MOZ_PGO"; then
     case "${branch}" in
     try)
@@ -37,70 +35,30 @@ if test -z "$SCCACHE_DISABLE" -a -z "$no
         *usw2.mozilla.com*)
             bucket=mozilla-releng-s3-cache-us-west-2-prod
             ;;
         esac
         ;;
     esac
 fi
 
-# taskcluster:
-else
-    # timeout after 1 second, and don't retry (failure indicates instance is not in ec2 or wget, network issue)
-    # availability_zone is of the form <region><letter> where region is e.g. us-west-2, and az is us-west-2a
-    availability_zone=$(wget -T 1 -t 1 -q -O - http://169.254.169.254/latest/meta-data/placement/availability-zone)
-    # region is az with last letter trimmed
-    region=${availability_zone%?}
-    if test -z "$SCCACHE_DISABLE" -a -z "$no_sccache" -a -z "$MOZ_PGO_IS_SET" -a -z "$MOZ_PGO"; then
-        # set S3 bucket according to tree (level)
-        case "${GECKO_HEAD_REPOSITORY}" in
-        *hg.mozilla.org/try*)
-            bucket=taskcluster-level-1-sccache-${region}
-            ;;
-        *hg.mozilla.org/integration/mozilla-inbound*|*hg.mozilla.org/integration/fx-team*)
-            bucket=taskcluster-level-3-sccache-${region}
-            ;;
-        esac
-    fi
-
-    # set a dummy master
-    case "${region}" in
-    us-east-1)
-        master=dummy.use1.mozilla.com
-        ;;
-    us-west-1)
-        master=dummy.usw1.mozilla.com
-        ;;
-    us-west-2)
-        master=dummy.usw2.mozilla.com
-        ;;
-    esac
-
-    # set platform based on the SYSTEMROOT env var
-    case "${SYSTEMROOT}" in
-    *Windows)
-        platform=windows
-        ;;
-    esac
-fi
-
 if test -z "$bucket"; then
     case "$platform" in
     win*) : ;;
     *)
         ac_add_options --with-ccache
     esac
 else
     if ! test -e $topsrcdir/sccache/sccache.py; then
         echo "Sccache missing in the tooltool manifest" >&2
         exit 1
     fi
     mk_add_options "export SCCACHE_BUCKET=$bucket"
     case "$master" in
-    *us[ew][12].mozilla.com*)
+    *use1.mozilla.com*|*usw2.mozilla.com*)
         mk_add_options "export SCCACHE_NAMESERVER=169.254.169.253"
         ;;
     esac
     ac_add_options "--with-compiler-wrapper=python2.7 $topsrcdir/sccache/sccache.py"
     mk_add_options MOZ_PREFLIGHT_ALL+=build/sccache.mk
     mk_add_options MOZ_POSTFLIGHT_ALL+=build/sccache.mk
     mk_add_options "UPLOAD_EXTRA_FILES+=sccache.log.gz"
     case "$platform" in
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -332,16 +332,32 @@ nsDOMWindowUtils::UpdateLayerTree()
       presShell->Paint(view, view->GetBounds(),
           nsIPresShell::PAINT_LAYERS | nsIPresShell::PAINT_SYNC_DECODE_IMAGES);
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::GetContentViewerSize(uint32_t *aDisplayWidth, uint32_t *aDisplayHeight)
+{
+  nsIPresShell* presShell = GetPresShell();
+  LayoutDeviceIntSize displaySize;
+
+  if (!presShell || !nsLayoutUtils::GetContentViewerSize(presShell->GetPresContext(), displaySize)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *aDisplayWidth = displaySize.width;
+  *aDisplayHeight = displaySize.height;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::GetViewportInfo(uint32_t aDisplayWidth,
                                   uint32_t aDisplayHeight,
                                   double *aDefaultZoom, bool *aAllowZoom,
                                   double *aMinZoom, double *aMaxZoom,
                                   uint32_t *aWidth, uint32_t *aHeight,
                                   bool *aAutoSize)
 {
   nsIDocument* doc = GetDocument();
@@ -535,24 +551,27 @@ nsDOMWindowUtils::SetResolutionAndScaleT
   }
 
   presShell->SetResolutionAndScaleTo(aResolution);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::SetRestoreResolution(float aResolution)
+nsDOMWindowUtils::SetRestoreResolution(float aResolution,
+                                       uint32_t aDisplayWidth,
+                                       uint32_t aDisplayHeight)
 {
   nsIPresShell* presShell = GetPresShell();
   if (!presShell) {
     return NS_ERROR_FAILURE;
   }
 
-  presShell->SetRestoreResolution(aResolution);
+  presShell->SetRestoreResolution(aResolution,
+    LayoutDeviceIntSize(aDisplayWidth, aDisplayHeight));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::GetResolution(float* aResolution)
 {
   nsIPresShell* presShell = GetPresShell();
--- a/dom/filesystem/tests/mochitest.ini
+++ b/dom/filesystem/tests/mochitest.ini
@@ -2,9 +2,8 @@
 support-files =
   filesystem_commons.js
   script_fileList.js
   worker_basic.js
 
 [test_basic.html]
 [test_webkitdirectory.html]
 [test_worker_basic.html]
-skip-if = true # bug 1283344
--- a/dom/filesystem/tests/test_basic.html
+++ b/dom/filesystem/tests/test_basic.html
@@ -3,27 +3,30 @@
 <head>
   <title>Test for Directory API</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="filesystem_commons.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 
 <body>
-<input id="fileList" type="file"></input>
 <script type="application/javascript;version=1.7">
 
 var directory;
+var fileList;
 
 function create_fileList(aPath) {
+  fileList = document.createElement('input');
+  fileList.setAttribute('type', 'file');
+  document.body.appendChild(fileList);
+
   var url = SimpleTest.getTestFileURL("script_fileList.js");
   var script = SpecialPowers.loadChromeScript(url);
 
   function onOpened(message) {
-    var fileList = document.getElementById('fileList');
     SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
 
     fileList.getFilesAndDirectories().then(function(array) {
       is(array.length, 1, "We want just 1 directory.");
       ok(array[0] instanceof Directory, "We want just 1 directory.");
 
       directory = array[0];
       script.destroy();
@@ -35,17 +38,16 @@ function create_fileList(aPath) {
   script.sendAsyncMessage("dir.open", { path: aPath });
 }
 
 function test_simpleFilePicker(aPath) {
   var url = SimpleTest.getTestFileURL("script_fileList.js");
   var script = SpecialPowers.loadChromeScript(url);
 
   function onOpened(message) {
-    var fileList = document.getElementById('fileList');
     SpecialPowers.wrap(fileList).mozSetFileArray([message.file]);
 
     is(fileList.files.length, 1, "we want 1 element");
     ok(fileList.files[0] instanceof File, "we want 1 file");
     ok("webkitRelativePath" in fileList.files[0], "we have webkitRelativePath attribute");
     is(fileList.files[0].webkitRelativePath, "", "No webkit relative path for normal filePicker");
 
     script.destroy();
@@ -56,17 +58,16 @@ function test_simpleFilePicker(aPath) {
   script.sendAsyncMessage("file.open");
 }
 
 function test_duplicateGetFilesAndDirectories() {
   var url = SimpleTest.getTestFileURL("script_fileList.js");
   var script = SpecialPowers.loadChromeScript(url);
 
   function onOpened(message) {
-    var fileList = document.getElementById('fileList');
     SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
 
     var p1 = fileList.getFilesAndDirectories();
     var p2 = fileList.getFilesAndDirectories();
 
     isnot(p1, p2, "We create 2 different promises");
 
     script.destroy();
@@ -77,17 +78,16 @@ function test_duplicateGetFilesAndDirect
   script.sendAsyncMessage("dir.open", { path: 'test' });
 }
 
 function test_inputGetFiles() {
   var url = SimpleTest.getTestFileURL("script_fileList.js");
   var script = SpecialPowers.loadChromeScript(url);
 
   function onOpened(message) {
-    var fileList = document.getElementById('fileList');
     SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
 
     fileList.getFilesAndDirectories()
     .then(function(result) {
        is(result.length, 1, "getFilesAndDirectories should return 1 element");
        ok(result[0] instanceof Directory, "getFilesAndDirectories should return 1 directory");
 
       return fileList.getFiles(false);
--- a/dom/filesystem/tests/test_worker_basic.html
+++ b/dom/filesystem/tests/test_worker_basic.html
@@ -3,36 +3,39 @@
 <head>
   <title>Test for Directory API in workers</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="filesystem_commons.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 
 <body>
-<input id="fileList" type="file"></input>
 <script type="application/javascript;version=1.7">
 
+var fileList;
+
 function create_fileList() {
+  fileList = document.createElement('input');
+  fileList.setAttribute('type', 'file');
+  document.body.appendChild(fileList);
+
   var url = SimpleTest.getTestFileURL("script_fileList.js");
   var script = SpecialPowers.loadChromeScript(url);
 
   function onOpened(message) {
-    var fileList = document.getElementById('fileList');
     SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
     script.destroy();
     next();
   }
 
   script.addMessageListener("dir.opened", onOpened);
   script.sendAsyncMessage("dir.open", { path: 'ProfD' });
 }
 
 function test_worker() {
-  var fileList = document.getElementById('fileList');
   fileList.getFilesAndDirectories().then(function(array) {
     var worker = new Worker('worker_basic.js');
     worker.onmessage = function(e) {
       if (e.data.type == 'finish') {
         next();
         return;
       }
 
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -117,16 +117,21 @@ interface nsIDOMWindowUtils : nsISupport
    */
   void getViewportInfo(in uint32_t aDisplayWidth, in uint32_t aDisplayHeight,
                        out double aDefaultZoom, out boolean aAllowZoom,
                        out double aMinZoom, out double aMaxZoom,
                        out uint32_t aWidth, out uint32_t aHeight,
                        out boolean aAutoSize);
 
   /**
+   * Information about the window size in device pixels.
+   */
+  void getContentViewerSize(out uint32_t aDisplayWidth, out uint32_t aDisplayHeight);
+
+  /**
    * For any scrollable element, this allows you to override the
    * visible region and draw more than what is visible, which is
    * useful for asynchronous drawing. The "displayport" will be
    * <xPx, yPx, widthPx, heightPx> in units of CSS pixels,
    * regardless of the size of the enclosing container.  This
    * will *not* trigger reflow.
    *
    * For the root scroll area, pass in the root document element.
@@ -219,20 +224,25 @@ interface nsIDOMWindowUtils : nsISupport
    * for pinch-zoom on mobile platforms.
    *
    * The caller of this method must have chrome privileges.
    */
   void setResolutionAndScaleTo(in float aResolution);
 
   /**
    * Set a resolution on the presShell which is the "restored" from history.
+   * The display dimensions are compared to their current values and used
+   * to scale the resolution value if necessary, e.g. if the device was
+   * rotated between saving and restoring of the session data.
    * This resolution should be used when painting for the first time. Calling
    * this too late may have no effect.
    */
-  void setRestoreResolution(in float aResolution);
+  void setRestoreResolution(in float aResolution,
+                            in uint32_t aDisplayWidth,
+                            in uint32_t aDisplayHeight);
 
   /**
    * Whether the resolution has been set by the user.
    * This gives a way to check whether the provided resolution is the default
    * value or restored from a previous session.
    *
    * Can only be accessed with chrome privileges.
    */
--- a/dom/push/test/xpcshell/xpcshell.ini
+++ b/dom/push/test/xpcshell/xpcshell.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 head = head.js head-http2.js
 tail =
+firefox-appdir = browser
 # Push notifications and alarms are currently disabled on Android.
 skip-if = toolkit == 'android'
 
 [test_clear_forgetAboutSite.js]
 [test_clear_origin_data.js]
 [test_crypto.js]
 [test_drop_expired.js]
 [test_handler_service.js]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_require_sri_meta.js
@@ -0,0 +1,1 @@
+var foo = 24;
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_require_sri_meta.sjs
@@ -0,0 +1,54 @@
+// custom *.sjs for Bug 1277557
+// META CSP: require-sri-for script;
+
+const PRE_INTEGRITY =
+  "<!DOCTYPE HTML>" +
+  "<html><head><meta charset=\"utf-8\">" +
+  "<title>Bug 1277557 - CSP require-sri-for does not block when CSP is in meta tag</title>" +
+  "<meta http-equiv=\"Content-Security-Policy\" content=\"require-sri-for script; script-src 'unsafe-inline' *\">" +
+  "</head>" +
+  "<body>" +
+  "<script id=\"testscript\"" +
+  // Using math.random() to avoid confusing cache behaviors within the test
+  "        src=\"http://mochi.test:8888/tests/dom/security/test/csp/file_require_sri_meta.js?" + Math.random() + "\"";
+
+const WRONG_INTEGRITY =
+  "        integrity=\"sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC\"";
+
+const CORRECT_INEGRITY =
+  "        integrity=\"sha384-PkcuZQHmjBQKRyv1v3x0X8qFmXiSyFyYIP+f9SU86XWvRneifdNCPg2cYFWBuKsF\"";
+
+const POST_INTEGRITY =
+  "        onload=\"window.parent.postMessage({result: 'script-loaded'}, '*');\"" +
+  "        onerror=\"window.parent.postMessage({result: 'script-blocked'}, '*');\"" +
+  "></script>" +
+  "</body>" +
+  "</html>";
+
+function handleRequest(request, response)
+{
+  // avoid confusing cache behaviors
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Content-Type", "text/html", false);
+
+  var queryString = request.queryString;
+
+  if (queryString === "no-sri") {
+    response.write(PRE_INTEGRITY + POST_INTEGRITY);
+    return;
+  }
+
+  if (queryString === "wrong-sri") {
+    response.write(PRE_INTEGRITY + WRONG_INTEGRITY + POST_INTEGRITY);
+    return;
+  }
+
+  if (queryString === "correct-sri") {
+    response.write(PRE_INTEGRITY + CORRECT_INEGRITY + POST_INTEGRITY);
+    return;
+  }
+
+  // we should never get here, but just in case
+  // return something unexpected
+  response.write("do'h");
+}
--- a/dom/security/test/csp/mochitest.ini
+++ b/dom/security/test/csp/mochitest.ini
@@ -177,16 +177,18 @@ support-files =
   file_sandbox_5.html
   file_sandbox_6.html
   file_sandbox_7.html
   file_sandbox_8.html
   file_sandbox_9.html
   file_sandbox_10.html
   file_sandbox_11.html
   file_sandbox_12.html
+  file_require_sri_meta.sjs
+  file_require_sri_meta.js
 
 [test_base-uri.html]
 [test_blob_data_schemes.html]
 [test_connect-src.html]
 [test_CSP.html]
 [test_allow_https_schemes.html]
 skip-if = buildapp == 'b2g' #no ssl support
 [test_bug663567.html]
@@ -267,8 +269,9 @@ tags = mcb
 [test_block_all_mixed_content_frame_navigation.html]
 tags = mcb
 [test_form_action_blocks_url.html]
 [test_meta_whitespace_skipping.html]
 [test_iframe_sandbox.html]
 [test_iframe_sandbox_top_1.html]
 [test_sandbox.html]
 [test_ping.html]
+[test_require_sri_meta.html]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_require_sri_meta.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Bug 1277557 - CSP require-sri-for does not block when CSP is in meta tag</title>
+  <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * We load scripts within an iframe and make sure that meta-csp of
+ * require-sri-for applies correctly to preloaded scripts.
+ * Please note that we have to use <script src=""> to kick
+ * off the html preloader.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.setBoolPref("security.csp.experimentalEnabled", true);
+
+var curTest;
+var counter = -1;
+
+const tests = [
+  { // test 1
+    description: "script with *no* SRI should be blocked",
+    query: "no-sri",
+    expected: "script-blocked"
+  },
+  { // test 2
+    description: "script-with *incorrect* SRI should be blocked",
+    query: "wrong-sri",
+    expected: "script-blocked"
+  },
+  { // test 3
+    description: "script-with *correct* SRI should be loaded",
+    query: "correct-sri",
+    expected: "script-loaded"
+  },
+];
+
+function finishTest() {
+  window.removeEventListener("message", receiveMessage, false);
+  SimpleTest.finish();
+}
+
+function checkResults(result) {
+  is(result, curTest.expected, curTest.description);
+  loadNextTest();
+}
+
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+  checkResults(event.data.result);
+}
+
+function loadNextTest() {
+  counter++;
+  if (counter == tests.length) {
+    finishTest();
+    return;
+  }
+  curTest = tests[counter];
+  var testframe = document.getElementById("testframe");
+  testframe.src = "file_require_sri_meta.sjs?" + curTest.query;
+}
+
+loadNextTest();
+
+</script>
+</body>
+</html>
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -616,22 +616,29 @@ BasicCompositor::BeginFrame(const nsIntR
   if (!aOpaqueRegion.IsEmpty()) {
     LayoutDeviceIntRegion clearRegion = mInvalidRegion;
     clearRegion.SubOut(LayoutDeviceIntRegion::FromUnknownRegion(aOpaqueRegion));
     clearRect = clearRegion.GetBounds();
   } else {
     clearRect = mInvalidRect;
   }
 
+  // Prevent CreateRenderTargetForWindow from clearing unwanted area.
+  gfxUtils::ClipToRegion(mDrawTarget,
+                         mInvalidRegion.ToUnknownRegion());
+
   // Setup an intermediate render target to buffer all compositing. We will
   // copy this into mDrawTarget (the widget), and/or mTarget in EndFrame()
   RefPtr<CompositingRenderTarget> target =
     CreateRenderTargetForWindow(mInvalidRect,
                                 clearRect,
                                 bufferMode);
+
+  mDrawTarget->PopClip();
+
   if (!target) {
     if (!mTarget) {
       mWidget->EndRemoteDrawingInRegion(mDrawTarget, mInvalidRegion);
     }
     return;
   }
   SetRenderTarget(target);
 
--- a/js/src/asmjs/WasmBaselineCompile.cpp
+++ b/js/src/asmjs/WasmBaselineCompile.cpp
@@ -2705,23 +2705,22 @@ class BaseCompiler
 
     //////////////////////////////////////////////////////////////////////
     //
     // Heap access.
 
 #if defined(JS_CODEGEN_X64)
     // Copied from CodeGenerator-x64.cpp
     // TODO / CLEANUP - share with the code generator.
-
-    wasm::MemoryAccess
-    AsmJSMemoryAccess(uint32_t before, wasm::MemoryAccess::OutOfBoundsBehavior throwBehavior,
-                      uint32_t offsetWithinWholeSimdVector = 0)
+    MemoryAccess
+    WasmMemoryAccess(uint32_t before)
     {
-        return wasm::MemoryAccess(before, throwBehavior, wasm::MemoryAccess::WrapOffset,
-                                  offsetWithinWholeSimdVector);
+        if (isCompilingAsmJS())
+            return MemoryAccess(before, MemoryAccess::CarryOn, MemoryAccess::WrapOffset);
+        return MemoryAccess(before, MemoryAccess::Throw, MemoryAccess::DontWrapOffset);
     }
 #endif
 
     void memoryBarrier(MemoryBarrierBits barrier) {
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
         if (barrier & MembarStoreLoad)
             masm.storeLoadFence();
 #else
@@ -2755,16 +2754,21 @@ class BaseCompiler
         // TODO / MISSING: this needs to be adapted from what's in the
         // platform's CodeGenerator; that code takes an LAllocation as
         // the last arg now.
 #endif
     }
 #endif
 
     void loadHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg dest) {
+        if (access.offset() > INT32_MAX) {
+            masm.jump(wasm::JumpTarget::OutOfBounds);
+            return;
+        }
+
 #if defined(JS_CODEGEN_X64)
         // CodeGeneratorX64::visitAsmJSLoadHeap()
 
         if (needsBoundsCheckBranch(access))
             MOZ_CRASH("BaseCompiler platform hook: bounds checking");
 
         Operand srcAddr(HeapReg, ptr.reg, TimesOne, access.offset());
 
@@ -2778,17 +2782,17 @@ class BaseCompiler
           case Scalar::Uint32:    masm.movl(srcAddr, dest.i32().reg); break;
           case Scalar::Float32:   masm.loadFloat32(srcAddr, dest.f32().reg); break;
           case Scalar::Float64:   masm.loadDouble(srcAddr, dest.f64().reg); break;
           default:
             MOZ_CRASH("Compiler bug: Unexpected array type");
         }
         uint32_t after = masm.size();
 
-        masm.append(AsmJSMemoryAccess(before, wasm::MemoryAccess::CarryOn));
+        masm.append(WasmMemoryAccess(before));
         verifyHeapAccessDisassembly(before, after, IsLoad(true), access.accessType(), 0, srcAddr, dest);
 #else
         MOZ_CRASH("BaseCompiler platform hook: loadHeap");
 #endif
     }
 
     void storeHeap(const MWasmMemoryAccess& access, RegI32 ptr, AnyReg src) {
 #if defined(JS_CODEGEN_X64)
@@ -2809,17 +2813,17 @@ class BaseCompiler
           case Scalar::Uint32:       masm.movl(src.i32().reg, dstAddr); break;
           case Scalar::Float32:      masm.storeFloat32(src.f32().reg, dstAddr); break;
           case Scalar::Float64:      masm.storeDouble(src.f64().reg, dstAddr); break;
           default:
               MOZ_CRASH("Compiler bug: Unexpected array type");
         }
         uint32_t after = masm.size();
 
-        masm.append(AsmJSMemoryAccess(before, wasm::MemoryAccess::CarryOn));
+        masm.append(WasmMemoryAccess(before));
         verifyHeapAccessDisassembly(before, after, IsLoad(false), access.accessType(), 0, dstAddr, src);
 #else
         MOZ_CRASH("BaseCompiler platform hook: storeHeap");
 #endif
     }
 
     ////////////////////////////////////////////////////////////
 
@@ -5205,18 +5209,17 @@ BaseCompiler::emitLoad(ValType type, Sca
         return false;
 
     if (deadCode_)
         return true;
 
     // TODO / OPTIMIZE: Disable bounds checking on constant accesses
     // below the minimum heap length.
 
-    MWasmMemoryAccess access(viewType, addr.align);
-    access.setOffset(addr.offset);
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset);
 
     switch (type) {
       case ValType::I32: {
         RegI32 rp = popI32();
         loadHeap(access, rp, AnyReg(rp));
         pushI32(rp);
         break;
       }
@@ -5255,18 +5258,17 @@ BaseCompiler::emitStore(ValType resultTy
         return false;
 
     if (deadCode_)
         return true;
 
     // TODO / OPTIMIZE: Disable bounds checking on constant accesses
     // below the minimum heap length.
 
-    MWasmMemoryAccess access(viewType, addr.align);
-    access.setOffset(addr.offset);
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset);
 
     switch (resultType) {
       case ValType::I32: {
         RegI32 rp, rv;
         pop2xI32(&rp, &rv);
         storeHeap(access, rp, AnyReg(rv));
         freeI32(rp);
         pushI32(rv);
@@ -5535,18 +5537,17 @@ BaseCompiler::emitStoreWithCoercion(ValT
         return false;
 
     if (deadCode_)
         return true;
 
     // TODO / OPTIMIZE: Disable bounds checking on constant accesses
     // below the minimum heap length.
 
-    MWasmMemoryAccess access(viewType, addr.align);
-    access.setOffset(addr.offset);
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset);
 
     if (resultType == ValType::F32 && viewType == Scalar::Float64) {
         RegF32 rv = popF32();
         RegF64 rw = needF64();
         masm.convertFloat32ToDouble(rv.reg, rw.reg);
         RegI32 rp = popI32();
         storeHeap(access, rp, AnyReg(rw));
         pushF32(rv);
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -589,26 +589,44 @@ class FunctionCompiler
         curBlock_->setSlot(info().localSlot(slot), def);
     }
 
   private:
     MDefinition* loadHeapPrivate(MDefinition* base, const MWasmMemoryAccess& access)
     {
         if (inDeadCode())
             return nullptr;
-        MAsmJSLoadHeap* load = MAsmJSLoadHeap::New(alloc(), base, access);
+
+        MInstruction* load = nullptr;
+        if (mg().kind == ModuleKind::Wasm) {
+            if (!mg().usesSignal.forOOB)
+                curBlock_->add(MWasmBoundsCheck::New(alloc(), base, access));
+            load = MWasmLoad::New(alloc(), base, access);
+        } else {
+            load = MAsmJSLoadHeap::New(alloc(), base, access);
+        }
+
         curBlock_->add(load);
         return load;
     }
 
     void storeHeapPrivate(MDefinition* base, const MWasmMemoryAccess& access, MDefinition* v)
     {
         if (inDeadCode())
             return;
-        MAsmJSStoreHeap* store = MAsmJSStoreHeap::New(alloc(), base, access, v);
+
+        MInstruction* store = nullptr;
+        if (mg().kind == ModuleKind::Wasm) {
+            if (!mg().usesSignal.forOOB)
+                curBlock_->add(MWasmBoundsCheck::New(alloc(), base, access));
+            store = MWasmStore::New(alloc(), base, access, v);
+        } else {
+            store = MAsmJSStoreHeap::New(alloc(), base, access, v);
+        }
+
         curBlock_->add(store);
     }
 
   public:
     MDefinition* loadHeap(MDefinition* base, const MWasmMemoryAccess& access)
     {
         MOZ_ASSERT(!Scalar::isSimdType(access.accessType()), "SIMD loads should use loadSimdHeap");
         return loadHeapPrivate(base, access);
@@ -2050,74 +2068,38 @@ EmitSelect(FunctionCompiler& f)
         return false;
 
     if (!IsVoid(type))
         f.iter().setResult(f.select(trueValue, falseValue, condition));
 
     return true;
 }
 
-enum class IsAtomic {
-    No = false,
-    Yes = true
-};
-
-static bool
-SetHeapAccessOffset(FunctionCompiler& f, uint32_t offset, MWasmMemoryAccess* access,
-                    MDefinition** base, IsAtomic atomic = IsAtomic::No)
-{
-    // TODO Remove this after implementing non-wraparound offset semantics.
-    uint32_t endOffset = offset + access->byteSize();
-    if (endOffset < offset)
-        return false;
-
-    // Assume worst case.
-    if (endOffset > f.mirGen().foldableOffsetRange(/* bounds check */ true, bool(atomic))) {
-        MDefinition* rhs = f.constant(Int32Value(offset), MIRType::Int32);
-        *base = f.binary<MAdd>(*base, rhs, MIRType::Int32);
-        access->setOffset(0);
-    } else {
-        access->setOffset(offset);
-    }
-
-    return true;
-}
-
 static bool
 EmitLoad(FunctionCompiler& f, ValType type, Scalar::Type viewType)
 {
     LinearMemoryAddress<MDefinition*> addr;
     if (!f.iter().readLoad(type, Scalar::byteSize(viewType), &addr))
         return false;
 
-    MWasmMemoryAccess access(viewType, addr.align);
-
-    MDefinition* base = addr.base;
-    if (!SetHeapAccessOffset(f, addr.offset, &access, &base))
-        return false;
-
-    f.iter().setResult(f.loadHeap(base, access));
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset);
+    f.iter().setResult(f.loadHeap(addr.base, access));
     return true;
 }
 
 static bool
 EmitStore(FunctionCompiler& f, ValType resultType, Scalar::Type viewType)
 {
     LinearMemoryAddress<MDefinition*> addr;
     MDefinition* value;
     if (!f.iter().readStore(resultType, Scalar::byteSize(viewType), &addr, &value))
         return false;
 
-    MWasmMemoryAccess access(viewType, addr.align);
-
-    MDefinition* base = addr.base;
-    if (!SetHeapAccessOffset(f, addr.offset, &access, &base))
-        return false;
-
-    f.storeHeap(base, access, value);
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset);
+    f.storeHeap(addr.base, access, value);
     return true;
 }
 
 static bool
 EmitStoreWithCoercion(FunctionCompiler& f, ValType resultType, Scalar::Type viewType)
 {
     LinearMemoryAddress<MDefinition*> addr;
     MDefinition* value;
@@ -2126,23 +2108,18 @@ EmitStoreWithCoercion(FunctionCompiler& 
 
     if (resultType == ValType::F32 && viewType == Scalar::Float64)
         value = f.unary<MToDouble>(value);
     else if (resultType == ValType::F64 && viewType == Scalar::Float32)
         value = f.unary<MToFloat32>(value);
     else
         MOZ_CRASH("unexpected coerced store");
 
-    MWasmMemoryAccess access(viewType, addr.align);
-
-    MDefinition* base = addr.base;
-    if (!SetHeapAccessOffset(f, addr.offset, &access, &base))
-        return false;
-
-    f.storeHeap(base, access, value);
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset);
+    f.storeHeap(addr.base, access, value);
     return true;
 }
 
 static bool
 EmitUnaryMathBuiltinCall(FunctionCompiler& f, uint32_t callOffset, SymbolicAddress callee,
                          ValType operandType)
 {
     uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode(callOffset);
@@ -2202,102 +2179,79 @@ EmitBinaryMathBuiltinCall(FunctionCompil
 static bool
 EmitAtomicsLoad(FunctionCompiler& f)
 {
     LinearMemoryAddress<MDefinition*> addr;
     Scalar::Type viewType;
     if (!f.iter().readAtomicLoad(&addr, &viewType))
         return false;
 
-    MWasmMemoryAccess access(viewType, addr.align, 0, MembarBeforeLoad, MembarAfterLoad);
-
-    MDefinition* base = addr.base;
-    if (!SetHeapAccessOffset(f, addr.offset, &access, &base, IsAtomic::Yes))
-        return false;
-
-    f.iter().setResult(f.loadAtomicHeap(base, access));
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset, 0,
+                             MembarBeforeLoad, MembarAfterLoad);
+    f.iter().setResult(f.loadAtomicHeap(addr.base, access));
     return true;
 }
 
 static bool
 EmitAtomicsStore(FunctionCompiler& f)
 {
     LinearMemoryAddress<MDefinition*> addr;
     Scalar::Type viewType;
     MDefinition* value;
     if (!f.iter().readAtomicStore(&addr, &viewType, &value))
         return false;
 
-    MWasmMemoryAccess access(viewType, addr.align, 0, MembarBeforeStore, MembarAfterStore);
-
-    MDefinition* base = addr.base;
-    if (!SetHeapAccessOffset(f, addr.offset, &access, &base, IsAtomic::Yes))
-        return false;
-
-    f.storeAtomicHeap(base, access, value);
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset, 0,
+                             MembarBeforeStore, MembarAfterStore);
+    f.storeAtomicHeap(addr.base, access, value);
     f.iter().setResult(value);
     return true;
 }
 
 static bool
 EmitAtomicsBinOp(FunctionCompiler& f)
 {
     LinearMemoryAddress<MDefinition*> addr;
     Scalar::Type viewType;
     jit::AtomicOp op;
     MDefinition* value;
     if (!f.iter().readAtomicBinOp(&addr, &viewType, &op, &value))
         return false;
 
-    MWasmMemoryAccess access(viewType, addr.align);
-
-    MDefinition* base = addr.base;
-    if (!SetHeapAccessOffset(f, addr.offset, &access, &base, IsAtomic::Yes))
-        return false;
-
-    f.iter().setResult(f.atomicBinopHeap(op, base, access, value));
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset);
+    f.iter().setResult(f.atomicBinopHeap(op, addr.base, access, value));
     return true;
 }
 
 static bool
 EmitAtomicsCompareExchange(FunctionCompiler& f)
 {
     LinearMemoryAddress<MDefinition*> addr;
     Scalar::Type viewType;
     MDefinition* oldValue;
     MDefinition* newValue;
     if (!f.iter().readAtomicCompareExchange(&addr, &viewType, &oldValue, &newValue))
         return false;
 
-    MWasmMemoryAccess access(viewType, addr.align);
-
-    MDefinition* base = addr.base;
-    if (!SetHeapAccessOffset(f, addr.offset, &access, &base, IsAtomic::Yes))
-        return false;
-
-    f.iter().setResult(f.atomicCompareExchangeHeap(base, access, oldValue, newValue));
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset);
+    f.iter().setResult(f.atomicCompareExchangeHeap(addr.base, access, oldValue, newValue));
     return true;
 }
 
 static bool
 EmitAtomicsExchange(FunctionCompiler& f)
 {
     LinearMemoryAddress<MDefinition*> addr;
     Scalar::Type viewType;
     MDefinition* value;
     if (!f.iter().readAtomicExchange(&addr, &viewType, &value))
         return false;
 
-    MWasmMemoryAccess access(viewType, addr.align);
-
-    MDefinition* base = addr.base;
-    if (!SetHeapAccessOffset(f, addr.offset, &access, &base, IsAtomic::Yes))
-        return false;
-
-    f.iter().setResult(f.atomicExchangeHeap(base, access, value));
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset);
+    f.iter().setResult(f.atomicExchangeHeap(addr.base, access, value));
     return true;
 }
 
 static bool
 EmitSimdUnary(FunctionCompiler& f, ValType type, SimdOperation simdOp)
 {
     MSimdUnaryArith::Operation op;
     switch (simdOp) {
@@ -2508,23 +2462,18 @@ EmitSimdLoad(FunctionCompiler& f, ValTyp
 
     if (!numElems)
         numElems = defaultNumElems;
 
     LinearMemoryAddress<MDefinition*> addr;
     if (!f.iter().readLoad(resultType, Scalar::byteSize(viewType), &addr))
         return false;
 
-    MWasmMemoryAccess access(viewType, addr.align, numElems);
-
-    MDefinition* base = addr.base;
-    if (!SetHeapAccessOffset(f, addr.offset, &access, &base))
-        return false;
-
-    f.iter().setResult(f.loadSimdHeap(base, access));
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset, numElems);
+    f.iter().setResult(f.loadSimdHeap(addr.base, access));
     return true;
 }
 
 static bool
 EmitSimdStore(FunctionCompiler& f, ValType resultType, unsigned numElems)
 {
     unsigned defaultNumElems;
     Scalar::Type viewType = SimdExprTypeToViewType(resultType, &defaultNumElems);
@@ -2532,23 +2481,18 @@ EmitSimdStore(FunctionCompiler& f, ValTy
     if (!numElems)
         numElems = defaultNumElems;
 
     LinearMemoryAddress<MDefinition*> addr;
     MDefinition* value;
     if (!f.iter().readStore(resultType, Scalar::byteSize(viewType), &addr, &value))
         return false;
 
-    MWasmMemoryAccess access(viewType, addr.align, numElems);
-
-    MDefinition* base = addr.base;
-    if (!SetHeapAccessOffset(f, addr.offset, &access, &base))
-        return false;
-
-    f.storeSimdHeap(base, access, value);
+    MWasmMemoryAccess access(viewType, addr.align, addr.offset, numElems);
+    f.storeSimdHeap(addr.base, access, value);
     return true;
 }
 
 static bool
 EmitSimdSelect(FunctionCompiler& f, ValType simdType)
 {
     MDefinition* trueValue;
     MDefinition* falseValue;
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -51,17 +51,17 @@ struct LinkData : LinkDataCacheablePod
     LinkDataCacheablePod& pod() { return *this; }
     const LinkDataCacheablePod& pod() const { return *this; }
 
     struct InternalLink {
         enum Kind {
             RawPointer,
             CodeLabel,
             InstructionImmediate
-        };	
+        };
         MOZ_INIT_OUTSIDE_CTOR uint32_t patchAtOffset;
         MOZ_INIT_OUTSIDE_CTOR uint32_t targetOffset;
 
         InternalLink() = default;
         explicit InternalLink(Kind kind);
         bool isRawPointerPatch();
     };
     typedef Vector<InternalLink, 0, SystemAllocPolicy> InternalLinkVector;
--- a/js/src/asmjs/WasmSignalHandlers.cpp
+++ b/js/src/asmjs/WasmSignalHandlers.cpp
@@ -645,16 +645,23 @@ EmulateHeapAccess(EMULATOR_CONTEXT* cont
                        "faulting address range");
     MOZ_RELEASE_ASSERT(accessAddress >= instance.memoryBase(),
                        "Access begins outside the asm.js heap");
     MOZ_RELEASE_ASSERT(accessAddress + access.size() <= instance.memoryBase() + MappedSize,
                        "Access extends beyond the asm.js heap guard region");
     MOZ_RELEASE_ASSERT(accessAddress + access.size() > instance.memoryBase() + instance.memoryLength(),
                        "Computed access address is not actually out of bounds");
 
+    // Wasm loads/stores don't wrap offsets at all, so hitting the guard page
+    // means we are out of bounds in any cases.
+    if (!memoryAccess->wrapOffset()) {
+        MOZ_ASSERT(memoryAccess->throwOnOOB());
+        return instance.codeSegment().outOfBoundsCode();
+    }
+
     // The basic sandbox model is that all heap accesses are a heap base
     // register plus an index, and the index is always computed with 32-bit
     // operations, so we know it can only be 4 GiB off of the heap base.
     //
     // However, we wish to support the optimization of folding immediates
     // and scaled indices into addresses, and any address arithmetic we fold
     // gets done at full pointer width, so it doesn't get properly wrapped.
     // We support this by extending MappedSize to the greatest size that could
@@ -728,18 +735,21 @@ EmulateHeapAccess(EMULATOR_CONTEXT* cont
 }
 
 #elif defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_UNALIGNED)
 
 MOZ_COLD static uint8_t*
 EmulateHeapAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
                   const MemoryAccess* memoryAccess, const Instance& instance)
 {
-    // TODO: Implement unaligned accesses.
-    return instance.codeSegment().outOfBoundsCode();
+    // We forbid ARM instruction sets below ARMv7, so that solves unaligned
+    // integer memory accesses. So the only way to land here is because of a
+    // non-default configured kernel or an unaligned floating-point access.
+    // TODO Handle FPU unaligned accesses on ARM (bug 1283121).
+    return instance.codeSegment().unalignedAccessCode();
 }
 
 #endif // defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_UNALIGNED)
 
 #if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS)
 
 MOZ_COLD static bool
 IsHeapAccessAddress(const Instance &instance, uint8_t* faultingAddress)
@@ -1092,26 +1102,38 @@ MachExceptionHandler::install(JSRuntime*
 
   error:
     uninstall();
     return false;
 }
 
 #else  // If not Windows or Mac, assume Unix
 
+enum class Signal {
+    SegFault,
+    BusError
+};
+
 // Be very cautious and default to not handling; we don't want to accidentally
 // silence real crashes from real bugs.
+template<Signal signal>
 static bool
 HandleFault(int signum, siginfo_t* info, void* ctx)
 {
     // The signals we're expecting come from access violations, accessing
     // mprotected memory. If the signal originates anywhere else, don't try
     // to handle it.
-    MOZ_RELEASE_ASSERT(signum == SIGSEGV);
-    if (info->si_code != SEGV_ACCERR)
+    if (signal == Signal::SegFault)
+        MOZ_RELEASE_ASSERT(signum == SIGSEGV);
+    else
+        MOZ_RELEASE_ASSERT(signum == SIGBUS);
+
+    if (signal == Signal::SegFault && info->si_code != SEGV_ACCERR)
+        return false;
+    if (signal == Signal::BusError && info->si_code != BUS_ADRALN)
         return false;
 
     CONTEXT* context = (CONTEXT*)ctx;
     uint8_t** ppc = ContextToPC(context);
     uint8_t* pc = *ppc;
 
     // Don't allow recursive handling of signals, see AutoSetHandlingSegFault.
     JSRuntime* rt = RuntimeForCurrentThread();
@@ -1130,53 +1152,58 @@ HandleFault(int signum, siginfo_t* info,
     uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(info->si_addr);
 
     // This check isn't necessary, but, since we can, check anyway to make
     // sure we aren't covering up a real bug.
     if (!IsHeapAccessAddress(instance, faultingAddress))
         return false;
 
     const MemoryAccess* memoryAccess = instance.lookupMemoryAccess(pc);
-    if (!memoryAccess)
+    if (signal == Signal::SegFault && !memoryAccess)
         return false;
 
     *ppc = EmulateHeapAccess(context, pc, faultingAddress, memoryAccess, instance);
 
     return true;
 }
 
 static struct sigaction sPrevSEGVHandler;
+static struct sigaction sPrevSIGBUSHandler;
 
+template<Signal signal>
 static void
 AsmJSFaultHandler(int signum, siginfo_t* info, void* context)
 {
-    if (HandleFault(signum, info, context))
+    if (HandleFault<signal>(signum, info, context))
         return;
 
+    struct sigaction* previousSignal = signal == Signal::SegFault
+                                       ? &sPrevSEGVHandler
+                                       : &sPrevSIGBUSHandler;
+
     // This signal is not for any asm.js code we expect, so we need to forward
     // the signal to the next handler. If there is no next handler (SIG_IGN or
     // SIG_DFL), then it's time to crash. To do this, we set the signal back to
     // its original disposition and return. This will cause the faulting op to
     // be re-executed which will crash in the normal way. The advantage of
     // doing this to calling _exit() is that we remove ourselves from the crash
     // stack which improves crash reports. If there is a next handler, call it.
     // It will either crash synchronously, fix up the instruction so that
     // execution can continue and return, or trigger a crash by returning the
     // signal to it's original disposition and returning.
     //
     // Note: the order of these tests matter.
-    if (sPrevSEGVHandler.sa_flags & SA_SIGINFO)
-        sPrevSEGVHandler.sa_sigaction(signum, info, context);
-    else if (sPrevSEGVHandler.sa_handler == SIG_DFL || sPrevSEGVHandler.sa_handler == SIG_IGN)
-        sigaction(signum, &sPrevSEGVHandler, nullptr);
+    if (previousSignal->sa_flags & SA_SIGINFO)
+        previousSignal->sa_sigaction(signum, info, context);
+    else if (previousSignal->sa_handler == SIG_DFL || previousSignal->sa_handler == SIG_IGN)
+        sigaction(signum, previousSignal, nullptr);
     else
-        sPrevSEGVHandler.sa_handler(signum);
+        previousSignal->sa_handler(signum);
 }
-#endif
-
+# endif // XP_WIN || XP_DARWIN || assume unix
 #endif // defined(ASMJS_MAY_USE_SIGNAL_HANDLERS)
 
 static void
 RedirectIonBackedgesToInterruptCheck(JSRuntime* rt)
 {
     if (jit::JitRuntime* jitRuntime = rt->jitRuntime()) {
         // If the backedge list is being mutated, the pc must be in C++ code and
         // thus not in a JIT iloop. We assume that the interrupt flag will be
@@ -1298,22 +1325,32 @@ wasm::EnsureSignalHandlersInstalled(JSRu
         return false;
 # elif defined(XP_DARWIN)
     // OSX handles seg faults via the Mach exception handler above, so don't
     // install AsmJSFaultHandler.
 # else
     // SA_NODEFER allows us to reenter the signal handler if we crash while
     // handling the signal, and fall through to the Breakpad handler by testing
     // handlingSegFault.
+
+#  if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB)
     struct sigaction faultHandler;
     faultHandler.sa_flags = SA_SIGINFO | SA_NODEFER;
-    faultHandler.sa_sigaction = &AsmJSFaultHandler;
+    faultHandler.sa_sigaction = &AsmJSFaultHandler<Signal::SegFault>;
     sigemptyset(&faultHandler.sa_mask);
     if (sigaction(SIGSEGV, &faultHandler, &sPrevSEGVHandler))
         MOZ_CRASH("unable to install segv handler");
+#  elif defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_UNALIGNED)
+    struct sigaction busHandler;
+    busHandler.sa_flags = SA_SIGINFO | SA_NODEFER;
+    busHandler.sa_sigaction = &AsmJSFaultHandler<Signal::BusError>;
+    sigemptyset(&busHandler.sa_mask);
+    if (sigaction(SIGBUS, &busHandler, &sPrevSIGBUSHandler))
+        MOZ_CRASH("unable to install sigbus handler");
+#  endif
 # endif
 #endif // defined(ASMJS_MAY_USE_SIGNAL_HANDLERS)
 
     sResult = true;
     return true;
 }
 
 // JSRuntime::requestInterrupt sets interrupt_ (which is checked frequently by
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1037,18 +1037,20 @@ js::GCMarker::eagerlyMarkChildren(Shape*
 {
     MOZ_ASSERT(shape->isMarked(this->markColor()));
     do {
         // Special case: if a base shape has a shape table then all its pointers
         // must point to this shape or an anscestor.  Since these pointers will
         // be traced by this loop they do not need to be traced here as well.
         BaseShape* base = shape->base();
         CheckTraversedEdge(shape, base);
-        if (mark(base))
+        if (mark(base)) {
+            MOZ_ASSERT(base->canSkipMarkingShapeTable(shape));
             base->traceChildrenSkipShapeTable(this);
+        }
 
         traverseEdge(shape, shape->propidRef().get());
 
         // When triggered between slices on belhalf of a barrier, these
         // objects may reside in the nursery, so require an extra check.
         // FIXME: Bug 1157967 - remove the isTenured checks.
         if (shape->hasGetterObject() && shape->getterObject()->isTenured())
             traverseEdge(shape, shape->getterObject());
--- a/js/src/jit-test/tests/wasm/basic-memory.js
+++ b/js/src/jit-test/tests/wasm/basic-memory.js
@@ -1,79 +1,70 @@
 // |jit-test| test-also-wasm-baseline
 load(libdir + "wasm.js");
 
+function loadModule(type, ext, offset, align) {
+    return wasmEvalText(
+    `(module
+       (memory 1
+         (segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")
+         (segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
+       )
+       (func (param i32) (result ${type})
+         (${type}.load${ext}
+          offset=${offset}
+          ${align != 0 ? 'align=' + align : ''}
+          (get_local 0)
+         )
+       ) (export "" 0))`
+    );
+}
+
+function storeModule(type, ext, offset, align) {
+    var load_ext = ext === '' ? '' : ext + '_s';
+    return wasmEvalText(
+    `(module
+       (memory 1
+         (segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")
+         (segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
+       )
+       (func (param i32) (param ${type}) (result ${type})
+         (${type}.store${ext}
+          offset=${offset}
+          ${align != 0 ? 'align=' + align : ''}
+          (get_local 0)
+          (get_local 1)
+         )
+       ) (export "store" 0)
+       (func (param i32) (result ${type})
+        (${type}.load${load_ext}
+         offset=${offset}
+         ${align != 0 ? 'align=' + align : ''}
+         (get_local 0)
+        )
+       ) (export "load" 1))`
+    );
+}
+
 function testLoad(type, ext, base, offset, align, expect) {
-  assertEq(wasmEvalText(
-    '(module' +
-    '  (memory 1' +
-    '    (segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")' +
-    '    (segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")' +
-    '  )' +
-    '  (func (param i32) (result ' + type + ')' +
-    '    (' + type + '.load' + ext +
-    '     offset=' + offset +
-    '     ' + (align != 0 ? 'align=' + align : '') +
-    '     (get_local 0)' +
-    '    )' +
-    '  ) (export "" 0))'
-  )(base), expect);
+    assertEq(loadModule(type, ext, offset, align)(base), expect);
+}
+
+function testLoadOOB(type, ext, base, offset, align) {
+    assertErrorMessage(() => loadModule(type, ext, offset, align)(base), Error, /invalid or out-of-range index/);
 }
 
 function testStore(type, ext, base, offset, align, value) {
-  assertEq(wasmEvalText(
-    '(module' +
-    '  (memory 1' +
-    '    (segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")' +
-    '    (segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")' +
-    '  )' +
-    '  (func (param i32) (param ' + type + ') (result ' + type + ')' +
-    '    (' + type + '.store' + ext +
-    '     offset=' + offset +
-    '     ' + (align != 0 ? 'align=' + align : '') +
-    '     (get_local 0)' +
-    '     (get_local 1)' +
-    '    )' +
-    '  ) (export "" 0))'
-  )(base, value), value);
+    let module = storeModule(type, ext, offset, align);
+    assertEq(module.store(base, value), value);
+    assertEq(module.load(base), value);
 }
 
-function testLoadError(type, ext, base, offset, align, errorMsg) {
-  assertErrorMessage(() => wasmEvalText(
-    '(module' +
-    '  (memory 1' +
-    '    (segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")' +
-    '    (segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")' +
-    '  )' +
-    '  (func (param i32) (result ' + type + ')' +
-    '    (' + type + '.load' + ext +
-    '     offset=' + offset +
-    '     ' + (align != 0 ? 'align=' + align : '') +
-    '     (get_local 0)' +
-    '    )' +
-    '  ) (export "" 0))'
-  ), Error, errorMsg);
-}
-
-function testStoreError(type, ext, base, offset, align, errorMsg) {
-  assertErrorMessage(() => wasmEvalText(
-    '(module' +
-    '  (memory 1' +
-    '    (segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")' +
-    '    (segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")' +
-    '  )' +
-    '  (func (param i32) (param ' + type + ') (result ' + type + ')' +
-    '    (' + type + '.store' + ext +
-    '     offset=' + offset +
-    '     ' + (align != 0 ? 'align=' + align : '') +
-    '     (get_local 0)' +
-    '     (get_local 1)' +
-    '    )' +
-    '  ) (export "" 0))'
-  ), Error, errorMsg);
+function testStoreOOB(type, ext, base, offset, align, value) {
+    assertErrorMessage(() => storeModule(type, ext, offset, align).store(base, value), Error, /invalid or out-of-range index/);
 }
 
 testLoad('i32', '', 0, 0, 0, 0x03020100);
 testLoad('i32', '', 1, 0, 0, 0x04030201);
 testLoad('i32', '', 0, 4, 0, 0x07060504);
 testLoad('i32', '', 1, 3, 4, 0x07060504);
 //testLoad('i64', '', 0, 0, 0, 0x0001020304050607); // TODO: i64 NYI
 //testLoad('i64', '', 1, 0, 0, 0x0102030405060708); // TODO: i64 NYI
@@ -138,35 +129,125 @@ testStore('f32', '', 1, 3, 4, 0.01234566
 testStore('f64', '', 0, 0, 0, 0.89012345);
 testStore('f64', '', 1, 0, 0, 0.89012345);
 testStore('f64', '', 0, 8, 0, 0.89012345);
 testStore('f64', '', 1, 7, 4, 0.89012345);
 
 testStore('i32', '8', 0, 0, 0, 0x23);
 testStore('i32', '16', 0, 0, 0, 0x2345);
 
-testLoadError('i32', '8_s', 0, 0, 2, /greater than natural alignment/);
-testLoadError('i32', '8_u', 0, 0, 2, /greater than natural alignment/);
-testLoadError('i32', '16_s', 0, 0, 4, /greater than natural alignment/);
-testLoadError('i32', '16_u', 0, 0, 4, /greater than natural alignment/);
-testLoadError('i32', '', 0, 0, 8, /greater than natural alignment/);
-testLoadError('f32', '', 0, 0, 8, /greater than natural alignment/);
-testLoadError('f64', '', 0, 0, 16, /greater than natural alignment/);
-testStoreError('i32', '8', 0, 0, 2, /greater than natural alignment/);
-testStoreError('i32', '16', 0, 0, 4, /greater than natural alignment/);
-testStoreError('i32', '', 0, 0, 8, /greater than natural alignment/);
-testStoreError('f32', '', 0, 0, 8, /greater than natural alignment/);
-testStoreError('f64', '', 0, 0, 16, /greater than natural alignment/);
+assertErrorMessage(() => wasmEvalText('(module (memory 2 1))'), TypeError, /maximum memory size less than initial memory size/);
+
+// Test bounds checks and edge cases.
+const align = 0;
+for (let offset of [0, 1, 2, 3, 4, 8, 16, 41, 0xfff8]) {
+    // Accesses of 1 byte.
+    let lastValidIndex = 0x10000 - 1 - offset;
+
+    testLoad('i32', '8_s', lastValidIndex, offset, align, 0);
+    testLoadOOB('i32', '8_s', lastValidIndex + 1, offset, align);
+
+    testLoad('i32', '8_u', lastValidIndex, offset, align, 0);
+    testLoadOOB('i32', '8_u', lastValidIndex + 1, offset, align);
+
+    testStore('i32', '8', lastValidIndex, offset, align, -42);
+    testStoreOOB('i32', '8', lastValidIndex + 1, offset, align, -42);
+
+    // Accesses of 2 bytes.
+    lastValidIndex = 0x10000 - 2 - offset;
+
+    testLoad('i32', '16_s', lastValidIndex, offset, align, 0);
+    testLoadOOB('i32', '16_s', lastValidIndex + 1, offset, align);
+
+    testLoad('i32', '16_u', lastValidIndex, offset, align, 0);
+    testLoadOOB('i32', '16_u', lastValidIndex + 1, offset, align);
+
+    testStore('i32', '16', lastValidIndex, offset, align, -32768);
+    testStoreOOB('i32', '16', lastValidIndex + 1, offset, align, -32768);
+
+    // Accesses of 4 bytes.
+    lastValidIndex = 0x10000 - 4 - offset;
 
-assertErrorMessage(() => wasmEvalText('(module (memory 2 1))'), TypeError, /maximum memory size less than initial memory size/);
+    testLoad('i32', '', lastValidIndex, offset, align, 0);
+    testLoadOOB('i32', '', lastValidIndex + 1, offset, align);
+
+    testLoad('f32', '', lastValidIndex, offset, align, 0);
+    testLoadOOB('f32', '', lastValidIndex + 1, offset, align);
+
+    testStore('i32', '', lastValidIndex, offset, align, 1337);
+    testStoreOOB('i32', '', lastValidIndex + 1, offset, align, 1337);
+
+    testStore('f32', '', lastValidIndex, offset, align, Math.fround(13.37));
+    testStoreOOB('f32', '', lastValidIndex + 1, offset, align, Math.fround(13.37));
+
+    // Accesses of 8 bytes.
+    lastValidIndex = 0x10000 - 8 - offset;
+
+    testLoad('f64', '', lastValidIndex, offset, align, 0);
+    testLoadOOB('f64', '', lastValidIndex + 1, offset, align);
+
+    testStore('f64', '', lastValidIndex, offset, align, 1.23456789);
+    testStoreOOB('f64', '', lastValidIndex + 1, offset, align, 1.23456789);
+}
+
+// Ensure wrapping doesn't apply.
+offset = 0x7fffffff; // maximum allowed offset that doesn't always throw.
+for (let index of [0, 1, 2, 3, 0x7fffffff, 0x80000000, 0x80000001]) {
+    testLoadOOB('i32', '8_s', index, offset, align);
+    testLoadOOB('i32', '16_s', index, offset, align);
+    testLoadOOB('i32', '', index, offset, align);
+    testLoadOOB('f32', '', index, offset, align);
+    testLoadOOB('f64', '', index, offset, align);
+}
 
 assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (f64.store offset=0 (i32.const 0) (i32.const 0))))'), TypeError, mismatchError("i32", "f64"));
 assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (f64.store offset=0 (i32.const 0) (f32.const 0))))'), TypeError, mismatchError("f32", "f64"));
 
 assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (f32.store offset=0 (i32.const 0) (i32.const 0))))'), TypeError, mismatchError("i32", "f32"));
 assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (f32.store offset=0 (i32.const 0) (f64.const 0))))'), TypeError, mismatchError("f64", "f32"));
 
 assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (i32.store offset=0 (i32.const 0) (f32.const 0))))'), TypeError, mismatchError("f32", "i32"));
 assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (i32.store offset=0 (i32.const 0) (f64.const 0))))'), TypeError, mismatchError("f64", "i32"));
 
 wasmEvalText('(module (memory 0 65535))')
 assertErrorMessage(() => wasmEvalText('(module (memory 0 65536))'), TypeError, /maximum memory size too big/);
 
+// Test high charge of registers
+function testRegisters() {
+    assertEq(wasmEvalText(
+    `(module
+       (memory 1
+         (segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")
+         (segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
+       )
+       (func (param i32) (local i32 i32 i32 i32 f32 f64) (result i32)
+         (set_local 1 (i32.load8_s offset=4 (get_local 0)))
+         (set_local 2 (i32.load16_s (get_local 1)))
+         (i32.store8 offset=4 (get_local 0) (get_local 1))
+         (set_local 3 (i32.load16_u (get_local 2)))
+         (i32.store16 (get_local 1) (get_local 2))
+         (set_local 4 (i32.load (get_local 2)))
+         (i32.store (get_local 1) (get_local 2))
+         (set_local 5 (f32.load (get_local 4)))
+         (f32.store (get_local 4) (get_local 5))
+         (set_local 6 (f64.load (get_local 4)))
+         (f64.store (get_local 4) (get_local 6))
+         (i32.add
+          (i32.add
+           (get_local 0)
+           (get_local 1)
+          )
+          (i32.add
+           (i32.add
+            (get_local 2)
+            (get_local 3)
+           )
+           (i32.add
+            (get_local 4)
+            (i32.reinterpret/f32 (get_local 5))
+           )
+          )
+         )
+       ) (export "" 0))`
+    )(1), 50464523);
+}
+
+testRegisters();
--- a/js/src/jit-test/tests/wasm/spec/address.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/address.wast.js
@@ -1,4 +1,2 @@
 // |jit-test| test-also-wasm-baseline
-// TODO: wrapping offsets
-quit();
 var importedArgs = ['address.wast']; load(scriptdir + '../spec.js');
--- a/js/src/jit-test/tests/wasm/spec/memory.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/memory.wast.js
@@ -1,4 +1,4 @@
 // |jit-test| test-also-wasm-baseline
-// TODO unaligned memory accesses
+// TODO i64 loads
 quit();
 var importedArgs = ['memory.wast']; load(scriptdir + '../spec.js');
--- a/js/src/jit-test/tests/wasm/spec/traps.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/traps.wast.js
@@ -1,4 +1,4 @@
 // |jit-test| test-also-wasm-baseline
-// TODO trap on OOB
+// TODO dce'd effectful instructions
 quit();
 var importedArgs = ['traps.wast']; load(scriptdir + '../spec.js');
--- a/js/src/jit/AliasAnalysisShared.cpp
+++ b/js/src/jit/AliasAnalysisShared.cpp
@@ -137,16 +137,18 @@ GetObject(const MDefinition* ins)
       case MDefinition::Op_SetArgumentsObjectArg:
       case MDefinition::Op_GetFrameArgument:
       case MDefinition::Op_SetFrameArgument:
       case MDefinition::Op_CompareExchangeTypedArrayElement:
       case MDefinition::Op_AtomicExchangeTypedArrayElement:
       case MDefinition::Op_AtomicTypedArrayElementBinop:
       case MDefinition::Op_AsmJSLoadHeap:
       case MDefinition::Op_AsmJSStoreHeap:
+      case MDefinition::Op_WasmLoad:
+      case MDefinition::Op_WasmStore:
       case MDefinition::Op_AsmJSCompareExchangeHeap:
       case MDefinition::Op_AsmJSAtomicBinopHeap:
       case MDefinition::Op_AsmJSLoadGlobalVar:
       case MDefinition::Op_AsmJSStoreGlobalVar:
       case MDefinition::Op_ArrayJoin:
         return nullptr;
       default:
 #ifdef DEBUG
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -12954,20 +12954,21 @@ class MWasmMemoryAccess
     uint32_t align_;
     Scalar::Type accessType_ : 8;
     bool needsBoundsCheck_;
     unsigned numSimdElems_;
     MemoryBarrierBits barrierBefore_;
     MemoryBarrierBits barrierAfter_;
 
   public:
-    explicit MWasmMemoryAccess(Scalar::Type accessType, uint32_t align, unsigned numSimdElems = 0,
+    explicit MWasmMemoryAccess(Scalar::Type accessType, uint32_t align, uint32_t offset,
+                               unsigned numSimdElems = 0,
                                MemoryBarrierBits barrierBefore = MembarNobits,
                                MemoryBarrierBits barrierAfter = MembarNobits)
-      : offset_(0),
+      : offset_(offset),
         align_(align),
         accessType_(accessType),
         needsBoundsCheck_(true),
         numSimdElems_(numSimdElems),
         barrierBefore_(barrierBefore),
         barrierAfter_(barrierAfter)
     {
         MOZ_ASSERT(numSimdElems <= ScalarTypeToLength(accessType));
@@ -12979,23 +12980,104 @@ class MWasmMemoryAccess
     uint32_t align() const { return align_; }
     Scalar::Type accessType() const { return accessType_; }
     unsigned byteSize() const {
         return Scalar::isSimdType(accessType())
                ? Scalar::scalarByteSize(accessType()) * numSimdElems()
                : TypedArrayElemSize(accessType());
     }
     bool needsBoundsCheck() const { return needsBoundsCheck_; }
-    void removeBoundsCheck() { needsBoundsCheck_ = false; }
     unsigned numSimdElems() const { MOZ_ASSERT(Scalar::isSimdType(accessType_)); return numSimdElems_; }
-    void setOffset(uint32_t o) { offset_ = o; }
-    void setAlign(uint32_t a) { MOZ_ASSERT(mozilla::IsPowerOfTwo(a)); align_ = a; }
     MemoryBarrierBits barrierBefore() const { return barrierBefore_; }
     MemoryBarrierBits barrierAfter() const { return barrierAfter_; }
     bool isAtomicAccess() const { return (barrierBefore_|barrierAfter_) != MembarNobits; }
+
+    void removeBoundsCheck() { needsBoundsCheck_ = false; }
+    void setOffset(uint32_t o) { offset_ = o; }
+};
+
+class MWasmBoundsCheck
+  : public MUnaryInstruction,
+    public MWasmMemoryAccess,
+    public NoTypePolicy::Data
+{
+    explicit MWasmBoundsCheck(MDefinition* index, const MWasmMemoryAccess& access)
+      : MUnaryInstruction(index),
+        MWasmMemoryAccess(access)
+    {
+        setMovable();
+        setGuard(); // Effectful: throws for OOB.
+    }
+
+  public:
+    INSTRUCTION_HEADER(WasmBoundsCheck)
+    TRIVIAL_NEW_WRAPPERS
+
+    bool congruentTo(const MDefinition* ins) const override {
+        if (!congruentIfOperandsEqual(ins))
+            return false;
+        const MWasmBoundsCheck* other = ins->toWasmBoundsCheck();
+        return accessType() == other->accessType() &&
+               offset() == other->offset() &&
+               align() == other->align();
+    }
+
+    AliasSet getAliasSet() const override {
+        return AliasSet::None();
+    }
+};
+
+class MWasmLoad
+  : public MUnaryInstruction,
+    public MWasmMemoryAccess,
+    public NoTypePolicy::Data
+{
+    MWasmLoad(MDefinition* base, const MWasmMemoryAccess& access)
+      : MUnaryInstruction(base),
+        MWasmMemoryAccess(access)
+    {
+        setGuard();
+        MOZ_ASSERT(access.accessType() != Scalar::Uint8Clamped, "unexpected load heap in wasm");
+        setResultType(ScalarTypeToMIRType(access.accessType()));
+    }
+
+  public:
+    INSTRUCTION_HEADER(WasmLoad)
+    TRIVIAL_NEW_WRAPPERS
+    NAMED_OPERANDS((0, base))
+
+    AliasSet getAliasSet() const override {
+        // When a barrier is needed, make the instruction effectful by giving
+        // it a "store" effect.
+        if (isAtomicAccess())
+            return AliasSet::Store(AliasSet::AsmJSHeap);
+        return AliasSet::Load(AliasSet::AsmJSHeap);
+    }
+};
+
+class MWasmStore
+  : public MBinaryInstruction,
+    public MWasmMemoryAccess,
+    public NoTypePolicy::Data
+{
+    MWasmStore(MDefinition* base, const MWasmMemoryAccess& access, MDefinition* value)
+      : MBinaryInstruction(base, value),
+        MWasmMemoryAccess(access)
+    {
+        setGuard();
+    }
+
+  public:
+    INSTRUCTION_HEADER(WasmStore)
+    TRIVIAL_NEW_WRAPPERS
+    NAMED_OPERANDS((0, base), (1, value))
+
+    AliasSet getAliasSet() const override {
+        return AliasSet::Store(AliasSet::AsmJSHeap);
+    }
 };
 
 class MAsmJSLoadHeap
   : public MUnaryInstruction,
     public MWasmMemoryAccess,
     public NoTypePolicy::Data
 {
     MAsmJSLoadHeap(MDefinition* base, const MWasmMemoryAccess& access)
--- a/js/src/jit/MIRGenerator.h
+++ b/js/src/jit/MIRGenerator.h
@@ -219,17 +219,16 @@ class MIRGenerator
     AsmJSPerfSpewer& perfSpewer() { return asmJSPerfSpewer_; }
 #endif
 
   public:
     const JitCompileOptions options;
 
     bool needsBoundsCheckBranch(const MWasmMemoryAccess* access) const;
     size_t foldableOffsetRange(const MWasmMemoryAccess* access) const;
-    size_t foldableOffsetRange(bool accessNeedsBoundsCheck, bool atomic) const;
 
   private:
     GraphSpewer gs_;
 
   public:
     GraphSpewer& graphSpewer() {
         return gs_;
     }
--- a/js/src/jit/MIRGraph.cpp
+++ b/js/src/jit/MIRGraph.cpp
@@ -121,46 +121,40 @@ MIRGenerator::needsBoundsCheckBranch(con
         return false;
 #endif
     return access->needsBoundsCheck();
 }
 
 size_t
 MIRGenerator::foldableOffsetRange(const MWasmMemoryAccess* access) const
 {
-    return foldableOffsetRange(access->needsBoundsCheck(), access->isAtomicAccess());
-}
-
-size_t
-MIRGenerator::foldableOffsetRange(bool accessNeedsBoundsCheck, bool atomic) const
-{
     // This determines whether it's ok to fold up to WasmImmediateRange
     // offsets, instead of just WasmCheckedImmediateRange.
 
     static_assert(WasmCheckedImmediateRange <= WasmImmediateRange,
                   "WasmImmediateRange should be the size of an unconstrained "
                   "address immediate");
 
 #ifdef ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB
     static_assert(wasm::Uint32Range + WasmImmediateRange + sizeof(wasm::Val) < wasm::MappedSize,
                   "When using signal handlers for bounds checking, a uint32 is added to the base "
                   "address followed by an immediate in the range [0, WasmImmediateRange). An "
                   "unaligned access (whose size is conservatively approximated by wasm::Val) may "
                   "spill over, so ensure a space at the end.");
 
     // Signal-handling can be dynamically disabled by OS bugs or flags.
     // Bug 1254935: Atomic accesses can't be handled with signal handlers yet.
-    if (usesSignalHandlersForAsmJSOOB_ && !atomic)
+    if (usesSignalHandlersForAsmJSOOB_ && !access->isAtomicAccess())
         return WasmImmediateRange;
 #endif
 
     // On 32-bit platforms, if we've proven the access is in bounds after
     // 32-bit wrapping, we can fold full offsets because they're added with
     // 32-bit arithmetic.
-    if (sizeof(intptr_t) == sizeof(int32_t) && !accessNeedsBoundsCheck)
+    if (sizeof(intptr_t) == sizeof(int32_t) && !access->needsBoundsCheck())
         return WasmImmediateRange;
 
     // Otherwise, only allow the checked size. This is always less than the
     // minimum heap length, and allows explicit bounds checks to fold in the
     // offset without overflow.
     return WasmCheckedImmediateRange;
 }
 
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -263,16 +263,19 @@ namespace jit {
     _(GetDOMProperty)                                                       \
     _(GetDOMMember)                                                         \
     _(SetDOMProperty)                                                       \
     _(IsConstructor)                                                        \
     _(IsCallable)                                                           \
     _(IsObject)                                                             \
     _(HasClass)                                                             \
     _(CopySign)                                                             \
+    _(WasmBoundsCheck)                                                      \
+    _(WasmLoad)                                                             \
+    _(WasmStore)                                                            \
     _(WasmTruncateToInt32)                                                  \
     _(AsmJSNeg)                                                             \
     _(AsmJSUnsignedToDouble)                                                \
     _(AsmJSUnsignedToFloat32)                                               \
     _(AsmJSLoadHeap)                                                        \
     _(AsmJSStoreHeap)                                                       \
     _(AsmJSLoadGlobalVar)                                                   \
     _(AsmJSStoreGlobalVar)                                                  \
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -1201,16 +1201,17 @@ class Assembler : public AssemblerShared
         AboveOrEqual = CS,
         Below = CC,
         BelowOrEqual = LS,
         GreaterThan = GT,
         GreaterThanOrEqual = GE,
         LessThan = LT,
         LessThanOrEqual = LE,
         Overflow = VS,
+        CarrySet = CS,
         Signed = MI,
         NotSigned = PL,
         Zero = EQ,
         NonZero = NE,
         Always  = AL,
 
         VFP_NotEqualOrUnordered = NE,
         VFP_Equal = EQ,
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -2184,16 +2184,129 @@ CodeGeneratorARM::visitAsmJSLoadHeap(LAs
             masm.ma_loadHeapAsmJS(ptrReg, size, isSigned, mir->needsBoundsCheck(),
                                   mir->isAtomicAccess(), ToRegister(ins->output()));
     }
 
     memoryBarrier(mir->barrierAfter());
 }
 
 void
+CodeGeneratorARM::visitWasmBoundsCheck(LWasmBoundsCheck* ins)
+{
+    MWasmBoundsCheck* mir = ins->mir();
+
+    uint32_t offset = mir->offset();
+    if (offset > INT32_MAX) {
+        masm.as_b(wasm::JumpTarget::OutOfBounds);
+        return;
+    }
+
+    // No guarantee that heapBase + endOffset can be properly encoded in
+    // the cmp immediate in ma_BoundsCheck, so use an explicit add instead.
+    uint32_t endOffset = mir->endOffset();
+
+    Register ptr = ToRegister(ins->ptr());
+
+    ScratchRegisterScope ptrPlusOffset(masm);
+    masm.move32(Imm32(endOffset), ptrPlusOffset);
+    masm.ma_add(ptr, ptrPlusOffset, SetCC);
+
+    // Detect unsigned overflow by checking the carry bit.
+    masm.as_b(wasm::JumpTarget::OutOfBounds, Assembler::CarrySet);
+
+    uint32_t cmpOffset = masm.ma_BoundsCheck(ptrPlusOffset).getOffset();
+    masm.append(wasm::BoundsCheck(cmpOffset));
+    masm.as_b(wasm::JumpTarget::OutOfBounds, Assembler::Above);
+}
+
+void
+CodeGeneratorARM::visitWasmLoad(LWasmLoad* lir)
+{
+    const MWasmLoad* mir = lir->mir();
+
+    MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
+
+    uint32_t offset = mir->offset();
+    if (offset > INT32_MAX) {
+        // This is unreachable because of bounds checks.
+        masm.breakpoint();
+        return;
+    }
+
+    Register ptr = ToRegister(lir->ptr());
+    AnyRegister output = ToAnyRegister(lir->output());
+
+    // Maybe add the offset.
+    if (offset) {
+        Register ptrPlusOffset = ToRegister(lir->ptrCopy());
+        masm.ma_add(Imm32(offset), ptrPlusOffset);
+        ptr = ptrPlusOffset;
+    } else {
+        MOZ_ASSERT(lir->ptrCopy()->isBogusTemp());
+    }
+
+    Scalar::Type type = mir->accessType();
+    bool isSigned = type == Scalar::Int8 || type == Scalar::Int16 || type == Scalar::Int32;
+    bool isFloat = output.isFloat();
+
+    unsigned byteSize = mir->byteSize();
+
+    if (isFloat) {
+        MOZ_ASSERT((byteSize == 4) == output.fpu().isSingle());
+        ScratchRegisterScope scratch(masm);
+        masm.ma_add(HeapReg, ptr, scratch);
+        masm.ma_vldr(Address(scratch, 0), output.fpu());
+    } else {
+        masm.ma_dataTransferN(IsLoad, byteSize * 8, isSigned, HeapReg, ptr, output.gpr());
+    }
+}
+
+void
+CodeGeneratorARM::visitWasmStore(LWasmStore* lir)
+{
+    const MWasmStore* mir = lir->mir();
+
+    MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
+
+    uint32_t offset = mir->offset();
+    if (offset > INT32_MAX) {
+        // This is unreachable because of bounds checks.
+        masm.breakpoint();
+        return;
+    }
+
+    Register ptr = ToRegister(lir->ptr());
+
+    // Maybe add the offset.
+    if (offset) {
+        Register ptrPlusOffset = ToRegister(lir->ptrCopy());
+        masm.ma_add(Imm32(offset), ptrPlusOffset);
+        ptr = ptrPlusOffset;
+    } else {
+        MOZ_ASSERT(lir->ptrCopy()->isBogusTemp());
+    }
+
+    AnyRegister value = ToAnyRegister(lir->value());
+    unsigned byteSize = mir->byteSize();
+    Scalar::Type type = mir->accessType();
+
+    if (value.isFloat()) {
+        FloatRegister val = value.fpu();
+        MOZ_ASSERT((byteSize == 4) == val.isSingle());
+        ScratchRegisterScope scratch(masm);
+        masm.ma_add(HeapReg, ptr, scratch);
+        masm.ma_vstr(val, Address(scratch, 0));
+    } else {
+        bool isSigned = type == Scalar::Uint32 || type == Scalar::Int32; // see AsmJSStoreHeap;
+        Register val = value.gpr();
+        masm.ma_dataTransferN(IsStore, 8 * byteSize /* bits */, isSigned, HeapReg, ptr, val);
+    }
+}
+
+void
 CodeGeneratorARM::visitAsmJSStoreHeap(LAsmJSStoreHeap* ins)
 {
     const MAsmJSStoreHeap* mir = ins->mir();
     bool isSigned;
     int size;
     bool isFloat = false;
     switch (mir->accessType()) {
       case Scalar::Int8:
--- a/js/src/jit/arm/CodeGenerator-arm.h
+++ b/js/src/jit/arm/CodeGenerator-arm.h
@@ -198,16 +198,19 @@ class CodeGeneratorARM : public CodeGene
     void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins);
     void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir);
     void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir);
     void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir);
     void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir);
     void visitAsmSelect(LAsmSelect* ins);
     void visitAsmReinterpret(LAsmReinterpret* ins);
     void visitAsmJSCall(LAsmJSCall* ins);
+    void visitWasmBoundsCheck(LWasmBoundsCheck* ins);
+    void visitWasmLoad(LWasmLoad* ins);
+    void visitWasmStore(LWasmStore* ins);
     void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins);
     void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins);
     void visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins);
     void visitAsmJSCompareExchangeCallout(LAsmJSCompareExchangeCallout* ins);
     void visitAsmJSAtomicExchangeHeap(LAsmJSAtomicExchangeHeap* ins);
     void visitAsmJSAtomicExchangeCallout(LAsmJSAtomicExchangeCallout* ins);
     void visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap* ins);
     void visitAsmJSAtomicBinopHeapForEffect(LAsmJSAtomicBinopHeapForEffect* ins);
--- a/js/src/jit/arm/Lowering-arm.cpp
+++ b/js/src/jit/arm/Lowering-arm.cpp
@@ -493,16 +493,58 @@ void
 LIRGeneratorARM::visitAsmJSUnsignedToFloat32(MAsmJSUnsignedToFloat32* ins)
 {
     MOZ_ASSERT(ins->input()->type() == MIRType::Int32);
     LAsmJSUInt32ToFloat32* lir = new(alloc()) LAsmJSUInt32ToFloat32(useRegisterAtStart(ins->input()));
     define(lir, ins);
 }
 
 void
+LIRGeneratorARM::visitWasmBoundsCheck(MWasmBoundsCheck* ins)
+{
+    MDefinition* input = ins->input();
+    MOZ_ASSERT(input->type() == MIRType::Int32);
+
+    LAllocation baseAlloc = useRegisterAtStart(input);
+    auto* lir = new(alloc()) LWasmBoundsCheck(baseAlloc);
+    add(lir, ins);
+}
+
+void
+LIRGeneratorARM::visitWasmLoad(MWasmLoad* ins)
+{
+    MDefinition* base = ins->base();
+    MOZ_ASSERT(base->type() == MIRType::Int32);
+
+    LAllocation baseAlloc = useRegisterAtStart(base);
+    auto* lir = new(alloc()) LWasmLoad(baseAlloc);
+
+    if (ins->offset())
+        lir->setTemp(0, tempCopy(base, 0));
+
+    define(lir, ins);
+}
+
+void
+LIRGeneratorARM::visitWasmStore(MWasmStore* ins)
+{
+    MDefinition* base = ins->base();
+    MOZ_ASSERT(base->type() == MIRType::Int32);
+
+    LAllocation baseAlloc = useRegisterAtStart(base);
+    LAllocation valueAlloc = useRegisterAtStart(ins->value());
+    auto* lir = new(alloc()) LWasmStore(baseAlloc, valueAlloc);
+
+    if (ins->offset())
+        lir->setTemp(0, tempCopy(base, 0));
+
+    add(lir, ins);
+}
+
+void
 LIRGeneratorARM::visitAsmJSLoadHeap(MAsmJSLoadHeap* ins)
 {
     MOZ_ASSERT(ins->offset() == 0);
 
     MDefinition* base = ins->base();
     MOZ_ASSERT(base->type() == MIRType::Int32);
     LAllocation baseAlloc;
 
--- a/js/src/jit/arm/Lowering-arm.h
+++ b/js/src/jit/arm/Lowering-arm.h
@@ -92,16 +92,19 @@ class LIRGeneratorARM : public LIRGenera
     void visitUnbox(MUnbox* unbox);
     void visitReturn(MReturn* ret);
     void lowerPhi(MPhi* phi);
     void visitGuardShape(MGuardShape* ins);
     void visitGuardObjectGroup(MGuardObjectGroup* ins);
     void visitAsmSelect(MAsmSelect* ins);
     void visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble* ins);
     void visitAsmJSUnsignedToFloat32(MAsmJSUnsignedToFloat32* ins);
+    void visitWasmBoundsCheck(MWasmBoundsCheck* ins);
+    void visitWasmLoad(MWasmLoad* ins);
+    void visitWasmStore(MWasmStore* ins);
     void visitAsmJSLoadHeap(MAsmJSLoadHeap* ins);
     void visitAsmJSStoreHeap(MAsmJSStoreHeap* ins);
     void visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr* ins);
     void visitAsmJSCompareExchangeHeap(MAsmJSCompareExchangeHeap* ins);
     void visitAsmJSAtomicExchangeHeap(MAsmJSAtomicExchangeHeap* ins);
     void visitAsmJSAtomicBinopHeap(MAsmJSAtomicBinopHeap* ins);
     void visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic* ins);
     void visitCompareExchangeTypedArrayElement(MCompareExchangeTypedArrayElement* ins);
--- a/js/src/jit/arm64/Lowering-arm64.cpp
+++ b/js/src/jit/arm64/Lowering-arm64.cpp
@@ -320,16 +320,34 @@ LIRGeneratorARM64::visitRandom(MRandom* 
 
 void
 LIRGeneratorARM64::visitWasmTruncateToInt64(MWasmTruncateToInt64* ins)
 {
     MOZ_CRASH("NY");
 }
 
 void
+LIRGeneratorARM64::visitWasmBoundsCheck(MWasmBoundsCheck* ins)
+{
+    MOZ_CRASH("NY");
+}
+
+void
+LIRGeneratorARM64::visitWasmLoad(MWasmLoad* ins)
+{
+    MOZ_CRASH("NY");
+}
+
+void
+LIRGeneratorARM64::visitWasmStore(MWasmStore* ins)
+{
+    MOZ_CRASH("NY");
+}
+
+void
 LIRGeneratorARM64::visitInt64ToFloatingPoint(MInt64ToFloatingPoint* ins)
 {
     MOZ_CRASH("NY");
 }
 
 void
 LIRGeneratorARM64::visitCopySign(MCopySign* ins)
 {
--- a/js/src/jit/arm64/Lowering-arm64.h
+++ b/js/src/jit/arm64/Lowering-arm64.h
@@ -108,16 +108,19 @@ class LIRGeneratorARM64 : public LIRGene
     void visitAsmJSAtomicBinopHeap(MAsmJSAtomicBinopHeap* ins);
     void visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic* ins);
     void visitCompareExchangeTypedArrayElement(MCompareExchangeTypedArrayElement* ins);
     void visitAtomicExchangeTypedArrayElement(MAtomicExchangeTypedArrayElement* ins);
     void visitAtomicTypedArrayElementBinop(MAtomicTypedArrayElementBinop* ins);
     void visitSubstr(MSubstr* ins);
     void visitRandom(MRandom* ins);
     void visitWasmTruncateToInt64(MWasmTruncateToInt64* ins);
+    void visitWasmBoundsCheck(MWasmBoundsCheck* ins);
+    void visitWasmLoad(MWasmLoad* ins);
+    void visitWasmStore(MWasmStore* ins);
     void visitInt64ToFloatingPoint(MInt64ToFloatingPoint* ins);
     void visitCopySign(MCopySign* ins);
 };
 
 typedef LIRGeneratorARM64 LIRGeneratorSpecific;
 
 } // namespace jit
 } // namespace js
--- a/js/src/jit/arm64/vixl/Debugger-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Debugger-vixl.cpp
@@ -56,32 +56,37 @@ class Token {
   virtual bool IsFPRegister() const { return false; }
   virtual bool IsIdentifier() const { return false; }
   virtual bool IsAddress() const { return false; }
   virtual bool IsInteger() const { return false; }
   virtual bool IsFormat() const { return false; }
   virtual bool IsUnknown() const { return false; }
   // Token properties.
   virtual bool CanAddressMemory() const { return false; }
-  virtual uint8_t* ToAddress(Debugger* debugger) const;
+  virtual uint8_t* ToAddress(Debugger* debugger) const = 0;
   virtual void Print(FILE* out = stdout) const = 0;
 
   static Token* Tokenize(const char* arg);
 };
 
 typedef mozilla::Vector<Token*, 0, js::SystemAllocPolicy> TokenVector;
 
 // Tokens often hold one value.
 template<typename T> class ValueToken : public Token {
  public:
   explicit ValueToken(T value) : value_(value) {}
   ValueToken() {}
 
   T value() const { return value_; }
 
+  virtual uint8_t* ToAddress(Debugger* debugger) const {
+    USE(debugger);
+    VIXL_ABORT();
+  }
+
  protected:
   T value_;
 };
 
 // Integer registers (X or W) and their aliases.
 // Format: wn or xn with 0 <= n < 32 or a name in the aliases list.
 class RegisterToken : public ValueToken<const Register> {
  public:
@@ -197,16 +202,21 @@ class FormatToken : public Token {
   FormatToken() {}
 
   virtual bool IsFormat() const { return true; }
   virtual int SizeOf() const = 0;
   virtual char type_code() const = 0;
   virtual void PrintData(void* data, FILE* out = stdout) const = 0;
   virtual void Print(FILE* out = stdout) const = 0;
 
+  virtual uint8_t* ToAddress(Debugger* debugger) const {
+    USE(debugger);
+    VIXL_ABORT();
+  }
+
   static Token* Tokenize(const char* arg);
   static FormatToken* Cast(Token* tok) {
     VIXL_ASSERT(tok->IsFormat());
     return reinterpret_cast<FormatToken*>(tok);
   }
 };
 
 
@@ -232,16 +242,20 @@ template<typename T> class Format : publ
 class UnknownToken : public Token {
  public:
   explicit UnknownToken(const char* arg) {
     size_t size = strlen(arg) + 1;
     unknown_ = (char*)js_malloc(size);
     strncpy(unknown_, arg, size);
   }
   virtual ~UnknownToken() { js_free(unknown_); }
+  virtual uint8_t* ToAddress(Debugger* debugger) const {
+    USE(debugger);
+    VIXL_ABORT();
+  }
 
   virtual bool IsUnknown() const { return true; }
   virtual void Print(FILE* out = stdout) const;
 
  private:
   char* unknown_;
 };
 
@@ -792,23 +806,16 @@ static bool StringToInt64(int64_t* value
     return false;
   }
 
   *value = parsed;
   return true;
 }
 
 
-uint8_t* Token::ToAddress(Debugger* debugger) const {
-  USE(debugger);
-  VIXL_UNREACHABLE();
-  return NULL;
-}
-
-
 Token* Token::Tokenize(const char* arg) {
   if ((arg == NULL) || (*arg == '\0')) {
     return NULL;
   }
 
   // The order is important. For example Identifier::Tokenize would consider
   // any register to be a valid identifier.
 
--- a/js/src/jit/arm64/vixl/Debugger-vixl.h
+++ b/js/src/jit/arm64/vixl/Debugger-vixl.h
@@ -19,16 +19,18 @@
 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+#ifdef JS_SIMULATOR_ARM64
+
 #ifndef VIXL_A64_DEBUGGER_A64_H_
 #define VIXL_A64_DEBUGGER_A64_H_
 
 #include <ctype.h>
 #include <errno.h>
 #include <limits.h>
 
 #include "jit/arm64/vixl/Constants-vixl.h"
@@ -106,8 +108,10 @@ class Debugger : public Simulator {
 
   // Length of the biggest command line accepted by the debugger shell.
   static const int kMaxDebugShellLine = 256;
 };
 
 }  // namespace vixl
 
 #endif  // VIXL_A64_DEBUGGER_A64_H_
+
+#endif  // JS_SIMULATOR_ARM64
--- a/js/src/jit/arm64/vixl/Decoder-vixl.h
+++ b/js/src/jit/arm64/vixl/Decoder-vixl.h
@@ -32,17 +32,17 @@
 #include "jsalloc.h"
 
 #include "jit/arm64/vixl/Globals-vixl.h"
 #include "jit/arm64/vixl/Instructions-vixl.h"
 
 
 // List macro containing all visitors needed by the decoder class.
 
-#define VISITOR_LIST(V)             \
+#define VISITOR_LIST_THAT_RETURN(V) \
   V(PCRelAddressing)                \
   V(AddSubImmediate)                \
   V(LogicalImmediate)               \
   V(MoveWideImmediate)              \
   V(Bitfield)                       \
   V(Extract)                        \
   V(UnconditionalBranch)            \
   V(UnconditionalBranchToRegister)  \
@@ -101,18 +101,24 @@
   V(NEONScalar3Same)                \
   V(NEONScalarByIndexedElement)     \
   V(NEONScalarCopy)                 \
   V(NEONScalarPairwise)             \
   V(NEONScalarShiftImmediate)       \
   V(NEONShiftImmediate)             \
   V(NEONTable)                      \
   V(NEONPerm)                       \
-  V(Unallocated)                    \
-  V(Unimplemented)
+
+#define VISITOR_LIST_THAT_DONT_RETURN(V)  \
+  V(Unallocated)                          \
+  V(Unimplemented)                        \
+
+#define VISITOR_LIST(V)             \
+  VISITOR_LIST_THAT_RETURN(V)       \
+  VISITOR_LIST_THAT_DONT_RETURN(V)  \
 
 namespace vixl {
 
 // The Visitor interface. Disassembler and simulator (and other tools)
 // must provide implementations for all of these functions.
 class DecoderVisitor {
  public:
   enum VisitorConstness {
--- a/js/src/jit/arm64/vixl/Logic-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Logic-vixl.cpp
@@ -19,16 +19,18 @@
 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+#ifdef JS_SIMULATOR_ARM64
+
 #include <cmath>
 
 #include "jit/arm64/vixl/Simulator-vixl.h"
 
 namespace vixl {
 
 template<> double Simulator::FPDefaultNaN<double>() {
   return kFP64DefaultNaN;
@@ -4867,8 +4869,10 @@ LogicVRegister Simulator::ucvtf(VectorFo
       dst.SetFloat<double>(i, result);
     }
   }
   return dst;
 }
 
 
 }  // namespace vixl
+
+#endif  // JS_SIMULATOR_ARM64
--- a/js/src/jit/arm64/vixl/MacroAssembler-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MacroAssembler-vixl.cpp
@@ -21,16 +21,18 @@
 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "jit/arm64/vixl/MacroAssembler-vixl.h"
 
+#include <ctype.h>
+
 namespace vixl {
 
 MacroAssembler::MacroAssembler()
     : js::jit::Assembler(),
       sp_(x28),
       tmp_list_(ip0, ip1),
       fptmp_list_(d31)
 {
--- a/js/src/jit/arm64/vixl/MacroAssembler-vixl.h
+++ b/js/src/jit/arm64/vixl/MacroAssembler-vixl.h
@@ -28,16 +28,18 @@
 #define VIXL_A64_MACRO_ASSEMBLER_A64_H_
 
 #include <algorithm>
 #include <limits>
 
 #include "jit/arm64/Assembler-arm64.h"
 #include "jit/arm64/vixl/Debugger-vixl.h"
 #include "jit/arm64/vixl/Globals-vixl.h"
+#include "jit/arm64/vixl/Instrument-vixl.h"
+#include "jit/arm64/vixl/Simulator-Constants-vixl.h"
 
 #define LS_MACRO_LIST(V)                                      \
   V(Ldrb, Register&, rt, LDRB_w)                              \
   V(Strb, Register&, rt, STRB_w)                              \
   V(Ldrsb, Register&, rt, rt.Is64Bits() ? LDRSB_x : LDRSB_w)  \
   V(Ldrh, Register&, rt, LDRH_w)                              \
   V(Strh, Register&, rt, STRH_w)                              \
   V(Ldrsh, Register&, rt, rt.Is64Bits() ? LDRSH_x : LDRSH_w)  \
new file mode 100644
--- /dev/null
+++ b/js/src/jit/arm64/vixl/Simulator-Constants-vixl.h
@@ -0,0 +1,141 @@
+// Copyright 2015, ARM Limited
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//   * Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//   * Redistributions in binary form must reproduce the above copyright notice
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//   * Neither the name of ARM Limited nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef VIXL_A64_SIMULATOR_CONSTANTS_A64_H_
+#define VIXL_A64_SIMULATOR_CONSTANTS_A64_H_
+
+namespace vixl {
+
+// Debug instructions.
+//
+// VIXL's macro-assembler and simulator support a few pseudo instructions to
+// make debugging easier. These pseudo instructions do not exist on real
+// hardware.
+//
+// TODO: Also consider allowing these pseudo-instructions to be disabled in the
+// simulator, so that users can check that the input is a valid native code.
+// (This isn't possible in all cases. Printf won't work, for example.)
+//
+// Each debug pseudo instruction is represented by a HLT instruction. The HLT
+// immediate field is used to identify the type of debug pseudo instruction.
+
+enum DebugHltOpcodes {
+  kUnreachableOpcode = 0xdeb0,
+  kPrintfOpcode,
+  kTraceOpcode,
+  kLogOpcode,
+  // Aliases.
+  kDebugHltFirstOpcode = kUnreachableOpcode,
+  kDebugHltLastOpcode = kLogOpcode
+};
+
+// Each pseudo instruction uses a custom encoding for additional arguments, as
+// described below.
+
+// Unreachable - kUnreachableOpcode
+//
+// Instruction which should never be executed. This is used as a guard in parts
+// of the code that should not be reachable, such as in data encoded inline in
+// the instructions.
+
+// Printf - kPrintfOpcode
+//  - arg_count: The number of arguments.
+//  - arg_pattern: A set of PrintfArgPattern values, packed into two-bit fields.
+//
+// Simulate a call to printf.
+//
+// Floating-point and integer arguments are passed in separate sets of registers
+// in AAPCS64 (even for varargs functions), so it is not possible to determine
+// the type of each argument without some information about the values that were
+// passed in. This information could be retrieved from the printf format string,
+// but the format string is not trivial to parse so we encode the relevant
+// information with the HLT instruction.
+//
+// Also, the following registers are populated (as if for a native A64 call):
+//    x0: The format string
+// x1-x7: Optional arguments, if type == CPURegister::kRegister
+// d0-d7: Optional arguments, if type == CPURegister::kFPRegister
+const unsigned kPrintfArgCountOffset = 1 * kInstructionSize;
+const unsigned kPrintfArgPatternListOffset = 2 * kInstructionSize;
+const unsigned kPrintfLength = 3 * kInstructionSize;
+
+const unsigned kPrintfMaxArgCount = 4;
+
+// The argument pattern is a set of two-bit-fields, each with one of the
+// following values:
+enum PrintfArgPattern {
+  kPrintfArgW = 1,
+  kPrintfArgX = 2,
+  // There is no kPrintfArgS because floats are always converted to doubles in C
+  // varargs calls.
+  kPrintfArgD = 3
+};
+static const unsigned kPrintfArgPatternBits = 2;
+
+// Trace - kTraceOpcode
+//  - parameter: TraceParameter stored as a uint32_t
+//  - command: TraceCommand stored as a uint32_t
+//
+// Allow for trace management in the generated code. This enables or disables
+// automatic tracing of the specified information for every simulated
+// instruction.
+const unsigned kTraceParamsOffset = 1 * kInstructionSize;
+const unsigned kTraceCommandOffset = 2 * kInstructionSize;
+const unsigned kTraceLength = 3 * kInstructionSize;
+
+// Trace parameters.
+enum TraceParameters {
+  LOG_DISASM     = 1 << 0,  // Log disassembly.
+  LOG_REGS       = 1 << 1,  // Log general purpose registers.
+  LOG_VREGS      = 1 << 2,  // Log NEON and floating-point registers.
+  LOG_SYSREGS    = 1 << 3,  // Log the flags and system registers.
+  LOG_WRITE      = 1 << 4,  // Log writes to memory.
+
+  LOG_NONE       = 0,
+  LOG_STATE      = LOG_REGS | LOG_VREGS | LOG_SYSREGS,
+  LOG_ALL        = LOG_DISASM | LOG_STATE | LOG_WRITE
+};
+
+// Trace commands.
+enum TraceCommand {
+  TRACE_ENABLE   = 1,
+  TRACE_DISABLE  = 2
+};
+
+// Log - kLogOpcode
+//  - parameter: TraceParameter stored as a uint32_t
+//
+// Print the specified information once. This mechanism is separate from Trace.
+// In particular, _all_ of the specified registers are printed, rather than just
+// the registers that the instruction writes.
+//
+// Any combination of the TraceParameters values can be used, except that
+// LOG_DISASM is not supported for Log.
+const unsigned kLogParamsOffset = 1 * kInstructionSize;
+const unsigned kLogLength = 2 * kInstructionSize;
+}  // namespace vixl
+
+#endif  // VIXL_A64_SIMULATOR_CONSTANTS_A64_H_
--- a/js/src/jit/arm64/vixl/Simulator-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Simulator-vixl.cpp
@@ -208,66 +208,49 @@ void Simulator::set_instruction_stats(bo
     } else {
       decoder_->RemoveVisitor(instrumentation_);
     }
     instruction_stats_ = value;
   }
 }
 
 // Helpers ---------------------------------------------------------------------
-int64_t Simulator::AddWithCarry(unsigned reg_size,
-                                bool set_flags,
-                                int64_t src1,
-                                int64_t src2,
-                                int64_t carry_in) {
+uint64_t Simulator::AddWithCarry(unsigned reg_size,
+                                 bool set_flags,
+                                 uint64_t left,
+                                 uint64_t right,
+                                 int carry_in) {
   VIXL_ASSERT((carry_in == 0) || (carry_in == 1));
   VIXL_ASSERT((reg_size == kXRegSize) || (reg_size == kWRegSize));
 
-  uint64_t u1, u2;
-  int64_t result;
-  int64_t signed_sum = src1 + src2 + carry_in;
-
-  uint32_t N, Z, C, V;
-
-  if (reg_size == kWRegSize) {
-    u1 = static_cast<uint64_t>(src1) & kWRegMask;
-    u2 = static_cast<uint64_t>(src2) & kWRegMask;
-
-    result = signed_sum & kWRegMask;
-    // Compute the C flag by comparing the sum to the max unsigned integer.
-    C = ((kWMaxUInt - u1) < (u2 + carry_in)) ||
-        ((kWMaxUInt - u1 - carry_in) < u2);
+  uint64_t max_uint = (reg_size == kWRegSize) ? kWMaxUInt : kXMaxUInt;
+  uint64_t reg_mask = (reg_size == kWRegSize) ? kWRegMask : kXRegMask;
+  uint64_t sign_mask = (reg_size == kWRegSize) ? kWSignMask : kXSignMask;
+
+  left &= reg_mask;
+  right &= reg_mask;
+  uint64_t result = (left + right + carry_in) & reg_mask;
+
+  if (set_flags) {
+    nzcv().SetN(CalcNFlag(result, reg_size));
+    nzcv().SetZ(CalcZFlag(result));
+
+    // Compute the C flag by comparing the result to the max unsigned integer.
+    uint64_t max_uint_2op = max_uint - carry_in;
+    bool C = (left > max_uint_2op) || ((max_uint_2op - left) < right);
+    nzcv().SetC(C ? 1 : 0);
+
     // Overflow iff the sign bit is the same for the two inputs and different
     // for the result.
-    int64_t s_src1 = src1 << (kXRegSize - kWRegSize);
-    int64_t s_src2 = src2 << (kXRegSize - kWRegSize);
-    int64_t s_result = result << (kXRegSize - kWRegSize);
-    V = ((s_src1 ^ s_src2) >= 0) && ((s_src1 ^ s_result) < 0);
-
-  } else {
-    u1 = static_cast<uint64_t>(src1);
-    u2 = static_cast<uint64_t>(src2);
-
-    result = signed_sum;
-    // Compute the C flag by comparing the sum to the max unsigned integer.
-    C = ((kXMaxUInt - u1) < (u2 + carry_in)) ||
-        ((kXMaxUInt - u1 - carry_in) < u2);
-    // Overflow iff the sign bit is the same for the two inputs and different
-    // for the result.
-    V = ((src1 ^ src2) >= 0) && ((src1 ^ result) < 0);
-  }
-
-  N = CalcNFlag(result, reg_size);
-  Z = CalcZFlag(result);
-
-  if (set_flags) {
-    nzcv().SetN(N);
-    nzcv().SetZ(Z);
-    nzcv().SetC(C);
-    nzcv().SetV(V);
+    uint64_t left_sign = left & sign_mask;
+    uint64_t right_sign = right & sign_mask;
+    uint64_t result_sign = result & sign_mask;
+    bool V = (left_sign == right_sign) && (left_sign != result_sign);
+    nzcv().SetV(V ? 1 : 0);
+
     LogSystemRegister(NZCV);
   }
   return result;
 }
 
 
 int64_t Simulator::ShiftOperand(unsigned reg_size,
                                 int64_t value,
--- a/js/src/jit/arm64/vixl/Simulator-vixl.h
+++ b/js/src/jit/arm64/vixl/Simulator-vixl.h
@@ -35,145 +35,32 @@
 
 #include "jsalloc.h"
 
 #include "jit/arm64/vixl/Assembler-vixl.h"
 #include "jit/arm64/vixl/Disasm-vixl.h"
 #include "jit/arm64/vixl/Globals-vixl.h"
 #include "jit/arm64/vixl/Instructions-vixl.h"
 #include "jit/arm64/vixl/Instrument-vixl.h"
+#include "jit/arm64/vixl/Simulator-Constants-vixl.h"
 #include "jit/arm64/vixl/Utils-vixl.h"
 #include "jit/IonTypes.h"
 #include "threading/Mutex.h"
 #include "vm/PosixNSPR.h"
 
 #define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror)             \
     JS_BEGIN_MACRO                                                              \
         if (cx->mainThread().simulator()->overRecursedWithExtra(extra)) {       \
             js::ReportOverRecursed(cx);                                         \
             onerror;                                                            \
         }                                                                       \
     JS_END_MACRO
 
 namespace vixl {
 
-// Debug instructions.
-//
-// VIXL's macro-assembler and simulator support a few pseudo instructions to
-// make debugging easier. These pseudo instructions do not exist on real
-// hardware.
-//
-// TODO: Provide controls to prevent the macro assembler from emitting
-// pseudo-instructions. This is important for ahead-of-time compilers, where the
-// macro assembler is built with USE_SIMULATOR but the code will eventually be
-// run on real hardware.
-//
-// TODO: Also consider allowing these pseudo-instructions to be disabled in the
-// simulator, so that users can check that the input is a valid native code.
-// (This isn't possible in all cases. Printf won't work, for example.)
-//
-// Each debug pseudo instruction is represented by a HLT instruction. The HLT
-// immediate field is used to identify the type of debug pseudo instruction.
-
-enum DebugHltOpcodes {
-  kUnreachableOpcode = 0xdeb0,
-  kPrintfOpcode,
-  kTraceOpcode,
-  kLogOpcode,
-  // Aliases.
-  kDebugHltFirstOpcode = kUnreachableOpcode,
-  kDebugHltLastOpcode = kLogOpcode
-};
-
-// Each pseudo instruction uses a custom encoding for additional arguments, as
-// described below.
-
-// Unreachable - kUnreachableOpcode
-//
-// Instruction which should never be executed. This is used as a guard in parts
-// of the code that should not be reachable, such as in data encoded inline in
-// the instructions.
-
-// Printf - kPrintfOpcode
-//  - arg_count: The number of arguments.
-//  - arg_pattern: A set of PrintfArgPattern values, packed into two-bit fields.
-//
-// Simulate a call to printf.
-//
-// Floating-point and integer arguments are passed in separate sets of registers
-// in AAPCS64 (even for varargs functions), so it is not possible to determine
-// the type of each argument without some information about the values that were
-// passed in. This information could be retrieved from the printf format string,
-// but the format string is not trivial to parse so we encode the relevant
-// information with the HLT instruction.
-//
-// Also, the following registers are populated (as if for a native A64 call):
-//    x0: The format string
-// x1-x7: Optional arguments, if type == CPURegister::kRegister
-// d0-d7: Optional arguments, if type == CPURegister::kFPRegister
-const unsigned kPrintfArgCountOffset = 1 * kInstructionSize;
-const unsigned kPrintfArgPatternListOffset = 2 * kInstructionSize;
-const unsigned kPrintfLength = 3 * kInstructionSize;
-
-const unsigned kPrintfMaxArgCount = 4;
-
-// The argument pattern is a set of two-bit-fields, each with one of the
-// following values:
-enum PrintfArgPattern {
-  kPrintfArgW = 1,
-  kPrintfArgX = 2,
-  // There is no kPrintfArgS because floats are always converted to doubles in C
-  // varargs calls.
-  kPrintfArgD = 3
-};
-static const unsigned kPrintfArgPatternBits = 2;
-
-// Trace - kTraceOpcode
-//  - parameter: TraceParameter stored as a uint32_t
-//  - command: TraceCommand stored as a uint32_t
-//
-// Allow for trace management in the generated code. This enables or disables
-// automatic tracing of the specified information for every simulated
-// instruction.
-const unsigned kTraceParamsOffset = 1 * kInstructionSize;
-const unsigned kTraceCommandOffset = 2 * kInstructionSize;
-const unsigned kTraceLength = 3 * kInstructionSize;
-
-// Trace parameters.
-enum TraceParameters {
-  LOG_DISASM     = 1 << 0,  // Log disassembly.
-  LOG_REGS       = 1 << 1,  // Log general purpose registers.
-  LOG_VREGS      = 1 << 2,  // Log NEON and floating-point registers.
-  LOG_SYSREGS    = 1 << 3,  // Log the flags and system registers.
-  LOG_WRITE      = 1 << 4,  // Log writes to memory.
-
-  LOG_NONE       = 0,
-  LOG_STATE      = LOG_REGS | LOG_VREGS | LOG_SYSREGS,
-  LOG_ALL        = LOG_DISASM | LOG_STATE | LOG_WRITE
-};
-
-// Trace commands.
-enum TraceCommand {
-  TRACE_ENABLE   = 1,
-  TRACE_DISABLE  = 2
-};
-
-// Log - kLogOpcode
-//  - parameter: TraceParameter stored as a uint32_t
-//
-// Print the specified information once. This mechanism is separate from Trace.
-// In particular, _all_ of the specified registers are printed, rather than just
-// the registers that the instruction writes.
-//
-// Any combination of the TraceParameters values can be used, except that
-// LOG_DISASM is not supported for Log.
-const unsigned kLogParamsOffset = 1 * kInstructionSize;
-const unsigned kLogLength = 2 * kInstructionSize;
-
-
 // Assemble the specified IEEE-754 components into the target type and apply
 // appropriate rounding.
 //  sign:     0 = positive, 1 = negative
 //  exponent: Unbiased IEEE-754 exponent.
 //  mantissa: The mantissa of the input. The top bit (which is not encoded for
 //            normal IEEE-754 values) must not be omitted. This bit has the
 //            value 'pow(2, exponent)'.
 //
@@ -872,19 +759,21 @@ class Simulator : public DecoderVisitor 
 
     pc_modified_ = false;
   }
 
   void ExecuteInstruction();
 
   // Declare all Visitor functions.
   #define DECLARE(A) virtual void Visit##A(const Instruction* instr);
-  VISITOR_LIST(DECLARE)
+  VISITOR_LIST_THAT_RETURN(DECLARE)
+  VISITOR_LIST_THAT_DONT_RETURN(DECLARE)
   #undef DECLARE
 
+
   // Integer register accessors.
 
   // Basic accessor: Read the register as the specified type.
   template<typename T>
   T reg(unsigned code, Reg31Mode r31mode = Reg31IsZeroRegister) const {
     VIXL_ASSERT(code < kNumberOfRegisters);
     if ((code == 31) && (r31mode == Reg31IsZeroRegister)) {
       T result;
@@ -1429,21 +1318,21 @@ class Simulator : public DecoderVisitor 
     return ConditionPassed(static_cast<Condition>(cond));
   }
 
   bool ConditionFailed(Condition cond) {
     return !ConditionPassed(cond);
   }
 
   void AddSubHelper(const Instruction* instr, int64_t op2);
-  int64_t AddWithCarry(unsigned reg_size,
-                       bool set_flags,
-                       int64_t src1,
-                       int64_t src2,
-                       int64_t carry_in = 0);
+  uint64_t AddWithCarry(unsigned reg_size,
+                        bool set_flags,
+                        uint64_t left,
+                        uint64_t right,
+                        int carry_in = 0);
   void LogicalHelper(const Instruction* instr, int64_t op2);
   void ConditionalCompareHelper(const Instruction* instr, int64_t op2);
   void LoadStoreHelper(const Instruction* instr,
                        int64_t offset,
                        AddrMode addrmode);
   void LoadStorePairHelper(const Instruction* instr, AddrMode addrmode);
   uintptr_t AddressModeHelper(unsigned addr_reg,
                               int64_t offset,
@@ -2672,17 +2561,17 @@ class Simulator : public DecoderVisitor 
     // is irrelevant, and is not checked here.
   }
 
   static int CalcNFlag(uint64_t result, unsigned reg_size) {
     return (result >> (reg_size - 1)) & 1;
   }
 
   static int CalcZFlag(uint64_t result) {
-    return result == 0;
+    return (result == 0) ? 1 : 0;
   }
 
   static const uint32_t kConditionFlagsMask = 0xf0000000;
 
   // Stack
   byte* stack_;
   static const int stack_protection_size_ = 128 * KBytes;
   static const int stack_size_ = (2 * MBytes) + (2 * stack_protection_size_);
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -7658,16 +7658,73 @@ class LAsmSelectI64 : public LAsmSelectB
     {
         static_assert(TrueExprIndex == 0, "trueExprIndex kept in sync");
         setInt64Operand(0, trueExpr);
         setInt64Operand(1, falseExpr);
         setOperand(2, cond);
     }
 };
 
+class LWasmBoundsCheck : public LInstructionHelper<0, 1, 0>
+{
+  public:
+    LIR_HEADER(WasmBoundsCheck);
+    explicit LWasmBoundsCheck(const LAllocation& ptr) {
+        setOperand(0, ptr);
+    }
+    MWasmBoundsCheck* mir() const {
+        return mir_->toWasmBoundsCheck();
+    }
+    const LAllocation* ptr() {
+        return getOperand(0);
+    }
+};
+
+class LWasmLoad : public LInstructionHelper<1, 1, 1>
+{
+  public:
+    LIR_HEADER(WasmLoad);
+    explicit LWasmLoad(const LAllocation& ptr) {
+        setOperand(0, ptr);
+        setTemp(0, LDefinition::BogusTemp());
+    }
+    MWasmLoad* mir() const {
+        return mir_->toWasmLoad();
+    }
+    const LAllocation* ptr() {
+        return getOperand(0);
+    }
+    const LDefinition* ptrCopy() {
+        return getTemp(0);
+    }
+};
+
+class LWasmStore : public LInstructionHelper<0, 2, 1>
+{
+  public:
+    LIR_HEADER(WasmStore);
+    LWasmStore(const LAllocation& ptr, const LAllocation& value) {
+        setOperand(0, ptr);
+        setOperand(1, value);
+        setTemp(0, LDefinition::BogusTemp());
+    }
+    MWasmStore* mir() const {
+        return mir_->toWasmStore();
+    }
+    const LAllocation* ptr() {
+        return getOperand(0);
+    }
+    const LDefinition* ptrCopy() {
+        return getTemp(0);
+    }
+    const LAllocation* value() {
+        return getOperand(1);
+    }
+};
+
 class LAsmJSLoadHeap : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(AsmJSLoadHeap);
     explicit LAsmJSLoadHeap(const LAllocation& ptr) {
         setOperand(0, ptr);
     }
     MAsmJSLoadHeap* mir() const {
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -379,16 +379,19 @@
     _(CallDOMNative)                \
     _(IsCallable)                   \
     _(IsConstructor)                \
     _(IsObject)                     \
     _(IsObjectAndBranch)            \
     _(HasClass)                     \
     _(AsmSelect)                    \
     _(AsmSelectI64)                 \
+    _(WasmLoad)                     \
+    _(WasmStore)                    \
+    _(WasmBoundsCheck)              \
     _(AsmJSLoadHeap)                \
     _(AsmJSStoreHeap)               \
     _(AsmJSLoadFuncPtr)             \
     _(AsmJSLoadGlobalVar)           \
     _(AsmJSStoreGlobalVar)          \
     _(AsmJSLoadFFIFunc)             \
     _(AsmJSParameter)               \
     _(AsmJSReturn)                  \
--- a/js/src/jit/x64/CodeGenerator-x64.cpp
+++ b/js/src/jit/x64/CodeGenerator-x64.cpp
@@ -623,16 +623,76 @@ CodeGeneratorX64::loadSimd(Scalar::Type 
 static wasm::MemoryAccess
 AsmJSMemoryAccess(uint32_t before, wasm::MemoryAccess::OutOfBoundsBehavior throwBehavior,
                   uint32_t offsetWithinWholeSimdVector = 0)
 {
     return wasm::MemoryAccess(before, throwBehavior, wasm::MemoryAccess::WrapOffset,
                               offsetWithinWholeSimdVector);
 }
 
+static wasm::MemoryAccess
+WasmMemoryAccess(uint32_t before)
+{
+    return wasm::MemoryAccess(before,
+                              wasm::MemoryAccess::Throw,
+                              wasm::MemoryAccess::DontWrapOffset);
+}
+
+void
+CodeGeneratorX64::visitWasmLoad(LWasmLoad* ins)
+{
+    const MWasmLoad* mir = ins->mir();
+
+    Scalar::Type accessType = mir->accessType();
+    MOZ_ASSERT(!Scalar::isSimdType(accessType), "SIMD NYI");
+    MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
+
+    if (mir->offset() > INT32_MAX) {
+        masm.jump(wasm::JumpTarget::OutOfBounds);
+        return;
+    }
+
+    const LAllocation* ptr = ins->ptr();
+    Operand srcAddr = ptr->isBogus()
+                      ? Operand(HeapReg, mir->offset())
+                      : Operand(HeapReg, ToRegister(ptr), TimesOne, mir->offset());
+
+    AnyRegister out = ToAnyRegister(ins->output());
+
+    uint32_t before = masm.size();
+    load(accessType, srcAddr, out);
+    masm.append(WasmMemoryAccess(before));
+}
+
+void
+CodeGeneratorX64::visitWasmStore(LWasmStore* ins)
+{
+    const MWasmStore* mir = ins->mir();
+
+    Scalar::Type accessType = mir->accessType();
+    MOZ_ASSERT(!Scalar::isSimdType(accessType), "SIMD NYI");
+    MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
+
+    if (mir->offset() > INT32_MAX) {
+        masm.jump(wasm::JumpTarget::OutOfBounds);
+        return;
+    }
+
+    const LAllocation* value = ins->value();
+    const LAllocation* ptr = ins->ptr();
+    Operand dstAddr = ptr->isBogus()
+                      ? Operand(HeapReg, mir->offset())
+                      : Operand(HeapReg, ToRegister(ptr), TimesOne, mir->offset());
+
+    uint32_t before = masm.size();
+    store(accessType, value, dstAddr);
+
+    masm.append(WasmMemoryAccess(before));
+}
+
 void
 CodeGeneratorX64::emitSimdLoad(LAsmJSLoadHeap* ins)
 {
     const MAsmJSLoadHeap* mir = ins->mir();
     Scalar::Type type = mir->accessType();
     FloatRegister out = ToFloatRegister(ins->output());
     const LAllocation* ptr = ins->ptr();
     Operand srcAddr = ptr->isBogus()
@@ -679,16 +739,39 @@ CodeGeneratorX64::emitSimdLoad(LAsmJSLoa
         masm.append(AsmJSMemoryAccess(before, wasm::MemoryAccess::Throw));
     }
 
     if (hasBoundsCheck)
         cleanupAfterAsmJSBoundsCheckBranch(mir, ToRegister(ptr));
 }
 
 void
+CodeGeneratorX64::load(Scalar::Type type, const Operand& srcAddr, AnyRegister out)
+{
+    switch (type) {
+      case Scalar::Int8:      masm.movsbl(srcAddr, out.gpr()); break;
+      case Scalar::Uint8:     masm.movzbl(srcAddr, out.gpr()); break;
+      case Scalar::Int16:     masm.movswl(srcAddr, out.gpr()); break;
+      case Scalar::Uint16:    masm.movzwl(srcAddr, out.gpr()); break;
+      case Scalar::Int32:
+      case Scalar::Uint32:    masm.movl(srcAddr, out.gpr()); break;
+      case Scalar::Float32:   masm.loadFloat32(srcAddr, out.fpu()); break;
+      case Scalar::Float64:   masm.loadDouble(srcAddr, out.fpu()); break;
+      case Scalar::Float32x4:
+      case Scalar::Int8x16:
+      case Scalar::Int16x8:
+      case Scalar::Int32x4:
+        MOZ_CRASH("SIMD loads should be handled in emitSimdLoad");
+      case Scalar::Uint8Clamped:
+      case Scalar::MaxTypedArrayViewType:
+        MOZ_CRASH("unexpected array type");
+    }
+}
+
+void
 CodeGeneratorX64::visitAsmJSLoadHeap(LAsmJSLoadHeap* ins)
 {
     const MAsmJSLoadHeap* mir = ins->mir();
     Scalar::Type accessType = mir->accessType();
 
     if (Scalar::isSimdType(accessType))
         return emitSimdLoad(ins);
 
@@ -699,49 +782,87 @@ CodeGeneratorX64::visitAsmJSLoadHeap(LAs
                       : Operand(HeapReg, ToRegister(ptr), TimesOne, mir->offset());
 
     memoryBarrier(mir->barrierBefore());
 
     OutOfLineLoadTypedArrayOutOfBounds* ool;
     DebugOnly<bool> hasBoundsCheck = maybeEmitAsmJSLoadBoundsCheck(mir, ins, &ool);
 
     uint32_t before = masm.size();
-    switch (accessType) {
-      case Scalar::Int8:      masm.movsbl(srcAddr, ToRegister(out)); break;
-      case Scalar::Uint8:     masm.movzbl(srcAddr, ToRegister(out)); break;
-      case Scalar::Int16:     masm.movswl(srcAddr, ToRegister(out)); break;
-      case Scalar::Uint16:    masm.movzwl(srcAddr, ToRegister(out)); break;
-      case Scalar::Int32:
-      case Scalar::Uint32:    masm.movl(srcAddr, ToRegister(out)); break;
-      case Scalar::Float32:   masm.loadFloat32(srcAddr, ToFloatRegister(out)); break;
-      case Scalar::Float64:   masm.loadDouble(srcAddr, ToFloatRegister(out)); break;
-      case Scalar::Float32x4:
-      case Scalar::Int8x16:
-      case Scalar::Int16x8:
-      case Scalar::Int32x4:   MOZ_CRASH("SIMD loads should be handled in emitSimdLoad");
-      case Scalar::Uint8Clamped:
-      case Scalar::MaxTypedArrayViewType:
-          MOZ_CRASH("unexpected array type");
-    }
+    load(accessType, srcAddr, ToAnyRegister(out));
     uint32_t after = masm.size();
 
     verifyHeapAccessDisassembly(before, after, /*isLoad=*/true, accessType, 0, srcAddr, *out->output());
 
     if (ool) {
         MOZ_ASSERT(hasBoundsCheck);
         cleanupAfterAsmJSBoundsCheckBranch(mir, ToRegister(ptr));
         masm.bind(ool->rejoin());
     }
 
     memoryBarrier(mir->barrierAfter());
 
     masm.append(AsmJSMemoryAccess(before, wasm::MemoryAccess::CarryOn));
 }
 
 void
+CodeGeneratorX64::store(Scalar::Type type, const LAllocation* value, const Operand& dstAddr)
+{
+    if (value->isConstant()) {
+        Imm32 cst(ToInt32(value));
+        switch (type) {
+          case Scalar::Int8:
+          case Scalar::Uint8:        masm.movb(cst, dstAddr); break;
+          case Scalar::Int16:
+          case Scalar::Uint16:       masm.movw(cst, dstAddr); break;
+          case Scalar::Int32:
+          case Scalar::Uint32:       masm.movl(cst, dstAddr); break;
+          case Scalar::Float32:
+          case Scalar::Float64:
+          case Scalar::Float32x4:
+          case Scalar::Int8x16:
+          case Scalar::Int16x8:
+          case Scalar::Int32x4:
+          case Scalar::Uint8Clamped:
+          case Scalar::MaxTypedArrayViewType:
+            MOZ_CRASH("unexpected array type");
+        }
+    } else {
+        switch (type) {
+          case Scalar::Int8:
+          case Scalar::Uint8:
+            masm.movb(ToRegister(value), dstAddr);
+            break;
+          case Scalar::Int16:
+          case Scalar::Uint16:
+            masm.movw(ToRegister(value), dstAddr);
+            break;
+          case Scalar::Int32:
+          case Scalar::Uint32:
+            masm.movl(ToRegister(value), dstAddr);
+            break;
+          case Scalar::Float32:
+            masm.storeUncanonicalizedFloat32(ToFloatRegister(value), dstAddr);
+            break;
+          case Scalar::Float64:
+            masm.storeUncanonicalizedDouble(ToFloatRegister(value), dstAddr);
+            break;
+          case Scalar::Float32x4:
+          case Scalar::Int8x16:
+          case Scalar::Int16x8:
+          case Scalar::Int32x4:
+            MOZ_CRASH("SIMD stores must be handled in emitSimdStore");
+          case Scalar::Uint8Clamped:
+          case Scalar::MaxTypedArrayViewType:
+            MOZ_CRASH("unexpected array type");
+        }
+    }
+}
+
+void
 CodeGeneratorX64::storeSimd(Scalar::Type type, unsigned numElems, FloatRegister in,
                             const Operand& dstAddr)
 {
     switch (type) {
       case Scalar::Float32x4: {
         switch (numElems) {
           // In memory-to-register mode, movss zeroes out the high lanes.
           case 1: masm.storeUncanonicalizedFloat32(in, dstAddr); break;
@@ -856,107 +977,49 @@ CodeGeneratorX64::visitAsmJSStoreHeap(LA
                       : Operand(HeapReg, ToRegister(ptr), TimesOne, mir->offset());
 
     memoryBarrier(mir->barrierBefore());
 
     Label* rejoin;
     DebugOnly<bool> hasBoundsCheck = maybeEmitAsmJSStoreBoundsCheck(mir, ins, &rejoin);
 
     uint32_t before = masm.size();
-    if (value->isConstant()) {
-        switch (accessType) {
-          case Scalar::Int8:
-          case Scalar::Uint8:        masm.movb(Imm32(ToInt32(value)), dstAddr); break;
-          case Scalar::Int16:
-          case Scalar::Uint16:       masm.movw(Imm32(ToInt32(value)), dstAddr); break;
-          case Scalar::Int32:
-          case Scalar::Uint32:       masm.movl(Imm32(ToInt32(value)), dstAddr); break;
-          case Scalar::Float32:
-          case Scalar::Float64:
-          case Scalar::Float32x4:
-          case Scalar::Int8x16:
-          case Scalar::Int16x8:
-          case Scalar::Int32x4:
-          case Scalar::Uint8Clamped:
-          case Scalar::MaxTypedArrayViewType:
-              MOZ_CRASH("unexpected array type");
-        }
-    } else {
-        switch (accessType) {
-          case Scalar::Int8:
-          case Scalar::Uint8:
-            masm.movb(ToRegister(value), dstAddr);
-            break;
-          case Scalar::Int16:
-          case Scalar::Uint16:
-            masm.movw(ToRegister(value), dstAddr);
-            break;
-          case Scalar::Int32:
-          case Scalar::Uint32:
-            masm.movl(ToRegister(value), dstAddr);
-            break;
-          case Scalar::Float32:
-            masm.storeUncanonicalizedFloat32(ToFloatRegister(value), dstAddr);
-            break;
-          case Scalar::Float64:
-            masm.storeUncanonicalizedDouble(ToFloatRegister(value), dstAddr);
-            break;
-          case Scalar::Float32x4:
-          case Scalar::Int8x16:
-          case Scalar::Int16x8:
-          case Scalar::Int32x4:
-            MOZ_CRASH("SIMD stores must be handled in emitSimdStore");
-          case Scalar::Uint8Clamped:
-          case Scalar::MaxTypedArrayViewType:
-            MOZ_CRASH("unexpected array type");
-        }
-    }
+    store(accessType, value, dstAddr);
     uint32_t after = masm.size();
 
     verifyHeapAccessDisassembly(before, after, /*isLoad=*/false, accessType, 0, dstAddr, *value);
 
     if (rejoin) {
         MOZ_ASSERT(hasBoundsCheck);
         cleanupAfterAsmJSBoundsCheckBranch(mir, ToRegister(ptr));
         masm.bind(rejoin);
     }
 
     memoryBarrier(mir->barrierAfter());
 
     masm.append(AsmJSMemoryAccess(before, wasm::MemoryAccess::CarryOn));
 }
 
-static void
-MaybeAddAtomicsBoundsCheck(MacroAssemblerX64& masm, MWasmMemoryAccess* mir, Register ptr)
-{
-    if (!mir->needsBoundsCheck())
-        return;
-
-    // Note that we can't use the same machinery as normal asm.js loads/stores
-    // since signal-handler bounds checking is not yet implemented for atomic
-    // accesses.
-    uint32_t cmpOffset = masm.cmp32WithPatch(ptr, Imm32(-mir->endOffset())).offset();
-    masm.append(wasm::BoundsCheck(cmpOffset));
-    masm.j(Assembler::Above, wasm::JumpTarget::OutOfBounds);
-}
-
 void
 CodeGeneratorX64::visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins)
 {
     MOZ_ASSERT(ins->addrTemp()->isBogusTemp());
 
     MAsmJSCompareExchangeHeap* mir = ins->mir();
     Scalar::Type accessType = mir->accessType();
 
     Register ptr = ToRegister(ins->ptr());
     BaseIndex srcAddr(HeapReg, ptr, TimesOne, mir->offset());
     Register oldval = ToRegister(ins->oldValue());
     Register newval = ToRegister(ins->newValue());
 
-    MaybeAddAtomicsBoundsCheck(masm, mir, ptr);
+    // Note that we can't use the same machinery as normal asm.js loads/stores
+    // since signal-handler bounds checking is not yet implemented for atomic
+    // accesses.
+    maybeEmitWasmBoundsCheckBranch(mir, ptr);
 
     masm.compareExchangeToTypedIntArray(accessType == Scalar::Uint32 ? Scalar::Int32 : accessType,
                                         srcAddr,
                                         oldval,
                                         newval,
                                         InvalidReg,
                                         ToAnyRegister(ins->output()));
     MOZ_ASSERT(mir->offset() == 0,
@@ -972,17 +1035,18 @@ CodeGeneratorX64::visitAsmJSAtomicExchan
 
     MAsmJSAtomicExchangeHeap* mir = ins->mir();
     Scalar::Type accessType = mir->accessType();
 
     Register ptr = ToRegister(ins->ptr());
     BaseIndex srcAddr(HeapReg, ptr, TimesOne, mir->offset());
     Register value = ToRegister(ins->value());
 
-    MaybeAddAtomicsBoundsCheck(masm, mir, ptr);
+    // See comment in visitAsmJSCompareExchangeHeap.
+    maybeEmitWasmBoundsCheckBranch(mir, ptr);
 
     masm.atomicExchangeToTypedIntArray(accessType == Scalar::Uint32 ? Scalar::Int32 : accessType,
                                        srcAddr,
                                        value,
                                        InvalidReg,
                                        ToAnyRegister(ins->output()));
     MOZ_ASSERT(mir->offset() == 0,
                "The AsmJS signal handler doesn't yet support emulating "
@@ -1001,17 +1065,18 @@ CodeGeneratorX64::visitAsmJSAtomicBinopH
     AtomicOp op = mir->operation();
 
     Register ptr = ToRegister(ins->ptr());
     Register temp = ins->temp()->isBogusTemp() ? InvalidReg : ToRegister(ins->temp());
     BaseIndex srcAddr(HeapReg, ptr, TimesOne, mir->offset());
 
     const LAllocation* value = ins->value();
 
-    MaybeAddAtomicsBoundsCheck(masm, mir, ptr);
+    // See comment in visitAsmJSCompareExchangeHeap.
+    maybeEmitWasmBoundsCheckBranch(mir, ptr);
 
     AnyRegister output = ToAnyRegister(ins->output());
     if (value->isConstant()) {
         atomicBinopToTypedIntArray(op, accessType, Imm32(ToInt32(value)), srcAddr, temp, InvalidReg,
                                    output);
     } else {
         atomicBinopToTypedIntArray(op, accessType, ToRegister(value), srcAddr, temp, InvalidReg,
                                    output);
@@ -1031,17 +1096,18 @@ CodeGeneratorX64::visitAsmJSAtomicBinopH
     MAsmJSAtomicBinopHeap* mir = ins->mir();
     Scalar::Type accessType = mir->accessType();
     AtomicOp op = mir->operation();
 
     Register ptr = ToRegister(ins->ptr());
     BaseIndex srcAddr(HeapReg, ptr, TimesOne, mir->offset());
     const LAllocation* value = ins->value();
 
-    MaybeAddAtomicsBoundsCheck(masm, mir, ptr);
+    // See comment in visitAsmJSCompareExchangeHeap.
+    maybeEmitWasmBoundsCheckBranch(mir, ptr);
 
     if (value->isConstant())
         atomicBinopToTypedIntArray(op, accessType, Imm32(ToInt32(value)), srcAddr);
     else
         atomicBinopToTypedIntArray(op, accessType, ToRegister(value), srcAddr);
     MOZ_ASSERT(mir->offset() == 0,
                "The AsmJS signal handler doesn't yet support emulating "
                "atomic accesses in the case of a fault from an unwrapped offset");
--- a/js/src/jit/x64/CodeGenerator-x64.h
+++ b/js/src/jit/x64/CodeGenerator-x64.h
@@ -22,19 +22,23 @@ class CodeGeneratorX64 : public CodeGene
     ValueOperand ToValue(LInstruction* ins, size_t pos);
     ValueOperand ToOutValue(LInstruction* ins);
     ValueOperand ToTempValue(LInstruction* ins, size_t pos);
 
     void storeUnboxedValue(const LAllocation* value, MIRType valueType,
                            Operand dest, MIRType slotType);
     void memoryBarrier(MemoryBarrierBits barrier);
 
+    void load(Scalar::Type type, const Operand& srcAddr, AnyRegister out);
     void loadSimd(Scalar::Type type, unsigned numElems, const Operand& srcAddr, FloatRegister out);
+
+    void store(Scalar::Type type, const LAllocation* value, const Operand& dstAddr);
+    void storeSimd(Scalar::Type type, unsigned numElems, FloatRegister in, const Operand& dstAddr);
+
     void emitSimdLoad(LAsmJSLoadHeap* ins);
-    void storeSimd(Scalar::Type type, unsigned numElems, FloatRegister in, const Operand& dstAddr);
     void emitSimdStore(LAsmJSStoreHeap* ins);
   public:
     CodeGeneratorX64(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm);
 
   public:
     void visitValue(LValue* value);
     void visitBox(LBox* box);
     void visitUnbox(LUnbox* unbox);
@@ -59,16 +63,18 @@ class CodeGeneratorX64 : public CodeGene
     void visitTruncateDToInt32(LTruncateDToInt32* ins);
     void visitTruncateFToInt32(LTruncateFToInt32* ins);
     void visitWrapInt64ToInt32(LWrapInt64ToInt32* lir);
     void visitExtendInt32ToInt64(LExtendInt32ToInt64* lir);
     void visitWasmTruncateToInt64(LWasmTruncateToInt64* lir);
     void visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir);
     void visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic* ins);
     void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins);
+    void visitWasmLoad(LWasmLoad* ins);
+    void visitWasmStore(LWasmStore* ins);
     void visitAsmSelectI64(LAsmSelectI64* ins);
     void visitAsmJSCall(LAsmJSCall* ins);
     void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins);
     void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins);
     void visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins);
     void visitAsmJSAtomicExchangeHeap(LAsmJSAtomicExchangeHeap* ins);
     void visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap* ins);
     void visitAsmJSAtomicBinopHeapForEffect(LAsmJSAtomicBinopHeapForEffect* ins);
--- a/js/src/jit/x64/Lowering-x64.cpp
+++ b/js/src/jit/x64/Lowering-x64.cpp
@@ -151,16 +151,50 @@ void
 LIRGeneratorX64::visitAsmJSUnsignedToFloat32(MAsmJSUnsignedToFloat32* ins)
 {
     MOZ_ASSERT(ins->input()->type() == MIRType::Int32);
     LAsmJSUInt32ToFloat32* lir = new(alloc()) LAsmJSUInt32ToFloat32(useRegisterAtStart(ins->input()));
     define(lir, ins);
 }
 
 void
+LIRGeneratorX64::visitWasmStore(MWasmStore* ins)
+{
+    MDefinition* base = ins->base();
+    MOZ_ASSERT(base->type() == MIRType::Int32);
+
+    LAllocation value;
+    switch (ins->accessType()) {
+      case Scalar::Int8:
+      case Scalar::Uint8:
+      case Scalar::Int16:
+      case Scalar::Uint16:
+      case Scalar::Int32:
+      case Scalar::Uint32:
+        value = useRegisterOrConstantAtStart(ins->value());
+        break;
+      case Scalar::Float32:
+      case Scalar::Float64:
+      case Scalar::Float32x4:
+      case Scalar::Int8x16:
+      case Scalar::Int16x8:
+      case Scalar::Int32x4:
+        value = useRegisterAtStart(ins->value());
+        break;
+      case Scalar::Uint8Clamped:
+      case Scalar::MaxTypedArrayViewType:
+        MOZ_CRASH("unexpected array type");
+    }
+
+    LAllocation baseAlloc = useRegisterOrZeroAtStart(base);
+    auto* lir = new(alloc()) LWasmStore(baseAlloc, value);
+    add(lir, ins);
+}
+
+void
 LIRGeneratorX64::visitAsmJSLoadHeap(MAsmJSLoadHeap* ins)
 {
     MDefinition* base = ins->base();
     MOZ_ASSERT(base->type() == MIRType::Int32);
 
     // For simplicity, require a register if we're going to emit a bounds-check
     // branch, so that we don't have special cases for constants.
     LAllocation baseAlloc = gen->needsBoundsCheckBranch(ins)
--- a/js/src/jit/x64/Lowering-x64.h
+++ b/js/src/jit/x64/Lowering-x64.h
@@ -51,16 +51,17 @@ class LIRGeneratorX64 : public LIRGenera
     void visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble* ins);
     void visitAsmJSUnsignedToFloat32(MAsmJSUnsignedToFloat32* ins);
     void visitAsmJSLoadHeap(MAsmJSLoadHeap* ins);
     void visitAsmJSStoreHeap(MAsmJSStoreHeap* ins);
     void visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr* ins);
     void visitAsmJSCompareExchangeHeap(MAsmJSCompareExchangeHeap* ins);
     void visitAsmJSAtomicExchangeHeap(MAsmJSAtomicExchangeHeap* ins);
     void visitAsmJSAtomicBinopHeap(MAsmJSAtomicBinopHeap* ins);
+    void visitWasmStore(MWasmStore* ins);
     void visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic* ins);
     void visitSubstr(MSubstr* ins);
     void visitRandom(MRandom* ins);
     void visitWasmTruncateToInt64(MWasmTruncateToInt64* ins);
     void visitInt64ToFloatingPoint(MInt64ToFloatingPoint* ins);
 };
 
 typedef LIRGeneratorX64 LIRGeneratorSpecific;
--- a/js/src/jit/x86-shared/Assembler-x86-shared.h
+++ b/js/src/jit/x86-shared/Assembler-x86-shared.h
@@ -1072,28 +1072,41 @@ class AssemblerX86Shared : public Assemb
     static void patchTwoByteNopToJump(uint8_t* jump, uint8_t* target) {
         X86Encoding::BaseAssembler::patchTwoByteNopToJump(jump, target);
     }
     static void patchJumpToTwoByteNop(uint8_t* jump) {
         X86Encoding::BaseAssembler::patchJumpToTwoByteNop(jump);
     }
 
     static void UpdateBoundsCheck(uint8_t* patchAt, uint32_t heapLength) {
-        // An access is out-of-bounds iff
-        //          ptr + offset + data-type-byte-size > heapLength
-        //     i.e. ptr > heapLength - data-type-byte-size - offset.
-        // data-type-byte-size and offset are already included in the addend so
-        // we just have to add the heap length here.
-        //
         // On x64, even with signal handling being used for most bounds checks,
         // there may be atomic operations that depend on explicit checks. All
         // accesses that have been recorded are the only ones that need bound
-        // checks (see also
-        // CodeGeneratorX64::visitAsmJS{Load,Store,CompareExchange,Exchange,AtomicBinop}Heap)
-        X86Encoding::AddInt32(patchAt, heapLength);
+        // checks.
+        //
+        // An access is out-of-bounds iff
+        //          ptr + offset + data-type-byte-size > heapLength
+        //     i.e  ptr + offset + data-type-byte-size - 1 >= heapLength
+        //     i.e. ptr >= heapLength - data-type-byte-size - offset + 1.
+        //
+        // before := data-type-byte-size + offset - 1
+        uint32_t before = reinterpret_cast<uint32_t*>(patchAt)[-1];
+        uint32_t after = before + heapLength;
+
+        // If the computed index `before` already is out of bounds,
+        // we need to make sure the bounds check will fail all the time.
+        // For bounds checks, the sequence of instructions we use is:
+        //      cmp(ptrReg, #before)
+        //      jae(OutOfBounds)
+        // so replace the cmp immediate with 0.
+        if (after > heapLength)
+            after = 0;
+
+        MOZ_ASSERT_IF(after, int32_t(after) >= int32_t(before));
+        reinterpret_cast<uint32_t*>(patchAt)[-1] = after;
     }
 
     void breakpoint() {
         masm.int3();
     }
 
     static bool HasSSE2() { return CPUInfo::IsSSE2Present(); }
     static bool HasSSE3() { return CPUInfo::IsSSE3Present(); }
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -449,28 +449,59 @@ CodeGeneratorX86Shared::emitAsmJSBoundsC
         addOutOfLineCode(oolCheck, mir);
     }
 
     // The bounds check is a comparison with an immediate value. The asm.js
     // module linking process will add the length of the heap to the immediate
     // field, so -access->endOffset() will turn into
     // (heapLength - access->endOffset()), allowing us to test whether the end
     // of the access is beyond the end of the heap.
-    uint32_t cmpOffset = masm.cmp32WithPatch(ptr, Imm32(-access->endOffset())).offset();
+    MOZ_ASSERT(access->endOffset() >= 1,
+               "need to subtract 1 to use JAE, see also AssemblerX86Shared::UpdateBoundsCheck");
+
+    uint32_t cmpOffset = masm.cmp32WithPatch(ptr, Imm32(1 - access->endOffset())).offset();
     if (maybeFail)
-        masm.j(Assembler::Above, maybeFail);
+        masm.j(Assembler::AboveOrEqual, maybeFail);
     else
-        masm.j(Assembler::Above, wasm::JumpTarget::OutOfBounds);
+        masm.j(Assembler::AboveOrEqual, wasm::JumpTarget::OutOfBounds);
 
     if (pass)
         masm.bind(pass);
 
     masm.append(wasm::BoundsCheck(cmpOffset));
 }
 
+void
+CodeGeneratorX86Shared::visitWasmBoundsCheck(LWasmBoundsCheck* ins)
+{
+    const MWasmBoundsCheck* mir = ins->mir();
+    MOZ_ASSERT(gen->needsBoundsCheckBranch(mir));
+    if (mir->offset() > INT32_MAX) {
+        masm.jump(wasm::JumpTarget::OutOfBounds);
+        return;
+    }
+
+    Register ptrReg = ToRegister(ins->ptr());
+    maybeEmitWasmBoundsCheckBranch(mir, ptrReg);
+}
+
+void
+CodeGeneratorX86Shared::maybeEmitWasmBoundsCheckBranch(const MWasmMemoryAccess* mir, Register ptr)
+{
+    if (!mir->needsBoundsCheck())
+        return;
+
+    MOZ_ASSERT(mir->endOffset() >= 1,
+               "need to subtract 1 to use JAE, see also AssemblerX86Shared::UpdateBoundsCheck");
+
+    uint32_t cmpOffset = masm.cmp32WithPatch(ptr, Imm32(1 - mir->endOffset())).offset();
+    masm.j(Assembler::AboveOrEqual, wasm::JumpTarget::OutOfBounds);
+    masm.append(wasm::BoundsCheck(cmpOffset));
+}
+
 bool
 CodeGeneratorX86Shared::maybeEmitThrowingAsmJSBoundsCheck(const MWasmMemoryAccess* access,
                                                           const MInstruction* mir,
                                                           const LAllocation* ptr)
 {
     if (!gen->needsBoundsCheckBranch(access))
         return false;
 
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h
@@ -89,34 +89,34 @@ class CodeGeneratorX86Shared : public Co
         LInstruction* ins() const { return ins_; }
 
         void accept(CodeGeneratorX86Shared* codegen) {
             codegen->visitOutOfLineSimdFloatToIntCheck(this);
         }
     };
 
   private:
-    void
-    emitAsmJSBoundsCheckBranch(const MWasmMemoryAccess* mir, const MInstruction* ins,
-                               Register ptr, Label* fail);
+    void emitAsmJSBoundsCheckBranch(const MWasmMemoryAccess* mir, const MInstruction* ins,
+                                    Register ptr, Label* fail);
+
+  protected:
+    void maybeEmitWasmBoundsCheckBranch(const MWasmMemoryAccess* mir, Register ptr);
 
   public:
     // For SIMD and atomic loads and stores (which throw on out-of-bounds):
-    bool
-    maybeEmitThrowingAsmJSBoundsCheck(const MWasmMemoryAccess* mir, const MInstruction* ins,
-                                      const LAllocation* ptr);
+    bool maybeEmitThrowingAsmJSBoundsCheck(const MWasmMemoryAccess* mir, const MInstruction* ins,
+                                           const LAllocation* ptr);
 
     // For asm.js plain and atomic loads that possibly require a bounds check:
-    bool
-    maybeEmitAsmJSLoadBoundsCheck(const MAsmJSLoadHeap* mir, LAsmJSLoadHeap* ins,
-                                  OutOfLineLoadTypedArrayOutOfBounds** ool);
+    bool maybeEmitAsmJSLoadBoundsCheck(const MAsmJSLoadHeap* mir, LAsmJSLoadHeap* ins,
+                                       OutOfLineLoadTypedArrayOutOfBounds** ool);
 
     // For asm.js plain and atomic stores that possibly require a bounds check:
-    bool
-    maybeEmitAsmJSStoreBoundsCheck(const MAsmJSStoreHeap* mir, LAsmJSStoreHeap* ins, Label** rejoin);
+    bool maybeEmitAsmJSStoreBoundsCheck(const MAsmJSStoreHeap* mir, LAsmJSStoreHeap* ins,
+                                        Label** rejoin);
 
     void cleanupAfterAsmJSBoundsCheckBranch(const MWasmMemoryAccess* mir, Register ptr);
 
     NonAssertingLabel deoptLabel_;
 
     Operand ToOperand(const LAllocation& a);
     Operand ToOperand(const LAllocation* a);
     Operand ToOperand(const LDefinition* def);
@@ -267,16 +267,17 @@ class CodeGeneratorX86Shared : public Co
     virtual void visitGuardObjectGroup(LGuardObjectGroup* guard);
     virtual void visitGuardClass(LGuardClass* guard);
     virtual void visitEffectiveAddress(LEffectiveAddress* ins);
     virtual void visitUDivOrMod(LUDivOrMod* ins);
     virtual void visitUDivOrModConstant(LUDivOrModConstant *ins);
     virtual void visitAsmJSPassStackArg(LAsmJSPassStackArg* ins);
     virtual void visitAsmSelect(LAsmSelect* ins);
     virtual void visitAsmReinterpret(LAsmReinterpret* lir);
+    virtual void visitWasmBoundsCheck(LWasmBoundsCheck* ins);
     virtual void visitMemoryBarrier(LMemoryBarrier* ins);
     virtual void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir);
     virtual void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir);
     virtual void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir);
     virtual void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir);
     virtual void visitCopySignD(LCopySignD* lir);
     virtual void visitCopySignF(LCopySignF* lir);
 
--- a/js/src/jit/x86-shared/Lowering-x86-shared.cpp
+++ b/js/src/jit/x86-shared/Lowering-x86-shared.cpp
@@ -318,16 +318,37 @@ LIRGeneratorX86Shared::visitAsmJSNeg(MAs
         defineReuseInput(new(alloc()) LNegD(useRegisterAtStart(ins->input())), ins, 0);
         break;
       default:
         MOZ_CRASH();
     }
 }
 
 void
+LIRGeneratorX86Shared::visitWasmBoundsCheck(MWasmBoundsCheck* ins)
+{
+    if (!gen->needsBoundsCheckBranch(ins))
+        return;
+
+    MDefinition* index = ins->input();
+    auto* lir = new(alloc()) LWasmBoundsCheck(useRegisterAtStart(index));
+    add(lir, ins);
+}
+
+void
+LIRGeneratorX86Shared::visitWasmLoad(MWasmLoad* ins)
+{
+    MDefinition* base = ins->base();
+    MOZ_ASSERT(base->type() == MIRType::Int32);
+
+    auto* lir = new(alloc()) LWasmLoad(useRegisterOrZeroAtStart(base));
+    define(lir, ins);
+}
+
+void
 LIRGeneratorX86Shared::lowerUDiv(MDiv* div)
 {
     if (div->rhs()->isConstant()) {
         uint32_t rhs = div->rhs()->toConstant()->toInt32();
         int32_t shift = FloorLog2(rhs);
 
         LAllocation lhs = useRegisterAtStart(div->lhs());
         if (rhs != 0 && uint32_t(1) << shift == rhs) {
--- a/js/src/jit/x86-shared/Lowering-x86-shared.h
+++ b/js/src/jit/x86-shared/Lowering-x86-shared.h
@@ -42,16 +42,18 @@ class LIRGeneratorX86Shared : public LIR
                      MDefinition* rhs);
     void lowerForCompIx4(LSimdBinaryCompIx4* ins, MSimdBinaryComp* mir,
                          MDefinition* lhs, MDefinition* rhs);
     void lowerForCompFx4(LSimdBinaryCompFx4* ins, MSimdBinaryComp* mir,
                          MDefinition* lhs, MDefinition* rhs);
     void lowerForBitAndAndBranch(LBitAndAndBranch* baab, MInstruction* mir,
                                  MDefinition* lhs, MDefinition* rhs);
     void visitAsmJSNeg(MAsmJSNeg* ins);
+    void visitWasmBoundsCheck(MWasmBoundsCheck* ins);
+    void visitWasmLoad(MWasmLoad* ins);
     void visitAsmSelect(MAsmSelect* ins);
     void lowerMulI(MMul* mul, MDefinition* lhs, MDefinition* rhs);
     void lowerDivI(MDiv* div);
     void lowerModI(MMod* mod);
     void lowerUDiv(MDiv* div);
     void lowerUMod(MMod* mod);
     void lowerUrshD(MUrsh* mir);
     void lowerTruncateDToInt32(MTruncateToInt32* ins);
--- a/js/src/jit/x86-shared/Patching-x86-shared.h
+++ b/js/src/jit/x86-shared/Patching-x86-shared.h
@@ -38,27 +38,16 @@ GetInt32(const void* where)
 
 inline void
 SetInt32(void* where, int32_t value)
 {
     reinterpret_cast<int32_t*>(where)[-1] = value;
 }
 
 inline void
-AddInt32(void* where, int32_t value)
-{
-#ifdef DEBUG
-    uint32_t x = reinterpret_cast<uint32_t*>(where)[-1];
-    uint32_t y = x + uint32_t(value);
-    MOZ_ASSERT(value >= 0 ? (int32_t(y) >= int32_t(x)) : (int32_t(y) < int32_t(x)));
-#endif
-    reinterpret_cast<uint32_t*>(where)[-1] += uint32_t(value);
-}
-
-inline void
 SetRel32(void* from, void* to)
 {
     intptr_t offset = reinterpret_cast<intptr_t>(to) - reinterpret_cast<intptr_t>(from);
     MOZ_ASSERT(offset == static_cast<int32_t>(offset),
                "offset is too great for a 32-bit relocation");
     if (offset != static_cast<int32_t>(offset))
         MOZ_CRASH("offset is too great for a 32-bit relocation");
 
--- a/js/src/jit/x86/CodeGenerator-x86.cpp
+++ b/js/src/jit/x86/CodeGenerator-x86.cpp
@@ -390,16 +390,67 @@ CodeGeneratorX86::loadSimd(Scalar::Type 
       case Scalar::Float64:
       case Scalar::Uint8Clamped:
       case Scalar::MaxTypedArrayViewType:
         MOZ_CRASH("should only handle SIMD types");
     }
 }
 
 void
+CodeGeneratorX86::visitWasmLoad(LWasmLoad* ins)
+{
+    const MWasmLoad* mir = ins->mir();
+
+    Scalar::Type accessType = mir->accessType();
+    MOZ_ASSERT(!Scalar::isSimdType(accessType), "SIMD NYI");
+    MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
+
+    if (mir->offset() > INT32_MAX) {
+        // This is unreachable because of the bounds check.
+        masm.breakpoint();
+        return;
+    }
+
+    const LAllocation* ptr = ins->ptr();
+    Operand srcAddr = ptr->isBogus()
+                      ? Operand(PatchedAbsoluteAddress(mir->offset()))
+                      : Operand(ToRegister(ptr), mir->offset());
+
+    load(accessType, srcAddr, ins->output());
+
+    masm.append(wasm::MemoryAccess(masm.size()));
+}
+
+void
+CodeGeneratorX86::visitWasmStore(LWasmStore* ins)
+{
+    const MWasmStore* mir = ins->mir();
+
+    Scalar::Type accessType = mir->accessType();
+    MOZ_ASSERT(!Scalar::isSimdType(accessType), "SIMD NYI");
+    MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
+
+    if (mir->offset() > INT32_MAX) {
+        // This is unreachable because of the bounds check.
+        masm.breakpoint();
+        return;
+    }
+
+    const LAllocation* value = ins->value();
+    const LAllocation* ptr = ins->ptr();
+    Operand dstAddr = ptr->isBogus()
+                      ? Operand(PatchedAbsoluteAddress(mir->offset()))
+                      : Operand(ToRegister(ptr), mir->offset());
+
+    store(accessType, value, dstAddr);
+
+    masm.append(wasm::MemoryAccess(masm.size()));
+}
+
+void
 CodeGeneratorX86::emitSimdLoad(LAsmJSLoadHeap* ins)
 {
     const MAsmJSLoadHeap* mir = ins->mir();
     Scalar::Type type = mir->accessType();
     FloatRegister out = ToFloatRegister(ins->output());
     const LAllocation* ptr = ins->ptr();
     Operand srcAddr = ptr->isBogus()
                       ? Operand(PatchedAbsoluteAddress(mir->offset()))
@@ -659,70 +710,64 @@ CodeGeneratorX86::visitAsmJSStoreHeap(LA
         masm.bind(rejoin);
     }
 
     memoryBarrier(mir->barrierAfter());
 
     masm.append(wasm::MemoryAccess(after));
 }
 
+// Perform bounds checking on the access if necessary; if it fails,
+// jump to out-of-line code that throws.  If the bounds check passes,
+// set up the heap address in addrTemp.
+
+void
+CodeGeneratorX86::asmJSAtomicComputeAddress(Register addrTemp, Register ptrReg,
+                                            const MWasmMemoryAccess* mir)
+{
+    maybeEmitWasmBoundsCheckBranch(mir, ptrReg);
+
+    // Add in the actual heap pointer explicitly, to avoid opening up
+    // the abstraction that is atomicBinopToTypedIntArray at this time.
+    masm.movl(ptrReg, addrTemp);
+    masm.addlWithPatch(Imm32(mir->offset()), addrTemp);
+    masm.append(wasm::MemoryAccess(masm.size()));
+}
+
 void
 CodeGeneratorX86::visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins)
 {
     MAsmJSCompareExchangeHeap* mir = ins->mir();
     Scalar::Type accessType = mir->accessType();
     Register ptrReg = ToRegister(ins->ptr());
     Register oldval = ToRegister(ins->oldValue());
     Register newval = ToRegister(ins->newValue());
     Register addrTemp = ToRegister(ins->addrTemp());
 
-    asmJSAtomicComputeAddress(addrTemp, ptrReg, mir->needsBoundsCheck(), mir->offset(),
-                              mir->endOffset());
+    asmJSAtomicComputeAddress(addrTemp, ptrReg, mir);
 
     Address memAddr(addrTemp, mir->offset());
     masm.compareExchangeToTypedIntArray(accessType == Scalar::Uint32 ? Scalar::Int32 : accessType,
                                         memAddr,
                                         oldval,
                                         newval,
                                         InvalidReg,
                                         ToAnyRegister(ins->output()));
 }
 
-// Perform bounds checking on the access if necessary; if it fails,
-// jump to out-of-line code that throws.  If the bounds check passes,
-// set up the heap address in addrTemp.
-
-void
-CodeGeneratorX86::asmJSAtomicComputeAddress(Register addrTemp, Register ptrReg, bool boundsCheck,
-                                            uint32_t offset, uint32_t endOffset)
-{
-    if (boundsCheck) {
-        uint32_t cmpOffset = masm.cmp32WithPatch(ptrReg, Imm32(-endOffset)).offset();
-        masm.j(Assembler::Above, wasm::JumpTarget::OutOfBounds);
-        masm.append(wasm::BoundsCheck(cmpOffset));
-    }
-
-    // Add in the actual heap pointer explicitly, to avoid opening up
-    // the abstraction that is atomicBinopToTypedIntArray at this time.
-    masm.movl(ptrReg, addrTemp);
-    masm.addlWithPatch(Imm32(offset), addrTemp);
-    masm.append(wasm::MemoryAccess(masm.size()));
-}
-
 void
 CodeGeneratorX86::visitAsmJSAtomicExchangeHeap(LAsmJSAtomicExchangeHeap* ins)
 {
     MAsmJSAtomicExchangeHeap* mir = ins->mir();
     Scalar::Type accessType = mir->accessType();
     Register ptrReg = ToRegister(ins->ptr());
     Register value = ToRegister(ins->value());
     Register addrTemp = ToRegister(ins->addrTemp());
 
-    asmJSAtomicComputeAddress(addrTemp, ptrReg, mir->needsBoundsCheck(), mir->offset(),
-                              mir->endOffset());
+    asmJSAtomicComputeAddress(addrTemp, ptrReg, mir);
 
     Address memAddr(addrTemp, mir->offset());
     masm.atomicExchangeToTypedIntArray(accessType == Scalar::Uint32 ? Scalar::Int32 : accessType,
                                        memAddr,
                                        value,
                                        InvalidReg,
                                        ToAnyRegister(ins->output()));
 }
@@ -733,18 +778,17 @@ CodeGeneratorX86::visitAsmJSAtomicBinopH
     MAsmJSAtomicBinopHeap* mir = ins->mir();
     Scalar::Type accessType = mir->accessType();
     Register ptrReg = ToRegister(ins->ptr());
     Register temp = ins->temp()->isBogusTemp() ? InvalidReg : ToRegister(ins->temp());
     Register addrTemp = ToRegister(ins->addrTemp());
     const LAllocation* value = ins->value();
     AtomicOp op = mir->operation();
 
-    asmJSAtomicComputeAddress(addrTemp, ptrReg, mir->needsBoundsCheck(), mir->offset(),
-                              mir->endOffset());
+    asmJSAtomicComputeAddress(addrTemp, ptrReg, mir);
 
     Address memAddr(addrTemp, mir->offset());
     if (value->isConstant()) {
         atomicBinopToTypedIntArray(op, accessType == Scalar::Uint32 ? Scalar::Int32 : accessType,
                                    Imm32(ToInt32(value)),
                                    memAddr,
                                    temp,
                                    InvalidReg,
@@ -766,18 +810,17 @@ CodeGeneratorX86::visitAsmJSAtomicBinopH
     Scalar::Type accessType = mir->accessType();
     Register ptrReg = ToRegister(ins->ptr());
     Register addrTemp = ToRegister(ins->addrTemp());
     const LAllocation* value = ins->value();
     AtomicOp op = mir->operation();
 
     MOZ_ASSERT(!mir->hasUses());
 
-    asmJSAtomicComputeAddress(addrTemp, ptrReg, mir->needsBoundsCheck(), mir->offset(),
-                              mir->endOffset());
+    asmJSAtomicComputeAddress(addrTemp, ptrReg, mir);
 
     Address memAddr(addrTemp, mir->offset());
     if (value->isConstant())
         atomicBinopToTypedIntArray(op, accessType, Imm32(ToInt32(value)), memAddr);
     else
         atomicBinopToTypedIntArray(op, accessType, ToRegister(value), memAddr);
 }
 
--- a/js/src/jit/x86/CodeGenerator-x86.h
+++ b/js/src/jit/x86/CodeGenerator-x86.h
@@ -53,34 +53,36 @@ class CodeGeneratorX86 : public CodeGene
     void visitCompareBitwiseAndBranch(LCompareBitwiseAndBranch* lir);
     void visitAsmJSUInt32ToDouble(LAsmJSUInt32ToDouble* lir);
     void visitAsmJSUInt32ToFloat32(LAsmJSUInt32ToFloat32* lir);
     void visitTruncateDToInt32(LTruncateDToInt32* ins);
     void visitTruncateFToInt32(LTruncateFToInt32* ins);
     void visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic* ins);
     void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins);
     void visitAsmJSCall(LAsmJSCall* ins);
+    void visitWasmLoad(LWasmLoad* ins);
+    void visitWasmStore(LWasmStore* ins);
     void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins);
     void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins);
     void visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins);
     void visitAsmJSAtomicExchangeHeap(LAsmJSAtomicExchangeHeap* ins);
     void visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap* ins);
     void visitAsmJSAtomicBinopHeapForEffect(LAsmJSAtomicBinopHeapForEffect* ins);
     void visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar* ins);
     void visitAsmJSStoreGlobalVar(LAsmJSStoreGlobalVar* ins);
     void visitAsmJSLoadFuncPtr(LAsmJSLoadFuncPtr* ins);
     void visitAsmJSLoadFFIFunc(LAsmJSLoadFFIFunc* ins);
     void visitWasmTruncateToInt32(LWasmTruncateToInt32* ins);
 
     void visitOutOfLineTruncate(OutOfLineTruncate* ool);
     void visitOutOfLineTruncateFloat32(OutOfLineTruncateFloat32* ool);
 
   private:
-    void asmJSAtomicComputeAddress(Register addrTemp, Register ptrReg, bool boundsCheck,
-                                   uint32_t offset, uint32_t endOffset);
+    void asmJSAtomicComputeAddress(Register addrTemp, Register ptrReg,
+                                   const MWasmMemoryAccess* access);
 };
 
 typedef CodeGeneratorX86 CodeGeneratorSpecific;
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_x86_CodeGenerator_x86_h */
--- a/js/src/jit/x86/Lowering-x86.cpp
+++ b/js/src/jit/x86/Lowering-x86.cpp
@@ -199,16 +199,50 @@ void
 LIRGeneratorX86::visitAsmJSUnsignedToFloat32(MAsmJSUnsignedToFloat32* ins)
 {
     MOZ_ASSERT(ins->input()->type() == MIRType::Int32);
     LAsmJSUInt32ToFloat32* lir = new(alloc()) LAsmJSUInt32ToFloat32(useRegisterAtStart(ins->input()), temp());
     define(lir, ins);
 }
 
 void
+LIRGeneratorX86::visitWasmStore(MWasmStore* ins)
+{
+    MDefinition* base = ins->base();
+    MOZ_ASSERT(base->type() == MIRType::Int32);
+
+    LAllocation baseAlloc = useRegisterOrZeroAtStart(base);
+
+    LAllocation valueAlloc;
+    switch (ins->accessType()) {
+      case Scalar::Int8: case Scalar::Uint8:
+        // See comment for LIRGeneratorX86::useByteOpRegister.
+        valueAlloc = useFixed(ins->value(), eax);
+        break;
+      case Scalar::Int16: case Scalar::Uint16:
+      case Scalar::Int32: case Scalar::Uint32:
+      case Scalar::Float32: case Scalar::Float64:
+      case Scalar::Float32x4:
+      case Scalar::Int8x16:
+      case Scalar::Int16x8:
+      case Scalar::Int32x4:
+        // For now, don't allow constant values. The immediate operand affects
+        // instruction layout which affects patching.
+        valueAlloc = useRegisterAtStart(ins->value());
+        break;
+      case Scalar::Uint8Clamped:
+      case Scalar::MaxTypedArrayViewType:
+        MOZ_CRASH("unexpected array type");
+    }
+
+    auto* lir = new(alloc()) LWasmStore(baseAlloc, valueAlloc);
+    add(lir, ins);
+}
+
+void
 LIRGeneratorX86::visitAsmJSLoadHeap(MAsmJSLoadHeap* ins)
 {
     MDefinition* base = ins->base();
     MOZ_ASSERT(base->type() == MIRType::Int32);
 
     // For simplicity, require a register if we're going to emit a bounds-check
     // branch, so that we don't have special cases for constants.
     LAllocation baseAlloc = gen->needsBoundsCheckBranch(ins)
--- a/js/src/jit/x86/Lowering-x86.h
+++ b/js/src/jit/x86/Lowering-x86.h
@@ -55,16 +55,17 @@ class LIRGeneratorX86 : public LIRGenera
     void visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble* ins);
     void visitAsmJSUnsignedToFloat32(MAsmJSUnsignedToFloat32* ins);
     void visitAsmJSLoadHeap(MAsmJSLoadHeap* ins);
     void visitAsmJSStoreHeap(MAsmJSStoreHeap* ins);
     void visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr* ins);
     void visitAsmJSCompareExchangeHeap(MAsmJSCompareExchangeHeap* ins);
     void visitAsmJSAtomicExchangeHeap(MAsmJSAtomicExchangeHeap* ins);
     void visitAsmJSAtomicBinopHeap(MAsmJSAtomicBinopHeap* ins);
+    void visitWasmStore(MWasmStore* ins);
     void visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic* ins);
     void visitSubstr(MSubstr* ins);
     void visitRandom(MRandom* ins);
     void visitWasmTruncateToInt64(MWasmTruncateToInt64* ins);
     void visitInt64ToFloatingPoint(MInt64ToFloatingPoint* ins);
     void lowerPhi(MPhi* phi);
 
     static bool allowTypedElementHoleCheck() {
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -457,17 +457,17 @@ ArrayBufferObject::createForWasm(JSConte
         }
 
         return buffer;
 #else
         MOZ_CRASH("shouldn't be using signal handlers for out-of-bounds");
 #endif
     }
 
-    auto buffer = ArrayBufferObject::create(cx, numBytes);
+    auto* buffer = ArrayBufferObject::create(cx, numBytes);
     if (!buffer)
         return nullptr;
 
     buffer->setIsWasmMalloced();
     return buffer;
 }
 
 /* static */ bool
@@ -628,16 +628,20 @@ ArrayBufferObject::create(JSContext* cx,
     size_t nslots = reservedSlots;
     bool allocated = false;
     if (contents) {
         if (ownsState == OwnsData) {
             // The ABO is taking ownership, so account the bytes against the zone.
             size_t nAllocated = nbytes;
             if (contents.kind() == MAPPED)
                 nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize());
+#ifdef ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB
+            else if (contents.kind() == WASM_MAPPED)
+                nAllocated = wasm::MappedSize;
+#endif
             cx->zone()->updateMallocCounter(nAllocated);
         }
     } else {
         MOZ_ASSERT(ownsState == OwnsData);
         size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots;
         if (nbytes <= usableSlots * sizeof(Value)) {
             int newSlots = (nbytes - 1) / sizeof(Value) + 1;
             MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -1353,16 +1353,38 @@ BaseShape::traceChildrenSkipShapeTable(J
 
 void
 BaseShape::traceShapeTable(JSTracer* trc)
 {
     if (hasTable())
         table().trace(trc);
 }
 
+#ifdef DEBUG
+bool
+BaseShape::canSkipMarkingShapeTable(Shape* lastShape)
+{
+    // Check that every shape in the shape table will be marked by marking
+    // |lastShape|.
+
+    if (!hasTable())
+        return true;
+
+    uint32_t count = 0;
+    for (Shape::Range<NoGC> r(lastShape); !r.empty(); r.popFront()) {
+        Shape* shape = &r.front();
+        ShapeTable::Entry& entry = table().search<MaybeAdding::NotAdding>(shape->propid());
+        if (entry.isLive())
+            count++;
+    }
+
+    return count == table().entryCount();
+}
+#endif
+
 #ifdef JSGC_HASH_TABLE_CHECKS
 
 void
 JSCompartment::checkBaseShapeTableAfterMovingGC()
 {
     if (!baseShapes.initialized())
         return;
 
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -124,32 +124,34 @@ enum class MaybeAdding { Adding = true, 
 
 /*
  * Shapes use multiplicative hashing, but specialized to
  * minimize footprint.
  */
 class ShapeTable {
   public:
     friend class NativeObject;
+    friend class BaseShape;
     static const uint32_t MIN_ENTRIES   = 11;
 
     class Entry {
         // js::Shape pointer tag bit indicating a collision.
         static const uintptr_t SHAPE_COLLISION = 1;
         static Shape* const SHAPE_REMOVED; // = SHAPE_COLLISION
 
         Shape* shape_;
 
         Entry() = delete;
         Entry(const Entry&) = delete;
         Entry& operator=(const Entry&) = delete;
 
       public:
         bool isFree() const { return shape_ == nullptr; }
         bool isRemoved() const { return shape_ == SHAPE_REMOVED; }
+        bool isLive() const { return !isFree() && !isRemoved(); }
         bool hadCollision() const { return uintptr_t(shape_) & SHAPE_COLLISION; }
 
         void setFree() { shape_ = nullptr; }
         void setRemoved() { shape_ = SHAPE_REMOVED; }
 
         Shape* shape() const {
             return reinterpret_cast<Shape*>(uintptr_t(shape_) & ~SHAPE_COLLISION);
         }
@@ -449,16 +451,20 @@ class BaseShape : public gc::TenuredCell
     /* For JIT usage */
     static inline size_t offsetOfFlags() { return offsetof(BaseShape, flags); }
 
     static const JS::TraceKind TraceKind = JS::TraceKind::BaseShape;
 
     void traceChildren(JSTracer* trc);
     void traceChildrenSkipShapeTable(JSTracer* trc);
 
+#ifdef DEBUG
+    bool canSkipMarkingShapeTable(Shape* lastShape);
+#endif
+
   private:
     static void staticAsserts() {
         JS_STATIC_ASSERT(offsetof(BaseShape, clasp_) == offsetof(js::shadow::BaseShape, clasp_));
         static_assert(sizeof(BaseShape) % gc::CellSize == 0,
                       "Things inheriting from gc::Cell must have a size that's "
                       "a multiple of gc::CellSize");
     }
 
--- a/layout/base/MobileViewportManager.cpp
+++ b/layout/base/MobileViewportManager.cpp
@@ -80,19 +80,23 @@ MobileViewportManager::Destroy()
     observerService->RemoveObserver(this, BEFORE_FIRST_PAINT.Data());
   }
 
   mDocument = nullptr;
   mPresShell = nullptr;
 }
 
 void
-MobileViewportManager::SetRestoreResolution(float aResolution)
+MobileViewportManager::SetRestoreResolution(float aResolution,
+                                            LayoutDeviceIntSize aDisplaySize)
 {
   mRestoreResolution = Some(aResolution);
+  ScreenIntSize restoreDisplaySize = ViewAs<ScreenPixel>(aDisplaySize,
+    PixelCastJustification::LayoutDeviceIsScreenForBounds);
+  mRestoreDisplaySize = Some(restoreDisplaySize);
 }
 
 void
 MobileViewportManager::RequestReflow()
 {
   MVM_LOG("%p: got a reflow request\n", this);
   RefreshViewportSize(false);
 }
@@ -162,30 +166,57 @@ MobileViewportManager::ClampZoom(const C
   }
   if (zoom > aViewportInfo.GetMaxZoom()) {
     zoom = aViewportInfo.GetMaxZoom();
     MVM_LOG("%p: Clamped to %f\n", this, zoom.scale);
   }
   return zoom;
 }
 
+LayoutDeviceToLayerScale
+MobileViewportManager::ScaleResolutionWithDisplayWidth(const LayoutDeviceToLayerScale& aRes,
+                                                       const float& aDisplayWidthChangeRatio,
+                                                       const CSSSize& aNewViewport,
+                                                       const CSSSize& aOldViewport)
+{
+  float cssViewportChangeRatio = (aOldViewport.width == 0)
+     ? 1.0f : aNewViewport.width / aOldViewport.width;
+  LayoutDeviceToLayerScale newRes(aRes.scale * aDisplayWidthChangeRatio
+    / cssViewportChangeRatio);
+  MVM_LOG("%p: Old resolution was %f, changed by %f/%f to %f\n", this, aRes.scale,
+    aDisplayWidthChangeRatio, cssViewportChangeRatio, newRes.scale);
+  return newRes;
+}
+
 CSSToScreenScale
 MobileViewportManager::UpdateResolution(const nsViewportInfo& aViewportInfo,
                                         const ScreenIntSize& aDisplaySize,
                                         const CSSSize& aViewport,
                                         const Maybe<float>& aDisplayWidthChangeRatio)
 {
   CSSToLayoutDeviceScale cssToDev =
       mPresShell->GetPresContext()->CSSToDevPixelScale();
   LayoutDeviceToLayerScale res(mPresShell->GetResolution());
 
   if (mIsFirstPaint) {
     CSSToScreenScale defaultZoom;
     if (mRestoreResolution) {
-      defaultZoom = CSSToScreenScale(mRestoreResolution.value() * cssToDev.scale);
+    LayoutDeviceToLayerScale restoreResolution(mRestoreResolution.value());
+      if (mRestoreDisplaySize) {
+        CSSSize prevViewport = mDocument->GetViewportInfo(mRestoreDisplaySize.value()).GetSize();
+        float restoreDisplayWidthChangeRatio = (mRestoreDisplaySize.value().width > 0)
+          ? (float)aDisplaySize.width / (float)mRestoreDisplaySize.value().width : 1.0f;
+
+        restoreResolution =
+          ScaleResolutionWithDisplayWidth(restoreResolution,
+                                          restoreDisplayWidthChangeRatio,
+                                          aViewport,
+                                          prevViewport);
+      }
+      defaultZoom = CSSToScreenScale(restoreResolution.scale * cssToDev.scale);
       MVM_LOG("%p: restored zoom is %f\n", this, defaultZoom.scale);
       defaultZoom = ClampZoom(defaultZoom, aViewportInfo);
     } else {
       defaultZoom = aViewportInfo.GetDefaultZoom();
       MVM_LOG("%p: default zoom from viewport is %f\n", this, defaultZoom.scale);
       if (!aViewportInfo.IsDefaultZoomValid()) {
         defaultZoom = MaxScaleRatio(ScreenSize(aDisplaySize), aViewport);
         MVM_LOG("%p: Intrinsic computed zoom is %f\n", this, defaultZoom.scale);
@@ -226,24 +257,19 @@ MobileViewportManager::UpdateResolution(
   // 1. screen size changes, CSS viewport does not (pages with no meta viewport
   //    or a fixed size viewport)
   // 2. screen size changes, CSS viewport also does (pages with a device-width
   //    viewport)
   // 3. screen size remains constant, but CSS viewport changes (meta viewport
   //    tag is added or removed)
   // 4. neither screen size nor CSS viewport changes
   if (aDisplayWidthChangeRatio) {
-    float cssViewportChangeRatio = (mMobileViewportSize.width == 0)
-       ? 1.0f : aViewport.width / mMobileViewportSize.width;
-    LayoutDeviceToLayerScale newRes(res.scale * aDisplayWidthChangeRatio.value()
-      / cssViewportChangeRatio);
-    MVM_LOG("%p: Old resolution was %f, changed by %f/%f to %f\n", this, res.scale,
-      aDisplayWidthChangeRatio.value(), cssViewportChangeRatio, newRes.scale);
-    mPresShell->SetResolutionAndScaleTo(newRes.scale);
-    res = newRes;
+    res = ScaleResolutionWithDisplayWidth(res, aDisplayWidthChangeRatio.value(),
+      aViewport, mMobileViewportSize);
+    mPresShell->SetResolutionAndScaleTo(res.scale);
   }
 
   return ViewTargetAs<ScreenPixel>(cssToDev * res / ParentLayerToLayerScale(1),
     PixelCastJustification::ScreenIsParentLayerForRoot);
 }
 
 void
 MobileViewportManager::UpdateSPCSPS(const ScreenIntSize& aDisplaySize,
--- a/layout/base/MobileViewportManager.h
+++ b/layout/base/MobileViewportManager.h
@@ -24,18 +24,22 @@ public:
   NS_DECL_NSIOBSERVER
 
   MobileViewportManager(nsIPresShell* aPresShell,
                         nsIDocument* aDocument);
   void Destroy();
 
   /* Provide a resolution to use during the first paint instead of the default
    * resolution computed from the viewport info metadata. This is in the same
-   * "units" as the argument to nsDOMWindowUtils::SetResolutionAndScaleTo. */
-  void SetRestoreResolution(float aResolution);
+   * "units" as the argument to nsDOMWindowUtils::SetResolutionAndScaleTo.
+   * Also takes the previous display dimensions as they were at the time the
+   * resolution was stored in order to correctly adjust the resolution if the
+   * device was rotated in the meantime. */
+  void SetRestoreResolution(float aResolution,
+                            mozilla::LayoutDeviceIntSize aDisplaySize);
 
   /* Notify the MobileViewportManager that a reflow was requested in the
    * presShell.*/
   void RequestReflow();
 
   /* Notify the MobileViewportManager that the resolution on the presShell was
    * updated, and the SPCSPS needs to be updated. */
   void ResolutionUpdated();
@@ -53,31 +57,41 @@ private:
 
   /* Secondary main helper method to update just the SPCSPS. */
   void RefreshSPCSPS();
 
   /* Helper to clamp the given zoom by the min/max in the viewport info. */
   mozilla::CSSToScreenScale ClampZoom(const mozilla::CSSToScreenScale& aZoom,
                                       const nsViewportInfo& aViewportInfo);
 
+  /* Helper to update the given resolution according to changed display and viewport widths. */
+  mozilla::LayoutDeviceToLayerScale
+  ScaleResolutionWithDisplayWidth(const mozilla::LayoutDeviceToLayerScale& aRes,
+                                  const float& aDisplayWidthChangeRatio,
+                                  const mozilla::CSSSize& aNewViewport,
+                                  const mozilla::CSSSize& aOldViewport);
+
   /* Updates the presShell resolution and returns the new zoom. */
   mozilla::CSSToScreenScale UpdateResolution(const nsViewportInfo& aViewportInfo,
                                              const mozilla::ScreenIntSize& aDisplaySize,
                                              const mozilla::CSSSize& aViewport,
                                              const mozilla::Maybe<float>& aDisplayWidthChangeRatio);
+
   /* Updates the scroll-position-clamping scrollport size */
   void UpdateSPCSPS(const mozilla::ScreenIntSize& aDisplaySize,
                     const mozilla::CSSToScreenScale& aZoom);
+
   /* Updates the displayport margins for the presShell's root scrollable frame */
   void UpdateDisplayPortMargins();
 
   nsCOMPtr<nsIDocument> mDocument;
   nsIPresShell* MOZ_NON_OWNING_REF mPresShell; // raw ref since the presShell owns this
   nsCOMPtr<nsIDOMEventTarget> mEventTarget;
   bool mIsFirstPaint;
   bool mPainted;
   mozilla::LayoutDeviceIntSize mDisplaySize;
   mozilla::CSSSize mMobileViewportSize;
   mozilla::Maybe<float> mRestoreResolution;
+  mozilla::Maybe<mozilla::ScreenIntSize> mRestoreDisplaySize;
 };
 
 #endif
 
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -43,16 +43,17 @@
 #include "nsWeakReference.h"
 #include <stdio.h> // for FILE definition
 #include "nsChangeHint.h"
 #include "nsRefPtrHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsPresArena.h"
 #include "nsMargin.h"
 #include "nsFrameState.h"
+#include "Units.h"
 #include "Visibility.h"
 
 #ifdef MOZ_B2G
 #include "nsIHardwareKeyHandler.h"
 #endif
 
 class nsDocShell;
 class nsIDocument;
@@ -1441,17 +1442,18 @@ public:
    * SetResolutionAndScaleTo(), and set to false by a call to SetResolution().
    */
   virtual bool ScaleToResolution() const = 0;
 
   /**
    * Used by session restore code to restore a resolution before the first
    * paint.
    */
-  virtual void SetRestoreResolution(float aResolution) = 0;
+  virtual void SetRestoreResolution(float aResolution,
+                                    mozilla::LayoutDeviceIntSize aDisplaySize) = 0;
 
   /**
    * Returns whether we are in a DrawWindow() call that used the
    * DRAWWINDOW_DO_NOT_FLUSH flag.
    */
   bool InDrawWindowNotFlushing() const
   { return mRenderFlags & STATE_DRAWWINDOW_NOT_FLUSHING; }
 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5628,20 +5628,21 @@ float PresShell::GetCumulativeNonRootSca
       currentShell = parentCtx->PresShell();
     } else {
       currentShell = nullptr;
     }
   }
   return resolution;
 }
 
-void PresShell::SetRestoreResolution(float aResolution)
+void PresShell::SetRestoreResolution(float aResolution,
+                                     LayoutDeviceIntSize aDisplaySize)
 {
   if (mMobileViewportManager) {
-    mMobileViewportManager->SetRestoreResolution(aResolution);
+    mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize);
   }
 }
 
 void PresShell::SetRenderingState(const RenderingState& aState)
 {
   if (mRenderFlags != aState.mRenderFlags) {
     // Rendering state changed in a way that forces us to flush any
     // retained layers we might already have.
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -235,17 +235,18 @@ public:
     return SetResolutionImpl(aResolution, /* aScaleToResolution = */ false);
   }
   virtual nsresult SetResolutionAndScaleTo(float aResolution) override {
     return SetResolutionImpl(aResolution, /* aScaleToResolution = */ true);
   }
   virtual bool ScaleToResolution() const override;
   virtual float GetCumulativeResolution() override;
   virtual float GetCumulativeNonRootScaleResolution() override;
-  virtual void SetRestoreResolution(float aResolution) override;
+  virtual void SetRestoreResolution(float aResolution,
+                                    mozilla::LayoutDeviceIntSize aDisplaySize) override;
 
   //nsIViewObserver interface
 
   virtual void Paint(nsView* aViewToPaint, const nsRegion& aDirtyRegion,
                      uint32_t aFlags) override;
   virtual nsresult HandleEvent(nsIFrame* aFrame,
                                mozilla::WidgetGUIEvent* aEvent,
                                bool aDontRetargetEvents,
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -903,16 +903,23 @@ nsStyleSet::GetContext(nsStyleContext* a
 
   RefPtr<nsStyleContext> result;
   if (aParentContext)
     result = aParentContext->FindChildWithRules(aPseudoTag, aRuleNode,
                                                 aVisitedRuleNode,
                                                 relevantLinkVisited);
 
   if (!result) {
+    // |aVisitedRuleNode| may have a ref-count of zero since we are yet
+    // to create the style context that will hold an owning reference to it.
+    // As a result, we need to make sure it stays alive until that point
+    // in case something in the first call to NS_NewStyleContext triggers a
+    // GC sweep of rule nodes.
+    RefPtr<nsRuleNode> kungFuDeathGrip{aVisitedRuleNode};
+
     result = NS_NewStyleContext(aParentContext, aPseudoTag, aPseudoType,
                                 aRuleNode,
                                 aFlags & eSkipParentDisplayBasedStyleFixup);
     if (aVisitedRuleNode) {
       RefPtr<nsStyleContext> resultIfVisited =
         NS_NewStyleContext(parentIfVisited, aPseudoTag, aPseudoType,
                            aVisitedRuleNode,
                            aFlags & eSkipParentDisplayBasedStyleFixup);
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -748,47 +748,37 @@ SessionStore.prototype = {
     content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
       Ci.nsIDOMWindowUtils).getResolution(zoom);
     scrolldata.zoom = {};
     scrolldata.zoom.resolution = zoom.value;
     log("onTabScroll() zoom level: " + zoom.value);
 
     // Save some data that'll help in adjusting the zoom level
     // when restoring in a different screen orientation.
-    let viewportInfo = this._getViewportInfo(aWindow.outerWidth, aWindow.outerHeight, content);
-    scrolldata.zoom.autoSize = viewportInfo.autoSize;
-    log("onTabScroll() autoSize: " + scrolldata.zoom.autoSize);
-    scrolldata.zoom.windowWidth = aWindow.outerWidth;
-    log("onTabScroll() windowWidth: " + scrolldata.zoom.windowWidth);
+    scrolldata.zoom.displaySize = this._getContentViewerSize(content);
+    log("onTabScroll() displayWidth: " + scrolldata.zoom.displaySize.width);
 
     // Save zoom and scroll data.
     data.scrolldata = scrolldata;
     log("onTabScroll() ran for tab " + aWindow.BrowserApp.getTabForBrowser(aBrowser).id);
     let evt = new Event("SSTabScrollCaptured", {"bubbles":true, "cancelable":false});
     aBrowser.dispatchEvent(evt);
     this.saveStateDelayed();
   },
 
-  _getViewportInfo: function ss_getViewportInfo(aDisplayWidth, aDisplayHeight, aWindow) {
-    let viewportInfo = {};
-    let defaultZoom = {}, allowZoom = {}, minZoom = {}, maxZoom ={},
-        width = {}, height = {}, autoSize = {};
+  _getContentViewerSize: function ss_getContentViewerSize(aWindow) {
+    let displaySize = {};
+    let width = {}, height = {};
     aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
-      Ci.nsIDOMWindowUtils).getViewportInfo(aDisplayWidth, aDisplayHeight,
-        defaultZoom, allowZoom, minZoom, maxZoom, width, height, autoSize);
+      Ci.nsIDOMWindowUtils).getContentViewerSize(width, height);
 
-    viewportInfo.defaultZoom = defaultZoom.value;
-    viewportInfo.allowZoom = allowZoom.value;
-    viewportInfo.minZoom = maxZoom.value;
-    viewportInfo.maxZoom = maxZoom.value;
-    viewportInfo.width = width.value;
-    viewportInfo.height = height.value;
-    viewportInfo.autoSize = autoSize.value;
+    displaySize.width = width.value;
+    displaySize.height = height.value;
 
-    return viewportInfo;
+    return displaySize;
   },
 
   saveStateDelayed: function ss_saveStateDelayed() {
     if (!this._saveTimer) {
       // Interval until the next disk operation is allowed
       let minimalDelay = this._lastSaveTime + this._interval - Date.now();
 
       // If we have to wait, set a timer, otherwise saveState directly
@@ -1380,51 +1370,34 @@ SessionStore.prototype = {
   _restoreTextData: function ss_restoreTextData(aFormData, aBrowser) {
     if (aFormData) {
       log("_restoreTextData()");
       FormData.restoreTree(aBrowser.contentWindow, aFormData);
     }
   },
 
   /**
-  * Restores the zoom level of the window. This needs to be called before
-  * first paint/load (whichever comes first) to take any effect.
-  */
+   * Restores the zoom level of the window. This needs to be called before
+   * first paint/load (whichever comes first) to take any effect.
+   */
   _restoreZoom: function ss_restoreZoom(aScrollData, aBrowser) {
-    if (aScrollData && aScrollData.zoom) {
-      let recalculatedZoom = this._recalculateZoom(aScrollData.zoom);
-      log("_restoreZoom(), resolution: " + recalculatedZoom);
+    if (aScrollData && aScrollData.zoom && aScrollData.zoom.displaySize) {
+      log("_restoreZoom(), resolution: " + aScrollData.zoom.resolution +
+          ", old displayWidth: " + aScrollData.zoom.displaySize.width);
 
       let utils = aBrowser.contentWindow.QueryInterface(
         Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
       // Restore zoom level.
-      utils.setRestoreResolution(recalculatedZoom);
+      utils.setRestoreResolution(aScrollData.zoom.resolution,
+                                 aScrollData.zoom.displaySize.width,
+                                 aScrollData.zoom.displaySize.height);
     }
   },
 
   /**
-  * Recalculates the zoom level to account for a changed display width,
-  * e.g. because the device was rotated.
-  */
-  _recalculateZoom: function ss_recalculateZoom(aZoomData) {
-    let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
-
-    // Pages with "width=device-width" won't need any zoom level scaling.
-    if (!aZoomData.autoSize) {
-      let oldWidth = aZoomData.windowWidth;
-      let newWidth = browserWin.outerWidth;
-      if (oldWidth != newWidth && oldWidth > 0 && newWidth > 0) {
-        log("_recalculateZoom(), old resolution: " + aZoomData.resolution);
-        return newWidth / oldWidth * aZoomData.resolution;
-      }
-    }
-    return aZoomData.resolution;
-  },
-
-  /**
   * Takes serialized scroll positions and restores them into the given browser.
   */
   _restoreScrollPosition: function ss_restoreScrollPosition(aScrollData, aBrowser) {
     if (aScrollData) {
       log("_restoreScrollPosition()");
       ScrollPosition.restoreTree(aBrowser.contentWindow, aScrollData);
     }
   },
--- a/mobile/android/config/mozconfigs/android-api-15/l10n-nightly
+++ b/mobile/android/config/mozconfigs/android-api-15/l10n-nightly
@@ -16,9 +16,13 @@ ac_add_options --enable-update-channel=$
 
 export MOZILLA_OFFICIAL=1
 export MOZ_DISABLE_GECKOVIEW=1
 
 ac_add_options --with-branding=mobile/android/branding/nightly
 
 ac_add_options --disable-stdcxx-compat
 
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/mobile/android/config/mozconfigs/android-api-15/l10n-release
+++ b/mobile/android/config/mozconfigs/android-api-15/l10n-release
@@ -17,9 +17,13 @@ ac_add_options --enable-update-channel=$
 export MOZILLA_OFFICIAL=1
 export MOZ_DISABLE_GECKOVIEW=1
 
 ac_add_options --enable-official-branding
 ac_add_options --with-branding=mobile/android/branding/beta
 
 ac_add_options --disable-stdcxx-compat
 
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/mobile/android/config/mozconfigs/android-x86/l10n-nightly
+++ b/mobile/android/config/mozconfigs/android-x86/l10n-nightly
@@ -15,9 +15,13 @@ ac_add_options --enable-update-channel=$
 
 export MOZILLA_OFFICIAL=1
 export MOZ_DISABLE_GECKOVIEW=1
 
 ac_add_options --with-branding=mobile/android/branding/nightly
 
 ac_add_options --disable-stdcxx-compat
 
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/mobile/android/config/mozconfigs/android-x86/l10n-release
+++ b/mobile/android/config/mozconfigs/android-x86/l10n-release
@@ -16,9 +16,13 @@ ac_add_options --enable-update-channel=$
 export MOZILLA_OFFICIAL=1
 export MOZ_DISABLE_GECKOVIEW=1
 
 ac_add_options --enable-official-branding
 ac_add_options --with-branding=mobile/android/branding/beta
 
 ac_add_options --disable-stdcxx-compat
 
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/mobile/android/tests/browser/chrome/test_session_scroll_position.html
+++ b/mobile/android/tests/browser/chrome/test_session_scroll_position.html
@@ -27,16 +27,18 @@ https://bugzilla.mozilla.org/show_bug.cg
   // The chrome window.
   let chromeWin;
 
   // Track the tabs where the tests are happening.
   let tabScroll;
 
   // Use something with enough content to allow for scrolling.
   const URL = "http://example.org/chrome/mobile/android/tests/browser/chrome/basic_article_mobile.html";
+  // Something to test the zoom level scaling on rotation with.
+  const URL_desktop = "http://example.org/chrome/mobile/android/tests/browser/chrome/basic_article.html";
 
   function dispatchUIEvent(browser, type) {
     let event = browser.contentDocument.createEvent("UIEvents");
     event.initUIEvent(type, true, false, browser.contentDocument.defaultView, 0);
     browser.dispatchEvent(event);
   }
 
   function setScrollPosition(browser, x, y) {
@@ -164,16 +166,84 @@ https://bugzilla.mozilla.org/show_bug.cg
     ok(fuzzyEquals(zoom.value, ZOOM), "zoom restored correctly");
     is(scrollX.value, SCROLL_X, "scrollX restored correctly");
     is(scrollY.value, SCROLL_Y, "scrollY restored correctly");
 
     // Remove the tab.
     BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser));
   });
 
+  add_task(function* test_sessionStoreZoomLevelRecalc() {
+    const ZOOM = 4.2;
+    const SCROLL_X = 42;
+    const SCROLL_Y = 42;
+
+    chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
+    let BrowserApp = chromeWin.BrowserApp;
+
+    // Creates a tab, sets a scroll position and zoom level and closes the tab.
+    function createAndRemoveTab() {
+      return Task.spawn(function () {
+        // Create a new tab.
+        tabScroll = BrowserApp.addTab(URL_desktop);
+        let browser = tabScroll.browser;
+        yield promiseBrowserEvent(browser, "pageshow");
+
+        // Modify scroll position and zoom level.
+        setZoomLevel(browser, ZOOM);
+        setScrollPosition(browser, SCROLL_X, SCROLL_Y);
+        yield promiseTabEvent(browser, "SSTabScrollCaptured");
+
+        // Check that we've actually scrolled and zoomed.
+        let ifreq = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor);
+        let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils);
+        let scrollX = {}, scrollY = {}, zoom = {};
+        utils.getResolution(zoom);
+        utils.getScrollXY(false, scrollX, scrollY);
+        ok(fuzzyEquals(zoom.value, ZOOM), "zoom set correctly");
+        is(scrollX.value, SCROLL_X, "scrollX set correctly");
+        is(scrollY.value, SCROLL_Y, "scrollY set correctly");
+
+        // Remove the tab.
+        BrowserApp.closeTab(tabScroll);
+        yield promiseTabEvent(browser, "SSTabCloseProcessed");
+      });
+    }
+
+    yield createAndRemoveTab();
+    let state = ss.getClosedTabs(chromeWin);
+    let [{scrolldata}] = state;
+    is(scrolldata.scroll, SCROLL_X + "," + SCROLL_Y, "stored scroll position is correct");
+    ok(fuzzyEquals(scrolldata.zoom.resolution, ZOOM), "stored zoom level is correct");
+
+    // Pretend the zoom level was originally saved on a rotated device.
+    let closedTabData = ss.getClosedTabs(chromeWin)[0];
+    let displayWidth = scrolldata.zoom.displaySize.width;
+    let displayHeight = scrolldata.zoom.displaySize.height;
+    closedTabData.scrolldata.zoom.displaySize.width = displayHeight;
+    closedTabData.scrolldata.zoom.displaySize.height = displayWidth;
+
+    // Restore the closed tab.
+    let browser = ss.undoCloseTab(chromeWin, closedTabData);
+    yield promiseBrowserEvent(browser, "pageshow");
+
+    // Check the scroll position and zoom level.
+    let ifreq = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor);
+    let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils);
+    let scrollX = {}, scrollY = {}, zoom = {};
+    utils.getResolution(zoom);
+    utils.getScrollXY(false, scrollX, scrollY);
+    ok(fuzzyEquals(zoom.value, ZOOM * displayWidth / displayHeight), "recalculated zoom restored correctly");
+    is(scrollX.value, SCROLL_X, "scrollX restored correctly");
+    is(scrollY.value, SCROLL_Y, "scrollY restored correctly");
+
+    // Remove the tab.
+    BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser));
+  });
+
   </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=810981">Mozilla Bug 810981</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -145,24 +145,34 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
   }
 
     // If CSP requires SRI (require-sri-for), then store that information
     // in the loadInfo so we can enforce SRI before loading the subresource.
     if (!mEnforceSRI) {
       // do not look into the CSP if already true:
       // a CSP saying that SRI isn't needed should not
       // overrule GetVerifySignedContent
-      nsCOMPtr<nsIContentSecurityPolicy> csp;
       if (aLoadingPrincipal) {
+        nsCOMPtr<nsIContentSecurityPolicy> csp;
         aLoadingPrincipal->GetCsp(getter_AddRefs(csp));
+        uint32_t externalType =
+          nsContentUtils::InternalContentPolicyTypeToExternal(aContentPolicyType);
         // csp could be null if loading principal is system principal
         if (csp) {
-          uint32_t loadType =
-            nsContentUtils::InternalContentPolicyTypeToExternal(aContentPolicyType);
-          csp->RequireSRIForType(loadType, &mEnforceSRI);
+          csp->RequireSRIForType(externalType, &mEnforceSRI);
+        }
+        // if CSP is delivered via a meta tag, it's speculatively available
+        // as 'preloadCSP'. If we are preloading a script or style, we have
+        // to apply that speculative 'preloadCSP' for such loads.
+        if (!mEnforceSRI && nsContentUtils::IsPreloadType(aContentPolicyType)) {
+          nsCOMPtr<nsIContentSecurityPolicy> preloadCSP;
+          aLoadingPrincipal->GetPreloadCsp(getter_AddRefs(preloadCSP));
+          if (preloadCSP) {
+            preloadCSP->RequireSRIForType(externalType, &mEnforceSRI);
+          }
         }
       }
     }
 
   if (!(mSecurityFlags & nsILoadInfo::SEC_FORCE_PRIVATE_BROWSING)) {
     if (aLoadingContext) {
       nsCOMPtr<nsILoadContext> loadContext =
         aLoadingContext->OwnerDoc()->GetLoadContext();
--- a/netwerk/base/nsILoadInfo.idl
+++ b/netwerk/base/nsILoadInfo.idl
@@ -28,17 +28,20 @@ typedef unsigned long nsSecurityFlags;
 
 /**
  * An nsILoadOwner represents per-load information about who started the load.
  */
 [scriptable, builtinclass, uuid(ddc65bf9-2f60-41ab-b22a-4f1ae9efcd36)]
 interface nsILoadInfo : nsISupports
 {
   /**
-   * No special security flags:
+   * *** DEPRECATED ***
+   * No LoadInfo created within Gecko should contain this security flag.
+   * Please use any of the five security flags defined underneath.
+   * We only keep this security flag to provide backwards compatibilty.
    */
   const unsigned long SEC_NORMAL = 0;
 
   /**
    * The following five flags determine the security mode and hence what kind of
    * security checks should be performed throughout the lifetime of the channel.
    *
    *    * SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS
--- a/taskcluster/ci/legacy/tasks/builds/firefox_windows_base.yml
+++ b/taskcluster/ci/legacy/tasks/builds/firefox_windows_base.yml
@@ -6,9 +6,9 @@ task:
   metadata:
     name: '[TC] Firefox {{build_name}} ({{build_type}})'
     description: 'Firefox {{build_name}} {{build_type}}'
   payload:
     command:
       - 'mkdir .\build\src'
       - 'hg share c:\builds\hg-shared\mozilla-central .\build\src'
       - 'hg pull -u -R .\build\src --rev %GECKO_HEAD_REV% %GECKO_HEAD_REPOSITORY%'
-      - 'c:\mozilla-build\python\python.exe .\build\src\testing\mozharness\scripts\fx_desktop_build.py --config builds\taskcluster_firefox_{{build_name}}_{{build_type}}.py --branch {{project}} --skip-buildbot-actions --work-dir %cd:X:=x:%\build'
+      - 'c:\mozilla-build\python\python.exe .\build\src\testing\mozharness\scripts\fx_desktop_build.py --config builds\taskcluster_firefox_{{build_name}}_{{build_type}}.py --branch {{project}} --skip-buildbot-actions --work-dir %cd:Z:=z:%\build'
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -7,16 +7,18 @@
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 Components.utils.import("resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
+                                  "resource:///modules/ContextualIdentityService.jsm");
 
 this.EXPORTED_SYMBOLS = ["ForgetAboutSite"];
 
 /**
  * Returns true if the string passed in is part of the root domain of the
  * current string.  For example, if this is "www.mozilla.org", and we pass in
  * "mozilla.org", this will return true.  It would return false the other way
  * around.
@@ -42,16 +44,24 @@ function hasRootDomain(str, aDomain)
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 this.ForgetAboutSite = {
   removeDataFromDomain: function CRH_removeDataFromDomain(aDomain)
   {
+    // Get all userContextId from the ContextualIdentityService and create
+    // all originAttributes.
+    let oaList = [ {} ]; // init the list with the default originAttributes.
+
+    for (let identity of ContextualIdentityService.getIdentities()) {
+      oaList.push({ userContextId: identity.userContextId});
+    }
+
     PlacesUtils.history.removePagesFromHost(aDomain, true);
 
     // Cache
     let cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
              getService(Ci.nsICacheStorageService);
     // NOTE: there is no way to clear just that domain, so we clear out
     //       everything)
     try {
@@ -69,20 +79,23 @@ this.ForgetAboutSite = {
     } catch (ex) {
       Cu.reportError("Exception thrown while clearing the image cache: " +
         ex.toString());
     }
 
     // Cookies
     let cm = Cc["@mozilla.org/cookiemanager;1"].
              getService(Ci.nsICookieManager2);
-    let enumerator = cm.getCookiesFromHost(aDomain, {});
-    while (enumerator.hasMoreElements()) {
-      let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
-      cm.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
+    let enumerator;
+    for (let originAttributes of oaList) {
+      enumerator = cm.getCookiesFromHost(aDomain, originAttributes);
+      while (enumerator.hasMoreElements()) {
+        let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
+        cm.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
+      }
     }
 
     // EME
     let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
                getService(Ci.mozIGeckoMediaPluginChromeService);
     mps.forgetThisSite(aDomain);
 
     // Plugin data
@@ -159,20 +172,24 @@ this.ForgetAboutSite = {
     // delete data from both HTTP and HTTPS sites
     let caUtils = {};
     let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                        getService(Ci.mozIJSSubScriptLoader);
     scriptLoader.loadSubScript("chrome://global/content/contentAreaUtils.js",
                                caUtils);
     let httpURI = caUtils.makeURI("http://" + aDomain);
     let httpsURI = caUtils.makeURI("https://" + aDomain);
-    let httpPrincipal = Services.scriptSecurityManager.createCodebasePrincipal(httpURI, {});
-    let httpsPrincipal = Services.scriptSecurityManager.createCodebasePrincipal(httpsURI, {});
-    qms.clearStoragesForPrincipal(httpPrincipal);
-    qms.clearStoragesForPrincipal(httpsPrincipal);
+    for (let originAttributes of oaList) {
+      let httpPrincipal = Services.scriptSecurityManager
+                                  .createCodebasePrincipal(httpURI, originAttributes);
+      let httpsPrincipal = Services.scriptSecurityManager
+                                   .createCodebasePrincipal(httpsURI, originAttributes);
+      qms.clearStoragesForPrincipal(httpPrincipal);
+      qms.clearStoragesForPrincipal(httpsPrincipal);
+    }
 
     function onContentPrefsRemovalFinished() {
       // Everybody else (including extensions)
       Services.obs.notifyObservers(null, "browser:purge-domain-data", aDomain);
     }
 
     // Content Preferences
     let cps2 = Cc["@mozilla.org/content-pref/service;1"].
--- a/toolkit/forgetaboutsite/test/unit/xpcshell.ini
+++ b/toolkit/forgetaboutsite/test/unit/xpcshell.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 head = head_forgetaboutsite.js ../../../../dom/push/test/xpcshell/head.js
 tail =
+firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 support-files =
   !/dom/push/test/xpcshell/head.js
 
 [test_removeDataFromDomain.js]