Merge inbound to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 21 Nov 2014 16:35:57 -0800
changeset 216948 7ab92d922d193944248a608e961598e1cb6a000f
parent 216898 87d4d6d5facc975177e54a4a60db0c70d64c59d2 (current diff)
parent 216947 5b2f62c1faf9146e0d2673030fc9fb2b076f917c (diff)
child 216949 530a1b287027d87a1f2f0eac8baadaf25b08e7ac
child 216979 3e56ff4fc166f403d22cf72ea211c9c207691018
child 217086 9bc239136a6b52ed8b6c6c08a7c4b7110f26d47f
child 217111 bedd9cba78a66b9a5bed76545f68214e38e0a76e
push id27868
push userkwierso@gmail.com
push dateSat, 22 Nov 2014 00:36:06 +0000
treeherdermozilla-central@7ab92d922d19 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone36.0a1
first release with
nightly linux32
7ab92d922d19 / 36.0a1 / 20141122030204 / files
nightly linux64
7ab92d922d19 / 36.0a1 / 20141122030204 / files
nightly mac
7ab92d922d19 / 36.0a1 / 20141122030204 / files
nightly win32
7ab92d922d19 / 36.0a1 / 20141122030204 / files
nightly win64
7ab92d922d19 / 36.0a1 / 20141122030204 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c a=merge
dom/webidl/moz.build
js/src/tests/ecma_6/TypedObject/simd/float32x4max.js
js/src/tests/ecma_6/TypedObject/simd/float32x4min.js
widget/VsyncDispatcher.cpp
widget/VsyncDispatcher.h
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -1550,16 +1550,19 @@ nsAccessibilityService::CreateAccessible
       break;
     case eHTMLImageMapType:
       newAcc = new HTMLImageMapAccessible(aContent, document);
       break;
     case eHTMLLiType:
       if (aContext->IsList() &&
           aContext->GetContent() == aContent->GetParent()) {
         newAcc = new HTMLLIAccessible(aContent, document);
+      } else {
+        // Otherwise create a generic text accessible to avoid text jamming.
+        newAcc = new HyperTextAccessibleWrap(aContent, document);
       }
       break;
     case eHTMLSelectListType:
       newAcc = new HTMLSelectListAccessible(aContent, document);
       break;
     case eHTMLMediaType:
       newAcc = new EnumRoleAccessible(aContent, document, roles::GROUPING);
       break;
--- a/accessible/tests/mochitest/tree/test_aria_list.html
+++ b/accessible/tests/mochitest/tree/test_aria_list.html
@@ -29,20 +29,25 @@
       testAccessibleTree("list", accTree);
 
       //////////////////////////////////////////////////////////////////////////
       // crazy list (mad mix of ARIA and HTML)
 
       accTree = { // div@role="list"
         role: ROLE_LIST,
         children: [
-          { // li text leaf
-             role: ROLE_TEXT_LEAF,
-             name: "item1",
-             children: [ ]
+          { // li
+            role: ROLE_PARAGRAPH,
+            children: [
+              { // li text leaf
+                role: ROLE_TEXT_LEAF,
+                name: "item1",
+                children: [ ]
+              }
+            ]
           },
           { // li@role="listitem"
             role: ROLE_LISTITEM,
             children: [
               { // text leaf
                 role: ROLE_TEXT_LEAF,
                 name: "item2",
                 children: [ ]
--- a/accessible/tests/mochitest/tree/test_aria_presentation.html
+++ b/accessible/tests/mochitest/tree/test_aria_presentation.html
@@ -51,16 +51,25 @@
             { CELL: [ //td
               { TEXT_LEAF: [ ] }
             ] }
           ] }
         ] }
       ] };
     testAccessibleTree("tblfocusable_cnt", tree);
 
+    // Presentation list, expose generic accesisble for list items.
+    tree =
+      { SECTION: [ // container
+        { PARAGRAPH: [ // li generic accessible
+          { TEXT_LEAF: [ ] } // li text
+        ] }
+      ] };
+    testAccessibleTree("list_cnt", tree);
+
     // Has ARIA globals or referred by ARIA relationship.
     tree =
       { SECTION: [ // container
         { LABEL: [ // label, has aria-owns
           { TEXT_LEAF: [ ] }
         ] },
         { TEXT_LEAF: [ ] },
         { LABEL: [ // label, referenced by aria-owns
@@ -108,15 +117,21 @@
   <div id="tblfocusable_cnt">
     <table role="presentation" tabindex="0">
       <tr>
         <td>cell</td>
       </tr>
     </table>
   </div>
 
+  <div id="list_cnt">
+    <ul role="presentation">
+      <li>item</li>
+    </ul>
+  </div>
+
   <div id="airaglobalprop_cnt">
     <label role="presentation" aria-owns="ariaowned">has aria-owns</label>
     <label role="presentation" id="ariaowned">referred by aria-owns</label>
   </div>
 
 </body>
 </html>
--- a/accessible/tests/mochitest/tree/test_brokencontext.html
+++ b/accessible/tests/mochitest/tree/test_brokencontext.html
@@ -75,25 +75,25 @@
     checkIfNotAccessible("tr_in_block_table");
     checkIfTDGeneric("td_in_block_table");
 
     ////////////////////////////////////////////////////////////////////////////
     // HTML list elements outside list context.
 
     ok(!isAccessible("presentation_ul"),
                      "presentational ul shouldn't be accessible");
-    ok(!isAccessible("item_in_presentation_ul"),
-                     "li in presentational ul shouldn't be accessible");
-    ok(!isAccessible("styleditem_in_presentation_ul"),
-                     "list styled span in presentational ul shouldn't be accessible");
+    ok(isAccessible("item_in_presentation_ul"),
+                    "li in presentational ul should have generic accessible");
+    ok(isAccessible("styleditem_in_presentation_ul"),
+                    "list styled span in presentational ul should have generic accessible");
 
     ok(!isAccessible("presentation_ol"),
                      "presentational ol shouldn't be accessible");
-    ok(!isAccessible("item_in_presentation_ol"),
-                     "li in presentational ol shouldn't be accessible");
+    ok(isAccessible("item_in_presentation_ol"),
+                    "li in presentational ol should have generic accessible");
 
     ok(!isAccessible("presentation_dl"),
                      "presentational dl shouldn't be accessible");
     ok(!isAccessible("dt_in_presentation_dl"),
                      "dt in presentational dl shouldn't be accessible");
     ok(!isAccessible("dd_in_presentation_dl"),
                      "dd in presentational dl shouldn't be accessible");
 
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -176,17 +176,16 @@ class Automation(object):
   def __all__(self):
     return [
            "UNIXISH",
            "IS_WIN32",
            "IS_MAC",
            "log",
            "runApp",
            "Process",
-           "addCommonOptions",
            "initializeProfile",
            "DIST_BIN",
            "DEFAULT_APP",
            "CERTS_SRC_DIR",
            "environment",
            "IS_TEST_BUILD",
            "IS_DEBUG_BUILD",
            "DEFAULT_TIMEOUT",
@@ -380,25 +379,16 @@ class Automation(object):
                       addons=addons,
                       locations=locations,
                       preferences=prefs,
                       restore=False,
                       apps=apps,
                       proxy=proxy)
     return profile
 
-  def addCommonOptions(self, parser):
-    "Adds command-line options which are common to mochitest and reftest."
-
-    parser.add_option("--setpref",
-                      action = "append", type = "string",
-                      default = [],
-                      dest = "extraPrefs", metavar = "PREF=VALUE",
-                      help = "defines an extra user preference")
-
   def fillCertificateDB(self, profileDir, certPath, utilityPath, xrePath):
     pwfilePath = os.path.join(profileDir, ".crtdbpw")
     pwfile = open(pwfilePath, "w")
     pwfile.write("\n")
     pwfile.close()
 
     # Create head of the ssltunnel configuration file
     sslTunnelConfigPath = os.path.join(profileDir, "ssltunnel.cfg")
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -13,17 +13,16 @@ import signal
 import subprocess
 import sys
 import tempfile
 import zipfile
 import mozinfo
 
 __all__ = [
   "ZipFileReader",
-  "addCommonOptions",
   "dumpLeakLog",
   "processLeakLog",
   'systemMemory',
   'environment',
   'dumpScreen',
   "ShutdownLeaks",
   "setAutomationLog",
   ]
@@ -135,40 +134,16 @@ def printstatus(status, name = ""):
     print "TEST-INFO | %s: exit %d\n" % (name, os.WEXITSTATUS(status))
   elif os.WIFSIGNALED(status):
     # The python stdlib doesn't appear to have strsignal(), alas
     print "TEST-INFO | {}: killed by {}".format(name,strsig(os.WTERMSIG(status)))
   else:
     # This is probably a can't-happen condition on Unix, but let's be defensive
     print "TEST-INFO | %s: undecodable exit status %04x\n" % (name, status)
 
-def addCommonOptions(parser, defaults={}):
-  parser.add_option("--xre-path",
-                    action = "store", type = "string", dest = "xrePath",
-                    # individual scripts will set a sane default
-                    default = None,
-                    help = "absolute path to directory containing XRE (probably xulrunner)")
-  if 'SYMBOLS_PATH' not in defaults:
-    defaults['SYMBOLS_PATH'] = None
-  parser.add_option("--symbols-path",
-                    action = "store", type = "string", dest = "symbolsPath",
-                    default = defaults['SYMBOLS_PATH'],
-                    help = "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols")
-  parser.add_option("--debugger",
-                    action = "store", dest = "debugger",
-                    help = "use the given debugger to launch the application")
-  parser.add_option("--debugger-args",
-                    action = "store", dest = "debuggerArgs",
-                    help = "pass the given args to the debugger _before_ "
-                           "the application on the command line")
-  parser.add_option("--debugger-interactive",
-                    action = "store_true", dest = "debuggerInteractive",
-                    help = "prevents the test harness from redirecting "
-                        "stdout and stderr for interactive debuggers")
-
 def dumpLeakLog(leakLogFile, filter = False):
   """Process the leak log, without parsing it.
 
   Use this function if you want the raw log only.
   Use it preferably with the |XPCOM_MEM_LEAK_LOG| environment variable.
   """
 
   # Don't warn (nor "info") if the log file is not there.
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -133,16 +133,17 @@
 #include "nsXBLService.h"
 #include "nsITextControlElement.h"
 #include "nsITextControlFrame.h"
 #include "nsISupportsImpl.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/VRDevice.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsIAtom*
 nsIContent::DoGetID() const
 {
   MOZ_ASSERT(HasID(), "Unexpected call");
@@ -3068,17 +3069,17 @@ GetFullScreenError(nsIDocument* aDoc)
   if (nsContentUtils::IsSitePermDeny(aDoc->NodePrincipal(), "fullscreen")) {
     return "FullScreenDeniedBlocked";
   }
 
   return nullptr;
 }
 
 void
-Element::MozRequestFullScreen()
+Element::MozRequestFullScreen(const RequestFullscreenOptions& aOptions)
 {
   // Only grant full-screen requests if this is called from inside a trusted
   // event handler (i.e. inside an event handler for a user initiated event).
   // This stops the full-screen from being abused similar to the popups of old,
   // and it also makes it harder for bad guys' script to go full-screen and
   // spoof the browser chrome/window and phish logins etc.
   // Note that requests for fullscreen inside a web app's origin are exempt
   // from this restriction.
@@ -3092,17 +3093,22 @@ Element::MozRequestFullScreen()
       new AsyncEventDispatcher(OwnerDoc(),
                                NS_LITERAL_STRING("mozfullscreenerror"),
                                true,
                                false);
     asyncDispatcher->PostDOMEvent();
     return;
   }
 
-  OwnerDoc()->AsyncRequestFullScreen(this);
+  FullScreenOptions opts;
+  if (aOptions.mVrDisplay) {
+    opts.mVRHMDDevice = aOptions.mVrDisplay->GetHMD();
+  }
+
+  OwnerDoc()->AsyncRequestFullScreen(this, opts);
 
   return;
 }
 
 void
 Element::MozRequestPointerLock()
 {
   OwnerDoc()->RequestPointerLock(this);
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -29,16 +29,17 @@
 #include "nsIScrollableFrame.h"
 #include "mozilla/dom/Attr.h"
 #include "nsISMILAttr.h"
 #include "mozilla/dom/DOMRect.h"
 #include "nsAttrValue.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/ElementBinding.h"
 #include "Units.h"
 
 class nsIDOMEventListener;
 class nsIFrame;
 class nsIDOMMozNamedAttrMap;
 class nsIDOMCSSStyleDeclaration;
 class nsIURI;
 class nsIControllers;
@@ -719,17 +720,17 @@ public:
     }
   }
   void ReleaseCapture()
   {
     if (nsIPresShell::GetCapturingContent() == this) {
       nsIPresShell::SetCapturingContent(nullptr, 0);
     }
   }
-  void MozRequestFullScreen();
+  void MozRequestFullScreen(const RequestFullscreenOptions& aOptions);
   void MozRequestPointerLock();
   Attr* GetAttributeNode(const nsAString& aName);
   already_AddRefed<Attr> SetAttributeNode(Attr& aNewAttr,
                                           ErrorResult& aError);
   already_AddRefed<Attr> RemoveAttributeNode(Attr& aOldAttr,
                                              ErrorResult& aError);
   Attr* GetAttributeNodeNS(const nsAString& aNamespaceURI,
                            const nsAString& aLocalName);
@@ -1763,17 +1764,17 @@ NS_IMETHOD SetCapture(bool retargetToEle
 }                                                                             \
 NS_IMETHOD ReleaseCapture(void) MOZ_FINAL                                     \
 {                                                                             \
   Element::ReleaseCapture();                                                  \
   return NS_OK;                                                               \
 }                                                                             \
 NS_IMETHOD MozRequestFullScreen(void) MOZ_FINAL                               \
 {                                                                             \
-  Element::MozRequestFullScreen();                                            \
+  Element::MozRequestFullScreen(mozilla::dom::RequestFullscreenOptions());    \
   return NS_OK;                                                               \
 }                                                                             \
 NS_IMETHOD MozRequestPointerLock(void) MOZ_FINAL                              \
 {                                                                             \
   Element::MozRequestPointerLock();                                           \
   return NS_OK;                                                               \
 }                                                                             \
 using nsINode::QuerySelector;                                                 \
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -35,16 +35,17 @@
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/CellBroadcast.h"
 #include "mozilla/dom/MobileMessageManager.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
 #include "mozilla/dom/Telephony.h"
 #include "mozilla/dom/Voicemail.h"
 #include "mozilla/dom/TVManager.h"
+#include "mozilla/dom/VRDevice.h"
 #include "mozilla/Hal.h"
 #include "nsISiteSpecificUserAgent.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StaticPtr.h"
 #include "Connection.h"
 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
 #include "nsGlobalWindow.h"
 #ifdef MOZ_B2G
@@ -1717,16 +1718,42 @@ Navigator::GetGamepads(nsTArray<nsRefPtr
   }
   NS_ENSURE_TRUE_VOID(mWindow->GetDocShell());
   nsGlobalWindow* win = static_cast<nsGlobalWindow*>(mWindow.get());
   win->SetHasGamepadEventListener(true);
   win->GetGamepads(aGamepads);
 }
 #endif
 
+already_AddRefed<Promise>
+Navigator::GetVRDevices(ErrorResult& aRv)
+{
+  if (!mWindow || !mWindow->GetDocShell()) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
+  nsRefPtr<Promise> p = Promise::Create(go, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsGlobalWindow* win = static_cast<nsGlobalWindow*>(mWindow.get());
+
+  nsTArray<nsRefPtr<VRDevice>> vrDevs;
+  if (!win->GetVRDevices(vrDevs)) {
+    p->MaybeReject(NS_ERROR_FAILURE);
+  } else {
+    p->MaybeResolve(vrDevs);
+  }
+
+  return p.forget();
+}
+
 //*****************************************************************************
 //    Navigator::nsIMozNavigatorNetwork
 //*****************************************************************************
 
 NS_IMETHODIMP
 Navigator::GetProperties(nsINetworkProperties** aProperties)
 {
   ErrorResult rv;
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -241,16 +241,17 @@ public:
 #endif
 #ifdef MOZ_B2G_RIL
   MobileConnectionArray* GetMozMobileConnections(ErrorResult& aRv);
   IccManager* GetMozIccManager(ErrorResult& aRv);
 #endif // MOZ_B2G_RIL
 #ifdef MOZ_GAMEPAD
   void GetGamepads(nsTArray<nsRefPtr<Gamepad> >& aGamepads, ErrorResult& aRv);
 #endif // MOZ_GAMEPAD
+  already_AddRefed<Promise> GetVRDevices(ErrorResult& aRv);
 #ifdef MOZ_B2G_FM
   FMRadio* GetMozFMRadio(ErrorResult& aRv);
 #endif
 #ifdef MOZ_B2G_BT
   bluetooth::BluetoothManager* GetMozBluetooth(ErrorResult& aRv);
 #endif // MOZ_B2G_BT
 #ifdef MOZ_TIME_MANAGER
   time::TimeManager* GetMozTime(ErrorResult& aRv);
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -10725,30 +10725,31 @@ nsIDocument::MozCancelFullScreen()
 
 // Runnable to set window full-screen mode. Used as a script runner
 // to ensure we only call nsGlobalWindow::SetFullScreen() when it's safe to
 // run script. nsGlobalWindow::SetFullScreen() dispatches a synchronous event
 // (handled in chome code) which is unsafe to run if this is called in
 // Element::UnbindFromTree().
 class nsSetWindowFullScreen : public nsRunnable {
 public:
-  nsSetWindowFullScreen(nsIDocument* aDoc, bool aValue)
-    : mDoc(aDoc), mValue(aValue) {}
+  nsSetWindowFullScreen(nsIDocument* aDoc, bool aValue, gfx::VRHMDInfo* aHMD = nullptr)
+    : mDoc(aDoc), mValue(aValue), mHMD(aHMD) {}
 
   NS_IMETHOD Run()
   {
     if (mDoc->GetWindow()) {
-      mDoc->GetWindow()->SetFullScreenInternal(mValue, false);
+      mDoc->GetWindow()->SetFullScreenInternal(mValue, false, mHMD);
     }
     return NS_OK;
   }
 
 private:
   nsCOMPtr<nsIDocument> mDoc;
   bool mValue;
+  nsRefPtr<gfx::VRHMDInfo> mHMD;
 };
 
 static nsIDocument*
 GetFullscreenRootDocument(nsIDocument* aDoc)
 {
   if (!aDoc) {
     return nullptr;
   }
@@ -10758,27 +10759,27 @@ GetFullscreenRootDocument(nsIDocument* a
          (!nsContentUtils::IsFullscreenApiContentOnly() ||
           !nsContentUtils::IsChromeDoc(parent))) {
     doc = parent;
   }
   return doc;
 }
 
 static void
-SetWindowFullScreen(nsIDocument* aDoc, bool aValue)
+SetWindowFullScreen(nsIDocument* aDoc, bool aValue, gfx::VRHMDInfo *aVRHMD = nullptr)
 {
   // Maintain list of fullscreen root documents.
   nsCOMPtr<nsIDocument> root = GetFullscreenRootDocument(aDoc);
   if (aValue) {
     FullscreenRoots::Add(root);
   } else {
     FullscreenRoots::Remove(root);
   }
   if (!nsContentUtils::IsFullscreenApiContentOnly()) {
-    nsContentUtils::AddScriptRunner(new nsSetWindowFullScreen(aDoc, aValue));
+    nsContentUtils::AddScriptRunner(new nsSetWindowFullScreen(aDoc, aValue, aVRHMD));
   }
 }
 
 class nsCallExitFullscreen : public nsRunnable {
 public:
   explicit nsCallExitFullscreen(nsIDocument* aDoc)
     : mDoc(aDoc) {}
   NS_IMETHOD Run()
@@ -11088,54 +11089,58 @@ bool
 nsDocument::IsFullScreenDoc()
 {
   return GetFullScreenElement() != nullptr;
 }
 
 class nsCallRequestFullScreen : public nsRunnable
 {
 public:
-  explicit nsCallRequestFullScreen(Element* aElement)
+  explicit nsCallRequestFullScreen(Element* aElement, FullScreenOptions& aOptions)
     : mElement(aElement),
       mDoc(aElement->OwnerDoc()),
       mWasCallerChrome(nsContentUtils::IsCallerChrome()),
       mHadRequestPending(static_cast<nsDocument*>(mDoc.get())->
-                           mAsyncFullscreenPending)
+                         mAsyncFullscreenPending),
+      mOptions(aOptions)
   {
     static_cast<nsDocument*>(mDoc.get())->
       mAsyncFullscreenPending = true;
   }
 
   NS_IMETHOD Run()
   {
     static_cast<nsDocument*>(mDoc.get())->
       mAsyncFullscreenPending = mHadRequestPending;
     nsDocument* doc = static_cast<nsDocument*>(mDoc.get());
     doc->RequestFullScreen(mElement,
+                           mOptions,
                            mWasCallerChrome,
                            /* aNotifyOnOriginChange */ true);
     return NS_OK;
   }
 
   nsRefPtr<Element> mElement;
   nsCOMPtr<nsIDocument> mDoc;
   bool mWasCallerChrome;
   bool mHadRequestPending;
+  FullScreenOptions mOptions;
 };
 
 void
-nsDocument::AsyncRequestFullScreen(Element* aElement)
+nsDocument::AsyncRequestFullScreen(Element* aElement,
+                                   FullScreenOptions& aOptions)
 {
   NS_ASSERTION(aElement,
     "Must pass non-null element to nsDocument::AsyncRequestFullScreen");
   if (!aElement) {
     return;
   }
   // Request full-screen asynchronously.
-  nsCOMPtr<nsIRunnable> event(new nsCallRequestFullScreen(aElement));
+  nsCOMPtr<nsIRunnable> event(new nsCallRequestFullScreen(aElement, aOptions));
   NS_DispatchToCurrentThread(event);
 }
 
 static void
 LogFullScreenDenied(bool aLogFailure, const char* aMessage, nsIDocument* aDoc)
 {
   if (!aLogFailure) {
     return;
@@ -11193,16 +11198,19 @@ nsDocument::CleanupFullscreenState()
 {
   if (!mFullScreenStack.IsEmpty()) {
     // The top element in the full-screen stack will have full-screen
     // style bits set on it and its ancestors. Remove the style bits.
     // Note the non-top elements won't have the style bits set.
     Element* top = FullScreenStackTop();
     NS_ASSERTION(top, "Should have a top when full-screen stack isn't empty");
     if (top) {
+      // Remove any VR state properties
+      top->DeleteProperty(nsGkAtoms::vr_state);
+
       EventStateManager::SetFullScreenState(top, false);
     }
     mFullScreenStack.Clear();
   }
   SetApprovedForFullscreen(false);
   RemoveFullscreenApprovedObserver();
   mFullscreenRoot = nullptr;
 }
@@ -11229,18 +11237,22 @@ nsDocument::FullScreenStackPush(Element*
 
 void
 nsDocument::FullScreenStackPop()
 {
   if (mFullScreenStack.IsEmpty()) {
     return;
   }
 
+  Element* top = FullScreenStackTop();
+
+  // Remove any VR state properties
+  top->DeleteProperty(nsGkAtoms::vr_state);
+
   // Remove styles from existing top element.
-  Element* top = FullScreenStackTop();
   EventStateManager::SetFullScreenState(top, false);
 
   // Remove top element. Note the remaining top element in the stack
   // will not have full-screen style bits set, so we will need to restore
   // them on the new top element before returning.
   uint32_t last = mFullScreenStack.Length() - 1;
   mFullScreenStack.RemoveElementAt(last);
 
@@ -11318,17 +11330,19 @@ IsInActiveTab(nsIDocument* aDoc)
 
 nsresult nsDocument::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
                                                   const nsAString& aOrigin)
 {
   // Ensure the frame element is the fullscreen element in this document.
   // If the frame element is already the fullscreen element in this document,
   // this has no effect.
   nsCOMPtr<nsIContent> content(do_QueryInterface(aFrameElement));
+  FullScreenOptions opts;
   RequestFullScreen(content->AsElement(),
+                    opts,
                     /* aWasCallerChrome */ false,
                     /* aNotifyOnOriginChange */ false);
 
   // Origin changed in child process, send notifiction, so that chrome can
   // update the UI to reflect the fullscreen origin change if necessary.
   // The BrowserElementChild listens on this, and forwards it over its
   // parent process, where it is redispatched. Chrome (in the root process,
   // which could be *this* process) listens for this notification so that
@@ -11344,18 +11358,27 @@ nsresult nsDocument::RemoteFrameFullscre
 }
 
 nsresult nsDocument::RemoteFrameFullscreenReverted()
 {
   RestorePreviousFullScreenState();
   return NS_OK;
 }
 
+static void
+ReleaseHMDInfoRef(void *, nsIAtom*, void *aPropertyValue, void *)
+{
+  if (aPropertyValue) {
+    static_cast<gfx::VRHMDInfo*>(aPropertyValue)->Release();
+  }
+}
+
 void
 nsDocument::RequestFullScreen(Element* aElement,
+                              FullScreenOptions& aOptions,
                               bool aWasCallerChrome,
                               bool aNotifyOnOriginChange)
 {
   NS_ASSERTION(aElement,
     "Must pass non-null element to nsDocument::RequestFullScreen");
   if (!aElement || aElement == GetFullScreenElement()) {
     return;
   }
@@ -11434,16 +11457,24 @@ nsDocument::RequestFullScreen(Element* a
   // If a document is already in fullscreen, then unlock the mouse pointer
   // before setting a new document to fullscreen
   nsCOMPtr<Element> pointerLockedElement =
     do_QueryReferent(EventStateManager::sPointerLockedElement);
   if (pointerLockedElement) {
     UnlockPointer();
   }
 
+  // Process options -- in this case, just HMD
+  if (aOptions.mVRHMDDevice) {
+    nsRefPtr<gfx::VRHMDInfo> hmdRef = aOptions.mVRHMDDevice;
+    aElement->SetProperty(nsGkAtoms::vr_state, hmdRef.forget().take(),
+                          ReleaseHMDInfoRef,
+                          true);
+  }
+
   // Set the full-screen element. This sets the full-screen style on the
   // element, and the full-screen-ancestor styles on ancestors of the element
   // in this document.
   DebugOnly<bool> x = FullScreenStackPush(aElement);
   NS_ASSERTION(x, "Full-screen state of requesting doc should always change!");
   changed.AppendElement(this);
 
   // Propagate up the document hierarchy, setting the full-screen element as
@@ -11538,17 +11569,17 @@ nsDocument::RequestFullScreen(Element* a
 
   // Make the window full-screen. Note we must make the state changes above
   // before making the window full-screen, as then the document reports as
   // being in full-screen mode when the chrome "fullscreen" event fires,
   // enabling chrome to distinguish between browser and dom full-screen
   // modes. Also note that nsGlobalWindow::SetFullScreen() (which
   // SetWindowFullScreen() calls) proxies to the root window in its hierarchy,
   // and does not operate on the a per-nsIDOMWindow basis.
-  SetWindowFullScreen(this, true);
+  SetWindowFullScreen(this, true, aOptions.mVRHMDDevice);
 }
 
 NS_IMETHODIMP
 nsDocument::GetMozFullScreenElement(nsIDOMElement **aFullScreenElement)
 {
   ErrorResult rv;
   Element* el = GetMozFullScreenElement(rv);
   if (rv.Failed()) {
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -1141,17 +1141,18 @@ public:
   virtual nsresult GetStateObject(nsIVariant** aResult) MOZ_OVERRIDE;
 
   virtual nsDOMNavigationTiming* GetNavigationTiming() const MOZ_OVERRIDE;
   virtual nsresult SetNavigationTiming(nsDOMNavigationTiming* aTiming) MOZ_OVERRIDE;
 
   virtual Element* FindImageMap(const nsAString& aNormalizedMapName) MOZ_OVERRIDE;
 
   virtual Element* GetFullScreenElement() MOZ_OVERRIDE;
-  virtual void AsyncRequestFullScreen(Element* aElement) MOZ_OVERRIDE;
+  virtual void AsyncRequestFullScreen(Element* aElement,
+                                      mozilla::dom::FullScreenOptions& aOptions) MOZ_OVERRIDE;
   virtual void RestorePreviousFullScreenState() MOZ_OVERRIDE;
   virtual bool IsFullscreenLeaf() MOZ_OVERRIDE;
   virtual bool IsFullScreenDoc() MOZ_OVERRIDE;
   virtual void SetApprovedForFullscreen(bool aIsApproved) MOZ_OVERRIDE;
   virtual nsresult RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
                                                 const nsAString& aNewOrigin) MOZ_OVERRIDE;
 
   virtual nsresult RemoteFrameFullscreenReverted() MOZ_OVERRIDE;
@@ -1192,16 +1193,17 @@ public:
   // by chrome code. aNotifyOnOriginChange denotes whether we should send a
   // fullscreen-origin-change notification if requesting fullscreen in this
   // document causes the origin which is fullscreen to change. We may want to
   // *not* send this notification if we're calling RequestFullscreen() as part
   // of a continuation of a request in a subdocument, whereupon the caller will
   // need to send the notification with the origin of the document which
   // originally requested fullscreen, not *this* document's origin.
   void RequestFullScreen(Element* aElement,
+                         mozilla::dom::FullScreenOptions& aOptions,
                          bool aWasCallerChrome,
                          bool aNotifyOnOriginChange);
 
   // Removes all elements from the full-screen stack, removing full-scren
   // styles from the top element in the stack.
   void CleanupFullscreenState();
 
   // Add/remove "fullscreen-approved" observer service notification listener.
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2320,8 +2320,10 @@ GK_ATOM(onsoundend, "onsoundend")
 GK_ATOM(onspeechstart, "onspeechstart")
 GK_ATOM(onspeechend, "onspeechend")
 GK_ATOM(onresult, "onresult")
 GK_ATOM(onnomatch, "onnomatch")
 GK_ATOM(onresume, "onresume")
 GK_ATOM(onmark, "onmark")
 GK_ATOM(onboundary, "onboundary")
 #endif
+
+GK_ATOM(vr_state, "vr-state")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -185,16 +185,18 @@
 #include "mozilla/dom/Promise.h"
 
 #include "mozilla/dom/StructuredCloneTags.h"
 
 #ifdef MOZ_GAMEPAD
 #include "mozilla/dom/GamepadService.h"
 #endif
 
+#include "mozilla/dom/VRDevice.h"
+
 #include "nsRefreshDriver.h"
 
 #include "mozilla/dom/SelectionChangeEvent.h"
 
 #include "mozilla/AddonPathService.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "nsLocation.h"
@@ -1129,17 +1131,18 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
 #endif
 #ifdef MOZ_B2G
     mNetworkUploadObserverEnabled(false),
     mNetworkDownloadObserverEnabled(false),
 #endif
     mCleanedUp(false),
     mDialogAbuseCount(0),
     mAreDialogsEnabled(true),
-    mCanSkipCCGeneration(0)
+    mCanSkipCCGeneration(0),
+    mVRDevicesInitialized(false)
 {
   nsLayoutStatics::AddRef();
 
   // Initialize the PRCList (this).
   PR_INIT_CLIST(this);
 
   if (aOuterWindow) {
     // |this| is an inner window, add this inner window to the outer
@@ -1766,16 +1769,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers)
 
 #ifdef MOZ_GAMEPAD
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads)
 #endif
 
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDevices)
+
   // Traverse stuff from nsPIDOMWindow
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedNode)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenubar)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mToolbar)
@@ -1827,16 +1832,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingStorageEvents)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleObservers)
 
 #ifdef MOZ_GAMEPAD
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads)
 #endif
 
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mVRDevices)
+
   // Unlink stuff from nsPIDOMWindow
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFocusedNode)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMenubar)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mToolbar)
@@ -5918,17 +5925,17 @@ NS_IMETHODIMP
 nsGlobalWindow::SetFullScreen(bool aFullScreen)
 {
   FORWARD_TO_OUTER(SetFullScreen, (aFullScreen), NS_ERROR_NOT_INITIALIZED);
 
   return SetFullScreenInternal(aFullScreen, true);
 }
 
 nsresult
-nsGlobalWindow::SetFullScreenInternal(bool aFullScreen, bool aRequireTrust)
+nsGlobalWindow::SetFullScreenInternal(bool aFullScreen, bool aRequireTrust, gfx::VRHMDInfo* aHMD)
 {
   MOZ_ASSERT(IsOuterWindow());
 
   NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
 
   // Only chrome can change our fullScreen mode, unless we're running in
   // untrusted mode.
   if (aFullScreen == FullScreen() ||
@@ -5940,17 +5947,17 @@ nsGlobalWindow::SetFullScreenInternal(bo
   // via the DocShell tree, and if we are not already the root,
   // call SetFullScreen on that window instead.
   nsCOMPtr<nsIDocShellTreeItem> rootItem;
   mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
   nsCOMPtr<nsPIDOMWindow> window = rootItem ? rootItem->GetWindow() : nullptr;
   if (!window)
     return NS_ERROR_FAILURE;
   if (rootItem != mDocShell)
-    return window->SetFullScreenInternal(aFullScreen, aRequireTrust);
+    return window->SetFullScreenInternal(aFullScreen, aRequireTrust, aHMD);
 
   // make sure we don't try to set full screen on a non-chrome window,
   // which might happen in embedding world
   if (mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome)
     return NS_ERROR_FAILURE;
 
   // If we are already in full screen mode, just return.
   if (mFullScreen == aFullScreen)
@@ -5975,18 +5982,23 @@ nsGlobalWindow::SetFullScreenInternal(bo
   mFullScreen = aFullScreen;
 
   // Sometimes we don't want the top-level widget to actually go fullscreen,
   // for example in the B2G desktop client, we don't want the emulated screen
   // dimensions to appear to increase when entering fullscreen mode; we just
   // want the content to fill the entire client area of the emulator window.
   if (!Preferences::GetBool("full-screen-api.ignore-widgets", false)) {
     nsCOMPtr<nsIWidget> widget = GetMainWidget();
-    if (widget)
-      widget->MakeFullScreen(aFullScreen);
+    if (widget) {
+      nsCOMPtr<nsIScreen> screen;
+      if (aHMD) {
+        screen = aHMD->GetScreen();
+      }
+      widget->MakeFullScreen(aFullScreen, screen);
+    }
   }
 
   if (!mFullScreen) {
     // Force exit from DOM full-screen mode. This is so that if we're in
     // DOM full-screen mode and the user exits full-screen mode with
     // the browser full-screen mode toggle keyboard-shortcut, we'll detect
     // that and leave DOM API full-screen mode too.
     nsIDocument::ExitFullscreen(mDoc, /* async */ false);
@@ -13411,17 +13423,36 @@ nsGlobalWindow::EnumGamepadsForSync(cons
 void
 nsGlobalWindow::SyncGamepadState()
 {
   MOZ_ASSERT(IsInnerWindow());
   if (mHasSeenGamepadInput) {
     mGamepads.EnumerateRead(EnumGamepadsForSync, nullptr);
   }
 }
-#endif
+#endif // MOZ_GAMEPAD
+
+bool
+nsGlobalWindow::GetVRDevices(nsTArray<nsRefPtr<mozilla::dom::VRDevice>>& aDevices)
+{
+  FORWARD_TO_INNER(GetVRDevices, (aDevices), false);
+
+  if (!mVRDevicesInitialized) {
+    bool ok = mozilla::dom::VRDevice::CreateAllKnownVRDevices(ToSupports(this), mVRDevices);
+    if (!ok) {
+      mVRDevices.Clear();
+      return false;
+    }
+  }
+
+  mVRDevicesInitialized = true;
+  aDevices = mVRDevices;
+  return true;
+}
+
 // nsGlobalChromeWindow implementation
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalChromeWindow)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGlobalChromeWindow,
                                                   nsGlobalWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserDOMWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -99,30 +99,34 @@ class nsWindowSizes;
 namespace mozilla {
 class DOMEventTargetHelper;
 namespace dom {
 class BarProp;
 class Console;
 class External;
 class Function;
 class Gamepad;
+class VRDevice;
 class MediaQueryList;
 class MozSelfSupport;
 class Navigator;
 class OwningExternalOrWindowProxy;
 class Promise;
 struct RequestInit;
 class RequestOrUSVString;
 class Selection;
 class SpeechSynthesis;
 class WakeLock;
 namespace indexedDB {
 class IDBFactory;
 } // namespace indexedDB
 } // namespace dom
+namespace gfx {
+class VRHMDInfo;
+} // namespace gfx
 } // namespace mozilla
 
 extern nsresult
 NS_CreateJSTimeoutHandler(nsGlobalWindow *aWindow,
                           bool *aIsInterval,
                           int32_t *aInterval,
                           nsIScriptTimeoutHandler **aRet);
 
@@ -459,17 +463,18 @@ public:
   // Outer windows only.
   virtual bool DispatchCustomEvent(const nsAString& aEventName);
   bool DispatchResizeEvent(const mozilla::CSSIntSize& aSize);
 
   // Inner windows only.
   virtual void RefreshCompartmentPrincipal();
 
   // Outer windows only.
-  virtual nsresult SetFullScreenInternal(bool aIsFullScreen, bool aRequireTrust);
+  virtual nsresult SetFullScreenInternal(bool aIsFullScreen, bool aRequireTrust,
+                                         mozilla::gfx::VRHMDInfo *aHMD = nullptr);
   bool FullScreen() const;
 
   // Inner windows only.
   virtual void SetHasGamepadEventListener(bool aHasGamepad = true);
 
   // nsIInterfaceRequestor
   NS_DECL_NSIINTERFACEREQUESTOR
 
@@ -729,16 +734,18 @@ public:
                                             void* aUserArg);
 #endif
 
   // Inner windows only.
   // Enable/disable updates for gamepad input.
   void EnableGamepadUpdates();
   void DisableGamepadUpdates();
 
+  // Get the VR devices for this window, initializing if necessary
+  bool GetVRDevices(nsTArray<nsRefPtr<mozilla::dom::VRDevice>>& aDevices);
 
 #define EVENT(name_, id_, type_, struct_)                                     \
   mozilla::dom::EventHandlerNonNull* GetOn##name_()                           \
   {                                                                           \
     mozilla::EventListenerManager* elm = GetExistingListenerManager();        \
     return elm ? elm->GetEventHandler(nsGkAtoms::on##name_, EmptyString())    \
                : nullptr;                                                     \
   }                                                                           \
@@ -1625,16 +1632,23 @@ protected:
 #ifdef MOZ_WEBSPEECH
   // mSpeechSynthesis is only used on inner windows.
   nsRefPtr<mozilla::dom::SpeechSynthesis> mSpeechSynthesis;
 #endif
 
   // This is the CC generation the last time we called CanSkip.
   uint32_t mCanSkipCCGeneration;
 
+  // Did VR get initialized for this window?
+  bool                                       mVRDevicesInitialized;
+  // The VRDevies for this window
+  nsTArray<nsRefPtr<mozilla::dom::VRDevice>> mVRDevices;
+  // Any attached HMD when fullscreen
+  nsRefPtr<mozilla::gfx::VRHMDInfo>          mVRHMDInfo;
+
   friend class nsDOMScriptableHelper;
   friend class nsDOMWindowUtils;
   friend class PostMessageEvent;
   friend class DesktopNotification;
 
   static WindowByIdTable* sWindowsById;
   static bool sWarnedAboutWindowInternal;
 };
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -22,16 +22,17 @@
 #include "mozilla/net/ReferrerPolicy.h"  // for member
 #include "nsWeakReference.h"
 #include "mozilla/dom/DocumentBinding.h"
 #include "mozilla/WeakPtr.h"
 #include "Units.h"
 #include "nsExpirationTracker.h"
 #include "nsClassHashtable.h"
 #include "prclist.h"
+#include "gfxVR.h"
 
 class imgIRequest;
 class nsAString;
 class nsBindingManager;
 class nsIDocShell;
 class nsDocShell;
 class nsDOMNavigationTiming;
 class nsFrameLoader;
@@ -130,16 +131,22 @@ class XPathEvaluator;
 class XPathExpression;
 class XPathNSResolver;
 class XPathResult;
 template<typename> class OwningNonNull;
 template<typename> class Sequence;
 
 template<typename, typename> class CallbackObjectHolder;
 typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
+
+struct FullScreenOptions {
+  FullScreenOptions() { }
+  nsRefPtr<gfx::VRHMDInfo> mVRHMDDevice;
+};
+
 } // namespace dom
 } // namespace mozilla
 
 #define NS_IDOCUMENT_IID \
 { 0x1f343423, 0x957c, 0x4da3, \
   { 0xaa, 0xa3, 0x07, 0x37, 0x54, 0x3e, 0x79, 0x2a } }
 
 // Enum for requesting a particular type of document when creating a doc
@@ -159,16 +166,17 @@ enum DocumentFlavor {
 
 // Some function forward-declarations
 class nsContentList;
 
 already_AddRefed<nsContentList>
 NS_GetContentList(nsINode* aRootNode,
                   int32_t aMatchNameSpaceId,
                   const nsAString& aTagname);
+
 //----------------------------------------------------------------------
 
 // Document interface.  This is implemented by all document objects in
 // Gecko.
 class nsIDocument : public nsINode
 {
   typedef mozilla::dom::GlobalObject GlobalObject;
 public:
@@ -1059,17 +1067,18 @@ public:
    * (if any) is pushed onto the fullscreen element stack, and it can be
    * returned to fullscreen status by calling RestorePreviousFullScreenState().
    *
    * Note that requesting fullscreen in a document also makes the element which
    * contains this document in this document's parent document fullscreen. i.e.
    * the <iframe> or <browser> that contains this document is also mode
    * fullscreen. This happens recursively in all ancestor documents.
    */
-  virtual void AsyncRequestFullScreen(Element* aElement) = 0;
+  virtual void AsyncRequestFullScreen(Element* aElement,
+                                      mozilla::dom::FullScreenOptions& aOptions) = 0;
 
   /**
    * Called when a frame in a child process has entered fullscreen or when a
    * fullscreen frame in a child process changes to another origin.
    * aFrameElement is the frame element which contains the child-process
    * fullscreen document, and aNewOrigin is the origin of the new fullscreen
    * document.
    */
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -33,16 +33,19 @@ class nsPIWindowRoot;
 class nsXBLPrototypeHandler;
 struct nsTimeout;
 
 namespace mozilla {
 namespace dom {
 class AudioContext;
 class Element;
 }
+namespace gfx {
+class VRHMDInfo;
+}
 }
 
 // Popup control state enum. The values in this enum must go from most
 // permissive to least permissive so that it's safe to push state in
 // all situations. Pushing popup state onto the stack never makes the
 // current popup state less permissive (see
 // nsGlobalWindow::PushPopupControlState()).
 enum PopupControlState {
@@ -55,18 +58,18 @@ enum PopupControlState {
 enum UIStateChangeType
 {
   UIStateChangeType_NoChange,
   UIStateChangeType_Set,
   UIStateChangeType_Clear
 };
 
 #define NS_PIDOMWINDOW_IID \
-{ 0x71412748, 0x6368, 0x4332, \
-  { 0x82, 0x66, 0xff, 0xaa, 0x19, 0xda, 0x09, 0x7c } }
+{ 0x19fb3019, 0x7b5d, 0x4235, \
+  { 0xa9, 0x59, 0xa2, 0x31, 0xa2, 0xe7, 0x94, 0x79 } }
 
 class nsPIDOMWindow : public nsIDOMWindowInternal
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIDOMWINDOW_IID)
 
   virtual nsPIDOMWindow* GetPrivateRoot() = 0;
 
@@ -455,19 +458,23 @@ public:
     return mMayHaveTouchCaret;
   }
 
   /**
    * Moves the top-level window into fullscreen mode if aIsFullScreen is true,
    * otherwise exits fullscreen. If aRequireTrust is true, this method only
    * changes window state in a context trusted for write.
    *
+   * If aHMD is not null, the window is made full screen on the given VR HMD
+   * device instead of its currrent display.
+   *
    * Outer windows only.
    */
-  virtual nsresult SetFullScreenInternal(bool aIsFullScreen, bool aRequireTrust) = 0;
+  virtual nsresult SetFullScreenInternal(bool aIsFullScreen, bool aRequireTrust,
+                                         mozilla::gfx::VRHMDInfo *aHMD = nullptr) = 0;
 
   /**
    * Call this to check whether some node (this window, its document,
    * or content in that document) has a mouseenter/leave event listener.
    */
   bool HasMouseEnterLeaveEventListeners()
   {
     return mMayHaveMouseEnterLeaveEventListener;
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1267,16 +1267,24 @@ DOMInterfaces = {
 'URL' : [{
     'wrapperCache': False,
 },
 {
     'workers': True,
     'wrapperCache': False,
 }],
 
+'VRFieldOfViewReadOnly': {
+    'concrete': False
+},
+
+'VRDevice': {
+    'concrete': False
+},
+
 'VTTCue': {
     'nativeType': 'mozilla::dom::TextTrackCue'
 },
 
 'VTTRegion': {
   'nativeType': 'mozilla::dom::TextTrackRegion',
 },
 
new file mode 100644
--- /dev/null
+++ b/dom/events/test/bug1096146_embedded.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Embedded iframe</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="">
+  <p id="display"></p>
+  <h1>Top</h1>
+  <input id="input" style="display: block;">
+  <pre id="test">
+  <div id="content" style="height: 2000px;"></div>
+  <h1>Bottom</h1>
+</body>
+</html>
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -171,8 +171,11 @@ skip-if = buildapp == 'mulet' || buildap
 support-files =
   bug989198_embedded.html
   bug989198_helper.js
 [test_dom_before_after_keyboard_event_remote.html]
 support-files =
   bug989198_embedded.html
   bug989198_helper.js
 skip-if = buildapp == 'b2g' || e10s
+[test_bug1096146.html]
+support-files =
+  bug1096146_embedded.html
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_bug1096146.html
@@ -0,0 +1,170 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1096146
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1096146</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTests();">
+<a target="_blank"
+   href="https://bugzilla.mozilla.org/show_bug.cgi?id=1096146">Mozilla Bug 1096146</a>
+<div id="content" style="display: none;">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+const kKeydownEvent = 0x1;
+const kScrollEvent  = 0x2;
+
+var gCurrentTest = 0;
+var kTests = [
+  {
+    description: "no preventDefault at 'mozbrowserbeforekeydown'",
+    expectedEvents: kKeydownEvent | kScrollEvent,
+    resultEvents: 0x0,
+    doPreventDefault: false
+  },
+  {
+    description: "do preventDefault at 'mozbrowserbeforekeydown'",
+    expectedEvents: 0x0,
+    resultEvents: 0x0,
+    doPreventDefault: true
+  }
+]
+
+function frameScript()
+{
+  function handler(e) {
+    sendSyncMessage("forwardevent", { type: e.type });
+  }
+  addEventListener('keydown', handler);
+  addEventListener('scroll', handler);
+}
+
+function testDefaultAction()
+{
+  synthesizeKey('VK_END', {}, document.getElementById("embedded").contentWindow);
+  setTimeout(function () {
+    is(kTests[gCurrentTest].expectedEvents,
+       kTests[gCurrentTest].resultEvents,
+       "verify result");
+    runTests();
+  }, 500);
+}
+
+function prepareTest()
+{
+  var handler;
+  if (kTests[gCurrentTest].doPreventDefault) {
+    handler = preventDefaultHandler;
+  } else {
+    handler = noPreventDefaultHandler;
+  }
+  window.addEventListener("mozbrowserbeforekeydown", handler);
+
+  var iframe = document.createElement("iframe");
+  iframe.id = "embedded";
+  iframe.src = "bug1096146_embedded.html";
+  iframe.setAttribute("remote", "false");
+  SpecialPowers.wrap(iframe).mozbrowser = true;
+
+  iframe.addEventListener("mozbrowserloadend", function onloadend() {
+    iframe.removeEventListener("mozbrowserloadend", onloadend);
+    iframe.focus();
+    var mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
+    mm.addMessageListener("forwardevent", function(msg) {
+      var value = 0;
+      switch(msg.json.type) {
+        case "scroll":
+          value = kScrollEvent;
+          break;
+        case "keydown":
+          value = kKeydownEvent;
+          break;
+        default:
+          ok(false, "unexpected event");
+      }
+
+      kTests[gCurrentTest].resultEvents =
+        kTests[gCurrentTest].resultEvents | value;
+    });
+    mm.loadFrameScript("data:,(" + frameScript.toString() + ")();", false);
+    runTests();
+    return;
+  });
+
+  document.body.appendChild(iframe);
+}
+
+function preventDefaultHandler(evt)
+{
+  ok(true, "receive " + evt.type + " and do preventDefault.");
+  evt.preventDefault();
+}
+
+function noPreventDefaultHandler(evt)
+{
+  ok(true, "receive " + evt.type + ".");
+}
+
+function teardownHandler()
+{
+  var handler;
+  if (kTests[gCurrentTest].doPreventDefault) {
+    handler = preventDefaultHandler;
+  } else {
+    handler = noPreventDefaultHandler;
+  }
+  window.removeEventListener("mozbrowserbeforekeydown", handler);
+
+  runTests();
+}
+
+var tests = [
+  function addPermissions() {
+    SpecialPowers.pushPermissions(
+      [{ type: "before-after-keyboard-event", allow: true, context: document },
+       { type: "browser", allow: true, context: document }],
+      runTests);
+  },
+  function addPreferences() {
+    SpecialPowers.pushPrefEnv(
+      { "set": [["dom.beforeAfterKeyboardEvent.enabled", true],
+                ["dom.mozBrowserFramesEnabled", true],
+                ["dom.ipc.tabs.disabled", false]] },
+      runTests);
+  },
+
+  prepareTest,
+  testDefaultAction,
+  teardownHandler,
+
+  function() {
+    gCurrentTest++;
+    runTests();
+  },
+  prepareTest,
+  testDefaultAction,
+  teardownHandler
+];
+
+function runTests()
+{
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -5342,16 +5342,23 @@ public:
     MOZ_ASSERT(!mRegisteredWithQuotaManager);
 
     mRegisteredWithQuotaManager = true;
   }
 
   void
   CloseOnOwningThread();
 
+  void
+  AssertInvalidatedOnMainThread() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mInvalidatedOnMainThread);
+  }
+
   NS_DECL_THREADSAFE_ISUPPORTS
 
 private:
   ~DatabaseOfflineStorage()
   {
     MOZ_ASSERT(!mDatabase);
     MOZ_ASSERT(!mRegisteredWithQuotaManager);
   }
@@ -9509,17 +9516,19 @@ QuotaClient::WaitForStoragesToComplete(n
 }
 
 void
 QuotaClient::AbortTransactionsForStorage(nsIOfflineStorage* aStorage)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aStorage);
   MOZ_ASSERT(aStorage->GetClient() == this);
-  MOZ_ASSERT(aStorage->IsClosed());
+
+  static_cast<DatabaseOfflineStorage*>(aStorage)->
+    AssertInvalidatedOnMainThread();
 
   // Nothing to do here, calling DatabaseOfflineStorage::Close() should have
   // aborted any transactions already.
 }
 
 bool
 QuotaClient::HasTransactionsForStorage(nsIOfflineStorage* aStorage)
 {
@@ -9904,18 +9913,16 @@ DatabaseOfflineStorage::InvalidateOnMain
   nsCOMPtr<nsIRunnable> runnable =
     NS_NewRunnableMethod(this,
                          &DatabaseOfflineStorage::InvalidateOnOwningThread);
   MOZ_ASSERT(runnable);
 
   nsCOMPtr<nsIEventTarget> owningThread = mOwningThread;
   MOZ_ASSERT(owningThread);
 
-  CloseOnMainThread();
-
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(owningThread->Dispatch(runnable,
                                                       NS_DISPATCH_NORMAL)));
 }
 
 void
 DatabaseOfflineStorage::InvalidateOnOwningThread()
 {
   AssertIsOnBackgroundThread();
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -745,16 +745,49 @@ private:
         MOZ_ASSERT(NS_IsMainThread());
         MOZ_ASSERT(mInfo);
 
         mInfo->FireCallback();
         return NS_OK;
     }
 };
 
+class TabChild::DelayedDeleteRunnable MOZ_FINAL
+  : public nsRunnable
+{
+    nsRefPtr<TabChild> mTabChild;
+
+public:
+    DelayedDeleteRunnable(TabChild* aTabChild)
+      : mTabChild(aTabChild)
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        MOZ_ASSERT(aTabChild);
+    }
+
+private:
+    ~DelayedDeleteRunnable()
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        MOZ_ASSERT(!mTabChild);
+    }
+
+    NS_IMETHOD
+    Run()
+    {
+        MOZ_ASSERT(NS_IsMainThread());
+        MOZ_ASSERT(mTabChild);
+
+        unused << PBrowserChild::Send__delete__(mTabChild);
+
+        mTabChild = nullptr;
+        return NS_OK;
+    }
+};
+
 StaticRefPtr<TabChild> sPreallocatedTab;
 
 /*static*/
 std::map<TabId, nsRefPtr<TabChild>>&
 TabChild::NestedTabChildMap()
 {
   MOZ_ASSERT(NS_IsMainThread());
   static std::map<TabId, nsRefPtr<TabChild>> sNestedTabChildMap;
@@ -2743,17 +2776,22 @@ TabChild::RecvDestroy()
     mozilla::services::GetObserverService();
 
   observerService->RemoveObserver(this, BROWSER_ZOOM_TO_RECT);
   observerService->RemoveObserver(this, BEFORE_FIRST_PAINT);
 
   // XXX what other code in ~TabChild() should we be running here?
   DestroyWindow();
 
-  return Send__delete__(this);
+  // Bounce through the event loop once to allow any delayed teardown runnables
+  // that were just generated to have a chance to run.
+  nsCOMPtr<nsIRunnable> deleteRunnable = new DelayedDeleteRunnable(this);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(deleteRunnable)));
+
+  return true;
 }
 
 bool
 TabChild::RecvSetUpdateHitRegion(const bool& aEnabled)
 {
     mUpdateHitRegion = aEnabled;
     return true;
 }
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -578,16 +578,17 @@ private:
       MOZ_ASSERT(mUniqueId == 0);
 
       mUniqueId = aTabId;
       NestedTabChildMap()[mUniqueId] = this;
     }
 
     class CachedFileDescriptorInfo;
     class CachedFileDescriptorCallbackRunnable;
+    class DelayedDeleteRunnable;
 
     TextureFactoryIdentifier mTextureFactoryIdentifier;
     nsCOMPtr<nsIWebNavigation> mWebNav;
     nsCOMPtr<nsIWidget> mWidget;
     nsCOMPtr<nsIURI> mLastURI;
     RenderFrameChild* mRemoteFrame;
     nsRefPtr<nsIContentChild> mManager;
     uint32_t mChromeFlags;
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -88,17 +88,17 @@ public:
                                         StreamTime aTrackOffset,
                                         uint32_t aTrackEvents,
                                         const MediaSegment& aQueuedMedia) MOZ_OVERRIDE
   {
     if (aTrackEvents & (TRACK_EVENT_CREATED | TRACK_EVENT_ENDED)) {
       nsRefPtr<TrackChange> runnable =
         new TrackChange(this, aID, aTrackOffset, aTrackEvents,
                         aQueuedMedia.GetType());
-      NS_DispatchToMainThread(runnable);
+      aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget());
     }
   }
 
 private:
   // These fields may only be accessed on the main thread
   DOMMediaStream* mStream;
 };
 
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -1131,17 +1131,17 @@ MediaStreamGraphImpl::PlayVideo(MediaStr
                               targetTime);
     } else {
       output->SetCurrentFrame(frame->GetIntrinsicSize(), frame->GetImage(),
                               targetTime);
     }
 
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(output, &VideoFrameContainer::Invalidate);
-    NS_DispatchToMainThread(event);
+    DispatchToMainThreadAfterStreamStateUpdate(event.forget());
   }
   if (!aStream->mNotifiedFinished) {
     aStream->mLastPlayedVideoFrame = *frame;
   }
 }
 
 bool
 MediaStreamGraphImpl::ShouldUpdateMainThread()
--- a/dom/media/TrackUnionStream.cpp
+++ b/dom/media/TrackUnionStream.cpp
@@ -76,18 +76,18 @@ TrackUnionStream::TrackUnionStream(DOMMe
       return;
     }
     nsAutoTArray<bool,8> mappedTracksFinished;
     nsAutoTArray<bool,8> mappedTracksWithMatchingInputTracks;
     for (uint32_t i = 0; i < mTrackMap.Length(); ++i) {
       mappedTracksFinished.AppendElement(true);
       mappedTracksWithMatchingInputTracks.AppendElement(false);
     }
-    bool allFinished = true;
-    bool allHaveCurrentData = true;
+    bool allFinished = !mInputs.IsEmpty();
+    bool allHaveCurrentData = !mInputs.IsEmpty();
     for (uint32_t i = 0; i < mInputs.Length(); ++i) {
       MediaStream* stream = mInputs[i]->GetSource();
       if (!stream->IsFinishedOnGraphThread()) {
         // XXX we really should check whether 'stream' has finished within time aTo,
         // not just that it's finishing when all its queued data eventually runs
         // out.
         allFinished = false;
       }
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -100,16 +100,17 @@ DIRS += [
     'inputmethod',
     'webidl',
     'xbl',
     'xml',
     'xslt',
     'xul',
     'resourcestats',
     'manifest',
+    'vr',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['plugins/ipc/hangui']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     DIRS += [
         'speakermanager',
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -437,16 +437,18 @@ var interfaceNamesInGlobalScope =
     {name: "GamepadEvent", b2g: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "HashChangeEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Headers", pref: "dom.fetch.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "History",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "HMDVRDevice", pref: "dom.vr.enabled"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "HTMLAllCollection",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "HTMLAnchorElement",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "HTMLAppletElement",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "HTMLAreaElement",
 // IMPORTANT: Do not change this list without review from a DOM peer!
@@ -875,16 +877,18 @@ var interfaceNamesInGlobalScope =
     {name: "PointerEvent", pref: "dom.w3c_pointer_events.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PopStateEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PopupBlockedEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PopupBoxObject", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "PositionSensorVRDevice", pref: "dom.vr.enabled"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "ProcessingInstruction",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ProgressEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Promise",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PropertyNodeList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
@@ -1287,16 +1291,24 @@ var interfaceNamesInGlobalScope =
     "UserProximityEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ValidityState",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "VideoPlaybackQuality", pref: "media.mediasource.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "VideoStreamTrack",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "VRDevice", pref: "dom.vr.enabled"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "VRPositionState", pref: "dom.vr.enabled"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "VRFieldOfView", pref: "dom.vr.enabled"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "VRFieldOfViewReadOnly", pref: "dom.vr.enabled"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "VTTCue",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "VTTRegion", pref: "media.webvtt.regions.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WaveShaperNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "WebGLActiveInfo",
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/vr/VRDevice.cpp
@@ -0,0 +1,322 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWrapperCache.h"
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/VRDeviceBinding.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/VRDevice.h"
+#include "gfxVR.h"
+#include "nsIFrame.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(VRFieldOfView, mParent)
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRFieldOfView, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRFieldOfView, Release)
+
+already_AddRefed<VRFieldOfView>
+VRFieldOfView::Constructor(const GlobalObject& aGlobal, const VRFieldOfViewInit& aParams,
+                           ErrorResult& aRV)
+{
+  nsRefPtr<VRFieldOfView> obj =
+    new VRFieldOfView(aGlobal.GetAsSupports(),
+                      aParams.mUpDegrees, aParams.mRightDegrees,
+                      aParams.mDownDegrees, aParams.mLeftDegrees);
+  return obj.forget();
+}
+
+already_AddRefed<VRFieldOfView>
+VRFieldOfView::Constructor(const GlobalObject& aGlobal,
+                           double aUpDegrees, double aRightDegrees,
+                           double aDownDegrees, double aLeftDegrees,
+                           ErrorResult& aRV)
+{
+  nsRefPtr<VRFieldOfView> obj =
+    new VRFieldOfView(aGlobal.GetAsSupports(),
+                      aUpDegrees, aRightDegrees,
+                      aDownDegrees, aLeftDegrees);
+  return obj.forget();
+}
+
+JSObject*
+VRFieldOfView::WrapObject(JSContext* aCx)
+{
+  return VRFieldOfViewBinding::Wrap(aCx, this);
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(VRPositionState, mParent)
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRPositionState, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRPositionState, Release)
+
+VRPositionState::VRPositionState(nsISupports* aParent, const gfx::VRHMDSensorState& aState)
+  : mParent(aParent)
+  , mVRState(aState)
+{
+  mTimeStamp = aState.timestamp;
+
+  if (aState.flags & gfx::VRHMDInfo::State_Position) {
+    mPosition = new DOMPoint(mParent, aState.position[0], aState.position[1], aState.position[2], 0.0);
+  }
+
+  if (aState.flags & gfx::VRHMDInfo::State_Orientation) {
+    mOrientation = new DOMPoint(mParent, aState.orientation[0], aState.orientation[1], aState.orientation[2], aState.orientation[3]);
+  }
+}
+
+DOMPoint*
+VRPositionState::GetLinearVelocity()
+{
+  if (!mLinearVelocity) {
+    mLinearVelocity = new DOMPoint(mParent, mVRState.linearVelocity[0], mVRState.linearVelocity[1], mVRState.linearVelocity[2], 0.0);
+  }
+  return mLinearVelocity;
+}
+
+DOMPoint*
+VRPositionState::GetLinearAcceleration()
+{
+  if (!mLinearAcceleration) {
+    mLinearAcceleration = new DOMPoint(mParent, mVRState.linearAcceleration[0], mVRState.linearAcceleration[1], mVRState.linearAcceleration[2], 0.0);
+  }
+  return mLinearAcceleration;
+}
+
+DOMPoint*
+VRPositionState::GetAngularVelocity()
+{
+  if (!mAngularVelocity) {
+    mAngularVelocity = new DOMPoint(mParent, mVRState.angularVelocity[0], mVRState.angularVelocity[1], mVRState.angularVelocity[2], 0.0);
+  }
+  return mAngularVelocity;
+}
+
+DOMPoint*
+VRPositionState::GetAngularAcceleration()
+{
+  if (!mAngularAcceleration) {
+    mAngularAcceleration = new DOMPoint(mParent, mVRState.angularAcceleration[0], mVRState.angularAcceleration[1], mVRState.angularAcceleration[2], 0.0);
+  }
+  return mAngularAcceleration;
+}
+
+JSObject*
+VRPositionState::WrapObject(JSContext* aCx)
+{
+  return VRPositionStateBinding::Wrap(aCx, this);
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(VRDevice)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(VRDevice)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(VRDevice)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(VRDevice, mParent)
+
+/* virtual */ JSObject*
+HMDVRDevice::WrapObject(JSContext* aCx)
+{
+  return HMDVRDeviceBinding::Wrap(aCx, this);
+}
+
+/* virtual */ JSObject*
+PositionSensorVRDevice::WrapObject(JSContext* aCx)
+{
+  return PositionSensorVRDeviceBinding::Wrap(aCx, this);
+}
+
+static void
+ReleaseHMDInfoRef(void *, nsIAtom*, void *aPropertyValue, void *)
+{
+  if (aPropertyValue) {
+    static_cast<VRHMDInfo*>(aPropertyValue)->Release();
+  }
+}
+
+void
+HMDVRDevice::XxxToggleElementVR(Element& aElement)
+{
+  VRHMDInfo* hmdPtr = static_cast<VRHMDInfo*>(aElement.GetProperty(nsGkAtoms::vr_state));
+  if (hmdPtr) {
+    aElement.DeleteProperty(nsGkAtoms::vr_state);
+    return;
+  }
+
+  nsRefPtr<VRHMDInfo> hmdRef = mHMD;
+  aElement.SetProperty(nsGkAtoms::vr_state, hmdRef.forget().take(),
+                       ReleaseHMDInfoRef,
+                       true);
+}
+
+namespace {
+
+gfx::VRHMDInfo::Eye
+EyeToEye(const VREye& aEye)
+{
+  return aEye == VREye::Left ? gfx::VRHMDInfo::Eye_Left : gfx::VRHMDInfo::Eye_Right;
+}
+
+class HMDInfoVRDevice : public HMDVRDevice
+{
+public:
+  HMDInfoVRDevice(nsISupports* aParent, gfx::VRHMDInfo* aHMD)
+    : HMDVRDevice(aParent, aHMD)
+  {
+    // XXX TODO use real names/IDs
+    mHWID.AppendPrintf("HMDInfo-0x%llx", aHMD);
+    mDeviceId.AssignLiteral("somedevid");
+    mDeviceName.AssignLiteral("HMD Device");
+
+    mValid = true;
+  }
+
+  virtual ~HMDInfoVRDevice() { }
+
+  /* If a field of view that is set to all 0's is passed in,
+   * the recommended field of view for that eye is used.
+   */
+  virtual void SetFieldOfView(const VRFieldOfViewInit& aLeftFOV,
+                              const VRFieldOfViewInit& aRightFOV,
+                              double zNear, double zFar) MOZ_OVERRIDE
+  {
+    gfx::VRFieldOfView left = gfx::VRFieldOfView(aLeftFOV.mUpDegrees, aLeftFOV.mRightDegrees,
+                                                 aLeftFOV.mDownDegrees, aLeftFOV.mLeftDegrees);
+    gfx::VRFieldOfView right = gfx::VRFieldOfView(aRightFOV.mUpDegrees, aRightFOV.mRightDegrees,
+                                                  aRightFOV.mDownDegrees, aRightFOV.mLeftDegrees);
+
+    if (left.IsZero())
+      left = mHMD->GetRecommendedEyeFOV(VRHMDInfo::Eye_Left);
+    if (right.IsZero())
+      right = mHMD->GetRecommendedEyeFOV(VRHMDInfo::Eye_Right);
+
+    mHMD->SetFOV(left, right, zNear, zFar);
+  }
+
+  virtual already_AddRefed<DOMPoint> GetEyeTranslation(VREye aEye) MOZ_OVERRIDE
+  {
+    gfx::Point3D p = mHMD->GetEyeTranslation(EyeToEye(aEye));
+
+    nsRefPtr<DOMPoint> obj = new DOMPoint(mParent, p.x, p.y, p.z, 0.0);
+    return obj.forget();
+  }
+
+  virtual already_AddRefed<VRFieldOfView> GetCurrentEyeFieldOfView(VREye aEye) MOZ_OVERRIDE
+  {
+    return CopyFieldOfView(mHMD->GetEyeFOV(EyeToEye(aEye)));
+  }
+
+  virtual already_AddRefed<VRFieldOfView> GetRecommendedEyeFieldOfView(VREye aEye) MOZ_OVERRIDE
+  {
+    return CopyFieldOfView(mHMD->GetRecommendedEyeFOV(EyeToEye(aEye)));
+  }
+
+  virtual already_AddRefed<VRFieldOfView> GetMaximumEyeFieldOfView(VREye aEye) MOZ_OVERRIDE
+  {
+    return CopyFieldOfView(mHMD->GetMaximumEyeFOV(EyeToEye(aEye)));
+  }
+
+  virtual already_AddRefed<DOMRect> GetRecommendedEyeRenderRect(VREye aEye) MOZ_OVERRIDE
+  {
+    const IntSize& a(mHMD->SuggestedEyeResolution());
+    nsRefPtr<DOMRect> obj =
+      new DOMRect(mParent,
+                  (aEye == VREye::Left) ? 0 : a.width, 0,
+                  a.width, a.height);
+    return obj.forget();
+  }
+
+protected:
+  already_AddRefed<VRFieldOfView>
+  CopyFieldOfView(const gfx::VRFieldOfView& aSrc)
+  {
+    nsRefPtr<VRFieldOfView> obj =
+      new VRFieldOfView(mParent, aSrc.upDegrees, aSrc.rightDegrees,
+                        aSrc.downDegrees, aSrc.leftDegrees);
+    return obj.forget();
+  }
+};
+
+class HMDPositionVRDevice : public PositionSensorVRDevice
+{
+public:
+  HMDPositionVRDevice(nsISupports* aParent, gfx::VRHMDInfo* aHMD)
+    : PositionSensorVRDevice(aParent)
+    , mHMD(aHMD)
+    , mTracking(false)
+  {
+    // XXX TODO use real names/IDs
+    mHWID.AppendPrintf("HMDInfo-0x%llx", aHMD);
+    mDeviceId.AssignLiteral("somedevid");
+    mDeviceName.AssignLiteral("HMD Position Device");
+
+    mValid = true;
+  }
+
+  ~HMDPositionVRDevice()
+  {
+    if (mTracking) {
+      mHMD->StopSensorTracking();
+    }
+  }
+
+  virtual already_AddRefed<VRPositionState> GetState(double timeOffset) MOZ_OVERRIDE
+  {
+    if (!mTracking) {
+      mHMD->StartSensorTracking();
+      mTracking = true;
+    }
+
+    gfx::VRHMDSensorState state = mHMD->GetSensorState(timeOffset);
+    nsRefPtr<VRPositionState> obj = new VRPositionState(mParent, state);
+
+    return obj.forget();
+  }
+
+  virtual void ZeroSensor() MOZ_OVERRIDE
+  {
+    mHMD->ZeroSensor();
+  }
+
+protected:
+  nsRefPtr<gfx::VRHMDInfo> mHMD;
+  bool mTracking;
+};
+
+} // namespace
+
+bool
+VRDevice::CreateAllKnownVRDevices(nsISupports *aParent, nsTArray<nsRefPtr<VRDevice>>& aDevices)
+{
+  if (!gfx::VRHMDManagerOculus::Init()) {
+    NS_WARNING("Failed to initialize Oculus HMD Manager");
+    return false;
+  }
+
+  nsTArray<nsRefPtr<gfx::VRHMDInfo>> hmds;
+  gfx::VRHMDManagerOculus::GetOculusHMDs(hmds);
+
+  for (size_t i = 0; i < hmds.Length(); ++i) {
+    uint32_t sensorBits = hmds[i]->GetSupportedSensorStateBits();
+    aDevices.AppendElement(new HMDInfoVRDevice(aParent, hmds[i]));
+
+    if (sensorBits &
+        (gfx::VRHMDInfo::State_Position | gfx::VRHMDInfo::State_Orientation))
+    {
+      aDevices.AppendElement(new HMDPositionVRDevice(aParent, hmds[i]));
+    }
+  }
+
+  return true;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/vr/VRDevice.h
@@ -0,0 +1,235 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_VRDevice_h_
+#define mozilla_dom_VRDevice_h_
+
+#include <stdint.h>
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/VRDeviceBinding.h"
+#include "mozilla/dom/DOMPoint.h"
+#include "mozilla/dom/DOMRect.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+#include "gfxVR.h"
+
+namespace mozilla {
+namespace dom {
+
+class Element;
+
+class VRFieldOfViewReadOnly : public nsWrapperCache
+{
+public:
+  VRFieldOfViewReadOnly(nsISupports* aParent,
+                        double aUpDegrees, double aRightDegrees,
+                        double aDownDegrees, double aLeftDegrees)
+    : mParent(aParent)
+    , mUpDegrees(aUpDegrees)
+    , mRightDegrees(aRightDegrees)
+    , mDownDegrees(aDownDegrees)
+    , mLeftDegrees(aLeftDegrees)
+  {
+  }
+
+  double UpDegrees() const { return mUpDegrees; }
+  double RightDegrees() const { return mRightDegrees; }
+  double DownDegrees() const { return mDownDegrees; }
+  double LeftDegrees() const { return mLeftDegrees; }
+
+protected:
+  nsCOMPtr<nsISupports> mParent;
+  double mUpDegrees;
+  double mRightDegrees;
+  double mDownDegrees;
+  double mLeftDegrees;
+};
+
+class VRFieldOfView MOZ_FINAL : public VRFieldOfViewReadOnly
+{
+  ~VRFieldOfView() {}
+public:
+  explicit VRFieldOfView(nsISupports* aParent,
+                         double aUpDegrees = 0.0, double aRightDegrees = 0.0,
+                         double aDownDegrees = 0.0, double aLeftDegrees = 0.0)
+    : VRFieldOfViewReadOnly(aParent, aUpDegrees, aRightDegrees, aDownDegrees, aLeftDegrees)
+  {}
+
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRFieldOfView)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRFieldOfView)
+
+  static already_AddRefed<VRFieldOfView>
+  Constructor(const GlobalObject& aGlobal, const VRFieldOfViewInit& aParams,
+              ErrorResult& aRv);
+
+  static already_AddRefed<VRFieldOfView>
+  Constructor(const GlobalObject& aGlobal,
+              double aUpDegrees, double aRightDegrees,
+              double aDownDegrees, double aLeftDegrees,
+              ErrorResult& aRv);
+
+  nsISupports* GetParentObject() const { return mParent; }
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  void SetUpDegrees(double aVal) { mUpDegrees = aVal; }
+  void SetRightDegrees(double aVal) { mRightDegrees = aVal; }
+  void SetDownDegrees(double aVal) { mDownDegrees = aVal; }
+  void SetLeftDegrees(double aVal) { mLeftDegrees = aVal; }
+};
+
+class VRPositionState MOZ_FINAL : public nsWrapperCache
+{
+  ~VRPositionState() {}
+public:
+  explicit VRPositionState(nsISupports* aParent, const gfx::VRHMDSensorState& aState);
+
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRPositionState)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRPositionState)
+
+  double TimeStamp() const { return mTimeStamp; }
+
+  bool HasPosition() const { return mPosition != nullptr; }
+  DOMPoint* GetPosition() const { return mPosition; }
+
+  bool HasOrientation() const { return mOrientation != nullptr; }
+  DOMPoint* GetOrientation() const { return mOrientation; }
+
+  // these are created lazily
+  DOMPoint* GetLinearVelocity();
+  DOMPoint* GetLinearAcceleration();
+  DOMPoint* GetAngularVelocity();
+  DOMPoint* GetAngularAcceleration();
+
+  nsISupports* GetParentObject() const { return mParent; }
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+protected:
+  nsCOMPtr<nsISupports> mParent;
+
+  double mTimeStamp;
+  gfx::VRHMDSensorState mVRState;
+
+  nsRefPtr<DOMPoint> mPosition;
+  nsRefPtr<DOMPoint> mLinearVelocity;
+  nsRefPtr<DOMPoint> mLinearAcceleration;
+
+  nsRefPtr<DOMPoint> mOrientation;
+  nsRefPtr<DOMPoint> mAngularVelocity;
+  nsRefPtr<DOMPoint> mAngularAcceleration;
+};
+
+class VRDevice : public nsISupports,
+                 public nsWrapperCache
+{
+public:
+  // create new VRDevice objects for all known underlying gfx::vr devices
+  static bool CreateAllKnownVRDevices(nsISupports *aParent, nsTArray<nsRefPtr<VRDevice>>& aDevices);
+
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(VRDevice)
+
+  void GetHardwareUnitId(nsAString& aHWID) const { aHWID = mHWID; }
+  void GetDeviceId(nsAString& aDeviceId) const { aDeviceId = mDeviceId; }
+  void GetDeviceName(nsAString& aDeviceName) const { aDeviceName = mDeviceName; }
+
+  bool IsValid() { return mValid; }
+
+  virtual void Shutdown() { }
+
+  nsISupports* GetParentObject() const
+  {
+    return mParent;
+  }
+
+  enum VRDeviceType {
+    HMD,
+    PositionSensor
+  };
+
+  VRDeviceType GetType() const { return mType; }
+
+protected:
+  VRDevice(nsISupports* aParent, VRDeviceType aType)
+    : mParent(aParent)
+    , mType(aType)
+    , mValid(false)
+  {
+    mHWID.AssignLiteral("uknown");
+    mDeviceId.AssignLiteral("unknown");
+    mDeviceName.AssignLiteral("unknown");
+  }
+
+  virtual ~VRDevice() {
+    Shutdown();
+  }
+
+  nsCOMPtr<nsISupports> mParent;
+  nsString mHWID;
+  nsString mDeviceId;
+  nsString mDeviceName;
+
+  VRDeviceType mType;
+
+  bool mValid;
+};
+
+class HMDVRDevice : public VRDevice
+{
+public:
+  virtual already_AddRefed<DOMPoint> GetEyeTranslation(VREye aEye) = 0;
+
+  virtual void SetFieldOfView(const VRFieldOfViewInit& aLeftFOV,
+                              const VRFieldOfViewInit& aRightFOV,
+                              double zNear, double zFar) = 0;
+  virtual already_AddRefed<VRFieldOfView> GetCurrentEyeFieldOfView(VREye aEye) = 0;
+  virtual already_AddRefed<VRFieldOfView> GetRecommendedEyeFieldOfView(VREye aEye) = 0;
+  virtual already_AddRefed<VRFieldOfView> GetMaximumEyeFieldOfView(VREye aEye) = 0;
+  virtual already_AddRefed<DOMRect> GetRecommendedEyeRenderRect(VREye aEye) = 0;
+
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  void XxxToggleElementVR(Element& aElement);
+
+  gfx::VRHMDInfo *GetHMD() { return mHMD.get(); }
+
+protected:
+  HMDVRDevice(nsISupports* aParent, gfx::VRHMDInfo* aHMD)
+    : VRDevice(aParent, VRDevice::HMD)
+    , mHMD(aHMD)
+  { }
+
+  virtual ~HMDVRDevice() { }
+
+  nsRefPtr<gfx::VRHMDInfo> mHMD;
+};
+
+class PositionSensorVRDevice : public VRDevice
+{
+public:
+  virtual already_AddRefed<VRPositionState> GetState(double timeOffset) = 0;
+
+  virtual void ZeroSensor() = 0;
+
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+protected:
+  PositionSensorVRDevice(nsISupports* aParent)
+    : VRDevice(aParent, VRDevice::PositionSensor)
+  { }
+
+  virtual ~PositionSensorVRDevice() { }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/vr/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.dom += [
+    'VRDevice.h',
+    ]
+
+UNIFIED_SOURCES = [
+    'VRDevice.cpp',
+    ]
+
+FAIL_ON_WARNINGS = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+    '/dom/base'
+]
--- a/dom/webidl/Element.webidl
+++ b/dom/webidl/Element.webidl
@@ -119,19 +119,21 @@ interface Element : Node {
    */
   void releaseCapture();
 
   // Mozilla extensions
   /**
    * Requests that this element be made the full-screen element, as per the DOM
    * full-screen api.
    *
+   * The fsOptions parameter is non-standard.
+   *
    * @see <https://wiki.mozilla.org/index.php?title=Gecko:FullScreenAPI>
    */
-  void mozRequestFullScreen();
+  void mozRequestFullScreen(optional RequestFullscreenOptions fsOptions);
 
   /**
    * Requests that this element be made the pointer-locked element, as per the DOM
    * pointer lock api.
    *
    * @see <http://dvcs.w3.org/hg/pointerlock/raw-file/default/index.html>
    */
   void mozRequestPointerLock();
@@ -230,8 +232,15 @@ partial interface Element {
   readonly attribute ShadowRoot? shadowRoot;
 };
 
 Element implements ChildNode;
 Element implements NonDocumentTypeChildNode;
 Element implements ParentNode;
 Element implements Animatable;
 Element implements GeometryUtils;
+
+// non-standard: allows passing options to Element.requestFullScreen
+dictionary RequestFullscreenOptions {
+  // Which HMDVRDevice to go full screen on; also enables VR rendering.
+  // If null, normal fullscreen is entered.
+  HMDVRDevice? vrDisplay = null;
+};
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -317,16 +317,21 @@ partial interface Navigator {
 #ifdef MOZ_GAMEPAD
 // https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#navigator-interface-extension
 partial interface Navigator {
   [Throws, Pref="dom.gamepad.enabled"]
   sequence<Gamepad?> getGamepads();
 };
 #endif // MOZ_GAMEPAD
 
+partial interface Navigator {
+  [Throws, Pref="dom.vr.enabled"]
+  Promise<sequence<VRDevice>> getVRDevices();
+};
+
 #ifdef MOZ_B2G_BT
 partial interface Navigator {
   [Throws, CheckPermissions="bluetooth"]
   readonly attribute BluetoothManager mozBluetooth;
 };
 #endif // MOZ_B2G_BT
 
 #ifdef MOZ_B2G_FM
new file mode 100644
--- /dev/null
+++ b/dom/webidl/VRDevice.webidl
@@ -0,0 +1,128 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+enum VREye {
+  "left",
+  "right"
+};
+
+[Pref="dom.vr.enabled",
+ HeaderFile="mozilla/dom/VRDevice.h"]
+interface VRFieldOfViewReadOnly {
+  readonly attribute double upDegrees;
+  readonly attribute double rightDegrees;
+  readonly attribute double downDegrees;
+  readonly attribute double leftDegrees;
+};
+
+[Pref="dom.vr.enabled",
+ HeaderFile="mozilla/dom/VRDevice.h",
+ Constructor(optional VRFieldOfViewInit fov),
+ Constructor(double upDegrees, double rightDegrees, double downDegrees, double leftDegrees)]
+interface VRFieldOfView : VRFieldOfViewReadOnly {
+  inherit attribute double upDegrees;
+  inherit attribute double rightDegrees;
+  inherit attribute double downDegrees;
+  inherit attribute double leftDegrees;
+};
+
+dictionary VRFieldOfViewInit {
+  double upDegrees = 0.0;
+  double rightDegrees = 0.0;
+  double downDegrees = 0.0;
+  double leftDegrees = 0.0;
+};
+
+[Pref="dom.vr.enabled",
+ HeaderFile="mozilla/dom/VRDevice.h"]
+interface VRPositionState {
+  readonly attribute double timeStamp;
+
+  readonly attribute boolean hasPosition;
+  readonly attribute DOMPoint? position;
+  readonly attribute DOMPoint? linearVelocity;
+  readonly attribute DOMPoint? linearAcceleration;
+
+  readonly attribute boolean hasOrientation;
+  // XXX should be DOMQuaternion as soon as we add that
+  readonly attribute DOMPoint? orientation;
+  readonly attribute DOMPoint? angularVelocity;
+  readonly attribute DOMPoint? angularAcceleration;
+};
+
+[Pref="dom.vr.enabled"]
+interface VRDevice {
+  /**
+   * An identifier for the distinct hardware unit that this
+   * VR Device is a part of.  All VRDevice/Sensors that come
+   * from the same hardware will have the same hardwareId
+   */
+  [Pure] readonly attribute DOMString hardwareUnitId;
+
+  /**
+   * An identifier for this distinct sensor/device on a physical
+   * hardware device.  This shouldn't change across browser
+   * restrats, allowing configuration data to be saved based on it.
+   */
+  [Pure] readonly attribute DOMString deviceId;
+
+  /**
+   * a device name, a user-readable name identifying it
+   */
+  [Pure] readonly attribute DOMString deviceName;
+};
+
+[Pref="dom.vr.enabled",
+ HeaderFile="mozilla/dom/VRDevice.h"]
+interface HMDVRDevice : VRDevice {
+  /* The translation that should be applied to the view matrix for rendering each eye */
+  DOMPoint getEyeTranslation(VREye whichEye);
+
+  // the FOV that the HMD was configured with
+  VRFieldOfView getCurrentEyeFieldOfView(VREye whichEye);
+
+  // the recommended FOV, per eye.
+  VRFieldOfView getRecommendedEyeFieldOfView(VREye whichEye);
+
+  // the maximum FOV, per eye.  Above this, rendering will look broken.
+  VRFieldOfView getMaximumEyeFieldOfView(VREye whichEye);
+
+  // Set a field of view.  If either of the fields of view is null,
+  // or if their values are all zeros, then the recommended field of view
+  // for that eye will be used.
+  void setFieldOfView(optional VRFieldOfViewInit leftFOV,
+                      optional VRFieldOfViewInit rightFOV,
+                      optional double zNear = 0.01,
+                      optional double zFar = 10000.0);
+
+  // return a recommended rect for this eye.  Only useful for Canvas rendering,
+  // the x/y coordinates will be the location in the canvas where this eye should
+  // begin, and the width/height are the dimensions.  Any canvas in the appropriate
+  // ratio will work.
+  DOMRect getRecommendedEyeRenderRect(VREye whichEye);
+
+  // hack for testing
+  void xxxToggleElementVR(Element element);
+};
+
+[Pref="dom.vr.enabled" ,
+ HeaderFile="mozilla/dom/VRDevice.h"]
+interface PositionSensorVRDevice : VRDevice {
+  /*
+   * Return a VRPositionState dictionary containing the state of this position sensor,
+   * at an optional past time or predicted for a future time if timeOffset is != 0.
+   *
+   * The VRPositionState will contain the position, orientation, and velocity
+   * and acceleration of each of these properties.  Use "hasPosition" and "hasOrientation"
+   * to check if the associated members are valid; if these are false, those members
+   * will be null.
+   */
+  VRPositionState getState(optional double timeOffset = 0.0);
+
+  /* Zero this sensor, treating its current position and orientation
+   * as the "origin/zero" values.
+   */
+  void zeroSensor();
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -523,16 +523,17 @@ WEBIDL_FILES = [
     'URLUtils.webidl',
     'URLUtilsReadOnly.webidl',
     'USSDSession.webidl',
     'ValidityState.webidl',
     'VideoPlaybackQuality.webidl',
     'VideoStreamTrack.webidl',
     'VideoTrack.webidl',
     'VideoTrackList.webidl',
+    'VRDevice.webidl',
     'VTTCue.webidl',
     'VTTRegion.webidl',
     'WaveShaperNode.webidl',
     'WebComponents.webidl',
     'WebGL2RenderingContext.webidl',
     'WebGLRenderingContext.webidl',
     'WebSocket.webidl',
     'WheelEvent.webidl',
--- a/gfx/2d/Matrix.h
+++ b/gfx/2d/Matrix.h
@@ -372,16 +372,21 @@ public:
             Float a31, Float a32, Float a33, Float a34,
             Float a41, Float a42, Float a43, Float a44)
     : _11(a11), _12(a12), _13(a13), _14(a14)
     , _21(a21), _22(a22), _23(a23), _24(a24)
     , _31(a31), _32(a32), _33(a33), _34(a34)
     , _41(a41), _42(a42), _43(a43), _44(a44)
   {}
 
+  Matrix4x4(const Matrix4x4& aOther)
+  {
+    memcpy(this, &aOther, sizeof(*this));
+  }
+
   Float _11, _12, _13, _14;
   Float _21, _22, _23, _24;
   Float _31, _32, _33, _34;
   Float _41, _42, _43, _44;
 
   Point4D& operator[](int aIndex)
   {
       MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index");
@@ -536,16 +541,21 @@ public:
   static Matrix4x4 Translation(Float aX, Float aY, Float aZ)
   {
     return Matrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
                      0.0f, 1.0f, 0.0f, 0.0f,
                      0.0f, 0.0f, 1.0f, 0.0f,
                        aX,   aY,   aZ, 1.0f);
   }
 
+  static Matrix4x4 Translation(const Point3D& aP)
+  {
+    return Translation(aP.x, aP.y, aP.z);
+  }
+
   /**
    * Apply a translation to this matrix.
    *
    * The "Pre" in this method's name means that the translation is applied
    * -before- this matrix's existing transformation. That is, any vector that
    * is multiplied by the resulting matrix will first be translated, then be
    * transformed by the original transform.
    *
@@ -565,16 +575,20 @@ public:
     _41 += aX * _11 + aY * _21 + aZ * _31;
     _42 += aX * _12 + aY * _22 + aZ * _32;
     _43 += aX * _13 + aY * _23 + aZ * _33;
     _44 += aX * _14 + aY * _24 + aZ * _34;
 
     return *this;
   }
 
+  Matrix4x4 &PreTranslate(const Point3D& aPoint) {
+    return PreTranslate(aPoint.x, aPoint.y, aPoint.z);
+  }
+
   /**
    * Similar to PreTranslate, but the translation is applied -after- this
    * matrix's existing transformation instead of before it.
    *
    * This method is generally less used than PreTranslate since typically code
    * wants to adjust an existing user space to device space matrix to create a
    * transform to device space from a -new- user space (translated from the
    * previous user space). In that case consumers will need to use the Pre*
@@ -595,16 +609,20 @@ public:
     _13 += _14 * aZ;
     _23 += _24 * aZ;
     _33 += _34 * aZ;
     _43 += _44 * aZ;
 
     return *this;
   }
 
+  Matrix4x4 &PostTranslate(const Point3D& aPoint) {
+    return PostTranslate(aPoint.x, aPoint.y, aPoint.z);
+  }
+
   static Matrix4x4 Scaling(Float aScaleX, Float aScaleY, float aScaleZ)
   {
     return Matrix4x4(aScaleX, 0.0f, 0.0f, 0.0f,
                      0.0f, aScaleY, 0.0f, 0.0f,
                      0.0f, 0.0f, aScaleZ, 0.0f,
                      0.0f, 0.0f, 0.0f, 1.0f);
   }
 
@@ -668,16 +686,29 @@ public:
     PreTranslate(-aX, -aY, -aZ);
 
     // Translate back into position after applying this matrix
     PostTranslate(aX, aY, aZ);
 
     return *this;
   }
 
+  Matrix4x4& Transpose() {
+    std::swap(_12, _21);
+    std::swap(_13, _31);
+    std::swap(_14, _41);
+
+    std::swap(_23, _32);
+    std::swap(_24, _42);
+
+    std::swap(_34, _43);
+
+    return *this;
+  }
+
   bool operator==(const Matrix4x4& o) const
   {
     // XXX would be nice to memcmp here, but that breaks IEEE 754 semantics
     return _11 == o._11 && _12 == o._12 && _13 == o._13 && _14 == o._14 &&
            _21 == o._21 && _22 == o._22 && _23 == o._23 && _24 == o._24 &&
            _31 == o._31 && _32 == o._32 && _33 == o._33 && _34 == o._34 &&
            _41 == o._41 && _42 == o._42 && _43 == o._43 && _44 == o._44;
   }
--- a/gfx/layers/CompositorTypes.h
+++ b/gfx/layers/CompositorTypes.h
@@ -145,16 +145,17 @@ MOZ_BEGIN_ENUM_CLASS(EffectTypes, uint8_
   BLEND_MODE,
   COLOR_MATRIX,
   MAX_SECONDARY, // sentinel for the count of secondary effect types
   RGB,
   YCBCR,
   COMPONENT_ALPHA,
   SOLID_COLOR,
   RENDER_TARGET,
+  VR_DISTORTION,
   MAX  //sentinel for the count of all effect types
 MOZ_END_ENUM_CLASS(EffectTypes)
 
 /**
  * How the Compositable should manage textures.
  */
 MOZ_BEGIN_ENUM_CLASS(CompositableType, uint8_t)
   BUFFER_UNKNOWN,
--- a/gfx/layers/Effects.cpp
+++ b/gfx/layers/Effects.cpp
@@ -64,8 +64,15 @@ EffectBlendMode::PrintInfo(std::stringst
 void
 EffectColorMatrix::PrintInfo(std::stringstream& aStream, const char* aPrefix)
 {
   aStream << aPrefix;
   aStream << nsPrintfCString("EffectColorMatrix (0x%p)", this).get();
   AppendToString(aStream, mColorMatrix, " [matrix=", "]");
 }
 
+void
+EffectVRDistortion::PrintInfo(std::stringstream& aStream, const char* aPrefix)
+{
+  aStream << aPrefix;
+  aStream << nsPrintfCString("EffectVRDistortion (0x%p) [hmd=%p] [render-target=%p] [texture=%p]",
+                             this, mHMD.get(), mRenderTarget.get(), mTexture).get();
+}
--- a/gfx/layers/Effects.h
+++ b/gfx/layers/Effects.h
@@ -13,16 +13,17 @@
 #include "mozilla/gfx/Rect.h"           // for Rect
 #include "mozilla/gfx/Types.h"          // for Filter, etc
 #include "mozilla/layers/CompositorTypes.h"  // for EffectTypes, etc
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/layers/TextureHost.h"  // for CompositingRenderTarget, etc
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "nscore.h"                     // for nsACString
 #include "mozilla/EnumeratedArray.h"
+#include "gfxVR.h"
 
 namespace mozilla {
 namespace layers {
 
 /**
  * Effects and effect chains are used by the compositor API (see Compositor.h).
  * An effect chain represents a rendering method, for example some shader and
  * the data required for that shader to run. An effect is some component of the
@@ -91,16 +92,46 @@ struct EffectMask : public Effect
   virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix);
 
   TextureSource* mMaskTexture;
   bool mIs3D;
   gfx::IntSize mSize;
   gfx::Matrix4x4 mMaskTransform;
 };
 
+struct EffectVRDistortion : public Effect
+{
+  EffectVRDistortion(gfx::VRHMDInfo* aHMD,
+                     CompositingRenderTarget* aRenderTarget)
+    : Effect(EffectTypes::VR_DISTORTION)
+    , mHMD(aHMD)
+    , mRenderTarget(aRenderTarget)
+    , mTexture(aRenderTarget)
+  {}
+
+  EffectVRDistortion(gfx::VRHMDInfo* aHMD,
+                     TextureSource* aTexture)
+    : Effect(EffectTypes::VR_DISTORTION)
+    , mHMD(aHMD)
+    , mRenderTarget(nullptr)
+    , mTexture(aTexture)
+  {}
+
+  virtual const char* Name() { return "EffectVRDistortion"; }
+  virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix);
+
+  nsRefPtr<gfx::VRHMDInfo> mHMD;
+  RefPtr<CompositingRenderTarget> mRenderTarget;
+  TextureSource* mTexture;
+
+  // The viewport for each eye in the source and
+  // destination textures.
+  gfx::IntRect mViewports[2];
+};
+
 struct EffectBlendMode : public Effect
 {
   explicit EffectBlendMode(gfx::CompositionOp aBlendMode)
     : Effect(EffectTypes::BLEND_MODE)
     , mBlendMode(aBlendMode)
   { }
 
   virtual const char* Name() { return "EffectBlendMode"; }
--- a/gfx/layers/LayerScope.cpp
+++ b/gfx/layers/LayerScope.cpp
@@ -8,16 +8,17 @@
 
 #include "Composer2D.h"
 #include "Effects.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Endian.h"
 #include "TexturePoolOGL.h"
 #include "mozilla/layers/CompositorOGL.h"
+#include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/TextureHostOGL.h"
 
 #include "gfxColor.h"
 #include "gfxContext.h"
 #include "gfxUtils.h"
 #include "gfxPrefs.h"
 #include "nsIWidget.h"
@@ -328,17 +329,17 @@ private:
 
 // Static class to create and destory LayerScopeWebSocketManager object
 class WebSocketHelper
 {
 public:
     static void CreateServerSocket()
     {
         // Create Web Server Socket (which has to be on the main thread)
-        NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+        MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
         if (!sWebSocketManager) {
             sWebSocketManager = new LayerScopeWebSocketManager();
         }
     }
 
     static void DestroyServerSocket()
     {
         // Destroy Web Server Socket
@@ -646,16 +647,26 @@ public:
     }
 
 protected:
     LinkedList<DebugGLData> mList;
 };
 
 NS_IMPL_ISUPPORTS(DebugDataSender, nsIRunnable);
 
+
+class CreateServerSocketRunnable : public nsRunnable
+{
+public:
+    NS_IMETHOD Run() {
+        WebSocketHelper::CreateServerSocket();
+        return NS_OK;
+    }
+};
+
 /*
  * LayerScope SendXXX Structure
  * 1. SendLayer
  * 2. SendEffectChain
  *   1. SendTexturedEffect
  *      -> SendTextureSource
  *   2. SendYCbCrEffect
  *      -> SendTextureSource
@@ -910,25 +921,29 @@ LayerScopeWebSocketManager::DispatchDebu
 // ----------------------------------------------
 void
 LayerScope::Init()
 {
     if (!gfxPrefs::LayerScopeEnabled()) {
         return;
     }
 
-    // Note: The server socket has to be created on the main thread
-    WebSocketHelper::CreateServerSocket();
-}
-
-void
-LayerScope::DeInit()
-{
-    // Destroy Web Server Socket
-    WebSocketHelper::DestroyServerSocket();
+    if (NS_IsMainThread()) {
+        WebSocketHelper::CreateServerSocket();
+    } else {
+        // Dispatch creation to main thread, and make sure we
+        // dispatch this only once after booting
+        static bool dispatched = false;
+        if (dispatched) {
+            return;
+        }
+        DebugOnly<nsresult> rv = NS_DispatchToMainThread(new CreateServerSocketRunnable());
+        MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to dispatch WebSocket Creation to main thread");
+        dispatched = true;
+    }
 }
 
 void
 LayerScope::SendEffectChain(gl::GLContext* aGLContext,
                             const EffectChain& aEffectChain,
                             int aWidth,
                             int aHeight)
 {
@@ -960,17 +975,24 @@ LayerScope::SendLayerDump(UniquePtr<Pack
     }
     WebSocketHelper::GetSocketManager()->AppendDebugData(
         new DebugGLLayersData(Move(aPacket)));
 }
 
 bool
 LayerScope::CheckSendable()
 {
+    // Only compositor threads check LayerScope status
+    MOZ_ASSERT(CompositorParent::IsInCompositorThread());
+
+    if (!gfxPrefs::LayerScopeEnabled()) {
+        return false;
+    }
     if (!WebSocketHelper::GetSocketManager()) {
+        Init();
         return false;
     }
     if (!WebSocketHelper::GetSocketManager()->IsConnected()) {
         return false;
     }
     return true;
 }
 
--- a/gfx/layers/LayerScope.h
+++ b/gfx/layers/LayerScope.h
@@ -19,28 +19,29 @@ namespace layers {
 
 namespace layerscope { class Packet; }
 
 struct EffectChain;
 class LayerComposite;
 
 class LayerScope {
 public:
-    static void Init();
-    static void DeInit();
     static void SendEffectChain(gl::GLContext* aGLContext,
                                 const EffectChain& aEffectChain,
                                 int aWidth,
                                 int aHeight);
     static void SendLayer(LayerComposite* aLayer,
                           int aWidth,
                           int aHeight);
     static void SendLayerDump(UniquePtr<layerscope::Packet> aPacket);
     static bool CheckSendable();
     static void CleanLayer();
+
+private:
+    static void Init();
 };
 
 // Perform BeginFrame and EndFrame automatically
 class LayerScopeAutoFrame {
 public:
     explicit LayerScopeAutoFrame(int64_t aFrameStamp);
     ~LayerScopeAutoFrame();
 
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -989,17 +989,18 @@ ContainerLayer::RepositionChild(Layer* a
   aChild->SetNextSibling(afterNext);
   return true;
 }
 
 void
 ContainerLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs)
 {
   aAttrs = ContainerLayerAttributes(mPreXScale, mPreYScale,
-                                    mInheritedXScale, mInheritedYScale);
+                                    mInheritedXScale, mInheritedYScale,
+                                    reinterpret_cast<uint64_t>(mHMDInfo.get()));
 }
 
 bool
 ContainerLayer::HasMultipleChildren()
 {
   uint32_t count = 0;
   for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) {
     const nsIntRect *clipRect = child->GetEffectiveClipRect();
@@ -1653,16 +1654,19 @@ ContainerLayer::PrintInfo(std::stringstr
 {
   Layer::PrintInfo(aStream, aPrefix);
   if (UseIntermediateSurface()) {
     aStream << " [usesTmpSurf]";
   }
   if (1.0 != mPreXScale || 1.0 != mPreYScale) {
     aStream << nsPrintfCString(" [preScale=%g, %g]", mPreXScale, mPreYScale).get();
   }
+  if (mHMDInfo) {
+    aStream << nsPrintfCString(" [hmd=%p]", mHMDInfo.get()).get();
+  }
 }
 
 void
 ContainerLayer::DumpPacket(layerscope::LayersPacket* aPacket, const void* aParent)
 {
   Layer::DumpPacket(aPacket, aParent);
   // Get this layer data
   using namespace layerscope;
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -40,16 +40,17 @@
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsSize.h"                     // for nsIntSize
 #include "nsString.h"                   // for nsCString
 #include "nsTArray.h"                   // for nsTArray
 #include "nsTArrayForwardDeclare.h"     // for InfallibleTArray
 #include "nscore.h"                     // for nsACString, nsAString
 #include "prlog.h"                      // for PRLogModuleInfo
 #include "gfx2DGlue.h"
+#include "gfxVR.h"
 
 class gfxContext;
 
 extern uint8_t gLayerManagerLayerBuilder;
 
 namespace mozilla {
 
 class ComputedTimingFunction;
@@ -1888,16 +1889,22 @@ public:
    * content flag set.
    */
   static bool HasOpaqueAncestorLayer(Layer* aLayer);
 
   void SetChildrenChanged(bool aVal) {
     mChildrenChanged = aVal;
   }
 
+  /**
+   * VR
+   */
+  void SetVRHMDInfo(gfx::VRHMDInfo* aHMD) { mHMDInfo = aHMD; }
+  gfx::VRHMDInfo* GetVRHMDInfo() { return mHMDInfo; }
+
 protected:
   friend class ReadbackProcessor;
 
   void DidInsertChild(Layer* aLayer);
   void DidRemoveChild(Layer* aLayer);
 
   ContainerLayer(LayerManager* aManager, void* aImplData);
 
@@ -1933,16 +1940,17 @@ protected:
   float mInheritedXScale;
   float mInheritedYScale;
   bool mUseIntermediateSurface;
   bool mSupportsComponentAlphaChildren;
   bool mMayHaveReadbackChild;
   // This is updated by ComputeDifferences. This will be true if we need to invalidate
   // the intermediate surface.
   bool mChildrenChanged;
+  nsRefPtr<gfx::VRHMDInfo> mHMDInfo;
 };
 
 /**
  * A Layer which just renders a solid color in its visible region. It actually
  * can fill any area that contains the visible region, so if you need to
  * restrict the area filled, set a clip region on this layer.
  */
 class ColorLayer : public Layer {
--- a/gfx/layers/composite/CanvasLayerComposite.cpp
+++ b/gfx/layers/composite/CanvasLayerComposite.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/Effects.h"     // for EffectChain
 #include "mozilla/mozalloc.h"           // for operator delete
 #include "nsAString.h"
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsString.h"                   // for nsAutoCString
+#include "gfxVR.h"
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 
 CanvasLayerComposite::CanvasLayerComposite(LayerManagerComposite* aManager)
   : CanvasLayer(aManager, nullptr)
--- a/gfx/layers/composite/ContainerLayerComposite.cpp
+++ b/gfx/layers/composite/ContainerLayerComposite.cpp
@@ -128,16 +128,115 @@ static void PrintUniformityInfo(Layer* a
 struct PreparedLayer
 {
   PreparedLayer(LayerComposite *aLayer, RenderTargetIntRect aClipRect) :
     mLayer(aLayer), mClipRect(aClipRect) {}
   LayerComposite* mLayer;
   RenderTargetIntRect mClipRect;
 };
 
+
+template<class ContainerT> void
+ContainerRenderVR(ContainerT* aContainer,
+                  LayerManagerComposite* aManager,
+                  const nsIntRect& aClipRect,
+                  gfx::VRHMDInfo* aHMD)
+{
+  RefPtr<CompositingRenderTarget> surface;
+
+  Compositor* compositor = aManager->GetCompositor();
+
+  RefPtr<CompositingRenderTarget> previousTarget = compositor->GetCurrentRenderTarget();
+
+  nsIntRect visibleRect = aContainer->GetEffectiveVisibleRegion().GetBounds();
+
+  float opacity = aContainer->GetEffectiveOpacity();
+
+  gfx::IntRect surfaceRect = gfx::IntRect(visibleRect.x, visibleRect.y,
+                                          visibleRect.width, visibleRect.height);
+  // we're about to create a framebuffer backed by textures to use as an intermediate
+  // surface. What to do if its size (as given by framebufferRect) would exceed the
+  // maximum texture size supported by the GL? The present code chooses the compromise
+  // of just clamping the framebuffer's size to the max supported size.
+  // This gives us a lower resolution rendering of the intermediate surface (children layers).
+  // See bug 827170 for a discussion.
+  int32_t maxTextureSize = compositor->GetMaxTextureSize();
+  surfaceRect.width = std::min(maxTextureSize, surfaceRect.width);
+  surfaceRect.height = std::min(maxTextureSize, surfaceRect.height);
+
+  // use NONE here, because we draw black to clear below
+  surface = compositor->CreateRenderTarget(surfaceRect, INIT_MODE_NONE);
+  if (!surface) {
+    return;
+  }
+
+  compositor->SetRenderTarget(surface);
+
+  nsAutoTArray<Layer*, 12> children;
+  aContainer->SortChildrenBy3DZOrder(children);
+
+  /**
+   * Render this container's contents.
+   */
+  nsIntRect surfaceClipRect(0, 0, surfaceRect.width, surfaceRect.height);
+  RenderTargetIntRect rtClipRect(0, 0, surfaceRect.width, surfaceRect.height);
+  for (uint32_t i = 0; i < children.Length(); i++) {
+    LayerComposite* layerToRender = static_cast<LayerComposite*>(children.ElementAt(i)->ImplData());
+    Layer* layer = layerToRender->GetLayer();
+
+    if (layer->GetEffectiveVisibleRegion().IsEmpty() &&
+        !layer->AsContainerLayer()) {
+      continue;
+    }
+
+    RenderTargetIntRect clipRect = layer->CalculateScissorRect(rtClipRect);
+    if (clipRect.IsEmpty()) {
+      continue;
+    }
+
+    layerToRender->Prepare(rtClipRect);
+    layerToRender->RenderLayer(surfaceClipRect);
+  }
+
+  // Unbind the current surface and rebind the previous one.
+#ifdef MOZ_DUMP_PAINTING
+  if (gfxUtils::sDumpPainting) {
+    RefPtr<gfx::DataSourceSurface> surf = surface->Dump(aManager->GetCompositor());
+    if (surf) {
+      WriteSnapshotToDumpFile(aContainer, surf);
+    }
+  }
+#endif
+
+  compositor->SetRenderTarget(previousTarget);
+
+  gfx::Rect rect(visibleRect.x, visibleRect.y, visibleRect.width, visibleRect.height);
+  gfx::Rect clipRect(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height);
+
+  // The VR geometry may not cover the entire area; we need to fill with a solid color
+  // first.
+  // XXX should DrawQuad handle this on its own?  Is there a time where we wouldn't want
+  // to do this? (e.g. something like Cardboard would not require distortion so will fill
+  // the entire rect)
+  EffectChain solidEffect(aContainer);
+  solidEffect.mPrimaryEffect = new EffectSolidColor(Color(0.0, 0.0, 0.0, 1.0));
+  aManager->GetCompositor()->DrawQuad(rect, clipRect, solidEffect, opacity,
+                                      aContainer->GetEffectiveTransform());
+
+  // draw the temporary surface with VR distortion to the original destination
+  EffectChain vrEffect(aContainer);
+  vrEffect.mPrimaryEffect = new EffectVRDistortion(aHMD, surface);
+
+  // XXX we shouldn't use visibleRect here -- the VR distortion needs to know the
+  // full rect, not just the visible one.  Luckily, right now, VR distortion is only
+  // rendered when the element is fullscreen, so the visibleRect will be right anyway.
+  aManager->GetCompositor()->DrawQuad(rect, clipRect, vrEffect, opacity,
+                                      aContainer->GetEffectiveTransform());
+}
+
 /* all of the prepared data that we need in RenderLayer() */
 struct PreparedData
 {
   RefPtr<CompositingRenderTarget> mTmpTarget;
   nsAutoTArray<PreparedLayer, 12> mLayers;
   bool mNeedsSurfaceCopy;
 };
 
@@ -145,16 +244,25 @@ struct PreparedData
 template<class ContainerT> void
 ContainerPrepare(ContainerT* aContainer,
                  LayerManagerComposite* aManager,
                  const RenderTargetIntRect& aClipRect)
 {
   aContainer->mPrepared = MakeUnique<PreparedData>();
   aContainer->mPrepared->mNeedsSurfaceCopy = false;
 
+  gfx::VRHMDInfo *hmdInfo = aContainer->GetVRHMDInfo();
+  if (hmdInfo && hmdInfo->GetConfiguration().IsValid()) {
+    // we're not going to do anything here; instead, we'll do it all in ContainerRender.
+    // XXX fix this; we can win with the same optimizations.  Specifically, we
+    // want to render thebes layers only once and then composite the intermeidate surfaces
+    // with different transforms twice.
+    return;
+  }
+
   /**
    * Determine which layers to draw.
    */
   nsAutoTArray<Layer*, 12> children;
   aContainer->SortChildrenBy3DZOrder(children);
 
   for (uint32_t i = 0; i < children.Length(); i++) {
     LayerComposite* layerToRender = static_cast<LayerComposite*>(children.ElementAt(i)->ImplData());
@@ -361,16 +469,24 @@ RenderIntermediate(ContainerT* aContaine
 }
 
 template<class ContainerT> void
 ContainerRender(ContainerT* aContainer,
                  LayerManagerComposite* aManager,
                  const nsIntRect& aClipRect)
 {
   MOZ_ASSERT(aContainer->mPrepared);
+
+  gfx::VRHMDInfo *hmdInfo = aContainer->GetVRHMDInfo();
+  if (hmdInfo && hmdInfo->GetConfiguration().IsValid()) {
+    ContainerRenderVR(aContainer, aManager, aClipRect, hmdInfo);
+    aContainer->mPrepared = nullptr;
+    return;
+  }
+
   if (aContainer->UseIntermediateSurface()) {
     RefPtr<CompositingRenderTarget> surface;
 
     if (aContainer->mPrepared->mNeedsSurfaceCopy) {
       // we needed to copy the background so we waited until now to render the intermediate
       surface = CreateTemporaryTargetAndCopyFromBackground(aContainer, aManager);
       RenderIntermediate(aContainer, aManager,
                          aClipRect, surface);
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -43,16 +43,20 @@
 #include "mozilla/layers/TextureHost.h"
 #include "mozilla/layers/AsyncCompositionManager.h"
 
 typedef std::vector<mozilla::layers::EditReply> EditReplyVector;
 
 using mozilla::layout::RenderFrameParent;
 
 namespace mozilla {
+namespace gfx {
+class VRHMDInfo;
+}
+
 namespace layers {
 
 class PGrallocBufferParent;
 
 //--------------------------------------------------
 // Convenience accessors
 static ShadowLayerParent*
 cast(const PLayerParent* in)
@@ -373,16 +377,25 @@ LayerTransactionParent::RecvUpdate(const
         ContainerLayerComposite* containerLayer = layerParent->AsContainerLayerComposite();
         if (!containerLayer) {
           return false;
         }
         const ContainerLayerAttributes& attrs =
           specific.get_ContainerLayerAttributes();
         containerLayer->SetPreScale(attrs.preXScale(), attrs.preYScale());
         containerLayer->SetInheritedScale(attrs.inheritedXScale(), attrs.inheritedYScale());
+
+        if (attrs.hmdInfo()) {
+          if (!IsSameProcess()) {
+            NS_WARNING("VR layers currently not supported with cross-process compositing");
+            return false;
+          }
+          containerLayer->SetVRHMDInfo(reinterpret_cast<mozilla::gfx::VRHMDInfo*>(attrs.hmdInfo()));
+        }
+
         break;
       }
       case Specific::TColorLayerAttributes: {
         MOZ_LAYERS_LOG(("[ParentSide]   color layer"));
 
         ColorLayerComposite* colorLayer = layerParent->AsColorLayerComposite();
         if (!colorLayer) {
           return false;
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -222,16 +222,20 @@ struct CommonLayerAttributes {
 struct PaintedLayerAttributes {
   nsIntRegion validRegion;
 };
 struct ContainerLayerAttributes {
   float preXScale;
   float preYScale;
   float inheritedXScale;
   float inheritedYScale;
+  // This is a bare pointer; LayerTransactionParent::RecvUpdate prevents this
+  // from being used when !IsSameProcess(), but we should make this truly
+  // cross process at some point by passing the HMDConfig
+  uint64_t hmdInfo;
 };
 struct ColorLayerAttributes     { LayerColor color; nsIntRect bounds; };
 struct CanvasLayerAttributes    { GraphicsFilterType filter; nsIntRect bounds; };
 struct RefLayerAttributes       { int64_t id; };
 struct ImageLayerAttributes     { GraphicsFilterType filter; IntSize scaleToSize; ScaleMode scaleMode; };
 
 union SpecificLayerAttributes {
   null_t;
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/layers/ISurfaceAllocator.h"     // for GfxMemoryImageReporter
 
 #include "prlog.h"
 #include "prprf.h"
 
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "gfxTextRun.h"
+#include "gfxVR.h"
 
 #ifdef XP_WIN
 #include <process.h>
 #define getpid _getpid
 #else
 #include <unistd.h>
 #endif
 
@@ -381,16 +382,20 @@ gfxPlatform::gfxPlatform()
 
     mSkiaGlue = nullptr;
 
     uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO) | BackendTypeBit(BackendType::SKIA);
     uint32_t contentMask = BackendTypeBit(BackendType::CAIRO);
     InitBackendPrefs(canvasMask, BackendType::CAIRO,
                      contentMask, BackendType::CAIRO);
     mTotalSystemMemory = mozilla::hal::GetTotalSystemMemory();
+
+    // give ovr_Initialize a chance to be called very early on; we don't
+    // care if it succeeds or not
+    VRHMDManagerOculus::PlatformInit();
 }
 
 gfxPlatform*
 gfxPlatform::GetPlatform()
 {
     if (!gPlatform) {
         Init();
     }
@@ -672,16 +677,19 @@ gfxPlatform::ShutdownLayersIPC()
     }
 }
 
 gfxPlatform::~gfxPlatform()
 {
     mScreenReferenceSurface = nullptr;
     mScreenReferenceDrawTarget = nullptr;
 
+    // Clean up any VR stuff
+    VRHMDManagerOculus::Destroy();
+
     // The cairo folks think we should only clean up in debug builds,
     // but we're generally in the habit of trying to shut down as
     // cleanly as possible even in production code, so call this
     // cairo_debug_* function unconditionally.
     //
     // because cairo can assert and thus crash on shutdown, don't do this in release builds
 #if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING) || defined(NS_TRACE_MALLOC) || defined(MOZ_VALGRIND)
 #ifdef USE_SKIA
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -321,16 +321,19 @@ private:
 
   DECL_GFX_PREF(Live, "ui.click_hold_context_menus.delay",     UiClickHoldContextMenusDelay, int32_t, 500);
 
   DECL_GFX_PREF(Once, "webgl.force-layers-readback",           WebGLForceLayersReadback, bool, false);
   DECL_GFX_PREF(Once, "webgl.angle.try-d3d11",                 WebGLANGLETryD3D11, bool, false);
   DECL_GFX_PREF(Once, "webgl.angle.force-d3d11",               WebGLANGLEForceD3D11, bool, false);
 
   DECL_GFX_PREF(Once, "layers.stereo-video.enabled",           StereoVideoEnabled, bool, false);
+
+  DECL_GFX_PREF(Once, "dom.vr.enabled",                        VREnabled, bool, false);
+  DECL_GFX_PREF(Once, "dom.vr.add-test-devices",               VRAddTestDevices, int32_t, 1);
 public:
   // Manage the singleton:
   static gfxPrefs& GetSingleton()
   {
     MOZ_ASSERT(!sInstanceHasBeenDestroyed, "Should never recreate a gfxPrefs instance!");
     if (!sInstance) {
       sInstance = new gfxPrefs;
     }
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxVR.cpp
@@ -0,0 +1,645 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <math.h>
+
+#include "prlink.h"
+#include "prmem.h"
+#include "prenv.h"
+#include "gfxPrefs.h"
+#include "gfxVR.h"
+
+#include "ovr_capi_dynamic.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsIScreenManager.h"
+
+#ifdef XP_WIN
+#include "gfxWindowsPlatform.h" // for gfxWindowsPlatform::GetDPIScale
+#endif
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846
+#endif
+
+namespace {
+
+#ifdef OVR_CAPI_LIMITED_MOZILLA
+static pfn_ovr_Initialize ovr_Initialize = nullptr;
+static pfn_ovr_Shutdown ovr_Shutdown = nullptr;
+static pfn_ovrHmd_Detect ovrHmd_Detect = nullptr;
+static pfn_ovrHmd_Create ovrHmd_Create = nullptr;
+static pfn_ovrHmd_Destroy ovrHmd_Destroy = nullptr;
+static pfn_ovrHmd_CreateDebug ovrHmd_CreateDebug = nullptr;
+static pfn_ovrHmd_GetLastError ovrHmd_GetLastError = nullptr;
+static pfn_ovrHmd_AttachToWindow ovrHmd_AttachToWindow = nullptr;
+static pfn_ovrHmd_GetEnabledCaps ovrHmd_GetEnabledCaps = nullptr;
+static pfn_ovrHmd_SetEnabledCaps ovrHmd_SetEnabledCaps = nullptr;
+static pfn_ovrHmd_ConfigureTracking ovrHmd_ConfigureTracking = nullptr;
+static pfn_ovrHmd_RecenterPose ovrHmd_RecenterPose = nullptr;
+static pfn_ovrHmd_GetTrackingState ovrHmd_GetTrackingState = nullptr;
+static pfn_ovrHmd_GetFovTextureSize ovrHmd_GetFovTextureSize = nullptr;
+static pfn_ovrHmd_GetRenderDesc ovrHmd_GetRenderDesc = nullptr;
+static pfn_ovrHmd_CreateDistortionMesh ovrHmd_CreateDistortionMesh = nullptr;
+static pfn_ovrHmd_DestroyDistortionMesh ovrHmd_DestroyDistortionMesh = nullptr;
+static pfn_ovrHmd_GetRenderScaleAndOffset ovrHmd_GetRenderScaleAndOffset = nullptr;
+static pfn_ovrHmd_GetFrameTiming ovrHmd_GetFrameTiming = nullptr;
+static pfn_ovrHmd_BeginFrameTiming ovrHmd_BeginFrameTiming = nullptr;
+static pfn_ovrHmd_EndFrameTiming ovrHmd_EndFrameTiming = nullptr;
+static pfn_ovrHmd_ResetFrameTiming ovrHmd_ResetFrameTiming = nullptr;
+static pfn_ovrHmd_GetEyePoses ovrHmd_GetEyePoses = nullptr;
+static pfn_ovrHmd_GetHmdPosePerEye ovrHmd_GetHmdPosePerEye = nullptr;
+static pfn_ovrHmd_GetEyeTimewarpMatrices ovrHmd_GetEyeTimewarpMatrices = nullptr;
+static pfn_ovrMatrix4f_Projection ovrMatrix4f_Projection = nullptr;
+static pfn_ovrMatrix4f_OrthoSubProjection ovrMatrix4f_OrthoSubProjection = nullptr;
+static pfn_ovr_GetTimeInSeconds ovr_GetTimeInSeconds = nullptr;
+
+#if defined(XP_WIN)
+# ifdef HAVE_64BIT_BUILD
+#  define OVR_LIB_NAME "libovr64.dll"
+# else
+#  define OVR_LIB_NAME "libovr.dll"
+# endif
+#elif defined(XP_MACOSX)
+# define OVR_LIB_NAME "libovr.dylib"
+#else
+# define OVR_LIB_NAME 0
+#endif
+
+static bool
+InitializeOculusCAPI()
+{
+  static PRLibrary *ovrlib = nullptr;
+
+  if (!ovrlib) {
+    const char *libName = OVR_LIB_NAME;
+
+    if (PR_GetEnv("OVR_LIB_NAME")) {
+      libName = PR_GetEnv("OVR_LIB_NAME");
+    }
+
+    if (!libName) {
+      printf_stderr("Don't know how to find Oculus VR library; missing OVR_LIB_NAME\n");
+      return false;
+    }
+
+    ovrlib = PR_LoadLibrary(libName);
+
+    if (!ovrlib) {
+      // Not found? Try harder. Needed mainly on OSX/etc. where
+      // the binary location is not in the search path.
+      const char *xulName = "libxul.so";
+#if defined(XP_MACOSX)
+      xulName = "XUL";
+#endif
+
+      char *xulpath = PR_GetLibraryFilePathname(xulName, (PRFuncPtr) &InitializeOculusCAPI);
+      if (xulpath) {
+        char *xuldir = strrchr(xulpath, '/');
+        if (xuldir) {
+          *xuldir = 0;
+          xuldir = xulpath;
+
+          char *ovrpath = PR_GetLibraryName(xuldir, libName);
+          ovrlib = PR_LoadLibrary(ovrpath);
+          PR_Free(ovrpath);
+        }
+        PR_Free(xulpath);
+      }
+    }
+
+    if (!ovrlib) {
+      printf_stderr("Failed to load Oculus VR library, tried '%s'\n", libName);
+      return false;
+    }
+  }
+
+  // was it already initialized?
+  if (ovr_Initialize)
+    return true;
+
+#define REQUIRE_FUNCTION(_x) do { \
+    *(void **)&_x = (void *) PR_FindSymbol(ovrlib, #_x);                \
+    if (!_x) { printf_stderr(#_x " symbol missing\n"); goto fail; }       \
+  } while (0)
+
+  REQUIRE_FUNCTION(ovr_Initialize);
+  REQUIRE_FUNCTION(ovr_Shutdown);
+  REQUIRE_FUNCTION(ovrHmd_Detect);
+  REQUIRE_FUNCTION(ovrHmd_Create);
+  REQUIRE_FUNCTION(ovrHmd_Destroy);
+  REQUIRE_FUNCTION(ovrHmd_CreateDebug);
+  REQUIRE_FUNCTION(ovrHmd_GetLastError);
+  REQUIRE_FUNCTION(ovrHmd_AttachToWindow);
+  REQUIRE_FUNCTION(ovrHmd_GetEnabledCaps);
+  REQUIRE_FUNCTION(ovrHmd_SetEnabledCaps);
+  REQUIRE_FUNCTION(ovrHmd_ConfigureTracking);
+  REQUIRE_FUNCTION(ovrHmd_RecenterPose);
+  REQUIRE_FUNCTION(ovrHmd_GetTrackingState);
+
+  REQUIRE_FUNCTION(ovrHmd_GetFovTextureSize);
+  REQUIRE_FUNCTION(ovrHmd_GetRenderDesc);
+  REQUIRE_FUNCTION(ovrHmd_CreateDistortionMesh);
+  REQUIRE_FUNCTION(ovrHmd_DestroyDistortionMesh);
+  REQUIRE_FUNCTION(ovrHmd_GetRenderScaleAndOffset);
+  REQUIRE_FUNCTION(ovrHmd_GetFrameTiming);
+  REQUIRE_FUNCTION(ovrHmd_BeginFrameTiming);
+  REQUIRE_FUNCTION(ovrHmd_EndFrameTiming);
+  REQUIRE_FUNCTION(ovrHmd_ResetFrameTiming);
+  REQUIRE_FUNCTION(ovrHmd_GetEyePoses);
+  REQUIRE_FUNCTION(ovrHmd_GetHmdPosePerEye);
+  REQUIRE_FUNCTION(ovrHmd_GetEyeTimewarpMatrices);
+  REQUIRE_FUNCTION(ovrMatrix4f_Projection);
+  REQUIRE_FUNCTION(ovrMatrix4f_OrthoSubProjection);
+  REQUIRE_FUNCTION(ovr_GetTimeInSeconds);
+
+#undef REQUIRE_FUNCTION
+
+  return true;
+
+ fail:
+  ovr_Initialize = nullptr;
+  return false;
+}
+
+#else
+// we're statically linked; it's available
+static bool InitializeOculusCAPI()
+{
+  return true;
+}
+#endif
+
+} // anonymous namespace
+
+using namespace mozilla::gfx;
+
+// Dummy nsIScreen implementation, for when we just need to specify a size
+class FakeScreen : public nsIScreen
+{
+public:
+  FakeScreen(const IntRect& aScreenRect)
+    : mScreenRect(aScreenRect)
+  { }
+
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD GetRect(int32_t *l, int32_t *t, int32_t *w, int32_t *h) {
+    *l = mScreenRect.x;
+    *t = mScreenRect.y;
+    *w = mScreenRect.width;
+    *h = mScreenRect.height;
+    return NS_OK;
+  }
+  NS_IMETHOD GetAvailRect(int32_t *l, int32_t *t, int32_t *w, int32_t *h) {
+    return GetRect(l, t, w, h);
+  }
+  NS_IMETHOD GetRectDisplayPix(int32_t *l, int32_t *t, int32_t *w, int32_t *h) {
+    return GetRect(l, t, w, h);
+  }
+  NS_IMETHOD GetAvailRectDisplayPix(int32_t *l, int32_t *t, int32_t *w, int32_t *h) {
+    return GetAvailRect(l, t, w, h);
+  }
+
+  NS_IMETHOD GetId(uint32_t* aId) { *aId = (uint32_t)-1; return NS_OK; }
+  NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth) { *aPixelDepth = 24; return NS_OK; }
+  NS_IMETHOD GetColorDepth(int32_t* aColorDepth) { *aColorDepth = 24; return NS_OK; }
+
+  NS_IMETHOD LockMinimumBrightness(uint32_t aBrightness) { return NS_ERROR_NOT_AVAILABLE; }
+  NS_IMETHOD UnlockMinimumBrightness(uint32_t aBrightness) { return NS_ERROR_NOT_AVAILABLE; }
+  NS_IMETHOD GetRotation(uint32_t* aRotation) {
+    *aRotation = nsIScreen::ROTATION_0_DEG;
+    return NS_OK;
+  }
+  NS_IMETHOD SetRotation(uint32_t aRotation) { return NS_ERROR_NOT_AVAILABLE; }
+  NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor) {
+    *aContentsScaleFactor = 1.0;
+    return NS_OK;
+  }
+
+protected:
+  virtual ~FakeScreen() {}
+
+  IntRect mScreenRect;
+};
+
+NS_IMPL_ISUPPORTS(FakeScreen, nsIScreen)
+
+class HMDInfoOculus : public VRHMDInfo {
+  friend class VRHMDManagerOculusImpl;
+public:
+  HMDInfoOculus(ovrHmd aHMD);
+
+  bool SetFOV(const VRFieldOfView& aFOVLeft, const VRFieldOfView& aFOVRight,
+              double zNear, double zFar) MOZ_OVERRIDE;
+
+  bool StartSensorTracking() MOZ_OVERRIDE;
+  VRHMDSensorState GetSensorState(double timeOffset) MOZ_OVERRIDE;
+  void StopSensorTracking() MOZ_OVERRIDE;
+  void ZeroSensor() MOZ_OVERRIDE;
+
+  void FillDistortionConstants(uint32_t whichEye,
+                               const IntSize& textureSize, const IntRect& eyeViewport,
+                               const Size& destViewport, const Rect& destRect,
+                               VRDistortionConstants& values) MOZ_OVERRIDE;
+
+  void Destroy();
+
+protected:
+  virtual ~HMDInfoOculus() {
+      Destroy();
+      MOZ_COUNT_DTOR_INHERITED(HMDInfoOculus, VRHMDInfo);
+  }
+
+  ovrHmd mHMD;
+  ovrFovPort mFOVPort[2];
+  uint32_t mStartCount;
+};
+
+static ovrFovPort
+ToFovPort(const VRFieldOfView& aFOV)
+{
+  ovrFovPort fovPort;
+  fovPort.LeftTan = tan(aFOV.leftDegrees * M_PI / 180.0);
+  fovPort.RightTan = tan(aFOV.rightDegrees * M_PI / 180.0);
+  fovPort.UpTan = tan(aFOV.upDegrees * M_PI / 180.0);
+  fovPort.DownTan = tan(aFOV.downDegrees * M_PI / 180.0);
+  return fovPort;
+}
+
+static VRFieldOfView
+FromFovPort(const ovrFovPort& aFOV)
+{
+  VRFieldOfView fovInfo;
+  fovInfo.leftDegrees = atan(aFOV.LeftTan) * 180.0 / M_PI;
+  fovInfo.rightDegrees = atan(aFOV.RightTan) * 180.0 / M_PI;
+  fovInfo.upDegrees = atan(aFOV.UpTan) * 180.0 / M_PI;
+  fovInfo.downDegrees = atan(aFOV.DownTan) * 180.0 / M_PI;
+  return fovInfo;
+}
+
+HMDInfoOculus::HMDInfoOculus(ovrHmd aHMD)
+  : VRHMDInfo(VRHMDType::Oculus)
+  , mHMD(aHMD)
+  , mStartCount(0)
+{
+  MOZ_COUNT_CTOR_INHERITED(HMDInfoOculus, VRHMDInfo);
+
+  mSupportedSensorBits = 0;
+  if (mHMD->TrackingCaps & ovrTrackingCap_Orientation)
+    mSupportedSensorBits |= State_Orientation;
+  if (mHMD->TrackingCaps & ovrTrackingCap_Position)
+    mSupportedSensorBits |= State_Position;
+
+  mRecommendedEyeFOV[Eye_Left] = FromFovPort(mHMD->DefaultEyeFov[ovrEye_Left]);
+  mRecommendedEyeFOV[Eye_Right] = FromFovPort(mHMD->DefaultEyeFov[ovrEye_Right]);
+
+  mMaximumEyeFOV[Eye_Left] = FromFovPort(mHMD->MaxEyeFov[ovrEye_Left]);
+  mMaximumEyeFOV[Eye_Right] = FromFovPort(mHMD->MaxEyeFov[ovrEye_Right]);
+
+  SetFOV(mRecommendedEyeFOV[Eye_Left], mRecommendedEyeFOV[Eye_Right], 0.01, 10000.0);
+
+  nsCOMPtr<nsIScreenManager> screenmgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
+  if (screenmgr) {
+    screenmgr->ScreenForRect(mHMD->WindowsPos.x, mHMD->WindowsPos.y,
+                             mHMD->Resolution.w, mHMD->Resolution.h,
+                             getter_AddRefs(mScreen));
+  }
+}
+
+void
+HMDInfoOculus::Destroy()
+{
+  if (mHMD) {
+    ovrHmd_Destroy(mHMD);
+    mHMD = nullptr;
+  }
+}
+
+bool
+HMDInfoOculus::SetFOV(const VRFieldOfView& aFOVLeft, const VRFieldOfView& aFOVRight,
+                      double zNear, double zFar)
+{
+  float pixelsPerDisplayPixel = 1.0;
+  ovrSizei texSize[2];
+
+  uint32_t caps = ovrDistortionCap_Chromatic | ovrDistortionCap_Vignette; // XXX TODO add TimeWarp
+
+  // get eye parameters and create the mesh
+  for (uint32_t eye = 0; eye < NumEyes; eye++) {
+    mEyeFOV[eye] = eye == 0 ? aFOVLeft : aFOVRight;
+    mFOVPort[eye] = ToFovPort(mEyeFOV[eye]);
+
+    ovrEyeRenderDesc renderDesc = ovrHmd_GetRenderDesc(mHMD, (ovrEyeType) eye, mFOVPort[eye]);
+
+    // these values are negated so that content can add the adjustment to its camera position,
+    // instead of subtracting
+    mEyeTranslation[eye] = Point3D(-renderDesc.ViewAdjust.x, -renderDesc.ViewAdjust.y, -renderDesc.ViewAdjust.z);
+
+    // note that we are using a right-handed coordinate system here, to match CSS
+    ovrMatrix4f projMatrix = ovrMatrix4f_Projection(mFOVPort[eye], zNear, zFar, true);
+
+    // XXX this is gross, we really need better methods on Matrix4x4
+    memcpy(&mEyeProjectionMatrix[eye], projMatrix.M, sizeof(ovrMatrix4f));
+    mEyeProjectionMatrix[eye].Transpose();
+
+    texSize[eye] = ovrHmd_GetFovTextureSize(mHMD, (ovrEyeType) eye, mFOVPort[eye], pixelsPerDisplayPixel);
+
+    ovrDistortionMesh mesh;
+    bool ok = ovrHmd_CreateDistortionMesh(mHMD, (ovrEyeType) eye, mFOVPort[eye], caps, &mesh);
+    if (!ok)
+      return false;
+
+    mDistortionMesh[eye].mVertices.SetLength(mesh.VertexCount);
+    mDistortionMesh[eye].mIndices.SetLength(mesh.IndexCount);
+
+    ovrDistortionVertex *srcv = mesh.pVertexData;
+    VRDistortionVertex *destv = mDistortionMesh[eye].mVertices.Elements();
+    memset(destv, 0, mesh.VertexCount * sizeof(VRDistortionVertex));
+    for (uint32_t i = 0; i < mesh.VertexCount; ++i) {
+      destv[i].pos[0] = srcv[i].ScreenPosNDC.x;
+      destv[i].pos[1] = srcv[i].ScreenPosNDC.y;
+
+      destv[i].texR[0] = srcv[i].TanEyeAnglesR.x;
+      destv[i].texR[1] = srcv[i].TanEyeAnglesR.y;
+      destv[i].texG[0] = srcv[i].TanEyeAnglesG.x;
+      destv[i].texG[1] = srcv[i].TanEyeAnglesG.y;
+      destv[i].texB[0] = srcv[i].TanEyeAnglesB.x;
+      destv[i].texB[1] = srcv[i].TanEyeAnglesB.y;
+
+      destv[i].genericAttribs[0] = srcv[i].VignetteFactor;
+      destv[i].genericAttribs[1] = srcv[i].TimeWarpFactor;
+    }
+
+    memcpy(mDistortionMesh[eye].mIndices.Elements(), mesh.pIndexData, mesh.IndexCount * sizeof(uint16_t));
+    ovrHmd_DestroyDistortionMesh(&mesh);
+  }
+
+  // take the max of both for eye resolution
+  mEyeResolution.width = std::max(texSize[Eye_Left].w, texSize[Eye_Right].w);
+  mEyeResolution.height = std::max(texSize[Eye_Left].h, texSize[Eye_Right].h);
+
+  mConfiguration.hmdType = mType;
+  mConfiguration.value = 0;
+  mConfiguration.fov[0] = aFOVLeft;
+  mConfiguration.fov[1] = aFOVRight;
+
+  return true;
+  //* need to call this during rendering each frame I think? */
+  //ovrHmd_GetRenderScaleAndOffset(fovPort, texSize, renderViewport, uvScaleOffsetOut);
+}
+
+void
+HMDInfoOculus::FillDistortionConstants(uint32_t whichEye,
+                                       const IntSize& textureSize,
+                                       const IntRect& eyeViewport,
+                                       const Size& destViewport,
+                                       const Rect& destRect,
+                                       VRDistortionConstants& values)
+{
+  ovrSizei texSize = { textureSize.width, textureSize.height };
+  ovrRecti eyePort = { { eyeViewport.x, eyeViewport.y }, { eyeViewport.width, eyeViewport.height } };
+  ovrVector2f scaleOut[2];
+
+  ovrHmd_GetRenderScaleAndOffset(mFOVPort[whichEye], texSize, eyePort, scaleOut);
+
+  values.eyeToSourceScaleAndOffset[0] = scaleOut[0].x;
+  values.eyeToSourceScaleAndOffset[1] = scaleOut[0].y;
+  values.eyeToSourceScaleAndOffset[2] = scaleOut[1].x;
+  values.eyeToSourceScaleAndOffset[3] = scaleOut[1].y;
+
+  // These values are in clip space [-1..1] range, but we're providing
+  // scaling in the 0..2 space for sanity.
+
+  // this is the destRect in clip space
+  float x0 = destRect.x / destViewport.width * 2.0 - 1.0;
+  float x1 = (destRect.x + destRect.width) / destViewport.width * 2.0 - 1.0;
+
+  float y0 = destRect.y / destViewport.height * 2.0 - 1.0;
+  float y1 = (destRect.y + destRect.height) / destViewport.height * 2.0 - 1.0;
+
+  // offset
+  values.destinationScaleAndOffset[0] = (x0+x1) / 2.0;
+  values.destinationScaleAndOffset[1] = (y0+y1) / 2.0;
+  // scale
+  values.destinationScaleAndOffset[2] = destRect.width / destViewport.width;
+  values.destinationScaleAndOffset[3] = destRect.height / destViewport.height;
+}
+
+bool
+HMDInfoOculus::StartSensorTracking()
+{
+  if (mStartCount == 0) {
+    bool ok = ovrHmd_ConfigureTracking(mHMD, ovrTrackingCap_Orientation | ovrTrackingCap_Position, 0);
+    if (!ok)
+      return false;
+  }
+
+  mStartCount++;
+  return true;
+}
+
+void
+HMDInfoOculus::StopSensorTracking()
+{
+  if (--mStartCount == 0) {
+    ovrHmd_ConfigureTracking(mHMD, 0, 0);
+  }
+}
+
+void
+HMDInfoOculus::ZeroSensor()
+{
+  ovrHmd_RecenterPose(mHMD);
+}
+
+VRHMDSensorState
+HMDInfoOculus::GetSensorState(double timeOffset)
+{
+  VRHMDSensorState result;
+  result.Clear();
+
+  // XXX this is the wrong time base for timeOffset; we need to figure out how to synchronize
+  // the Oculus time base and the browser one.
+  ovrTrackingState state = ovrHmd_GetTrackingState(mHMD, ovr_GetTimeInSeconds() + timeOffset);
+  ovrPoseStatef& pose(state.HeadPose);
+
+  result.timestamp = pose.TimeInSeconds;
+
+  if (state.StatusFlags & ovrStatus_OrientationTracked) {
+    result.flags |= State_Orientation;
+
+    result.orientation[0] = pose.ThePose.Orientation.x;
+    result.orientation[1] = pose.ThePose.Orientation.y;
+    result.orientation[2] = pose.ThePose.Orientation.z;
+    result.orientation[3] = pose.ThePose.Orientation.w;
+    
+    result.angularVelocity[0] = pose.AngularVelocity.x;
+    result.angularVelocity[1] = pose.AngularVelocity.y;
+    result.angularVelocity[2] = pose.AngularVelocity.z;
+
+    result.angularAcceleration[0] = pose.AngularAcceleration.x;
+    result.angularAcceleration[1] = pose.AngularAcceleration.y;
+    result.angularAcceleration[2] = pose.AngularAcceleration.z;
+  }
+
+  if (state.StatusFlags & ovrStatus_PositionTracked) {
+    result.flags |= State_Position;
+
+    result.position[0] = pose.ThePose.Position.x;
+    result.position[1] = pose.ThePose.Position.y;
+    result.position[2] = pose.ThePose.Position.z;
+    
+    result.linearVelocity[0] = pose.LinearVelocity.x;
+    result.linearVelocity[1] = pose.LinearVelocity.y;
+    result.linearVelocity[2] = pose.LinearVelocity.z;
+
+    result.linearAcceleration[0] = pose.LinearAcceleration.x;
+    result.linearAcceleration[1] = pose.LinearAcceleration.y;
+    result.linearAcceleration[2] = pose.LinearAcceleration.z;
+  }
+
+  return result;
+}
+
+namespace mozilla {
+namespace gfx {
+
+class VRHMDManagerOculusImpl {
+public:
+  VRHMDManagerOculusImpl() : mOculusInitialized(false), mOculusPlatformInitialized(false)
+  { }
+
+  bool PlatformInit();
+  bool Init();
+  void Destroy();
+  void GetOculusHMDs(nsTArray<nsRefPtr<VRHMDInfo> >& aHMDResult);
+protected:
+  nsTArray<nsRefPtr<HMDInfoOculus>> mOculusHMDs;
+  bool mOculusInitialized;
+  bool mOculusPlatformInitialized;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+VRHMDManagerOculusImpl *VRHMDManagerOculus::mImpl = nullptr;
+
+// These just forward to the Impl class, to have a non-static container for various
+// objects.
+
+bool
+VRHMDManagerOculus::PlatformInit()
+{
+  if (!mImpl) {
+    mImpl = new VRHMDManagerOculusImpl;
+  }
+  return mImpl->PlatformInit();
+}
+
+bool
+VRHMDManagerOculus::Init()
+{
+  if (!mImpl) {
+    mImpl = new VRHMDManagerOculusImpl;
+  }
+  return mImpl->Init();
+}
+
+void
+VRHMDManagerOculus::GetOculusHMDs(nsTArray<nsRefPtr<VRHMDInfo>>& aHMDResult)
+{
+  if (!mImpl) {
+    mImpl = new VRHMDManagerOculusImpl;
+  }
+  mImpl->GetOculusHMDs(aHMDResult);
+}
+
+void
+VRHMDManagerOculus::Destroy()
+{
+  if (!mImpl)
+    return;
+  mImpl->Destroy();
+  delete mImpl;
+  mImpl = nullptr;
+}
+
+bool
+VRHMDManagerOculusImpl::PlatformInit()
+{
+  if (mOculusPlatformInitialized)
+    return true;
+
+  if (!gfxPrefs::VREnabled())
+    return false;
+
+  if (!InitializeOculusCAPI())
+    return false;
+
+  bool ok = ovr_Initialize();
+
+  if (!ok)
+    return false;
+
+  mOculusPlatformInitialized = true;
+  return true;
+}
+
+bool
+VRHMDManagerOculusImpl::Init()
+{
+  if (mOculusInitialized)
+    return true;
+
+  if (!PlatformInit())
+    return false;
+
+  int count = ovrHmd_Detect();
+
+  for (int i = 0; i < count; ++i) {
+    ovrHmd hmd = ovrHmd_Create(i);
+    nsRefPtr<HMDInfoOculus> oc = new HMDInfoOculus(hmd);
+    mOculusHMDs.AppendElement(oc);
+  }
+
+  // VRAddTestDevices == 1: add test device only if no real devices present
+  // VRAddTestDevices == 2: add test device always
+  if ((count == 0 && gfxPrefs::VRAddTestDevices() == 1) ||
+      (gfxPrefs::VRAddTestDevices() == 2))
+  {
+    ovrHmd hmd = ovrHmd_CreateDebug(ovrHmd_DK2);
+    nsRefPtr<HMDInfoOculus> oc = new HMDInfoOculus(hmd);
+    mOculusHMDs.AppendElement(oc);
+  }
+
+  mOculusInitialized = true;
+  return true;
+}
+
+void
+VRHMDManagerOculusImpl::Destroy()
+{
+  if (!mOculusInitialized)
+    return;
+
+  for (size_t i = 0; i < mOculusHMDs.Length(); ++i) {
+    mOculusHMDs[i]->Destroy();
+  }
+
+  mOculusHMDs.Clear();
+
+  ovr_Shutdown();
+  mOculusInitialized = false;
+}
+
+void
+VRHMDManagerOculusImpl::GetOculusHMDs(nsTArray<nsRefPtr<VRHMDInfo>>& aHMDResult)
+{
+  Init();
+  for (size_t i = 0; i < mOculusHMDs.Length(); ++i) {
+    aHMDResult.AppendElement(mOculusHMDs[i]);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxVR.h
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_VR_H
+#define GFX_VR_H
+
+#include "nsTArray.h"
+#include "nsIScreen.h"
+#include "nsCOMPtr.h"
+#include "nsRefPtr.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/EnumeratedArray.h"
+
+namespace mozilla {
+namespace gfx {
+
+MOZ_BEGIN_ENUM_CLASS(VRHMDType, uint16_t)
+  Oculus,
+  NumHMDTypes
+MOZ_END_ENUM_CLASS(VRHMDType)
+
+struct VRFieldOfView {
+  static VRFieldOfView FromCSSPerspectiveInfo(double aPerspectiveDistance,
+                                              const Point& aPerspectiveOrigin,
+                                              const Point& aTransformOrigin,
+                                              const Rect& aContentRectangle)
+  {
+    /**/
+    return VRFieldOfView();
+  }
+
+  VRFieldOfView() {}
+  VRFieldOfView(double up, double right, double down, double left)
+    : upDegrees(up), rightDegrees(right), downDegrees(down), leftDegrees(left)
+  {}
+
+  bool operator==(const VRFieldOfView& other) const {
+    return other.upDegrees == upDegrees &&
+           other.downDegrees == downDegrees &&
+           other.rightDegrees == rightDegrees &&
+           other.leftDegrees == leftDegrees;
+  }
+
+  bool operator!=(const VRFieldOfView& other) const {
+    return !(*this == other);
+  }
+
+  bool IsZero() const {
+    return upDegrees == 0.0 ||
+      rightDegrees == 0.0 ||
+      downDegrees == 0.0 ||
+      leftDegrees == 0.0;
+  }
+
+  double upDegrees;
+  double rightDegrees;
+  double downDegrees;
+  double leftDegrees;
+};
+
+// 12 floats per vertex. Position, tex coordinates
+// for each channel, and 4 generic attributes
+struct VRDistortionConstants {
+  float eyeToSourceScaleAndOffset[4];
+  float destinationScaleAndOffset[4];
+};
+
+struct VRDistortionVertex {
+  float pos[2];
+  float texR[2];
+  float texG[2];
+  float texB[2];
+  float genericAttribs[4];
+};
+
+struct VRDistortionMesh {
+  nsTArray<VRDistortionVertex> mVertices;
+  nsTArray<uint16_t> mIndices;
+};
+
+struct VRHMDSensorState {
+  double timestamp;
+  uint32_t flags;
+  float orientation[4];
+  float position[3];
+  float angularVelocity[3];
+  float angularAcceleration[3];
+  float linearVelocity[3];
+  float linearAcceleration[3];
+
+  void Clear() {
+    memset(this, 0, sizeof(VRHMDSensorState));
+  }
+};
+
+/* A pure data struct that can be used to see if
+ * the configuration of one HMDInfo matches another; for rendering purposes,
+ * really asking "can the rendering details of this one be used for the other"
+ */
+struct VRHMDConfiguration {
+  VRHMDConfiguration() : hmdType(VRHMDType::NumHMDTypes) {}
+
+  bool operator==(const VRHMDConfiguration& other) const {
+    return hmdType == other.hmdType &&
+      value == other.value &&
+      fov[0] == other.fov[0] &&
+      fov[1] == other.fov[1];
+  }
+
+  bool operator!=(const VRHMDConfiguration& other) const {
+    return hmdType != other.hmdType ||
+      value != other.value ||
+      fov[0] != other.fov[0] ||
+      fov[1] != other.fov[1];
+  }
+
+  bool IsValid() const {
+    return hmdType != VRHMDType::NumHMDTypes;
+  }
+
+  VRHMDType hmdType;
+  uint32_t value;
+  VRFieldOfView fov[2];
+};
+
+class VRHMDInfo {
+public:
+  enum Eye {
+    Eye_Left,
+    Eye_Right,
+    NumEyes
+  };
+
+  enum StateValidFlags {
+    State_Position = 1 << 1,
+    State_Orientation = 1 << 2
+  };
+
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRHMDInfo)
+
+  VRHMDType GetType() const { return mType; }
+
+  virtual const VRFieldOfView& GetRecommendedEyeFOV(uint32_t whichEye) { return mRecommendedEyeFOV[whichEye]; }
+  virtual const VRFieldOfView& GetMaximumEyeFOV(uint32_t whichEye) { return mMaximumEyeFOV[whichEye]; }
+
+  const VRHMDConfiguration& GetConfiguration() const { return mConfiguration; }
+
+  /* set the FOV for this HMD unit; this triggers a computation of all the remaining bits.  Returns false if it fails */
+  virtual bool SetFOV(const VRFieldOfView& aFOVLeft, const VRFieldOfView& aFOVRight,
+                      double zNear, double zFar) = 0;
+  const VRFieldOfView& GetEyeFOV(uint32_t whichEye)  { return mEyeFOV[whichEye]; }
+
+  /* Suggested resolution for rendering a single eye.
+   * Assumption is that left/right rendering will be 2x of this size.
+   * XXX fix this for vertical displays
+   */
+  const IntSize& SuggestedEyeResolution() const { return mEyeResolution; }
+  const Point3D& GetEyeTranslation(uint32_t whichEye) const { return mEyeTranslation[whichEye]; }
+  const Matrix4x4& GetEyeProjectionMatrix(uint32_t whichEye) const { return mEyeProjectionMatrix[whichEye]; }
+
+  virtual uint32_t GetSupportedSensorStateBits() { return mSupportedSensorBits; }
+  virtual bool StartSensorTracking() = 0;
+  virtual VRHMDSensorState GetSensorState(double timeOffset = 0.0) = 0;
+  virtual void StopSensorTracking() = 0;
+
+  virtual void ZeroSensor() = 0;
+
+  virtual void FillDistortionConstants(uint32_t whichEye,
+                                       const IntSize& textureSize, // the full size of the texture
+                                       const IntRect& eyeViewport, // the viewport within the texture for the current eye
+                                       const Size& destViewport,   // the size of the destination viewport
+                                       const Rect& destRect,       // the rectangle within the dest viewport that this should be rendered
+                                       VRDistortionConstants& values) = 0;
+
+  virtual const VRDistortionMesh& GetDistortionMesh(uint32_t whichEye) const { return mDistortionMesh[whichEye]; }
+
+  // The nsIScreen that represents this device
+  virtual nsIScreen* GetScreen() { return mScreen; }
+
+protected:
+  VRHMDInfo(VRHMDType aType) : mType(aType) { MOZ_COUNT_CTOR(VRHMDInfo); }
+  virtual ~VRHMDInfo() { MOZ_COUNT_DTOR(VRHMDInfo); }
+
+  VRHMDType mType;
+  VRHMDConfiguration mConfiguration;
+
+  VRFieldOfView mEyeFOV[NumEyes];
+  IntSize mEyeResolution;
+  Point3D mEyeTranslation[NumEyes];
+  Matrix4x4 mEyeProjectionMatrix[NumEyes];
+  VRDistortionMesh mDistortionMesh[NumEyes];
+  uint32_t mSupportedSensorBits;
+
+  VRFieldOfView mRecommendedEyeFOV[NumEyes];
+  VRFieldOfView mMaximumEyeFOV[NumEyes];
+
+  nsCOMPtr<nsIScreen> mScreen;
+};
+
+class VRHMDManagerOculusImpl;
+class VRHMDManagerOculus {
+  static VRHMDManagerOculusImpl *mImpl;
+public:
+  static bool PlatformInit();
+  static bool Init();
+  static void Destroy();
+  static void GetOculusHMDs(nsTArray<nsRefPtr<VRHMDInfo> >& aHMDResult);
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* GFX_VR_H */
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -82,26 +82,28 @@ using namespace mozilla::widget;
 using namespace mozilla::image;
 
 DCFromDrawTarget::DCFromDrawTarget(DrawTarget& aDrawTarget)
 {
   mDC = nullptr;
   if (aDrawTarget.GetBackendType() == BackendType::CAIRO) {
     cairo_surface_t *surf = (cairo_surface_t*)
         aDrawTarget.GetNativeSurface(NativeSurfaceType::CAIRO_SURFACE);
-    cairo_surface_type_t surfaceType = cairo_surface_get_type(surf);
-    if (surfaceType == CAIRO_SURFACE_TYPE_WIN32 ||
-        surfaceType == CAIRO_SURFACE_TYPE_WIN32_PRINTING) {
-      mDC = cairo_win32_surface_get_dc(surf);
-      mNeedsRelease = false;
-      SaveDC(mDC);
-      cairo_t* ctx = (cairo_t*)
-          aDrawTarget.GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT);
-      cairo_scaled_font_t* scaled = cairo_get_scaled_font(ctx);
-      cairo_win32_scaled_font_select_font(scaled, mDC);
+    if (surf) {
+      cairo_surface_type_t surfaceType = cairo_surface_get_type(surf);
+      if (surfaceType == CAIRO_SURFACE_TYPE_WIN32 ||
+          surfaceType == CAIRO_SURFACE_TYPE_WIN32_PRINTING) {
+        mDC = cairo_win32_surface_get_dc(surf);
+        mNeedsRelease = false;
+        SaveDC(mDC);
+        cairo_t* ctx = (cairo_t*)
+            aDrawTarget.GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT);
+        cairo_scaled_font_t* scaled = cairo_get_scaled_font(ctx);
+        cairo_win32_scaled_font_select_font(scaled, mDC);
+      }
     }
     if (!mDC) {
       mDC = GetDC(nullptr);
       SetGraphicsMode(mDC, GM_ADVANCED);
       mNeedsRelease = true;
     }
   }
 }
--- a/gfx/thebes/moz.build
+++ b/gfx/thebes/moz.build
@@ -44,16 +44,17 @@ EXPORTS += [
     'gfxSharedQuartzSurface.h',
     'gfxSkipChars.h',
     'gfxSVGGlyphs.h',
     'gfxTeeSurface.h',
     'gfxTextRun.h',
     'gfxTypes.h',
     'gfxUserFontSet.h',
     'gfxUtils.h',
+    'gfxVR.h',
     'GraphicsFilter.h',
     'RoundedRect.h',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     EXPORTS += [
         'gfxAndroidPlatform.h',
         'gfxFT2FontBase.h',
@@ -238,16 +239,17 @@ UNIFIED_SOURCES += [
     'gfxReusableSharedImageSurfaceWrapper.cpp',
     'gfxScriptItemizer.cpp',
     'gfxSkipChars.cpp',
     'gfxSVGGlyphs.cpp',
     'gfxTeeSurface.cpp',
     'gfxTextRun.cpp',
     'gfxUserFontSet.cpp',
     'gfxUtils.cpp',
+    'gfxVR.cpp',
     'nsUnicodeRange.cpp',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     UNIFIED_SOURCES += [
         'gfxMacPlatformFontList.mm',
     ]
 
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/ovr_capi_dynamic.h
@@ -0,0 +1,228 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This file contains just the needed struct definitions for
+ * interacting with the Oculus VR C API, without needing to #include
+ * OVR_CAPI.h directly.  Note that it uses the same type names as the
+ * CAPI, and cannot be #included at the same time as OVR_CAPI.h.  It
+ * does not include the entire C API, just want's needed.
+ */
+
+#ifdef OVR_CAPI_h
+#warning OVR_CAPI.h included before ovr_capi_dynamic.h, skpping this
+#define mozilla_ovr_capi_dynamic_h_
+#endif
+
+#ifndef mozilla_ovr_capi_dynamic_h_
+#define mozilla_ovr_capi_dynamic_h_
+
+#define OVR_CAPI_LIMITED_MOZILLA 1
+
+#ifdef __cplusplus 
+extern "C" {
+#endif
+
+typedef char ovrBool;
+typedef struct { int x, y; } ovrVector2i;
+typedef struct { int w, h; } ovrSizei;
+typedef struct { ovrVector2i Pos; ovrSizei Size; } ovrRecti;
+typedef struct { float x, y, z, w; } ovrQuatf;
+typedef struct { float x, y; } ovrVector2f;
+typedef struct { float x, y, z; } ovrVector3f;
+typedef struct { float M[4][4]; } ovrMatrix4f;
+
+typedef struct {
+  ovrQuatf Orientation;
+  ovrVector3f Position;
+} ovrPosef;
+
+typedef struct {
+  ovrPosef ThePose;
+  ovrVector3f AngularVelocity;
+  ovrVector3f LinearVelocity;
+  ovrVector3f AngularAcceleration;
+  ovrVector3f LinearAcceleration;
+  double TimeInSeconds;
+} ovrPoseStatef;
+
+typedef struct {
+  float UpTan;
+  float DownTan;
+  float LeftTan;
+  float RightTan;
+} ovrFovPort;
+
+typedef enum {
+  ovrHmd_None             = 0,    
+  ovrHmd_DK1              = 3,
+  ovrHmd_DKHD             = 4,
+  ovrHmd_DK2              = 6,
+  ovrHmd_Other
+} ovrHmdType;
+
+typedef enum {
+  ovrHmdCap_Present           = 0x0001,
+  ovrHmdCap_Available         = 0x0002,
+  ovrHmdCap_Captured          = 0x0004,
+  ovrHmdCap_ExtendDesktop     = 0x0008,
+  ovrHmdCap_DisplayOff        = 0x0040,
+  ovrHmdCap_LowPersistence    = 0x0080,
+  ovrHmdCap_DynamicPrediction = 0x0200,
+  ovrHmdCap_NoVSync           = 0x1000,
+  ovrHmdCap_NoMirrorToWindow  = 0x2000
+} ovrHmdCapBits;
+
+typedef enum
+{
+  ovrTrackingCap_Orientation      = 0x0010,
+  ovrTrackingCap_MagYawCorrection = 0x0020,
+  ovrTrackingCap_Position         = 0x0040,
+  ovrTrackingCap_Idle             = 0x0100
+} ovrTrackingCaps;
+
+typedef enum {
+  ovrDistortionCap_Chromatic = 0x01,
+  ovrDistortionCap_TimeWarp  = 0x02,
+  ovrDistortionCap_Vignette  = 0x08,
+  ovrDistortionCap_NoRestore = 0x10,
+  ovrDistortionCap_FlipInput = 0x20,
+  ovrDistortionCap_SRGB      = 0x40,
+  ovrDistortionCap_Overdrive = 0x80,
+  ovrDistortionCap_ProfileNoTimewarpSpinWaits = 0x10000
+} ovrDistortionCaps;
+
+typedef enum {
+  ovrEye_Left  = 0,
+  ovrEye_Right = 1,
+  ovrEye_Count = 2
+} ovrEyeType;
+
+typedef struct ovrHmdDesc_ {
+  void* Handle;
+  ovrHmdType  Type;
+  const char* ProductName;    
+  const char* Manufacturer;
+  short VendorId;
+  short ProductId;
+  char SerialNumber[24];
+  short FirmwareMajor;
+  short FirmwareMinor;
+  float CameraFrustumHFovInRadians;
+  float CameraFrustumVFovInRadians;
+  float CameraFrustumNearZInMeters;
+  float CameraFrustumFarZInMeters;
+
+  unsigned int HmdCaps;
+  unsigned int TrackingCaps;
+  unsigned int DistortionCaps;
+
+  ovrFovPort  DefaultEyeFov[ovrEye_Count];
+  ovrFovPort  MaxEyeFov[ovrEye_Count];
+  ovrEyeType  EyeRenderOrder[ovrEye_Count];
+
+  ovrSizei    Resolution;
+  ovrVector2i WindowsPos;
+
+  const char* DisplayDeviceName;
+  int         DisplayId;
+} ovrHmdDesc;
+
+typedef const ovrHmdDesc* ovrHmd;
+
+typedef enum {
+  ovrStatus_OrientationTracked    = 0x0001,
+  ovrStatus_PositionTracked       = 0x0002,
+  ovrStatus_CameraPoseTracked     = 0x0004,
+  ovrStatus_PositionConnected     = 0x0020,
+  ovrStatus_HmdConnected          = 0x0080
+} ovrStatusBits;
+
+typedef struct ovrSensorData_ {
+  ovrVector3f    Accelerometer;
+  ovrVector3f    Gyro;
+  ovrVector3f    Magnetometer;
+  float          Temperature;
+  float          TimeInSeconds;
+} ovrSensorData;
+
+
+typedef struct ovrTrackingState_ {
+  ovrPoseStatef HeadPose;
+  ovrPosef CameraPose;
+  ovrPosef LeveledCameraPose;
+  ovrSensorData RawSensorData;
+  unsigned int StatusFlags;
+  double LastVisionProcessingTime;
+  double LastVisionFrameLatency;
+  uint32_t LastCameraFrameCounter;
+} ovrTrackingState;
+
+typedef struct ovrFrameTiming_ {
+  float DeltaSeconds;
+  double ThisFrameSeconds;
+  double TimewarpPointSeconds;
+  double NextFrameSeconds;
+  double ScanoutMidpointSeconds;
+  double EyeScanoutSeconds[2];    
+} ovrFrameTiming;
+
+typedef struct ovrEyeRenderDesc_ {
+  ovrEyeType  Eye;        
+  ovrFovPort  Fov;
+  ovrRecti DistortedViewport;
+  ovrVector2f PixelsPerTanAngleAtCenter;
+  ovrVector3f ViewAdjust;
+} ovrEyeRenderDesc;
+
+typedef struct ovrDistortionVertex_ {
+  ovrVector2f ScreenPosNDC;
+  float       TimeWarpFactor;
+  float       VignetteFactor;
+  ovrVector2f TanEyeAnglesR;
+  ovrVector2f TanEyeAnglesG;
+  ovrVector2f TanEyeAnglesB;    
+} ovrDistortionVertex;
+
+typedef struct ovrDistortionMesh_ {
+  ovrDistortionVertex* pVertexData;
+  unsigned short*      pIndexData;
+  unsigned int         VertexCount;
+  unsigned int         IndexCount;
+} ovrDistortionMesh;
+
+typedef ovrBool (*pfn_ovr_Initialize)();
+typedef void (*pfn_ovr_Shutdown)();
+typedef int (*pfn_ovrHmd_Detect)();
+typedef ovrHmd (*pfn_ovrHmd_Create)(int index);
+typedef void (*pfn_ovrHmd_Destroy)(ovrHmd hmd);
+typedef ovrHmd (*pfn_ovrHmd_CreateDebug)(ovrHmdType type);
+typedef const char* (*pfn_ovrHmd_GetLastError)(ovrHmd hmd);
+typedef ovrBool (*pfn_ovrHmd_AttachToWindow)(ovrHmd hmd, void* window, const ovrRecti* destMirrorRect, const ovrRecti* sourceRenderTargetRect);
+typedef unsigned int (*pfn_ovrHmd_GetEnabledCaps)(ovrHmd hmd);
+typedef void (*pfn_ovrHmd_SetEnabledCaps)(ovrHmd hmd, unsigned int hmdCaps);
+typedef ovrBool (*pfn_ovrHmd_ConfigureTracking)(ovrHmd hmd, unsigned int supportedTrackingCaps, unsigned int requiredTrackingCaps); 
+typedef void (*pfn_ovrHmd_RecenterPose)(ovrHmd hmd);
+typedef ovrTrackingState (*pfn_ovrHmd_GetTrackingState)(ovrHmd hmd, double absTime);
+typedef ovrSizei (*pfn_ovrHmd_GetFovTextureSize)(ovrHmd hmd, ovrEyeType eye, ovrFovPort fov, float pixelsPerDisplayPixel);
+typedef ovrEyeRenderDesc (*pfn_ovrHmd_GetRenderDesc)(ovrHmd hmd, ovrEyeType eyeType, ovrFovPort fov);
+typedef ovrBool (*pfn_ovrHmd_CreateDistortionMesh)(ovrHmd hmd, ovrEyeType eyeType, ovrFovPort fov, unsigned int distortionCaps, ovrDistortionMesh *meshData);
+typedef void (*pfn_ovrHmd_DestroyDistortionMesh)(ovrDistortionMesh* meshData);
+typedef void (*pfn_ovrHmd_GetRenderScaleAndOffset)(ovrFovPort fov, ovrSizei textureSize, ovrRecti renderViewport, ovrVector2f uvScaleOffsetOut[2]);
+typedef ovrFrameTiming (*pfn_ovrHmd_GetFrameTiming)(ovrHmd hmd, unsigned int frameIndex);
+typedef ovrFrameTiming (*pfn_ovrHmd_BeginFrameTiming)(ovrHmd hmd, unsigned int frameIndex);
+typedef void (*pfn_ovrHmd_EndFrameTiming)(ovrHmd hmd);
+typedef void (*pfn_ovrHmd_ResetFrameTiming)(ovrHmd hmd, unsigned int frameIndex, bool vsync);
+typedef void (*pfn_ovrHmd_GetEyePoses)(ovrHmd hmd, unsigned int frameIndex, ovrVector3f hmdToEyeViewOffset[2], ovrPosef outEyePoses[2], ovrTrackingState* outHmdTrackingState);
+typedef ovrPosef (*pfn_ovrHmd_GetHmdPosePerEye)(ovrHmd hmd, ovrEyeType eye);
+typedef void (*pfn_ovrHmd_GetEyeTimewarpMatrices)(ovrHmd hmd, ovrEyeType eye, ovrPosef renderPose, ovrMatrix4f twmOut[2]);
+typedef ovrMatrix4f (*pfn_ovrMatrix4f_Projection) (ovrFovPort fov, float znear, float zfar, ovrBool rightHanded );
+typedef ovrMatrix4f (*pfn_ovrMatrix4f_OrthoSubProjection) (ovrFovPort fov, ovrVector2f orthoScale, float orthoDistance, float eyeViewAdjustX);
+typedef double (*pfn_ovr_GetTimeInSeconds)();
+
+#ifdef __cplusplus 
+}
+#endif
+
+#endif /* mozilla_ovr_capi_dynamic_h_ */
--- a/js/src/asmjs/AsmJSModule.cpp
+++ b/js/src/asmjs/AsmJSModule.cpp
@@ -749,52 +749,84 @@ AsmJSModule::staticallyLink(ExclusiveCon
         exitDatum.exit = interpExitTrampoline(exits_[i]);
         exitDatum.fun = nullptr;
         exitDatum.ionScript = nullptr;
     }
 
     MOZ_ASSERT(isStaticallyLinked());
 }
 
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+static inline size_t
+ViewTypeByteSize(AsmJSHeapAccess::ViewType vt)
+{
+    switch (vt) {
+      case AsmJSHeapAccess::Int8:
+      case AsmJSHeapAccess::Uint8:
+      case AsmJSHeapAccess::Uint8Clamped:
+      case AsmJSHeapAccess::Int16:
+      case AsmJSHeapAccess::Uint16:
+      case AsmJSHeapAccess::Int32:
+      case AsmJSHeapAccess::Uint32:
+      case AsmJSHeapAccess::Float32:
+      case AsmJSHeapAccess::Float64:
+        return 1 << TypedArrayShift(Scalar::Type(vt));
+      case AsmJSHeapAccess::Float32x4:
+      case AsmJSHeapAccess::Int32x4:
+        return 16;
+    }
+    MOZ_CRASH("unexpected view type");
+}
+#endif // JS_CODEGEN_X86 || JS_CODEGEN_X64
+
 void
 AsmJSModule::initHeap(Handle<ArrayBufferObjectMaybeShared *> heap, JSContext *cx)
 {
     MOZ_ASSERT_IF(heap->is<ArrayBufferObject>(), heap->as<ArrayBufferObject>().isAsmJS());
     MOZ_ASSERT(IsValidAsmJSHeapLength(heap->byteLength()));
     MOZ_ASSERT(dynamicallyLinked_);
     MOZ_ASSERT(!maybeHeap_);
 
     maybeHeap_ = heap;
     heapDatum() = heap->dataPointer();
 
 #if defined(JS_CODEGEN_X86)
     uint8_t *heapOffset = heap->dataPointer();
-    void *heapLength = (void*)heap->byteLength();
     for (unsigned i = 0; i < heapAccesses_.length(); i++) {
         const jit::AsmJSHeapAccess &access = heapAccesses_[i];
-        if (access.hasLengthCheck())
-            X86Assembler::setPointer(access.patchLengthAt(code_), heapLength);
+        if (access.hasLengthCheck()) {
+            // An access is out-of-bounds iff
+            //      ptr + data-type-byte-size > heapLength
+            // i.e. ptr >= heapLength + 1 - data-type-byte-size
+            // (Note that we need >= as this is what codegen uses.)
+            AsmJSHeapAccess::ViewType vt = access.viewType();
+            X86Assembler::setPointer(access.patchLengthAt(code_),
+                                     (void*)(heap->byteLength() + 1 - ViewTypeByteSize(vt)));
+        }
         void *addr = access.patchOffsetAt(code_);
         uint32_t disp = reinterpret_cast<uint32_t>(X86Assembler::getPointer(addr));
         MOZ_ASSERT(disp <= INT32_MAX);
         X86Assembler::setPointer(addr, (void *)(heapOffset + disp));
     }
 #elif defined(JS_CODEGEN_X64)
     // Even with signal handling being used for most bounds checks, there may be
     // atomic operations that depend on explicit checks.
     //
     // If we have any explicit bounds checks, we need to patch the heap length
     // checks at the right places. All accesses that have been recorded are the
     // only ones that need bound checks (see also
     // CodeGeneratorX64::visitAsmJS{Load,Store,CompareExchange,AtomicBinop}Heap)
     int32_t heapLength = int32_t(intptr_t(heap->byteLength()));
     for (size_t i = 0; i < heapAccesses_.length(); i++) {
         const jit::AsmJSHeapAccess &access = heapAccesses_[i];
-        if (access.hasLengthCheck())
-            X86Assembler::setInt32(access.patchLengthAt(code_), heapLength);
+        if (access.hasLengthCheck()) {
+            // See comment above for x86 codegen.
+            X86Assembler::setInt32(access.patchLengthAt(code_),
+                                   heapLength + 1 - ViewTypeByteSize(access.viewType()));
+        }
     }
 #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     uint32_t heapLength = heap->byteLength();
     for (unsigned i = 0; i < heapAccesses_.length(); i++) {
         jit::Assembler::UpdateBoundsCheck(heapLength,
                                           (jit::Instruction*)(heapAccesses_[i].offset() + code_));
     }
 #endif
--- a/js/src/asmjs/AsmJSSignalHandlers.cpp
+++ b/js/src/asmjs/AsmJSSignalHandlers.cpp
@@ -326,55 +326,82 @@ ContextToPC(CONTEXT *context)
 #else
      return reinterpret_cast<uint8_t**>(&PC_sig(context));
 #endif
 }
 
 #if defined(JS_CODEGEN_X64)
 template <class T>
 static void
-SetXMMRegToNaN(bool isFloat32, T *xmm_reg)
+SetXMMRegToNaN(AsmJSHeapAccess::ViewType viewType, T *xmm_reg)
 {
-    if (isFloat32) {
+    switch (viewType) {
+      case AsmJSHeapAccess::Float32: {
         JS_STATIC_ASSERT(sizeof(T) == 4 * sizeof(float));
         float *floats = reinterpret_cast<float*>(xmm_reg);
         floats[0] = GenericNaN();
         floats[1] = 0;
         floats[2] = 0;
         floats[3] = 0;
-    } else {
+        break;
+      }
+      case AsmJSHeapAccess::Float64: {
         JS_STATIC_ASSERT(sizeof(T) == 2 * sizeof(double));
         double *dbls = reinterpret_cast<double*>(xmm_reg);
         dbls[0] = GenericNaN();
         dbls[1] = 0;
+        break;
+      }
+      case AsmJSHeapAccess::Float32x4: {
+        JS_STATIC_ASSERT(sizeof(T) == 4 * sizeof(float));
+        float *floats = reinterpret_cast<float*>(xmm_reg);
+        for (unsigned i = 0; i < 4; i++)
+            floats[i] = GenericNaN();
+        break;
+      }
+      case AsmJSHeapAccess::Int32x4: {
+        JS_STATIC_ASSERT(sizeof(T) == 4 * sizeof(int32_t));
+        int32_t *ints = reinterpret_cast<int32_t*>(xmm_reg);
+        for (unsigned i = 0; i < 4; i++)
+            ints[i] = 0;
+        break;
+      }
+      case AsmJSHeapAccess::Int8:
+      case AsmJSHeapAccess::Uint8:
+      case AsmJSHeapAccess::Int16:
+      case AsmJSHeapAccess::Uint16:
+      case AsmJSHeapAccess::Int32:
+      case AsmJSHeapAccess::Uint32:
+      case AsmJSHeapAccess::Uint8Clamped:
+        MOZ_CRASH("unexpected type in SetXMMRegToNaN");
     }
 }
 
 # if !defined(XP_MACOSX)
 static void
-SetRegisterToCoercedUndefined(CONTEXT *context, bool isFloat32, AnyRegister reg)
+SetRegisterToCoercedUndefined(CONTEXT *context, AsmJSHeapAccess::ViewType viewType, AnyRegister reg)
 {
     if (reg.isFloat()) {
         switch (reg.fpu().code()) {
-          case X86Registers::xmm0:  SetXMMRegToNaN(isFloat32, &XMM_sig(context, 0)); break;
-          case X86Registers::xmm1:  SetXMMRegToNaN(isFloat32, &XMM_sig(context, 1)); break;
-          case X86Registers::xmm2:  SetXMMRegToNaN(isFloat32, &XMM_sig(context, 2)); break;
-          case X86Registers::xmm3:  SetXMMRegToNaN(isFloat32, &XMM_sig(context, 3)); break;
-          case X86Registers::xmm4:  SetXMMRegToNaN(isFloat32, &XMM_sig(context, 4)); break;
-          case X86Registers::xmm5:  SetXMMRegToNaN(isFloat32, &XMM_sig(context, 5)); break;
-          case X86Registers::xmm6:  SetXMMRegToNaN(isFloat32, &XMM_sig(context, 6)); break;
-          case X86Registers::xmm7:  SetXMMRegToNaN(isFloat32, &XMM_sig(context, 7)); break;
-          case X86Registers::xmm8:  SetXMMRegToNaN(isFloat32, &XMM_sig(context, 8)); break;
-          case X86Registers::xmm9:  SetXMMRegToNaN(isFloat32, &XMM_sig(context, 9)); break;
-          case X86Registers::xmm10: SetXMMRegToNaN(isFloat32, &XMM_sig(context, 10)); break;
-          case X86Registers::xmm11: SetXMMRegToNaN(isFloat32, &XMM_sig(context, 11)); break;
-          case X86Registers::xmm12: SetXMMRegToNaN(isFloat32, &XMM_sig(context, 12)); break;
-          case X86Registers::xmm13: SetXMMRegToNaN(isFloat32, &XMM_sig(context, 13)); break;
-          case X86Registers::xmm14: SetXMMRegToNaN(isFloat32, &XMM_sig(context, 14)); break;
-          case X86Registers::xmm15: SetXMMRegToNaN(isFloat32, &XMM_sig(context, 15)); break;
+          case X86Registers::xmm0:  SetXMMRegToNaN(viewType, &XMM_sig(context, 0)); break;
+          case X86Registers::xmm1:  SetXMMRegToNaN(viewType, &XMM_sig(context, 1)); break;
+          case X86Registers::xmm2:  SetXMMRegToNaN(viewType, &XMM_sig(context, 2)); break;
+          case X86Registers::xmm3:  SetXMMRegToNaN(viewType, &XMM_sig(context, 3)); break;
+          case X86Registers::xmm4:  SetXMMRegToNaN(viewType, &XMM_sig(context, 4)); break;
+          case X86Registers::xmm5:  SetXMMRegToNaN(viewType, &XMM_sig(context, 5)); break;
+          case X86Registers::xmm6:  SetXMMRegToNaN(viewType, &XMM_sig(context, 6)); break;
+          case X86Registers::xmm7:  SetXMMRegToNaN(viewType, &XMM_sig(context, 7)); break;
+          case X86Registers::xmm8:  SetXMMRegToNaN(viewType, &XMM_sig(context, 8)); break;
+          case X86Registers::xmm9:  SetXMMRegToNaN(viewType, &XMM_sig(context, 9)); break;
+          case X86Registers::xmm10: SetXMMRegToNaN(viewType, &XMM_sig(context, 10)); break;
+          case X86Registers::xmm11: SetXMMRegToNaN(viewType, &XMM_sig(context, 11)); break;
+          case X86Registers::xmm12: SetXMMRegToNaN(viewType, &XMM_sig(context, 12)); break;
+          case X86Registers::xmm13: SetXMMRegToNaN(viewType, &XMM_sig(context, 13)); break;
+          case X86Registers::xmm14: SetXMMRegToNaN(viewType, &XMM_sig(context, 14)); break;
+          case X86Registers::xmm15: SetXMMRegToNaN(viewType, &XMM_sig(context, 15)); break;
           default: MOZ_CRASH();
         }
     } else {
         switch (reg.gpr().code()) {
           case X86Registers::eax: RAX_sig(context) = 0; break;
           case X86Registers::ecx: RCX_sig(context) = 0; break;
           case X86Registers::edx: RDX_sig(context) = 0; break;
           case X86Registers::ebx: RBX_sig(context) = 0; break;
@@ -450,17 +477,17 @@ HandleFault(PEXCEPTION_POINTERS exceptio
 
     // We now know that this is an out-of-bounds access made by an asm.js
     // load/store that we should handle. If this is a load, assign the
     // JS-defined result value to the destination register (ToInt32(undefined)
     // or ToNumber(undefined), determined by the type of the destination
     // register) and set the PC to the next op. Upon return from the handler,
     // execution will resume at this next PC.
     if (heapAccess->isLoad())
-        SetRegisterToCoercedUndefined(context, heapAccess->isFloat32Load(), heapAccess->loadedReg());
+        SetRegisterToCoercedUndefined(context, heapAccess->viewType(), heapAccess->loadedReg());
     *ppc += heapAccess->opLength();
 
     return true;
 # else
     return false;
 # endif
 }
 
@@ -500,34 +527,34 @@ SetRegisterToCoercedUndefined(mach_port_
         kern_return_t kret;
 
         x86_float_state64_t fstate;
         unsigned int count = x86_FLOAT_STATE64_COUNT;
         kret = thread_get_state(rtThread, x86_FLOAT_STATE64, (thread_state_t) &fstate, &count);
         if (kret != KERN_SUCCESS)
             return false;
 
-        bool f32 = heapAccess.isFloat32Load();
+        AsmJSHeapAccess::ViewType viewType = heapAccess.viewType();
         switch (heapAccess.loadedReg().fpu().code()) {
-          case X86Registers::xmm0:  SetXMMRegToNaN(f32, &fstate.__fpu_xmm0); break;
-          case X86Registers::xmm1:  SetXMMRegToNaN(f32, &fstate.__fpu_xmm1); break;
-          case X86Registers::xmm2:  SetXMMRegToNaN(f32, &fstate.__fpu_xmm2); break;
-          case X86Registers::xmm3:  SetXMMRegToNaN(f32, &fstate.__fpu_xmm3); break;
-          case X86Registers::xmm4:  SetXMMRegToNaN(f32, &fstate.__fpu_xmm4); break;
-          case X86Registers::xmm5:  SetXMMRegToNaN(f32, &fstate.__fpu_xmm5); break;
-          case X86Registers::xmm6:  SetXMMRegToNaN(f32, &fstate.__fpu_xmm6); break;
-          case X86Registers::xmm7:  SetXMMRegToNaN(f32, &fstate.__fpu_xmm7); break;
-          case X86Registers::xmm8:  SetXMMRegToNaN(f32, &fstate.__fpu_xmm8); break;
-          case X86Registers::xmm9:  SetXMMRegToNaN(f32, &fstate.__fpu_xmm9); break;
-          case X86Registers::xmm10: SetXMMRegToNaN(f32, &fstate.__fpu_xmm10); break;
-          case X86Registers::xmm11: SetXMMRegToNaN(f32, &fstate.__fpu_xmm11); break;
-          case X86Registers::xmm12: SetXMMRegToNaN(f32, &fstate.__fpu_xmm12); break;
-          case X86Registers::xmm13: SetXMMRegToNaN(f32, &fstate.__fpu_xmm13); break;
-          case X86Registers::xmm14: SetXMMRegToNaN(f32, &fstate.__fpu_xmm14); break;
-          case X86Registers::xmm15: SetXMMRegToNaN(f32, &fstate.__fpu_xmm15); break;
+          case X86Registers::xmm0:  SetXMMRegToNaN(viewType, &fstate.__fpu_xmm0); break;
+          case X86Registers::xmm1:  SetXMMRegToNaN(viewType, &fstate.__fpu_xmm1); break;
+          case X86Registers::xmm2:  SetXMMRegToNaN(viewType, &fstate.__fpu_xmm2); break;
+          case X86Registers::xmm3:  SetXMMRegToNaN(viewType, &fstate.__fpu_xmm3); break;
+          case X86Registers::xmm4:  SetXMMRegToNaN(viewType, &fstate.__fpu_xmm4); break;
+          case X86Registers::xmm5:  SetXMMRegToNaN(viewType, &fstate.__fpu_xmm5); break;
+          case X86Registers::xmm6:  SetXMMRegToNaN(viewType, &fstate.__fpu_xmm6); break;
+          case X86Registers::xmm7:  SetXMMRegToNaN(viewType, &fstate.__fpu_xmm7); break;
+          case X86Registers::xmm8:  SetXMMRegToNaN(viewType, &fstate.__fpu_xmm8); break;
+          case X86Registers::xmm9:  SetXMMRegToNaN(viewType, &fstate.__fpu_xmm9); break;
+          case X86Registers::xmm10: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm10); break;
+          case X86Registers::xmm11: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm11); break;
+          case X86Registers::xmm12: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm12); break;
+          case X86Registers::xmm13: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm13); break;
+          case X86Registers::xmm14: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm14); break;
+          case X86Registers::xmm15: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm15); break;
           default: MOZ_CRASH();
         }
 
         kret = thread_set_state(rtThread, x86_FLOAT_STATE64, (thread_state_t)&fstate, x86_FLOAT_STATE64_COUNT);
         if (kret != KERN_SUCCESS)
             return false;
     } else {
         switch (heapAccess.loadedReg().gpr().code()) {
@@ -842,17 +869,17 @@ HandleFault(int signum, siginfo_t *info,
 
     // We now know that this is an out-of-bounds access made by an asm.js
     // load/store that we should handle. If this is a load, assign the
     // JS-defined result value to the destination register (ToInt32(undefined)
     // or ToNumber(undefined), determined by the type of the destination
     // register) and set the PC to the next op. Upon return from the handler,
     // execution will resume at this next PC.
     if (heapAccess->isLoad())
-        SetRegisterToCoercedUndefined(context, heapAccess->isFloat32Load(), heapAccess->loadedReg());
+        SetRegisterToCoercedUndefined(context, heapAccess->viewType(), heapAccess->loadedReg());
     *ppc += heapAccess->opLength();
 
     return true;
 # else
     return false;
 # endif
 }
 
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -561,64 +561,39 @@ class Type
           case Float32x4:
             return MIRType_Float32x4;
           case Void:
             return MIRType_None;
         }
         MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Invalid Type");
     }
 
-    Type simdToScalarType() const {
+    AsmJSSimdType simdType() const {
         MOZ_ASSERT(isSimd());
         switch (which_) {
           case Int32x4:
-            return Signed;
+            return AsmJSSimdType_int32x4;
           case Float32x4:
-            return Float;
+            return AsmJSSimdType_float32x4;
           // Scalar types
           case Double:
           case DoubleLit:
           case MaybeDouble:
           case Float:
           case MaybeFloat:
           case Floatish:
           case Fixnum:
           case Int:
           case Signed:
           case Unsigned:
           case Intish:
           case Void:
             break;
         }
-        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Invalid SIMD Type");
-    }
-
-    Type simdToCoercedScalarType() const {
-        MOZ_ASSERT(isSimd());
-        switch (which_) {
-          case Int32x4:
-            return Intish;
-          case Float32x4:
-            return Floatish;
-          // Scalar types
-          case Double:
-          case DoubleLit:
-          case MaybeDouble:
-          case Float:
-          case MaybeFloat:
-          case Floatish:
-          case Fixnum:
-          case Int:
-          case Signed:
-          case Unsigned:
-          case Intish:
-          case Void:
-            break;
-        }
-        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Invalid SIMD Type");
+        MOZ_CRASH("not a SIMD Type");
     }
 
     const char *toChars() const {
         switch (which_) {
           case Double:      return "double";
           case DoubleLit:   return "doublelit";
           case MaybeDouble: return "double?";
           case Float:       return "float";
@@ -2776,28 +2751,29 @@ class FunctionCompiler
 
     void assign(const Local &local, MDefinition *def)
     {
         if (inDeadCode())
             return;
         curBlock_->setSlot(info().localSlot(local.slot), def);
     }
 
-    MDefinition *loadHeap(Scalar::Type vt, MDefinition *ptr, NeedsBoundsCheck chk)
+    MDefinition *loadHeap(AsmJSHeapAccess::ViewType vt, MDefinition *ptr, NeedsBoundsCheck chk)
     {
         if (inDeadCode())
             return nullptr;
 
         bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK && !m().usesSignalHandlersForOOB();
         MAsmJSLoadHeap *load = MAsmJSLoadHeap::New(alloc(), vt, ptr, needsBoundsCheck);
         curBlock_->add(load);
         return load;
     }
 
-    void storeHeap(Scalar::Type vt, MDefinition *ptr, MDefinition *v, NeedsBoundsCheck chk)
+    void storeHeap(AsmJSHeapAccess::ViewType vt, MDefinition *ptr, MDefinition *v,
+                   NeedsBoundsCheck chk)
     {
         if (inDeadCode())
             return;
 
         bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK && !m().usesSignalHandlersForOOB();
         MAsmJSStoreHeap *store = MAsmJSStoreHeap::New(alloc(), vt, ptr, v, needsBoundsCheck);
         curBlock_->add(store);
     }
@@ -2805,53 +2781,56 @@ class FunctionCompiler
     void memoryBarrier(MemoryBarrierBits type)
     {
         if (inDeadCode())
             return;
         MMemoryBarrier *ins = MMemoryBarrier::New(alloc(), type);
         curBlock_->add(ins);
     }
 
-    MDefinition *atomicLoadHeap(Scalar::Type vt, MDefinition *ptr, NeedsBoundsCheck chk)
+    MDefinition *atomicLoadHeap(AsmJSHeapAccess::ViewType vt, MDefinition *ptr, NeedsBoundsCheck chk)
     {
         if (inDeadCode())
             return nullptr;
 
         bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK && !m().usesSignalHandlersForOOB();
         MAsmJSLoadHeap *load = MAsmJSLoadHeap::New(alloc(), vt, ptr, needsBoundsCheck,
                                                    MembarBeforeLoad, MembarAfterLoad);
         curBlock_->add(load);
         return load;
     }
 
-    void atomicStoreHeap(Scalar::Type vt, MDefinition *ptr, MDefinition *v, NeedsBoundsCheck chk)
+    void atomicStoreHeap(AsmJSHeapAccess::ViewType vt, MDefinition *ptr, MDefinition *v,
+                         NeedsBoundsCheck chk)
     {
         if (inDeadCode())
             return;
 
         bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK && !m().usesSignalHandlersForOOB();
         MAsmJSStoreHeap *store = MAsmJSStoreHeap::New(alloc(), vt, ptr, v, needsBoundsCheck,
                                                       MembarBeforeStore, MembarAfterStore);
         curBlock_->add(store);
     }
 
-    MDefinition *atomicCompareExchangeHeap(Scalar::Type vt, MDefinition *ptr, MDefinition *oldv, MDefinition *newv, NeedsBoundsCheck chk)
+    MDefinition *atomicCompareExchangeHeap(AsmJSHeapAccess::ViewType vt, MDefinition *ptr,
+                                           MDefinition *oldv, MDefinition *newv, NeedsBoundsCheck chk)
     {
         if (inDeadCode())
             return nullptr;
 
         // The code generator requires explicit bounds checking for compareExchange.
         bool needsBoundsCheck = true;
         MAsmJSCompareExchangeHeap *cas =
             MAsmJSCompareExchangeHeap::New(alloc(), vt, ptr, oldv, newv, needsBoundsCheck);
         curBlock_->add(cas);
         return cas;
     }
 
-    MDefinition *atomicBinopHeap(js::jit::AtomicOp op, Scalar::Type vt, MDefinition *ptr, MDefinition *v, NeedsBoundsCheck chk)
+    MDefinition *atomicBinopHeap(js::jit::AtomicOp op, AsmJSHeapAccess::ViewType vt,
+                                 MDefinition *ptr, MDefinition *v, NeedsBoundsCheck chk)
     {
         if (inDeadCode())
             return nullptr;
 
         // The code generator requires explicit bounds checking for the binops.
         bool needsBoundsCheck = true;
         MAsmJSAtomicBinopHeap *binop =
             MAsmJSAtomicBinopHeap::New(alloc(), op, vt, ptr, v, needsBoundsCheck);
@@ -4469,17 +4448,17 @@ static bool
 CheckLoadArray(FunctionCompiler &f, ParseNode *elem, MDefinition **def, Type *type)
 {
     Scalar::Type viewType;
     MDefinition *pointerDef;
     NeedsBoundsCheck needsBoundsCheck;
     if (!CheckArrayAccess(f, ElemBase(elem), ElemIndex(elem), &viewType, &pointerDef, &needsBoundsCheck))
         return false;
 
-    *def = f.loadHeap(viewType, pointerDef, needsBoundsCheck);
+    *def = f.loadHeap(AsmJSHeapAccess::ViewType(viewType), pointerDef, needsBoundsCheck);
     *type = TypedArrayLoadType(viewType);
     return true;
 }
 
 static bool
 CheckDotAccess(FunctionCompiler &f, ParseNode *elem, MDefinition **def, Type *type)
 {
     MOZ_ASSERT(elem->isKind(PNK_DOT));
@@ -4510,17 +4489,21 @@ CheckDotAccess(FunctionCompiler &f, Pars
         lane = LaneY;
     else if (field == names.z)
         lane = LaneZ;
     else if (field == names.w)
         lane = LaneW;
     else
         return f.fail(base, "dot access field must be a lane name (x, y, z, w) or signMask");
 
-    *type = baseType.simdToScalarType();
+    switch (baseType.simdType()) {
+      case AsmJSSimdType_int32x4:   *type = Type::Signed; break;
+      case AsmJSSimdType_float32x4: *type = Type::Float;  break;
+    }
+
     *def = f.extractSimdElement(lane, baseDef, type->toMIRType());
     return true;
 }
 
 static bool
 CheckStoreArray(FunctionCompiler &f, ParseNode *lhs, ParseNode *rhs, MDefinition **def, Type *type)
 {
     Scalar::Type viewType;
@@ -4559,17 +4542,17 @@ CheckStoreArray(FunctionCompiler &f, Par
             rhsDef = f.unary<MToDouble>(rhsDef);
         else if (!rhsType.isMaybeDouble())
             return f.failf(lhs, "%s is not a subtype of float? or double?", rhsType.toChars());
         break;
       default:
         MOZ_CRASH("Unexpected view type");
     }
 
-    f.storeHeap(viewType, pointerDef, rhsDef, needsBoundsCheck);
+    f.storeHeap(AsmJSHeapAccess::ViewType(viewType), pointerDef, rhsDef, needsBoundsCheck);
 
     *def = rhsDef;
     *type = rhsType;
     return true;
 }
 
 static bool
 CheckAssignName(FunctionCompiler &f, ParseNode *lhs, ParseNode *rhs, MDefinition **def, Type *type)
@@ -4827,17 +4810,17 @@ CheckAtomicsLoad(FunctionCompiler &f, Pa
     ParseNode *indexArg = NextNode(arrayArg);
 
     Scalar::Type viewType;
     MDefinition *pointerDef;
     NeedsBoundsCheck needsBoundsCheck;
     if (!CheckSharedArrayAtomicAccess(f, arrayArg, indexArg, &viewType, &pointerDef, &needsBoundsCheck))
         return false;
 
-    *def = f.atomicLoadHeap(viewType, pointerDef, needsBoundsCheck);
+    *def = f.atomicLoadHeap(AsmJSHeapAccess::ViewType(viewType), pointerDef, needsBoundsCheck);
     *type = Type::Signed;
     return true;
 }
 
 static bool
 CheckAtomicsStore(FunctionCompiler &f, ParseNode *call, MDefinition **def, Type *type)
 {
     if (CallArgListLength(call) != 3)
@@ -4856,17 +4839,17 @@ CheckAtomicsStore(FunctionCompiler &f, P
     MDefinition *rhsDef;
     Type rhsType;
     if (!CheckExpr(f, valueArg, &rhsDef, &rhsType))
         return false;
 
     if (!rhsType.isIntish())
         return f.failf(arrayArg, "%s is not a subtype of intish", rhsType.toChars());
 
-    f.atomicStoreHeap(viewType, pointerDef, rhsDef, needsBoundsCheck);
+    f.atomicStoreHeap(AsmJSHeapAccess::ViewType(viewType), pointerDef, rhsDef, needsBoundsCheck);
 
     *def = rhsDef;
     *type = Type::Signed;
     return true;
 }
 
 static bool
 CheckAtomicsBinop(FunctionCompiler &f, ParseNode *call, MDefinition **def, Type *type, js::jit::AtomicOp op)
@@ -4887,17 +4870,18 @@ CheckAtomicsBinop(FunctionCompiler &f, P
     MDefinition *valueArgDef;
     Type valueArgType;
     if (!CheckExpr(f, valueArg, &valueArgDef, &valueArgType))
         return false;
 
     if (!valueArgType.isIntish())
         return f.failf(valueArg, "%s is not a subtype of intish", valueArgType.toChars());
 
-    *def = f.atomicBinopHeap(op, viewType, pointerDef, valueArgDef, needsBoundsCheck);
+    *def = f.atomicBinopHeap(op, AsmJSHeapAccess::ViewType(viewType), pointerDef, valueArgDef,
+                             needsBoundsCheck);
     *type = Type::Signed;
     return true;
 }
 
 static bool
 CheckAtomicsCompareExchange(FunctionCompiler &f, ParseNode *call, MDefinition **def, Type *type)
 {
     if (CallArgListLength(call) != 4)
@@ -4925,17 +4909,18 @@ CheckAtomicsCompareExchange(FunctionComp
         return false;
 
     if (!oldValueArgType.isIntish())
         return f.failf(oldValueArg, "%s is not a subtype of intish", oldValueArgType.toChars());
 
     if (!newValueArgType.isIntish())
         return f.failf(newValueArg, "%s is not a subtype of intish", newValueArgType.toChars());
 
-    *def = f.atomicCompareExchangeHeap(viewType, pointerDef, oldValueArgDef, newValueArgDef, needsBoundsCheck);
+    *def = f.atomicCompareExchangeHeap(AsmJSHeapAccess::ViewType(viewType), pointerDef,
+                                       oldValueArgDef, newValueArgDef, needsBoundsCheck);
     *type = Type::Signed;
     return true;
 }
 
 static bool
 CheckAtomicsBuiltinCall(FunctionCompiler &f, ParseNode *callNode, AsmJSAtomicsBuiltinFunction func,
                         MDefinition **resultDef, Type *resultType)
 {
@@ -5360,65 +5345,77 @@ CheckSimdCallArgs(FunctionCompiler &f, P
     return true;
 }
 
 class CheckArgIsSubtypeOf
 {
     Type formalType_;
 
   public:
-    explicit CheckArgIsSubtypeOf(Type t) : formalType_(t) {}
+    explicit CheckArgIsSubtypeOf(AsmJSSimdType t) : formalType_(t) {}
 
     bool operator()(FunctionCompiler &f, ParseNode *arg, unsigned argIndex, Type actualType,
                     MDefinition **def) const
     {
         if (!(actualType <= formalType_)) {
             return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
                            formalType_.toChars());
         }
         return true;
     }
 };
 
+static inline Type
+SimdToCoercedScalarType(AsmJSSimdType t)
+{
+    switch (t) {
+      case AsmJSSimdType_int32x4:
+        return Type::Intish;
+      case AsmJSSimdType_float32x4:
+        return Type::Floatish;
+    }
+    MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected SIMD type");
+}
+
 class CheckSimdScalarArgs
 {
-    Type simdType_;
+    AsmJSSimdType simdType_;
     Type formalType_;
 
   public:
-    CheckSimdScalarArgs(Type simdType)
-      : simdType_(simdType), formalType_(simdType.simdToCoercedScalarType())
+    CheckSimdScalarArgs(AsmJSSimdType simdType)
+      : simdType_(simdType), formalType_(SimdToCoercedScalarType(simdType))
     {}
 
     bool operator()(FunctionCompiler &f, ParseNode *arg, unsigned argIndex, Type actualType,
                     MDefinition **def) const
     {
         if (!(actualType <= formalType_)) {
             // As a special case, accept doublelit arguments to float32x4 ops by
             // re-emitting them as float32 constants.
-            if (!simdType_.isFloat32x4() || !actualType.isDoubleLit()) {
+            if (simdType_ != AsmJSSimdType_float32x4 || !actualType.isDoubleLit()) {
                 return f.failf(arg, "%s is not a subtype of %s%s",
                                actualType.toChars(), formalType_.toChars(),
-                               simdType_.isFloat32x4() ? " or doublelit" : "");
+                               simdType_ == AsmJSSimdType_float32x4 ? " or doublelit" : "");
             }
 
             AsmJSNumLit doubleLit = ExtractNumericLiteral(f.m(), arg);
             MOZ_ASSERT(doubleLit.which() == AsmJSNumLit::Double);
             *def = f.constant(doubleLit.scalarValue(), Type::Float);
         }
         return true;
     }
 };
 
 class CheckSimdSelectArgs
 {
     Type formalType_;
 
   public:
-    explicit CheckSimdSelectArgs(Type t) : formalType_(t) {}
+    explicit CheckSimdSelectArgs(AsmJSSimdType t) : formalType_(t) {}
 
     bool operator()(FunctionCompiler &f, ParseNode *arg, unsigned argIndex, Type actualType,
                     MDefinition **def) const
     {
         if (argIndex == 0) {
             // First argument of select is an int32x4 mask.
             if (!(actualType <= Type::Int32x4))
                 return f.failf(arg, "%s is not a subtype of Int32x4", actualType.toChars());
@@ -5430,144 +5427,151 @@ class CheckSimdSelectArgs
                            formalType_.toChars());
         }
         return true;
     }
 };
 
 class CheckSimdVectorScalarArgs
 {
-    Type formalType_;
+    AsmJSSimdType formalSimdType_;
 
   public:
-    explicit CheckSimdVectorScalarArgs(Type t) : formalType_(t) {}
+    explicit CheckSimdVectorScalarArgs(AsmJSSimdType t) : formalSimdType_(t) {}
 
     bool operator()(FunctionCompiler &f, ParseNode *arg, unsigned argIndex, Type actualType,
                     MDefinition **def) const
     {
         MOZ_ASSERT(argIndex < 2);
         if (argIndex == 0) {
             // First argument is the vector
-            if (!(actualType <= formalType_)) {
+            if (!(actualType <= Type(formalSimdType_))) {
                 return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
-                               formalType_.toChars());
+                               Type(formalSimdType_).toChars());
             }
             return true;
         }
 
         // Second argument is the scalar
-        Type coercedFormalType = formalType_.simdToCoercedScalarType();
+        Type coercedFormalType = SimdToCoercedScalarType(formalSimdType_);
         if (!(actualType <= coercedFormalType)) {
             return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
                            coercedFormalType.toChars());
         }
         return true;
     }
 };
 
 } // anonymous namespace
 
 static inline bool
-CheckSimdUnary(FunctionCompiler &f, ParseNode *call, Type retType, MSimdUnaryArith::Operation op,
-               MDefinition **def, Type *type)
+CheckSimdUnary(FunctionCompiler &f, ParseNode *call, AsmJSSimdType opType,
+               MSimdUnaryArith::Operation op, MDefinition **def, Type *type)
 {
     DefinitionVector defs;
-    if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(retType), &defs))
-        return false;
-    *def = f.unarySimd(defs[0], op, retType.toMIRType());
-    *type = retType;
+    if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(opType), &defs))
+        return false;
+    *type = opType;
+    *def = f.unarySimd(defs[0], op, type->toMIRType());
     return true;
 }
 
 template<class OpEnum>
 static inline bool
-CheckSimdBinary(FunctionCompiler &f, ParseNode *call, Type retType, OpEnum op, MDefinition **def,
-                Type *type)
+CheckSimdBinary(FunctionCompiler &f, ParseNode *call, AsmJSSimdType opType, OpEnum op,
+                MDefinition **def, Type *type)
 {
     DefinitionVector argDefs;
-    if (!CheckSimdCallArgs(f, call, 2, CheckArgIsSubtypeOf(retType), &argDefs))
-        return false;
-    *def = f.binarySimd(argDefs[0], argDefs[1], op, retType.toMIRType());
-    *type = retType;
+    if (!CheckSimdCallArgs(f, call, 2, CheckArgIsSubtypeOf(opType), &argDefs))
+        return false;
+    *type = opType;
+    *def = f.binarySimd(argDefs[0], argDefs[1], op, type->toMIRType());
+    return true;
+}
+
+template<>
+inline bool
+CheckSimdBinary<MSimdBinaryComp::Operation>(FunctionCompiler &f, ParseNode *call,
+                                            AsmJSSimdType opType, MSimdBinaryComp::Operation op,
+                                            MDefinition **def, Type *type)
+{
+    DefinitionVector argDefs;
+    if (!CheckSimdCallArgs(f, call, 2, CheckArgIsSubtypeOf(opType), &argDefs))
+        return false;
+    *type = Type::Int32x4;
+    *def = f.binarySimd<MSimdBinaryComp>(argDefs[0], argDefs[1], op);
     return true;
 }
 
 template<>
 inline bool
-CheckSimdBinary<MSimdBinaryComp::Operation>(FunctionCompiler &f, ParseNode *call, Type retType,
-                                            MSimdBinaryComp::Operation op, MDefinition **def,
-                                            Type *type)
-{
-    DefinitionVector argDefs;
-    if (!CheckSimdCallArgs(f, call, 2, CheckArgIsSubtypeOf(retType), &argDefs))
-        return false;
-    *def = f.binarySimd<MSimdBinaryComp>(argDefs[0], argDefs[1], op);
-    *type = Type::Int32x4;
-    return true;
-}
-
-template<>
-inline bool
-CheckSimdBinary<MSimdShift::Operation>(FunctionCompiler &f, ParseNode *call, Type retType,
+CheckSimdBinary<MSimdShift::Operation>(FunctionCompiler &f, ParseNode *call, AsmJSSimdType opType,
                                        MSimdShift::Operation op, MDefinition **def, Type *type)
 {
     DefinitionVector argDefs;
-    if (!CheckSimdCallArgs(f, call, 2, CheckSimdVectorScalarArgs(retType), &argDefs))
-        return false;
+    if (!CheckSimdCallArgs(f, call, 2, CheckSimdVectorScalarArgs(opType), &argDefs))
+        return false;
+    *type = Type::Int32x4;
     *def = f.binarySimd<MSimdShift>(argDefs[0], argDefs[1], op);
-    *type = Type::Int32x4;
     return true;
 }
 
 static bool
-CheckSimdWith(FunctionCompiler &f, ParseNode *call, Type retType, SimdLane lane, MDefinition **def,
-              Type *type)
+CheckSimdWith(FunctionCompiler &f, ParseNode *call, AsmJSSimdType opType, SimdLane lane,
+              MDefinition **def, Type *type)
 {
     DefinitionVector defs;
-    if (!CheckSimdCallArgs(f, call, 2, CheckSimdVectorScalarArgs(retType), &defs))
-        return false;
-    *def = f.insertElementSimd(defs[0], defs[1], lane, retType.toMIRType());
-    *type = retType;
+    if (!CheckSimdCallArgs(f, call, 2, CheckSimdVectorScalarArgs(opType), &defs))
+        return false;
+    *type = opType;
+    *def = f.insertElementSimd(defs[0], defs[1], lane, type->toMIRType());
     return true;
 }
 
+namespace {
+// Include CheckSimdCast in unnamed namespace to avoid MSVC name lookup bug (due to the use of Type).
+
 template<class T>
 static bool
-CheckSimdCast(FunctionCompiler &f, ParseNode *call, Type fromType, Type toType, MDefinition **def,
-              Type *type)
+CheckSimdCast(FunctionCompiler &f, ParseNode *call, AsmJSSimdType fromType, AsmJSSimdType toType,
+              MDefinition **def, Type *type)
 {
     DefinitionVector defs;
     if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(fromType), &defs))
         return false;
-    *def = f.convertSimd<T>(defs[0], fromType.toMIRType(), toType.toMIRType());
     *type = toType;
+    *def = f.convertSimd<T>(defs[0], Type(fromType).toMIRType(), type->toMIRType());
     return true;
 }
 
+}
+
 static bool
 CheckSimdShuffleSelectors(FunctionCompiler &f, ParseNode *lane, int32_t lanes[4], uint32_t maxLane)
 {
     for (unsigned i = 0; i < 4; i++, lane = NextNode(lane)) {
         uint32_t u32;
         if (!IsLiteralInt(f.m(), lane, &u32))
             return f.failf(lane, "lane selector should be a constant integer literal");
         if (u32 >= maxLane)
             return f.failf(lane, "lane selector should be less than %u", maxLane);
         lanes[i] = int32_t(u32);
     }
     return true;
 }
 
 static bool
-CheckSimdSwizzle(FunctionCompiler &f, ParseNode *call, Type retType, MDefinition **def, Type *type)
+CheckSimdSwizzle(FunctionCompiler &f, ParseNode *call, AsmJSSimdType opType, MDefinition **def,
+                 Type *type)
 {
     unsigned numArgs = CallArgListLength(call);
     if (numArgs != 5)
         return f.failf(call, "expected 5 arguments to SIMD swizzle, got %u", numArgs);
 
+    Type retType = opType;
     ParseNode *vec = CallArgList(call);
     MDefinition *vecDef;
     Type vecType;
     if (!CheckExpr(f, vec, &vecDef, &vecType))
         return false;
     if (!(vecType <= retType))
         return f.failf(vec, "%s is not a subtype of %s", vecType.toChars(), retType.toChars());
 
@@ -5576,22 +5580,24 @@ CheckSimdSwizzle(FunctionCompiler &f, Pa
         return false;
 
     *def = f.swizzleSimd(vecDef, lanes[0], lanes[1], lanes[2], lanes[3], retType.toMIRType());
     *type = retType;
     return true;
 }
 
 static bool
-CheckSimdShuffle(FunctionCompiler &f, ParseNode *call, Type retType, MDefinition **def, Type *type)
+CheckSimdShuffle(FunctionCompiler &f, ParseNode *call, AsmJSSimdType opType, MDefinition **def,
+                 Type *type)
 {
     unsigned numArgs = CallArgListLength(call);
     if (numArgs != 6)
         return f.failf(call, "expected 6 arguments to SIMD shuffle, got %u", numArgs);
 
+    Type retType = opType;
     ParseNode *arg = CallArgList(call);
     MDefinition *vecs[2];
     for (unsigned i = 0; i < 2; i++, arg = NextNode(arg)) {
         Type type;
         if (!CheckExpr(f, arg, &vecs[i], &type))
             return false;
         if (!(type <= retType))
             return f.failf(arg, "%s is not a subtype of %s", type.toChars(), retType.toChars());
@@ -5603,116 +5609,225 @@ CheckSimdShuffle(FunctionCompiler &f, Pa
 
     *def = f.shuffleSimd(vecs[0], vecs[1], lanes[0], lanes[1], lanes[2], lanes[3],
                          retType.toMIRType());
     *type = retType;
     return true;
 }
 
 static bool
+CheckSimdLoadStoreArgs(FunctionCompiler &f, ParseNode *call, AsmJSSimdType opType,
+                       AsmJSHeapAccess::ViewType *viewType, MDefinition **index,
+                       NeedsBoundsCheck *needsBoundsCheck)
+{
+    ParseNode *view = CallArgList(call);
+    if (!view->isKind(PNK_NAME))
+        return f.fail(view, "expected Uint8Array view as SIMD.*.store first argument");
+
+    const ModuleCompiler::Global *global = f.lookupGlobal(view->name());
+    if (!global ||
+        global->which() != ModuleCompiler::Global::ArrayView ||
+        global->viewType() != Scalar::Uint8)
+    {
+        return f.fail(view, "expected Uint8Array view as SIMD.*.store first argument");
+    }
+
+    *needsBoundsCheck = NEEDS_BOUNDS_CHECK;
+
+    switch (opType) {
+      case AsmJSSimdType_int32x4:   *viewType = AsmJSHeapAccess::Int32x4;   break;
+      case AsmJSSimdType_float32x4: *viewType = AsmJSHeapAccess::Float32x4; break;
+    }
+
+    ParseNode *indexExpr = NextNode(view);
+    uint32_t indexLit;
+    if (IsLiteralOrConstInt(f, indexExpr, &indexLit)) {
+        if (indexLit > INT32_MAX)
+            return f.fail(indexExpr, "constant index out of range");
+
+        if (!f.m().tryRequireHeapLengthToBeAtLeast(indexLit + Simd128DataSize)) {
+            return f.failf(indexExpr, "constant index outside heap size range declared by the "
+                                      "change-heap function (0x%x - 0x%x)",
+                                      f.m().minHeapLength(), f.m().module().maxHeapLength());
+        }
+
+        *needsBoundsCheck = NO_BOUNDS_CHECK;
+        *index = f.constant(Int32Value(indexLit), Type::Int);
+        return true;
+    }
+
+    f.enterHeapExpression();
+
+    Type indexType;
+    if (!CheckExpr(f, indexExpr, index, &indexType))
+        return false;
+    if (!indexType.isIntish())
+        return f.failf(indexExpr, "%s is not a subtype of intish", indexType.toChars());
+
+    f.leaveHeapExpression();
+
+    return true;
+}
+
+static bool
+CheckSimdLoad(FunctionCompiler &f, ParseNode *call, AsmJSSimdType opType, MDefinition **def, Type *type)
+{
+    unsigned numArgs = CallArgListLength(call);
+    if (numArgs != 2)
+        return f.failf(call, "expected 2 arguments to SIMD load, got %u", numArgs);
+
+    AsmJSHeapAccess::ViewType viewType;
+    MDefinition *index;
+    NeedsBoundsCheck needsBoundsCheck;
+    if (!CheckSimdLoadStoreArgs(f, call, opType, &viewType, &index, &needsBoundsCheck))
+        return false;
+
+    *def = f.loadHeap(viewType, index, needsBoundsCheck);
+    *type = opType;
+    return true;
+}
+
+static bool
+CheckSimdStore(FunctionCompiler &f, ParseNode *call, AsmJSSimdType opType, MDefinition **def, Type *type)
+{
+    unsigned numArgs = CallArgListLength(call);
+    if (numArgs != 3)
+        return f.failf(call, "expected 3 arguments to SIMD load, got %u", numArgs);
+
+    AsmJSHeapAccess::ViewType viewType;
+    MDefinition *index;
+    NeedsBoundsCheck needsBoundsCheck;
+    if (!CheckSimdLoadStoreArgs(f, call, opType, &viewType, &index, &needsBoundsCheck))
+        return false;
+
+    Type retType = opType;
+    ParseNode *vecExpr = NextNode(NextNode(CallArgList(call)));
+    MDefinition *vec;
+    Type vecType;
+    if (!CheckExpr(f, vecExpr, &vec, &vecType))
+        return false;
+    if (!(vecType <= retType))
+        return f.failf(vecExpr, "%s is not a subtype of %s", vecType.toChars(), retType.toChars());
+
+    f.storeHeap(viewType, index, vec, needsBoundsCheck);
+    *def = vec;
+    *type = vecType;
+    return true;
+}
+
+static bool
 CheckSimdOperationCall(FunctionCompiler &f, ParseNode *call, const ModuleCompiler::Global *global,
                        MDefinition **def, Type *type)
 {
     MOZ_ASSERT(global->isSimdOperation());
 
-    Type retType = global->simdOperationType();
+    AsmJSSimdType opType = global->simdOperationType();
 
     switch (global->simdOperation()) {
       case AsmJSSimdOperation_add:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryArith::Add, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryArith::Add, def, type);
       case AsmJSSimdOperation_sub:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryArith::Sub, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryArith::Sub, def, type);
       case AsmJSSimdOperation_mul:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryArith::Mul, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryArith::Mul, def, type);
       case AsmJSSimdOperation_div:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryArith::Div, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryArith::Div, def, type);
       case AsmJSSimdOperation_max:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryArith::Max, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryArith::Max, def, type);
       case AsmJSSimdOperation_min:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryArith::Min, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryArith::Min, def, type);
+      case AsmJSSimdOperation_maxNum:
+        return CheckSimdBinary(f, call, opType, MSimdBinaryArith::MaxNum, def, type);
+      case AsmJSSimdOperation_minNum:
+        return CheckSimdBinary(f, call, opType, MSimdBinaryArith::MinNum, def, type);
 
       case AsmJSSimdOperation_lessThan:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryComp::lessThan, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryComp::lessThan, def, type);
       case AsmJSSimdOperation_lessThanOrEqual:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryComp::lessThanOrEqual, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryComp::lessThanOrEqual, def, type);
       case AsmJSSimdOperation_equal:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryComp::equal, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryComp::equal, def, type);
       case AsmJSSimdOperation_notEqual:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryComp::notEqual, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryComp::notEqual, def, type);
       case AsmJSSimdOperation_greaterThan:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryComp::greaterThan, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryComp::greaterThan, def, type);
       case AsmJSSimdOperation_greaterThanOrEqual:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryComp::greaterThanOrEqual, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryComp::greaterThanOrEqual, def, type);
 
       case AsmJSSimdOperation_and:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryBitwise::and_, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryBitwise::and_, def, type);
       case AsmJSSimdOperation_or:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryBitwise::or_, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryBitwise::or_, def, type);
       case AsmJSSimdOperation_xor:
-        return CheckSimdBinary(f, call, retType, MSimdBinaryBitwise::xor_, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdBinaryBitwise::xor_, def, type);
 
       case AsmJSSimdOperation_withX:
-        return CheckSimdWith(f, call, retType, SimdLane::LaneX, def, type);
+        return CheckSimdWith(f, call, opType, SimdLane::LaneX, def, type);
       case AsmJSSimdOperation_withY:
-        return CheckSimdWith(f, call, retType, SimdLane::LaneY, def, type);
+        return CheckSimdWith(f, call, opType, SimdLane::LaneY, def, type);
       case AsmJSSimdOperation_withZ:
-        return CheckSimdWith(f, call, retType, SimdLane::LaneZ, def, type);
+        return CheckSimdWith(f, call, opType, SimdLane::LaneZ, def, type);
       case AsmJSSimdOperation_withW:
-        return CheckSimdWith(f, call, retType, SimdLane::LaneW, def, type);
+        return CheckSimdWith(f, call, opType, SimdLane::LaneW, def, type);
 
       case AsmJSSimdOperation_fromInt32x4:
-        return CheckSimdCast<MSimdConvert>(f, call, Type::Int32x4, retType, def, type);
+        return CheckSimdCast<MSimdConvert>(f, call, AsmJSSimdType_int32x4, opType, def, type);
       case AsmJSSimdOperation_fromInt32x4Bits:
-        return CheckSimdCast<MSimdReinterpretCast>(f, call, Type::Int32x4, retType, def, type);
+        return CheckSimdCast<MSimdReinterpretCast>(f, call, AsmJSSimdType_int32x4, opType, def, type);
       case AsmJSSimdOperation_fromFloat32x4:
-        return CheckSimdCast<MSimdConvert>(f, call, Type::Float32x4, retType, def, type);
+        return CheckSimdCast<MSimdConvert>(f, call, AsmJSSimdType_float32x4, opType, def, type);
       case AsmJSSimdOperation_fromFloat32x4Bits:
-        return CheckSimdCast<MSimdReinterpretCast>(f, call, Type::Float32x4, retType, def, type);
+        return CheckSimdCast<MSimdReinterpretCast>(f, call, AsmJSSimdType_float32x4, opType, def, type);
 
       case AsmJSSimdOperation_shiftLeft:
-        return CheckSimdBinary(f, call, Type::Int32x4, MSimdShift::lsh, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdShift::lsh, def, type);
       case AsmJSSimdOperation_shiftRight:
-        return CheckSimdBinary(f, call, Type::Int32x4, MSimdShift::rsh, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdShift::rsh, def, type);
       case AsmJSSimdOperation_shiftRightLogical:
-        return CheckSimdBinary(f, call, Type::Int32x4, MSimdShift::ursh, def, type);
+        return CheckSimdBinary(f, call, opType, MSimdShift::ursh, def, type);
 
       case AsmJSSimdOperation_abs:
-        return CheckSimdUnary(f, call, retType, MSimdUnaryArith::abs, def, type);
+        return CheckSimdUnary(f, call, opType, MSimdUnaryArith::abs, def, type);
       case AsmJSSimdOperation_neg:
-        return CheckSimdUnary(f, call, retType, MSimdUnaryArith::neg, def, type);
+        return CheckSimdUnary(f, call, opType, MSimdUnaryArith::neg, def, type);
       case AsmJSSimdOperation_not:
-        return CheckSimdUnary(f, call, retType, MSimdUnaryArith::not_, def, type);
+        return CheckSimdUnary(f, call, opType, MSimdUnaryArith::not_, def, type);
       case AsmJSSimdOperation_sqrt:
-        return CheckSimdUnary(f, call, retType, MSimdUnaryArith::sqrt, def, type);
+        return CheckSimdUnary(f, call, opType, MSimdUnaryArith::sqrt, def, type);
       case AsmJSSimdOperation_reciprocal:
-        return CheckSimdUnary(f, call, retType, MSimdUnaryArith::reciprocal, def, type);
+        return CheckSimdUnary(f, call, opType, MSimdUnaryArith::reciprocal, def, type);
       case AsmJSSimdOperation_reciprocalSqrt:
-        return CheckSimdUnary(f, call, retType, MSimdUnaryArith::reciprocalSqrt, def, type);
+        return CheckSimdUnary(f, call, opType, MSimdUnaryArith::reciprocalSqrt, def, type);
 
       case AsmJSSimdOperation_swizzle:
-        return CheckSimdSwizzle(f, call, retType, def, type);
+        return CheckSimdSwizzle(f, call, opType, def, type);
       case AsmJSSimdOperation_shuffle:
-        return CheckSimdShuffle(f, call, retType, def, type);
+        return CheckSimdShuffle(f, call, opType, def, type);
+
+      case AsmJSSimdOperation_load:
+        return CheckSimdLoad(f, call, opType, def, type);
+      case AsmJSSimdOperation_store:
+        return CheckSimdStore(f, call, opType, def, type);
 
       case AsmJSSimdOperation_splat: {
         DefinitionVector defs;
-        if (!CheckSimdCallArgs(f, call, 1, CheckSimdScalarArgs(retType), &defs))
-            return false;
-        *def = f.splatSimd(defs[0], retType.toMIRType());
-        *type = retType;
+        if (!CheckSimdCallArgs(f, call, 1, CheckSimdScalarArgs(opType), &defs))
+            return false;
+        *type = opType;
+        *def = f.splatSimd(defs[0], type->toMIRType());
         return true;
       }
 
       case AsmJSSimdOperation_select: {
         DefinitionVector defs;
-        if (!CheckSimdCallArgs(f, call, 3, CheckSimdSelectArgs(retType), &defs))
-            return false;
+        if (!CheckSimdCallArgs(f, call, 3, CheckSimdSelectArgs(opType), &defs))
+            return false;
+        *type = opType;
         *def = f.ternarySimd(defs[0], defs[1], defs[2], MSimdTernaryBitwise::select,
-                             retType.toMIRType());
-        *type = retType;
+                             type->toMIRType());
         return true;
       }
     }
     MOZ_CRASH("unexpected simd operation in CheckSimdOperationCall");
 }
 
 static bool
 CheckSimdCtorCall(FunctionCompiler &f, ParseNode *call, const ModuleCompiler::Global *global,
@@ -5721,26 +5836,25 @@ CheckSimdCtorCall(FunctionCompiler &f, P
     MOZ_ASSERT(call->isKind(PNK_CALL));
 
     AsmJSCoercion coercion;
     ParseNode *argNode;
     if (IsCoercionCall(f.m(), call, &coercion, &argNode))
         return CheckCoercionArg(f, argNode, coercion, def, type);
 
     AsmJSSimdType simdType = global->simdCtorType();
-    Type retType = simdType;
     unsigned length = SimdTypeToLength(simdType);
     DefinitionVector defs;
-    if (!CheckSimdCallArgs(f, call, length, CheckSimdScalarArgs(retType), &defs))
+    if (!CheckSimdCallArgs(f, call, length, CheckSimdScalarArgs(simdType), &defs))
         return false;
 
     // This code will need to be generalized when we handle float64x2
     MOZ_ASSERT(length == 4);
-    *def = f.constructSimd<MSimdValueX4>(defs[0], defs[1], defs[2], defs[3], retType.toMIRType());
-    *type = retType;
+    *type = simdType;
+    *def = f.constructSimd<MSimdValueX4>(defs[0], defs[1], defs[2], defs[3], type->toMIRType());
     return true;
 }
 
 static bool
 CheckUncoercedCall(FunctionCompiler &f, ParseNode *expr, MDefinition **def, Type *type)
 {
     MOZ_ASSERT(expr->isKind(PNK_CALL));
 
--- a/js/src/asmjs/AsmJSValidate.h
+++ b/js/src/asmjs/AsmJSValidate.h
@@ -52,17 +52,19 @@ ValidateAsmJS(ExclusiveContext *cx, AsmJ
 
 // The assumed page size; dynamically checked in ValidateAsmJS.
 const size_t AsmJSPageSize = 4096;
 
 #ifdef JS_CPU_X64
 // On x64, the internal ArrayBuffer data array is inflated to 4GiB (only the
 // byteLength portion of which is accessible) so that out-of-bounds accesses
 // (made using a uint32 index) are guaranteed to raise a SIGSEGV.
-static const size_t AsmJSMappedSize = 4 * 1024ULL * 1024ULL * 1024ULL;
+// Unaligned accesses and mask optimizations might also try to access a few
+// bytes after this limit, so just inflate it by AsmJSPageSize.
+static const size_t AsmJSMappedSize = 4 * 1024ULL * 1024ULL * 1024ULL + AsmJSPageSize;
 #endif
 
 // From the asm.js spec Linking section:
 //  the heap object's byteLength must be either
 //    2^n for n in [12, 24)
 //  or
 //    2^24 * n for n >= 1.
 
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -503,21 +503,29 @@ struct Div {
     static inline T apply(T l, T r) { return l / r; }
 };
 template<typename T>
 struct Mul {
     static inline T apply(T l, T r) { return l * r; }
 };
 template<typename T>
 struct Minimum {
-    static inline T apply(T l, T r) { return l < r ? l : r; }
+    static inline T apply(T l, T r) { return math_min_impl(l, r); }
+};
+template<typename T>
+struct MinNum {
+    static inline T apply(T l, T r) { return IsNaN(l) ? r : (IsNaN(r) ? l : math_min_impl(l, r)); }
 };
 template<typename T>
 struct Maximum {
-    static inline T apply(T l, T r) { return l > r ? l : r; }
+    static inline T apply(T l, T r) { return math_max_impl(l, r); }
+};
+template<typename T>
+struct MaxNum {
+    static inline T apply(T l, T r) { return IsNaN(l) ? r : (IsNaN(r) ? l : math_max_impl(l, r)); }
 };
 template<typename T>
 struct LessThan {
     static inline int32_t apply(T l, T r) { return l < r ? 0xFFFFFFFF : 0x0; }
 };
 template<typename T>
 struct LessThanOrEqual {
     static inline int32_t apply(T l, T r) { return l <= r ? 0xFFFFFFFF : 0x0; }
@@ -966,16 +974,94 @@ Float32x4Select(JSContext *cx, unsigned 
     int32_t orInt[Int32x4::lanes];
     for (unsigned i = 0; i < Int32x4::lanes; i++)
         orInt[i] = Or<int32_t>::apply(tr[i], fr[i]);
 
     float *result = reinterpret_cast<float *>(orInt);
     return StoreResult<Float32x4>(cx, args, result);
 }
 
+template<class VElem, unsigned NumElem>
+static bool
+TypedArrayDataPtrFromArgs(JSContext *cx, const CallArgs &args, VElem **data)
+{
+    if (!args[0].isObject())
+        return ErrorBadArgs(cx);
+
+    JSObject &argobj = args[0].toObject();
+    if (!argobj.is<TypedArrayObject>())
+        return ErrorBadArgs(cx);
+
+    Rooted<TypedArrayObject*> typedArray(cx, &argobj.as<TypedArrayObject>());
+
+    int32_t index;
+    if (!ToInt32(cx, args[1], &index))
+        return false;
+
+    int32_t byteStart = index * typedArray->bytesPerElement();
+    if (byteStart < 0 || (uint32_t(byteStart) + NumElem * sizeof(VElem)) > typedArray->byteLength())
+        return ErrorBadArgs(cx);
+
+    *data = reinterpret_cast<VElem*>(static_cast<char*>(typedArray->viewData()) + byteStart);
+    return true;
+}
+
+template<class V, unsigned NumElem>
+static bool
+Load(JSContext *cx, unsigned argc, Value *vp)
+{
+    typedef typename V::Elem Elem;
+
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (args.length() != 2)
+        return ErrorBadArgs(cx);
+
+    Elem *typedArrayData = nullptr;
+    if (!TypedArrayDataPtrFromArgs<Elem, NumElem>(cx, args, &typedArrayData))
+        return false;
+
+    Rooted<TypeDescr*> typeDescr(cx, &V::GetTypeDescr(*cx->global()));
+    MOZ_ASSERT(typeDescr);
+    Rooted<TypedObject *> result(cx, OutlineTypedObject::createZeroed(cx, typeDescr, 0));
+    if (!result)
+        return false;
+
+    Elem *dest = reinterpret_cast<Elem*>(result->typedMem());
+    for (unsigned i = 0; i < NumElem; i++)
+        dest[i] = typedArrayData[i];
+
+    args.rval().setObject(*result);
+    return true;
+}
+
+template<class V, unsigned NumElem>
+static bool
+Store(JSContext *cx, unsigned argc, Value *vp)
+{
+    typedef typename V::Elem Elem;
+
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (args.length() != 3)
+        return ErrorBadArgs(cx);
+
+    Elem *typedArrayData = nullptr;
+    if (!TypedArrayDataPtrFromArgs<Elem, NumElem>(cx, args, &typedArrayData))
+        return false;
+
+    if (!IsVectorObject<V>(args[2]))
+        return ErrorBadArgs(cx);
+
+    Elem *src = TypedObjectMemory<Elem*>(args[2]);
+    for (unsigned i = 0; i < NumElem; i++)
+        typedArrayData[i] = src[i];
+
+    args.rval().setObject(args[2].toObject());
+    return true;
+}
+
 #define DEFINE_SIMD_FLOAT32X4_FUNCTION(Name, Func, Operands, Flags) \
 bool                                                                \
 js::simd_float32x4_##Name(JSContext *cx, unsigned argc, Value *vp)  \
 {                                                                   \
     return Func(cx, argc, vp);                                      \
 }
 FLOAT32X4_FUNCTION_LIST(DEFINE_SIMD_FLOAT32X4_FUNCTION)
 #undef DEFINE_SIMD_FLOAT32x4_FUNCTION
--- a/js/src/builtin/SIMD.h
+++ b/js/src/builtin/SIMD.h
@@ -36,22 +36,32 @@
   V(add, (BinaryFunc<Float32x4, Add, Float32x4>), 2, 0)                             \
   V(and, (CoercedBinaryFunc<Float32x4, Int32x4, And, Float32x4>), 2, 0)             \
   V(div, (BinaryFunc<Float32x4, Div, Float32x4>), 2, 0)                             \
   V(equal, (CompareFunc<Float32x4, Equal>), 2, 0)                                   \
   V(greaterThan, (CompareFunc<Float32x4, GreaterThan>), 2, 0)                       \
   V(greaterThanOrEqual, (CompareFunc<Float32x4, GreaterThanOrEqual>), 2, 0)         \
   V(lessThan, (CompareFunc<Float32x4, LessThan>), 2, 0)                             \
   V(lessThanOrEqual, (CompareFunc<Float32x4, LessThanOrEqual>), 2, 0)               \
+  V(load,    (Load<Float32x4, 4>), 2, 0)                                            \
+  V(loadXYZ, (Load<Float32x4, 3>), 2, 0)                                            \
+  V(loadXY,  (Load<Float32x4, 2>), 2, 0)                                            \
+  V(loadX,   (Load<Float32x4, 1>), 2, 0)                                            \
   V(max, (BinaryFunc<Float32x4, Maximum, Float32x4>), 2, 0)                         \
+  V(maxNum, (BinaryFunc<Float32x4, MaxNum, Float32x4>), 2, 0)                       \
   V(min, (BinaryFunc<Float32x4, Minimum, Float32x4>), 2, 0)                         \
+  V(minNum, (BinaryFunc<Float32x4, MinNum, Float32x4>), 2, 0)                       \
   V(mul, (BinaryFunc<Float32x4, Mul, Float32x4>), 2, 0)                             \
   V(notEqual, (CompareFunc<Float32x4, NotEqual>), 2, 0)                             \
   V(or, (CoercedBinaryFunc<Float32x4, Int32x4, Or, Float32x4>), 2, 0)               \
   V(scale, (FuncWith<Float32x4, Scale>), 2, 0)                                      \
+  V(store,    (Store<Float32x4, 4>), 3, 0)                                          \
+  V(storeXYZ, (Store<Float32x4, 3>), 3, 0)                                          \
+  V(storeXY,  (Store<Float32x4, 2>), 3, 0)                                          \
+  V(storeX,   (Store<Float32x4, 1>), 3, 0)                                          \
   V(sub, (BinaryFunc<Float32x4, Sub, Float32x4>), 2, 0)                             \
   V(withX, (FuncWith<Float32x4, WithX>), 2, 0)                                      \
   V(withY, (FuncWith<Float32x4, WithY>), 2, 0)                                      \
   V(withZ, (FuncWith<Float32x4, WithZ>), 2, 0)                                      \
   V(withW, (FuncWith<Float32x4, WithW>), 2, 0)                                      \
   V(xor, (CoercedBinaryFunc<Float32x4, Int32x4, Xor, Float32x4>), 2, 0)
 
 #define FLOAT32X4_TERNARY_FUNCTION_LIST(V)                                          \
@@ -80,22 +90,30 @@
   V(splat, (FuncSplat<Int32x4>), 0, 0)
 
 #define INT32X4_BINARY_FUNCTION_LIST(V)                                             \
   V(add, (BinaryFunc<Int32x4, Add, Int32x4>), 2, 0)                                 \
   V(and, (BinaryFunc<Int32x4, And, Int32x4>), 2, 0)                                 \
   V(equal, (CompareFunc<Int32x4, Equal>), 2, 0)                                     \
   V(greaterThan, (CompareFunc<Int32x4, GreaterThan>), 2, 0)                         \
   V(lessThan, (CompareFunc<Int32x4, LessThan>), 2, 0)                               \
+  V(load,    (Load<Int32x4, 4>), 2, 0)                                              \
+  V(loadXYZ, (Load<Int32x4, 3>), 2, 0)                                              \
+  V(loadXY,  (Load<Int32x4, 2>), 2, 0)                                              \
+  V(loadX,   (Load<Int32x4, 1>), 2, 0)                                              \
   V(mul, (BinaryFunc<Int32x4, Mul, Int32x4>), 2, 0)                                 \
   V(or, (BinaryFunc<Int32x4, Or, Int32x4>), 2, 0)                                   \
   V(sub, (BinaryFunc<Int32x4, Sub, Int32x4>), 2, 0)                                 \
   V(shiftLeft, (Int32x4BinaryScalar<ShiftLeft>), 2, 0)                              \
   V(shiftRight, (Int32x4BinaryScalar<ShiftRight>), 2, 0)                            \
   V(shiftRightLogical, (Int32x4BinaryScalar<ShiftRightLogical>), 2, 0)              \
+  V(store,    (Store<Int32x4, 4>), 3, 0)                                            \
+  V(storeXYZ, (Store<Int32x4, 3>), 3, 0)                                            \
+  V(storeXY,  (Store<Int32x4, 2>), 3, 0)                                            \
+  V(storeX,   (Store<Int32x4, 1>), 3, 0)                                            \
   V(withFlagX, (FuncWith<Int32x4, WithFlagX>), 2, 0)                                \
   V(withFlagY, (FuncWith<Int32x4, WithFlagY>), 2, 0)                                \
   V(withFlagZ, (FuncWith<Int32x4, WithFlagZ>), 2, 0)                                \
   V(withFlagW, (FuncWith<Int32x4, WithFlagW>), 2, 0)                                \
   V(withX, (FuncWith<Int32x4, WithX>), 2, 0)                                        \
   V(withY, (FuncWith<Int32x4, WithY>), 2, 0)                                        \
   V(withZ, (FuncWith<Int32x4, WithZ>), 2, 0)                                        \
   V(withW, (FuncWith<Int32x4, WithW>), 2, 0)                                        \
@@ -131,16 +149,18 @@
     _(reciprocal)                    \
     _(reciprocalSqrt)                \
     _(fromInt32x4)                   \
     _(fromInt32x4Bits)               \
     _(mul)                           \
     _(div)                           \
     _(max)                           \
     _(min)                           \
+    _(maxNum)                        \
+    _(minNum)                        \
     _(lessThanOrEqual)               \
     _(notEqual)                      \
     _(greaterThanOrEqual)
 #define FOREACH_COMMONX4_SIMD_OP(_)  \
     _(add)                           \
     _(sub)                           \
     _(lessThan)                      \
     _(equal)                         \
@@ -152,17 +172,19 @@
     _(swizzle)                       \
     _(shuffle)                       \
     _(splat)                         \
     _(withX)                         \
     _(withY)                         \
     _(withZ)                         \
     _(withW)                         \
     _(not)                           \
-    _(neg)
+    _(neg)                           \
+    _(load)                          \
+    _(store)
 #define FORALL_SIMD_OP(_)            \
     FOREACH_INT32X4_SIMD_OP(_)       \
     FOREACH_FLOAT32X4_SIMD_OP(_)     \
     FOREACH_COMMONX4_SIMD_OP(_)
 
 namespace js {
 
 class SIMDObject : public JSObject
--- a/js/src/devtools/rootAnalysis/analyzeRoots.js
+++ b/js/src/devtools/rootAnalysis/analyzeRoots.js
@@ -416,17 +416,17 @@ function unsafeVariableAddressTaken(supp
             }
         }
     }
     return null;
 }
 
 function computePrintedLines(functionName)
 {
-    assert(!system("xdbfind src_body.xdb '" + functionName + "' > " + tmpfile));
+    assert(!os.system("xdbfind src_body.xdb '" + functionName + "' > " + tmpfile));
     var lines = snarf(tmpfile).split('\n');
 
     for (var body of functionBodies)
         body.lines = [];
 
     // Distribute lines of output to the block they originate from.
     var currentBody = null;
     for (var line of lines) {
--- a/js/src/jit-test/tests/asm.js/testSIMD.js
+++ b/js/src/jit-test/tests/asm.js/testSIMD.js
@@ -439,16 +439,28 @@ CheckF4(F32S, 'var x=f4(13.37,2,3,4); va
 assertAsmTypeFail('glob', USE_ASM + I32 + "var f4m=i4.mul; function f() {} return f");
 assertAsmTypeFail('glob', USE_ASM + I32 + "var f4d=i4.div; function f() {} return f");
 
 CheckF4(F32M, 'var x=f4(1,2,3,4); x=f4m(x,x)', [1,4,9,16]);
 CheckF4(F32M, 'var x=f4(1,2,3,4); var y=f4(4,3,5,2); x=f4m(x,y)', [4,6,15,8]);
 CheckF4(F32M, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=f4m(x,y)', [Math.fround(13.37) * 4,6,15,8]);
 CheckF4(F32M, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=f4(f4m(x,y))', [Math.fround(13.37) * 4,6,15,8]);
 
+var f32x4 = SIMD.float32x4(0, NaN, -0, NaN);
+var another = SIMD.float32x4(NaN, -1, -0, NaN);
+assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + F32M + "function f(x, y) {x=f4(x); y=f4(y); x=f4m(x,y); return f4(x);} return f"), this)(f32x4, another), [NaN, NaN, 0, NaN]);
+
+CheckF4(F32D, 'var x=f4(1,2,3,4); x=f4d(x,x)', [1,1,1,1]);
+CheckF4(F32D, 'var x=f4(1,2,3,4); var y=f4(4,3,5,2); x=f4d(x,y)', [1/4,2/3,3/5,2]);
+CheckF4(F32D, 'var x=f4(13.37,1,1,4); var y=f4(4,0,-0.,2); x=f4d(x,y)', [Math.fround(13.37) / 4,+Infinity,-Infinity,2]);
+
+var f32x4 = SIMD.float32x4(0, 0, -0, NaN);
+var another = SIMD.float32x4(0, -0, 0, 0);
+assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + F32D + "function f(x,y) {x=f4(x); y=f4(y); x=f4d(x,y); return f4(x);} return f"), this)(f32x4, another), [NaN, NaN, NaN, NaN]);
+
 // Unary arithmetic operators
 function CheckUnaryF4(op, checkFunc, assertFunc) {
     var _ = asmLink(asmCompile('glob', USE_ASM + F32 + 'var op=f4.' + op + '; function f(x){x=f4(x); return f4(op(x)); } return f'), this);
     return function(input) {
         var simd = SIMD.float32x4(input[0], input[1], input[2], input[3]);
 
         var exp = input.map(Math.fround).map(checkFunc).map(Math.fround);
         var obs = _(simd);
@@ -508,41 +520,46 @@ CheckRecp([NaN, -Infinity, Infinity, 0])
 
 // Min/Max
 assertAsmTypeFail('glob', USE_ASM + I32 + "var f4m=i4.min; function f() {} return f");
 assertAsmTypeFail('glob', USE_ASM + I32 + "var f4d=i4.max; function f() {} return f");
 
 const F32MIN = 'var min = f4.min;'
 const F32MAX = 'var max = f4.max;'
 
-// TODO amend tests once float32x4.min/max is fully specified, for NaN and -0
-// vs 0. See also comment in js::jit::CodeGeneratorX86Shared::visitSimdBinaryArithFx4.
-// In these tests, we assume x86/x64, so min(+0, -0) === +0, as specified in
-// the Intel developer manual. See also bug 1068028.
 CheckF4(F32MIN, 'var x=f4(1,2,3,4); x=min(x,x)', [1,2,3,4]);
 CheckF4(F32MIN, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=min(x,y)', [4,2,3,2]);
-CheckF4(F32MIN + FROUND + 'var Infinity = glob.Infinity;', 'var x=f4(0,0,0,0); var y=f4(2310,3,5,0); x=f4(f32(+Infinity),f32(-Infinity),f32(3),f32(-0.)); x=min(x,y)', [2310,-Infinity,3,0]);
+CheckF4(F32MIN + FROUND + 'var Infinity = glob.Infinity;', 'var x=f4(0,0,0,0); var y=f4(2310,3,5,0); x=f4(f32(+Infinity),f32(-Infinity),f32(3),f32(-0.)); x=min(x,y)', [2310,-Infinity,3,-0]);
+
+CheckF4(F32MIN, 'var x=f4(0,0,-0,-0); var y=f4(0,-0,0,-0); x=min(x,y)', [0,-0,-0,-0]);
+CheckF4(F32MIN + FROUND + 'var NaN = glob.NaN;', 'var x=f4(0,0,0,0); var y=f4(0,0,0,0); var n=f32(0); n=f32(NaN); x=f4(n,0.,n,0.); y=f4(n,n,0.,0.); x=min(x,y)', [NaN, NaN, NaN, 0]);
 
 CheckF4(F32MAX, 'var x=f4(1,2,3,4); x=max(x,x)', [1,2,3,4]);
 CheckF4(F32MAX, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=max(x,y)', [13.37, 3, 5, 4]);
 CheckF4(F32MAX + FROUND + 'var Infinity = glob.Infinity;', 'var x=f4(0,0,0,0); var y=f4(2310,3,5,0); x=f4(f32(+Infinity),f32(-Infinity),f32(3),f32(-0.)); x=max(x,y)', [+Infinity,3,5,0]);
 
-// Test NaN
-var f32x4 = SIMD.float32x4(0, NaN, -0, NaN);
-var another = SIMD.float32x4(NaN, -1, -0, NaN);
-assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + F32M + "function f(x, y) {x=f4(x); y=f4(y); x=f4m(x,y); return f4(x);} return f"), this)(f32x4, another), [NaN, NaN, 0, NaN]);
+CheckF4(F32MAX, 'var x=f4(0,0,-0,-0); var y=f4(0,-0,0,-0); x=max(x,y)', [0,0,0,-0]);
+CheckF4(F32MAX + FROUND + 'var NaN = glob.NaN;', 'var x=f4(0,0,0,0); var y=f4(0,0,0,0); var n=f32(0); n=f32(NaN); x=f4(n,0.,n,0.); y=f4(n,n,0.,0.); x=max(x,y)', [NaN, NaN, NaN, 0]);
+
+const F32MINNUM = 'var min = f4.minNum;'
+const F32MAXNUM = 'var max = f4.maxNum;'
+
+CheckF4(F32MINNUM, 'var x=f4(1,2,3,4); x=min(x,x)', [1,2,3,4]);
+CheckF4(F32MINNUM, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=min(x,y)', [4,2,3,2]);
+CheckF4(F32MINNUM + FROUND + 'var Infinity = glob.Infinity;', 'var x=f4(0,0,0,0); var y=f4(2310,3,5,0); x=f4(f32(+Infinity),f32(-Infinity),f32(3),f32(-0.)); x=min(x,y)', [2310,-Infinity,3,-0]);
 
-CheckF4(F32D, 'var x=f4(1,2,3,4); x=f4d(x,x)', [1,1,1,1]);
-CheckF4(F32D, 'var x=f4(1,2,3,4); var y=f4(4,3,5,2); x=f4d(x,y)', [1/4,2/3,3/5,2]);
-CheckF4(F32D, 'var x=f4(13.37,1,1,4); var y=f4(4,0,-0.,2); x=f4d(x,y)', [Math.fround(13.37) / 4,+Infinity,-Infinity,2]);
+CheckF4(F32MINNUM, 'var x=f4(0,0,-0,-0); var y=f4(0,-0,0,-0); x=min(x,y)', [0,-0,-0,-0]);
+CheckF4(F32MINNUM + FROUND + 'var NaN = glob.NaN;', 'var x=f4(0,0,0,0); var y=f4(0,0,0,0); var n=f32(0); n=f32(NaN); x=f4(n,0.,n,0.); y=f4(n,n,0.,0.); x=min(x,y)', [NaN, 0, 0, 0]);
 
-// Test NaN
-var f32x4 = SIMD.float32x4(0, 0, -0, NaN);
-var another = SIMD.float32x4(0, -0, 0, 0);
-assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + F32D + "function f(x,y) {x=f4(x); y=f4(y); x=f4d(x,y); return f4(x);} return f"), this)(f32x4, another), [NaN, NaN, NaN, NaN]);
+CheckF4(F32MAXNUM, 'var x=f4(1,2,3,4); x=max(x,x)', [1,2,3,4]);
+CheckF4(F32MAXNUM, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=max(x,y)', [13.37, 3, 5, 4]);
+CheckF4(F32MAXNUM + FROUND + 'var Infinity = glob.Infinity;', 'var x=f4(0,0,0,0); var y=f4(2310,3,5,0); x=f4(f32(+Infinity),f32(-Infinity),f32(3),f32(-0.)); x=max(x,y)', [+Infinity,3,5,0]);
+
+CheckF4(F32MAXNUM, 'var x=f4(0,0,-0,-0); var y=f4(0,-0,0,-0); x=max(x,y)', [0,0,0,-0]);
+CheckF4(F32MAXNUM + FROUND + 'var NaN = glob.NaN;', 'var x=f4(0,0,0,0); var y=f4(0,0,0,0); var n=f32(0); n=f32(NaN); x=f4(n,0.,n,0.); y=f4(n,n,0.,0.); x=max(x,y)', [NaN, 0, 0, 0]);
 
 // With
 const WXF = 'var w = f4.withX;';
 const WYF = 'var w = f4.withY;';
 const WZF = 'var w = f4.withZ;';
 const WWF = 'var w = f4.withW;';
 
 assertAsmTypeFail('glob', USE_ASM + F32 + WXF + "function f() {var x = f4(1,2,3,4); x = w(x, 1);} return f");
@@ -940,16 +957,234 @@ assertAsmTypeFail('glob', USE_ASM + I32 
 // Can't pass SIMD arguments to FFI
 assertAsmTypeFail('glob', 'ffi', USE_ASM + I32 + "var func=ffi.func; function f() {var x=i4(1,2,3,4); func(x);} return f");
 assertAsmTypeFail('glob', 'ffi', USE_ASM + F32 + "var func=ffi.func; function f() {var x=f4(1,2,3,4); func(x);} return f");
 
 // Can't have FFI return SIMD values
 assertAsmTypeFail('glob', 'ffi', USE_ASM + I32 + "var func=ffi.func; function f() {var x=i4(1,2,3,4); x=i4(func());} return f");
 assertAsmTypeFail('glob', 'ffi', USE_ASM + F32 + "var func=ffi.func; function f() {var x=f4(1,2,3,4); x=f4(func());} return f");
 
+// Load / Store
+(function testLoadStore() {
+
+var IMPORTS = USE_ASM + 'var H=new glob.Uint8Array(heap); var i4=glob.SIMD.int32x4; var load=i4.load; var store=i4.store;';
+
+//      Bad number of args
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load();} return f");
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(3);} return f");
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(3, 4, 5);} return f");
+
+//      Bad type of args
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(3, 5);} return f");
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(H, 5.0);} return f");
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){var i=0.;load(H, i);} return f");
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "var H2=new glob.Int32Array(heap); function f(){var i=0;load(H2, i)} return f");
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "var H2=42; function f(){var i=0;load(H2, i)} return f");
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){var i=0;load(H2, i)} return f");
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "var f4=glob.SIMD.float32x4; function f(){var i=0;var vec=f4(1,2,3,4); store(H, i, vec)} return f");
+
+//      Bad coercions of returned values
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){var i=0;return load(H, i)|0;} return f");
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){var i=0;return +load(H, i);} return f");
+
+//      Literal index constants
+var buf = new ArrayBuffer(BUF_MIN);
+var asI32 = new Int32Array(buf);
+asI32[(BUF_MIN >> 2) - 4] = 4;
+asI32[(BUF_MIN >> 2) - 3] = 3;
+asI32[(BUF_MIN >> 2) - 2] = 2;
+asI32[(BUF_MIN >> 2) - 1] = 1;
+
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(H, " + (INT32_MAX + 1) + ");} return f");
+assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(H, " + (INT32_MAX + 1 - 15) + ");} return f");
+asmCompile('glob', 'ffi', 'heap', IMPORTS + "function f(){load(H, " + (INT32_MAX + 1 - 16) + ");} return f");
+
+assertAsmLinkFail(asmCompile('glob', 'ffi', 'heap', IMPORTS + "function f() {return i4(load(H, " + (BUF_MIN - 15) + "));} return f"), this, {}, buf);
+assertEqX4(asmLink(asmCompile('glob', 'ffi', 'heap', IMPORTS + "function f() {return i4(load(H, " + (BUF_MIN - 16) + "));} return f"), this, {}, buf)(), [4, 3, 2, 1]);
+assertEqX4(asmLink(asmCompile('glob', 'ffi', 'heap', IMPORTS + "function f() {return i4(load(H, " + BUF_MIN + " - 16 | 0));} return f"), this, {}, buf)(), [4, 3, 2, 1]);
+
+var CONSTANT_INDEX = 42;
+var CONSTANT_BYTE_INDEX = CONSTANT_INDEX << 2;
+
+var loadStoreCode = `
+    "use asm";
+
+    var H = new glob.Uint8Array(heap);
+
+    var i4 = glob.SIMD.int32x4;
+    var i4load = i4.load;
+    var i4store = i4.store;
+
+    var f4 = glob.SIMD.float32x4;
+    var f4load = f4.load;
+    var f4store = f4.store;
+
+    function f32l(i) { i=i|0; return f4(f4load(H, i|0)); }
+    function f32lcst() { return f4(f4load(H, ${CONSTANT_BYTE_INDEX})); }
+    function f32s(i, vec) { i=i|0; vec=f4(vec); f4store(H, i|0, vec); }
+    function f32scst(vec) { vec=f4(vec); f4store(H, ${CONSTANT_BYTE_INDEX}, vec); }
+
+    function i32l(i) { i=i|0; return i4(i4load(H, i|0)); }
+    function i32lcst() { return i4(i4load(H, ${CONSTANT_BYTE_INDEX})); }
+    function i32s(i, vec) { i=i|0; vec=i4(vec); i4store(H, i|0, vec); }
+    function i32scst(vec) { vec=i4(vec); i4store(H, ${CONSTANT_BYTE_INDEX}, vec); }
+
+    function f32lbndcheck(i) {
+        i=i|0;
+        if ((i|0) > ${CONSTANT_BYTE_INDEX}) i=${CONSTANT_BYTE_INDEX};
+        if ((i|0) < 0) i = 0;
+        return f4(f4load(H, i|0));
+    }
+    function f32sbndcheck(i, vec) {
+        i=i|0;
+        vec=f4(vec);
+        if ((i|0) > ${CONSTANT_BYTE_INDEX}) i=${CONSTANT_BYTE_INDEX};
+        if ((i|0) < 0) i = 0;
+        return f4(f4store(H, i|0, vec));
+    }
+
+    return {
+        f32l: f32l,
+        f32lcst: f32lcst,
+        f32s: f32s,
+        f32scst: f32scst,
+        f32lbndcheck: f32lbndcheck,
+        f32sbndcheck: f32sbndcheck,
+        i32l: i32l,
+        i32lcst: i32lcst,
+        i32s: i32s,
+        i32scst: i32scst
+    }
+`;
+
+const SIZE = 0x8000;
+
+var F32 = new Float32Array(SIZE);
+var reset = function() {
+    for (var i = 0; i < SIZE; i++)
+        F32[i] = i + 1;
+};
+reset();
+
+var buf = F32.buffer;
+var m = asmLink(asmCompile('glob', 'ffi', 'heap', loadStoreCode), this, null, buf);
+
+function slice(TA, i, n) { return Array.prototype.slice.call(TA, i, i + n); }
+
+// Float32x4.load
+function f32l(n) { return m.f32l((n|0) << 2 | 0); };
+
+//      Correct accesses
+assertEqX4(f32l(0), slice(F32, 0, 4));
+assertEqX4(f32l(1), slice(F32, 1, 4));
+assertEqX4(f32l(SIZE - 4), slice(F32, SIZE - 4, 4));
+
+assertEqX4(m.f32lcst(), slice(F32, CONSTANT_INDEX, 4));
+assertEqX4(m.f32lbndcheck(CONSTANT_BYTE_INDEX), slice(F32, CONSTANT_INDEX, 4));
+
+//      OOB
+var BatNaN = [NaN, NaN, NaN, NaN] // NaNNaNNaNNaN etc.
+assertEqX4(f32l(-1), BatNaN);
+assertEqX4(f32l(SIZE), BatNaN);
+assertEqX4(f32l(SIZE - 1), BatNaN);
+assertEqX4(f32l(SIZE - 2), BatNaN);
+assertEqX4(f32l(SIZE - 3), BatNaN);
+
+// Float32x4.store
+function f32s(n, v) { return m.f32s((n|0) << 2 | 0, v); };
+
+var vec  = SIMD.float32x4(5,6,7,8);
+var vec2 = SIMD.float32x4(0,1,2,3);
+
+reset();
+f32s(0, vec);
+assertEqX4(vec, slice(F32, 0, 4));
+
+reset();
+f32s(0, vec2);
+assertEqX4(vec2, slice(F32, 0, 4));
+
+reset();
+f32s(4, vec);
+assertEqX4(vec, slice(F32, 4, 4));
+
+reset();
+m.f32scst(vec2);
+assertEqX4(vec2, slice(F32, CONSTANT_INDEX, 4));
+
+reset();
+m.f32sbndcheck(CONSTANT_BYTE_INDEX, vec);
+assertEqX4(vec, slice(F32, CONSTANT_INDEX, 4));
+
+//      OOB
+reset();
+f32s(SIZE - 3, vec);
+f32s(SIZE - 2, vec);
+f32s(SIZE - 1, vec);
+f32s(SIZE, vec);
+for (var i = 0; i < SIZE; i++)
+    assertEq(F32[i], i + 1);
+
+// Int32x4.load
+var I32 = new Int32Array(buf);
+reset = function () {
+    for (var i = 0; i < SIZE; i++)
+        I32[i] = i + 1;
+};
+reset();
+
+function i32(n) { return m.i32l((n|0) << 2 | 0); };
+
+//      Correct accesses
+assertEqX4(i32(0), slice(I32, 0, 4));
+assertEqX4(i32(1), slice(I32, 1, 4));
+assertEqX4(i32(SIZE - 4), slice(I32, SIZE - 4, 4));
+
+assertEqX4(m.i32lcst(), slice(I32, CONSTANT_INDEX, 4));
+
+//      OOB
+assertEqX4(i32(-1), [0,0,0,0]);
+assertEqX4(i32(SIZE), [0,0,0,0]);
+assertEqX4(i32(SIZE - 1), [0,0,0,0]);
+assertEqX4(i32(SIZE - 2), [0,0,0,0]);
+assertEqX4(i32(SIZE - 3), [0,0,0,0]);
+
+// Int32x4.store
+function i32s(n, v) { return m.i32s((n|0) << 2 | 0, v); };
+
+var vec  = SIMD.int32x4(5,6,7,8);
+var vec2 = SIMD.int32x4(0,1,2,3);
+
+reset();
+i32s(0, vec);
+assertEqX4(vec, slice(I32, 0, 4));
+
+reset();
+i32s(0, vec2);
+assertEqX4(vec2, slice(I32, 0, 4));
+
+reset();
+i32s(4, vec);
+assertEqX4(vec, slice(I32, 4, 4));
+
+reset();
+m.i32scst(vec2);
+assertEqX4(vec2, slice(I32, CONSTANT_INDEX, 4));
+
+//      OOB
+reset();
+i32s(SIZE - 3, vec);
+i32s(SIZE - 2, vec);
+i32s(SIZE - 1, vec);
+i32s(SIZE - 0, vec);
+for (var i = 0; i < SIZE; i++)
+    assertEq(I32[i], i + 1);
+
+})();
+
 // 3.3 Internal calls
 // asm.js -> asm.js
 // Retrieving values from asm.js
 var code = USE_ASM + I32 + I32A + `
     var check = ffi.check;
 
     function g() {
         var i = 0;
--- a/js/src/jit-test/tests/ion/dce-with-rinstructions.js
+++ b/js/src/jit-test/tests/ion/dce-with-rinstructions.js
@@ -922,16 +922,58 @@ function rregexp_m_literal_replace(i) {
 
     assertEq(re.lastIndex == 0, true);
 
     if (uceFault_regexp_m_literal_replace(i) || uceFault_regexp_m_literal_replace(i))
         assertEq(res, "abc\nabc");
     return i;
 }
 
+var uceFault_string_replace = eval(uneval(uceFault).replace('uceFault', 'uceFault_string_replace'))
+function rstring_replace(i) {
+    var re = /str\d+9/;
+
+    assertEq(re.lastIndex == 0, true);
+    var res = "str00123456789".replace(re, "abc");
+    if (uceFault_string_replace(i) || uceFault_string_replace(i)) {
+        assertEq(res, "abc");
+    }
+    assertEq(re.lastIndex == 0, true);
+
+    return i;
+}
+
+var uceFault_string_replace_y = eval(uneval(uceFault).replace('uceFault', 'uceFault_string_replace_y'))
+function rstring_replace_y(i) {
+    var re = /str\d+9/y;
+
+    assertEq(re.lastIndex == 0, true);
+    var res = "str00123456789".replace(re, "abc");
+    if (uceFault_string_replace_y(i) || uceFault_string_replace_y(i)) {
+        assertEq(res, "abc");
+    }
+    assertEq(re.lastIndex == 0, true);
+
+    return i;
+}
+
+var uceFault_string_replace_g = eval(uneval(uceFault).replace('uceFault', 'uceFault_string_replace_g'))
+function rstring_replace_g(i) {
+    var re = /str\d+9/g;
+
+    assertEq(re.lastIndex == 0, true);
+    var res = "str00123456789str00123456789".replace(re, "abc");
+    if (uceFault_string_replace_g(i) || uceFault_string_replace_g(i)) {
+        assertEq(res, "abcabc");
+    }
+    assertEq(re.lastIndex == 0, true);
+
+    return i;
+}
+
 var uceFault_typeof = eval(uneval(uceFault).replace('uceFault', 'uceFault_typeof'))
 function rtypeof(i) {
     var inputs = [ {}, [], 1, true, undefined, function(){}, null ];
     var types = [ "object", "object", "number", "boolean", "undefined", "function", "object"];
     if (typeof Symbol === "function") {
       inputs.push(Symbol());
       types.push("symbol");
     }
@@ -1093,16 +1135,19 @@ for (i = 0; i < 100; i++) {
     rregexp_y_replace(i);
     rregexp_y_literal_replace(i);
     rregexp_g_replace(i);
     rregexp_g_literal_replace(i);
     rregexp_i_replace(i);
     rregexp_i_literal_replace(i);
     rregexp_m_replace(i);
     rregexp_m_literal_replace(i);
+    rstring_replace(i);
+    rstring_replace_y(i);
+    rstring_replace_g(i);
     rtypeof(i);
     rtodouble_value(i);
     rtodouble_number(i);
     rtofloat32_number(i);
     rtofloat32_object(i);
     rhypot_number(i);
     rhypot_object(i);
 }
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -361,49 +361,55 @@ class LSimdBinaryCompIx4 : public LSimdB
 class LSimdBinaryCompFx4 : public LSimdBinaryComp
 {
   public:
     LIR_HEADER(SimdBinaryCompFx4);
     LSimdBinaryCompFx4() : LSimdBinaryComp() {}
 };
 
 // Binary SIMD arithmetic operation between two SIMD operands
-class LSimdBinaryArith : public LInstructionHelper<1, 2, 0>
+template<size_t Temps>
+class LSimdBinaryArith : public LInstructionHelper<1, 2, Temps>
 {
   public:
     LSimdBinaryArith() {}
 
     const LAllocation *lhs() {
-        return getOperand(0);
+        return this->getOperand(0);
     }
     const LAllocation *rhs() {
-        return getOperand(1);
-    }
+        return this->getOperand(1);
+    }
+
     MSimdBinaryArith::Operation operation() const {
-        return mir_->toSimdBinaryArith()->operation();
+        return this->mir_->toSimdBinaryArith()->operation();
     }
     const char *extraName() const {
         return MSimdBinaryArith::OperationName(operation());
     }
 };
 
 // Binary SIMD arithmetic operation between two Int32x4 operands
-class LSimdBinaryArithIx4 : public LSimdBinaryArith
+class LSimdBinaryArithIx4 : public LSimdBinaryArith<0>
 {
   public:
     LIR_HEADER(SimdBinaryArithIx4);
-    LSimdBinaryArithIx4() : LSimdBinaryArith() {}
+    LSimdBinaryArithIx4() : LSimdBinaryArith<0>() {}
 };
 
 // Binary SIMD arithmetic operation between two Float32x4 operands
-class LSimdBinaryArithFx4 : public LSimdBinaryArith
+class LSimdBinaryArithFx4 : public LSimdBinaryArith<1>
 {
   public:
     LIR_HEADER(SimdBinaryArithFx4);
-    LSimdBinaryArithFx4() : LSimdBinaryArith() {}
+    LSimdBinaryArithFx4() : LSimdBinaryArith<1>() {}
+
+    const LDefinition *temp() {
+        return getTemp(0);
+    }
 };
 
 // Unary SIMD arithmetic operation on a SIMD operand
 class LSimdUnaryArith : public LInstructionHelper<1, 1, 0>
 {
   public:
     explicit LSimdUnaryArith(const LAllocation &in) {
         setOperand(0, in);
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -4067,30 +4067,33 @@ LIRGenerator::visitSimdBinaryComp(MSimdB
 
 bool
 LIRGenerator::visitSimdBinaryArith(MSimdBinaryArith *ins)
 {
     MOZ_ASSERT(IsSimdType(ins->type()));
 
     MDefinition *lhs = ins->lhs();
     MDefinition *rhs = ins->rhs();
+
     if (ins->isCommutative())
         ReorderCommutative(&lhs, &rhs, ins);
 
-    if (ins->type() == MIRType_Int32x4) {
-        LSimdBinaryArithIx4 *add = new(alloc()) LSimdBinaryArithIx4();
-        return lowerForFPU(add, ins, lhs, rhs);
-    }
-
-    if (ins->type() == MIRType_Float32x4) {
-        LSimdBinaryArithFx4 *add = new(alloc()) LSimdBinaryArithFx4();
-        return lowerForFPU(add, ins, lhs, rhs);
-    }
-
-    MOZ_CRASH("Unknown SIMD kind when adding values");
+    if (ins->type() == MIRType_Int32x4)
+        return lowerForFPU(new(alloc()) LSimdBinaryArithIx4(), ins, lhs, rhs);
+
+    MOZ_ASSERT(ins->type() == MIRType_Float32x4, "unknown simd type on binary arith operation");
+
+    LSimdBinaryArithFx4 *lir = new(alloc()) LSimdBinaryArithFx4();
+
+    bool needsTemp = ins->operation() == MSimdBinaryArith::Max ||
+                     ins->operation() == MSimdBinaryArith::MinNum ||
+                     ins->operation() == MSimdBinaryArith::MaxNum;
+    lir->setTemp(0, needsTemp ? temp(LDefinition::FLOAT32X4) : LDefinition::BogusTemp());
+
+    return lowerForFPU(lir, ins, lhs, rhs);
 }
 
 bool
 LIRGenerator::visitSimdBinaryBitwise(MSimdBinaryBitwise *ins)
 {
     MOZ_ASSERT(IsSimdType(ins->type()));
 
     MDefinition *lhs = ins->lhs();
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -1852,27 +1852,31 @@ class MSimdBinaryArith : public MBinaryI
 {
   public:
     enum Operation {
         Add,
         Sub,
         Mul,
         Div,
         Min,
-        Max
+        Max,
+        MinNum,
+        MaxNum
     };
 
     static const char* OperationName(Operation op) {
         switch (op) {
-          case Add: return "Add";
-          case Sub: return "Sub";
-          case Mul: return "Mul";
-          case Div: return "Div";
-          case Min: return "Min";
-          case Max: return "Max";
+          case Add:    return "Add";
+          case Sub:    return "Sub";
+          case Mul:    return "Mul";
+          case Div:    return "Div";
+          case Min:    return "Min";
+          case Max:    return "Max";
+          case MinNum: return "MinNum";
+          case MaxNum: return "MaxNum";
         }
         MOZ_CRASH("unexpected operation");
     }
 
   private:
     Operation operation_;
 
     MSimdBinaryArith(MDefinition *left, MDefinition *right, Operation op, MIRType type)
@@ -6945,16 +6949,23 @@ class MStringReplace
 
     bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+
+    bool writeRecoverData(CompactBufferWriter &writer) const;
+    bool canRecoverOnBailout() const {
+        if (pattern()->isRegExp())
+            return !pattern()->toRegExp()->source()->global();
+        return false;
+    }
 };
 
 class MSubstr
   : public MTernaryInstruction,
     public Mix3Policy<StringPolicy<0>, IntPolicy<1>, IntPolicy<2>>
 {
   private:
 
@@ -12087,57 +12098,80 @@ class MAsmJSNeg : public MUnaryInstructi
     INSTRUCTION_HEADER(AsmJSNeg);
     static MAsmJSNeg *NewAsmJS(TempAllocator &alloc, MDefinition *op, MIRType type) {
         return new(alloc) MAsmJSNeg(op, type);
     }
 };
 
 class MAsmJSHeapAccess
 {
-    Scalar::Type viewType_;
+  protected:
+    typedef AsmJSHeapAccess::ViewType ViewType;
+
+  private:
+    ViewType viewType_;
     bool needsBoundsCheck_;
 
   public:
-    MAsmJSHeapAccess(Scalar::Type vt, bool needsBoundsCheck)
+    MAsmJSHeapAccess(ViewType vt, bool needsBoundsCheck)
       : viewType_(vt), needsBoundsCheck_(needsBoundsCheck)
     {}
 
-    Scalar::Type viewType() const { return viewType_; }
+    ViewType viewType() const { return viewType_; }
     bool needsBoundsCheck() const { return needsBoundsCheck_; }
     void removeBoundsCheck() { needsBoundsCheck_ = false; }
 };
 
 class MAsmJSLoadHeap : public MUnaryInstruction, public MAsmJSHeapAccess
 {
     MemoryBarrierBits barrierBefore_;
     MemoryBarrierBits barrierAfter_;
 
-    MAsmJSLoadHeap(Scalar::Type vt, MDefinition *ptr, bool needsBoundsCheck,
+    MAsmJSLoadHeap(ViewType vt, MDefinition *ptr, bool needsBoundsCheck,
                    MemoryBarrierBits before, MemoryBarrierBits after)
       : MUnaryInstruction(ptr),
         MAsmJSHeapAccess(vt, needsBoundsCheck),
         barrierBefore_(before),
         barrierAfter_(after)
     {
         if (before|after)
             setGuard();         // Not removable
         else
             setMovable();
-        if (vt == Scalar::Float32)
+
+        switch (vt) {
+          case AsmJSHeapAccess::Int8:
+          case AsmJSHeapAccess::Uint8:
+          case AsmJSHeapAccess::Int16:
+          case AsmJSHeapAccess::Uint16:
+          case AsmJSHeapAccess::Int32:
+          case AsmJSHeapAccess::Uint32:
+            setResultType(MIRType_Int32);
+            break;
+          case AsmJSHeapAccess::Float32:
             setResultType(MIRType_Float32);
-        else if (vt == Scalar::Float64)
+            break;
+          case AsmJSHeapAccess::Float64:
             setResultType(MIRType_Double);
-        else
-            setResultType(MIRType_Int32);
+            break;
+          case AsmJSHeapAccess::Float32x4:
+            setResultType(MIRType_Float32x4);
+            break;
+          case AsmJSHeapAccess::Int32x4:
+            setResultType(MIRType_Int32x4);
+            break;
+          case AsmJSHeapAccess::Uint8Clamped:
+            MOZ_CRASH("unexpected uint8clamped load heap in asm.js");
+        }
     }
 
   public:
     INSTRUCTION_HEADER(AsmJSLoadHeap);
 
-    static MAsmJSLoadHeap *New(TempAllocator &alloc, Scalar::Type vt,
+    static MAsmJSLoadHeap *New(TempAllocator &alloc, ViewType vt,
                                MDefinition *ptr, bool needsBoundsCheck,
                                MemoryBarrierBits barrierBefore = MembarNobits,
                                MemoryBarrierBits barrierAfter = MembarNobits)
     {
         return new(alloc) MAsmJSLoadHeap(vt, ptr, needsBoundsCheck, barrierBefore, barrierAfter);
     }
 
     MDefinition *ptr() const { return getOperand(0); }
@@ -12151,31 +12185,31 @@ class MAsmJSLoadHeap : public MUnaryInst
     bool mightAlias(const MDefinition *def) const;
 };
 
 class MAsmJSStoreHeap : public MBinaryInstruction, public MAsmJSHeapAccess
 {
     MemoryBarrierBits barrierBefore_;
     MemoryBarrierBits barrierAfter_;
 
-    MAsmJSStoreHeap(Scalar::Type vt, MDefinition *ptr, MDefinition *v, bool needsBoundsCheck,
+    MAsmJSStoreHeap(ViewType vt, MDefinition *ptr, MDefinition *v, bool needsBoundsCheck,
                     MemoryBarrierBits before, MemoryBarrierBits after)
       : MBinaryInstruction(ptr, v),
         MAsmJSHeapAccess(vt, needsBoundsCheck),
         barrierBefore_(before),
         barrierAfter_(after)
     {
         if (before|after)
             setGuard();         // Not removable
     }
 
   public:
     INSTRUCTION_HEADER(AsmJSStoreHeap);
 
-    static MAsmJSStoreHeap *New(TempAllocator &alloc, Scalar::Type vt,
+    static MAsmJSStoreHeap *New(TempAllocator &alloc, ViewType vt,
                                 MDefinition *ptr, MDefinition *v, bool needsBoundsCheck,
                                 MemoryBarrierBits barrierBefore = MembarNobits,
                                 MemoryBarrierBits barrierAfter = MembarNobits)
     {
         return new(alloc) MAsmJSStoreHeap(vt, ptr, v, needsBoundsCheck,
                                           barrierBefore, barrierAfter);
     }
 
@@ -12186,29 +12220,29 @@ class MAsmJSStoreHeap : public MBinaryIn
 
     AliasSet getAliasSet() const {
         return AliasSet::Store(AliasSet::AsmJSHeap);
     }
 };
 
 class MAsmJSCompareExchangeHeap : public MTernaryInstruction, public MAsmJSHeapAccess
 {
-    MAsmJSCompareExchangeHeap(Scalar::Type vt, MDefinition *ptr, MDefinition *oldv, MDefinition *newv,
+    MAsmJSCompareExchangeHeap(ViewType vt, MDefinition *ptr, MDefinition *oldv, MDefinition *newv,
                               bool needsBoundsCheck)
         : MTernaryInstruction(ptr, oldv, newv),
           MAsmJSHeapAccess(vt, needsBoundsCheck)
     {
         setGuard();             // Not removable
         setResultType(MIRType_Int32);
     }
 
   public:
     INSTRUCTION_HEADER(AsmJSCompareExchangeHeap);
 
-    static MAsmJSCompareExchangeHeap *New(TempAllocator &alloc, Scalar::Type vt,
+    static MAsmJSCompareExchangeHeap *New(TempAllocator &alloc, ViewType vt,
                                           MDefinition *ptr, MDefinition *oldv,
                                           MDefinition *newv, bool needsBoundsCheck)
     {
         return new(alloc) MAsmJSCompareExchangeHeap(vt, ptr, oldv, newv, needsBoundsCheck);
     }
 
     MDefinition *ptr() const { return getOperand(0); }
     MDefinition *oldValue() const { return getOperand(1); }
@@ -12218,30 +12252,30 @@ class MAsmJSCompareExchangeHeap : public
         return AliasSet::Store(AliasSet::AsmJSHeap);
     }
 };
 
 class MAsmJSAtomicBinopHeap : public MBinaryInstruction, public MAsmJSHeapAccess
 {
     AtomicOp op_;
 
-    MAsmJSAtomicBinopHeap(AtomicOp op, Scalar::Type vt, MDefinition *ptr, MDefinition *v,
+    MAsmJSAtomicBinopHeap(AtomicOp op, ViewType vt, MDefinition *ptr, MDefinition *v,
                           bool needsBoundsCheck)
         : MBinaryInstruction(ptr, v),
           MAsmJSHeapAccess(vt, needsBoundsCheck),
           op_(op)
     {
         setGuard();         // Not removable
         setResultType(MIRType_Int32);
     }
 
   public:
     INSTRUCTION_HEADER(AsmJSAtomicBinopHeap);
 
-    static MAsmJSAtomicBinopHeap *New(TempAllocator &alloc, AtomicOp op, Scalar::Type vt,
+    static MAsmJSAtomicBinopHeap *New(TempAllocator &alloc, AtomicOp op, ViewType vt,
                                       MDefinition *ptr, MDefinition *v, bool needsBoundsCheck)
     {
         return new(alloc) MAsmJSAtomicBinopHeap(op, vt, ptr, v, needsBoundsCheck);
     }
 
     AtomicOp operation() const { return op_; }
     MDefinition *ptr() const { return getOperand(0); }
     MDefinition *value() const { return getOperand(1); }
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -1253,8 +1253,33 @@ RArrayState::recover(JSContext *cx, Snap
 
         object->initDenseElement(index, val);
     }
 
     result.setObject(*object);
     iter.storeInstructionResult(result);
     return true;
 }
+
+bool
+MStringReplace::writeRecoverData(CompactBufferWriter &writer) const
+{
+    MOZ_ASSERT(canRecoverOnBailout());
+    writer.writeUnsigned(uint32_t(RInstruction::Recover_StringReplace));
+    return true;
+}
+
+RStringReplace::RStringReplace(CompactBufferReader &reader)
+{ }
+
+bool RStringReplace::recover(JSContext *cx, SnapshotIterator &iter) const
+{
+    RootedString string(cx, iter.read().toString());
+    RootedString pattern(cx, iter.read().toString());
+    RootedString replace(cx, iter.read().toString());
+    RootedValue result(cx);
+
+    if (!js::str_replace_string_raw(cx, string, pattern, replace, &result))
+        return false;
+
+    iter.storeInstructionResult(result);
+    return true;
+}
--- a/js/src/jit/Recover.h
+++ b/js/src/jit/Recover.h
@@ -46,16 +46,17 @@ namespace jit {
     _(Abs)                                      \
     _(Sqrt)                                     \
     _(Atan2)                                    \
     _(Hypot)                                    \
     _(StringSplit)                              \
     _(RegExpExec)                               \
     _(RegExpTest)                               \
     _(RegExpReplace)                            \
+    _(StringReplace)                            \
     _(TypeOf)                                   \
     _(ToDouble)                                 \
     _(ToFloat32)                                \
     _(NewObject)                                \
     _(NewArray)                                 \
     _(NewDerivedTypedObject)                    \
     _(CreateThisWithTemplate)                   \
     _(ObjectState)                              \
@@ -513,16 +514,28 @@ class RRegExpReplace MOZ_FINAL : public 
 
     virtual uint32_t numOperands() const {
         return 3;
     }
 
     bool recover(JSContext *cx, SnapshotIterator &iter) const;
 };
 
+class RStringReplace MOZ_FINAL : public RInstruction
+{
+  public:
+    RINSTRUCTION_HEADER_(StringReplace)
+
+    virtual uint32_t numOperands() const {
+        return 3;
+    }
+
+    bool recover(JSContext *cx, SnapshotIterator &iter) const;
+};
+
 class RTypeOf MOZ_FINAL : public RInstruction
 {
   public:
     RINSTRUCTION_HEADER_(TypeOf)
 
     virtual uint32_t numOperands() const {
         return 1;
     }
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -2023,17 +2023,20 @@ CodeGeneratorARM::visitAsmJSStoreHeap(LA
     masm.append(AsmJSHeapAccess(bo.getOffset()));
     return true;
 }
 
 bool
 CodeGeneratorARM::visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap *ins)
 {
     MAsmJSCompareExchangeHeap *mir = ins->mir();
-    Scalar::Type vt = mir->viewType();
+
+    MOZ_ASSERT(mir->viewType() <= AsmJSHeapAccess::Uint32);
+    Scalar::Type vt = Scalar::Type(mir->viewType());
+
     const LAllocation *ptr = ins->ptr();
     Register ptrReg = ToRegister(ptr);
     BaseIndex srcAddr(HeapReg, ptrReg, TimesOne);
 
     Register oldval = ToRegister(ins->oldValue());
     Register newval = ToRegister(ins->newValue());
 
     Label rejoin;
@@ -2058,17 +2061,20 @@ CodeGeneratorARM::visitAsmJSCompareExcha
     }
     return true;
 }
 
 bool
 CodeGeneratorARM::visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap *ins)
 {
     MAsmJSAtomicBinopHeap *mir = ins->mir();
-    Scalar::Type vt = mir->viewType();
+
+    MOZ_ASSERT(mir->viewType() <= AsmJSHeapAccess::Uint32);
+    Scalar::Type vt = Scalar::Type(mir->viewType());
+
     const LAllocation *ptr = ins->ptr();
     Register ptrReg = ToRegister(ptr);
     Register temp = ins->temp()->isBogusTemp() ? InvalidReg : ToRegister(ins->temp());
     const LAllocation* value = ins->value();
     AtomicOp op = mir->operation();
 
     BaseIndex srcAddr(HeapReg, ptrReg, TimesOne);
 
--- a/js/src/jit/arm/Lowering-arm.cpp
+++ b/js/src/jit/arm/Lowering-arm.cpp
@@ -194,25 +194,31 @@ bool
 LIRGeneratorARM::lowerForFPU(LInstructionHelper<1, 1, 0> *ins, MDefinition *mir, MDefinition *input)
 {
     ins->setOperand(0, useRegisterAtStart(input));
     return define(ins, mir,
                   LDefinition(LDefinition::TypeFrom(mir->type()), LDefinition::REGISTER));
 
 }
 
+template<size_t Temps>
 bool
-LIRGeneratorARM::lowerForFPU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir, MDefinition *lhs, MDefinition *rhs)
+LIRGeneratorARM::lowerForFPU(LInstructionHelper<1, 2, Temps> *ins, MDefinition *mir, MDefinition *lhs, MDefinition *rhs)
 {
     ins->setOperand(0, useRegisterAtStart(lhs));
     ins->setOperand(1, useRegisterAtStart(rhs));
     return define(ins, mir,
                   LDefinition(LDefinition::TypeFrom(mir->type()), LDefinition::REGISTER));
 }
 
+template bool LIRGeneratorARM::lowerForFPU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir,
+                                           MDefinition *lhs, MDefinition *rhs);
+template bool LIRGeneratorARM::lowerForFPU(LInstructionHelper<1, 2, 1> *ins, MDefinition *mir,
+                                           MDefinition *lhs, MDefinition *rhs);
+
 bool
 LIRGeneratorARM::lowerForBitAndAndBranch(LBitAndAndBranch *baab, MInstruction *mir,
                                          MDefinition *lhs, MDefinition *rhs)
 {
     baab->setOperand(0, useRegisterAtStart(lhs));
     baab->setOperand(1, useRegisterOrConstantAtStart(rhs));
     return add(baab, mir);
 }
@@ -635,37 +641,33 @@ LIRGeneratorARM::visitCompareExchangeTyp
         new(alloc()) LCompareExchangeTypedArrayElement(elements, index, oldval, newval, tempDef);
 
     return define(lir, ins);
 }
 
 bool
 LIRGeneratorARM::visitAsmJSCompareExchangeHeap(MAsmJSCompareExchangeHeap *ins)
 {
-    MOZ_ASSERT(ins->viewType() != Scalar::Uint8Clamped);
-    MOZ_ASSERT(ins->viewType() != Scalar::Float32);
-    MOZ_ASSERT(ins->viewType() != Scalar::Float64);
+    MOZ_ASSERT(ins->viewType() < AsmJSHeapAccess::Float32);
 
     MDefinition *ptr = ins->ptr();
     MOZ_ASSERT(ptr->type() == MIRType_Int32);
 
     LAsmJSCompareExchangeHeap *lir =
         new(alloc()) LAsmJSCompareExchangeHeap(useRegister(ptr),
                                                useRegister(ins->oldValue()),
                                                useRegister(ins->newValue()));
 
     return define(lir, ins);
 }
 
 bool
 LIRGeneratorARM::visitAsmJSAtomicBinopHeap(MAsmJSAtomicBinopHeap *ins)
 {
-    MOZ_ASSERT(ins->viewType() != Scalar::Uint8Clamped);
-    MOZ_ASSERT(ins->viewType() != Scalar::Float32);
-    MOZ_ASSERT(ins->viewType() != Scalar::Float64);
+    MOZ_ASSERT(ins->viewType() < AsmJSHeapAccess::Float32);
 
     MDefinition *ptr = ins->ptr();
     MOZ_ASSERT(ptr->type() == MIRType_Int32);
 
     LAsmJSAtomicBinopHeap *lir =
         new(alloc()) LAsmJSAtomicBinopHeap(useRegister(ptr),
                                            useRegister(ins->value()),
                                            LDefinition::BogusTemp());
--- a/js/src/jit/arm/Lowering-arm.h
+++ b/js/src/jit/arm/Lowering-arm.h
@@ -51,17 +51,18 @@ class LIRGeneratorARM : public LIRGenera
 
     bool lowerForALU(LInstructionHelper<1, 1, 0> *ins, MDefinition *mir,
                      MDefinition *input);
     bool lowerForALU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir,
                      MDefinition *lhs, MDefinition *rhs);
 
     bool lowerForFPU(LInstructionHelper<1, 1, 0> *ins, MDefinition *mir,
                      MDefinition *src);
-    bool lowerForFPU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir,
+    template<size_t Temps>
+    bool lowerForFPU(LInstructionHelper<1, 2, Temps> *ins, MDefinition *mir,
                      MDefinition *lhs, MDefinition *rhs);
 
     bool lowerForCompIx4(LSimdBinaryCompIx4 *ins, MSimdBinaryComp *mir,
                          MDefinition *lhs, MDefinition *rhs)
     {
         return lowerForFPU(ins, mir, lhs, rhs);
     }
     bool lowerForCompFx4(LSimdBinaryCompFx4 *ins, MSimdBinaryComp *mir,
--- a/js/src/jit/mips/Lowering-mips.cpp
+++ b/js/src/jit/mips/Lowering-mips.cpp
@@ -196,26 +196,32 @@ bool
 LIRGeneratorMIPS::lowerForFPU(LInstructionHelper<1, 1, 0> *ins, MDefinition *mir,
                               MDefinition *input)
 {
     ins->setOperand(0, useRegister(input));
     return define(ins, mir,
                   LDefinition(LDefinition::TypeFrom(mir->type()), LDefinition::REGISTER));
 }
 
+template<size_t Temps>
 bool
-LIRGeneratorMIPS::lowerForFPU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir,
+LIRGeneratorMIPS::lowerForFPU(LInstructionHelper<1, 2, Temps> *ins, MDefinition *mir,
                               MDefinition *lhs, MDefinition *rhs)
 {
     ins->setOperand(0, useRegister(lhs));
     ins->setOperand(1, useRegister(rhs));
     return define(ins, mir,
                   LDefinition(LDefinition::TypeFrom(mir->type()), LDefinition::REGISTER));
 }
 
+template bool LIRGeneratorMIPS::lowerForFPU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir,
+                                            MDefinition *lhs, MDefinition *rhs);
+template bool LIRGeneratorMIPS::lowerForFPU(LInstructionHelper<1, 2, 1> *ins, MDefinition *mir,
+                                            MDefinition *lhs, MDefinition *rhs);
+
 bool
 LIRGeneratorMIPS::lowerForBitAndAndBranch(LBitAndAndBranch *baab, MInstruction *mir,
                                           MDefinition *lhs, MDefinition *rhs)
 {
     baab->setOperand(0, useRegisterAtStart(lhs));
     baab->setOperand(1, useRegisterOrConstantAtStart(rhs));
     return add(baab, mir);
 }
--- a/js/src/jit/mips/Lowering-mips.h
+++ b/js/src/jit/mips/Lowering-mips.h
@@ -51,17 +51,18 @@ class LIRGeneratorMIPS : public LIRGener
 
     bool lowerForALU(LInstructionHelper<1, 1, 0> *ins, MDefinition *mir,
                      MDefinition *input);
     bool lowerForALU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir,
                      MDefinition *lhs, MDefinition *rhs);
 
     bool lowerForFPU(LInstructionHelper<1, 1, 0> *ins, MDefinition *mir,
                      MDefinition *src);
-    bool lowerForFPU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir,
+    template<size_t Temps>
+    bool lowerForFPU(LInstructionHelper<1, 2, Temps> *ins, MDefinition *mir,
                      MDefinition *lhs, MDefinition *rhs);
 
     bool lowerForCompIx4(LSimdBinaryCompIx4 *ins, MSimdBinaryComp *mir,
                          MDefinition *lhs, MDefinition *rhs)
     {
         return lowerForFPU(ins, mir, lhs, rhs);
     }
     bool lowerForCompFx4(LSimdBinaryCompFx4 *ins, MSimdBinaryComp *mir,
--- a/js/src/jit/shared/Assembler-shared.h
+++ b/js/src/jit/shared/Assembler-shared.h
@@ -726,46 +726,63 @@ static const unsigned AsmJSNaN32GlobalDa
 
 // Summarizes a heap access made by asm.js code that needs to be patched later
 // and/or looked up by the asm.js signal handlers. Different architectures need
 // to know different things (x64: offset and length, ARM: where to patch in
 // heap length, x86: where to patch in heap length and base) hence the massive
 // #ifdefery.
 class AsmJSHeapAccess
 {
+  public:
+    enum ViewType {
+         Int8         = Scalar::Int8,
+         Uint8        = Scalar::Uint8,
+         Int16        = Scalar::Int16,
+         Uint16       = Scalar::Uint16,
+         Int32        = Scalar::Int32,
+         Uint32       = Scalar::Uint32,
+         Float32      = Scalar::Float32,
+         Float64      = Scalar::Float64,
+         Uint8Clamped = Scalar::Uint8Clamped,
+         Float32x4,
+         Int32x4
+    };
+
+  private:
     uint32_t offset_;
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
     uint8_t cmpDelta_;  // the number of bytes from the cmp to the load/store instruction
     uint8_t opLength_;  // the length of the load/store instruction
-    uint8_t isFloat32Load_;
+    ViewType viewType_;
     AnyRegister::Code loadedReg_ : 8;
 #endif
 
     JS_STATIC_ASSERT(AnyRegister::Total < UINT8_MAX);
 
   public:
     AsmJSHeapAccess() {}
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
     static const uint32_t NoLengthCheck = UINT32_MAX;
 
     // If 'cmp' equals 'offset' or if it is not supplied then the
     // cmpDelta_ is zero indicating that there is no length to patch.
-    AsmJSHeapAccess(uint32_t offset, uint32_t after, Scalar::Type vt,
+    AsmJSHeapAccess(uint32_t offset, uint32_t after, ViewType viewType,
                     AnyRegister loadedReg, uint32_t cmp = NoLengthCheck)
       : offset_(offset),
         cmpDelta_(cmp == NoLengthCheck ? 0 : offset - cmp),
         opLength_(after - offset),
-        isFloat32Load_(vt == Scalar::Float32),
+        viewType_(viewType),
         loadedReg_(loadedReg.code())
     {}
-    AsmJSHeapAccess(uint32_t offset, uint8_t after, uint32_t cmp = NoLengthCheck)
+    AsmJSHeapAccess(uint32_t offset, uint8_t after, ViewType viewType,
+                    uint32_t cmp = NoLengthCheck)
       : offset_(offset),
         cmpDelta_(cmp == NoLengthCheck ? 0 : offset - cmp),
         opLength_(after - offset),
-        isFloat32Load_(false),
+        viewType_(viewType),
         loadedReg_(UINT8_MAX)
     {}
 #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS)
     explicit AsmJSHeapAccess(uint32_t offset)
       : offset_(offset)
     {}
 #endif
 
@@ -774,17 +791,17 @@ class AsmJSHeapAccess
 #if defined(JS_CODEGEN_X86)
     void *patchOffsetAt(uint8_t *code) const { return code + (offset_ + opLength_); }
 #endif
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
     bool hasLengthCheck() const { return cmpDelta_ > 0; }
     void *patchLengthAt(uint8_t *code) const { return code + (offset_ - cmpDelta_); }
     unsigned opLength() const { return opLength_; }
     bool isLoad() const { return loadedReg_ != UINT8_MAX; }
-    bool isFloat32Load() const { return isFloat32Load_; }
+    ViewType viewType() const { return viewType_; }
     AnyRegister loadedReg() const { return AnyRegister::FromCode(loadedReg_); }
 #endif
 };
 
 typedef Vector<AsmJSHeapAccess, 0, SystemAllocPolicy> AsmJSHeapAccessVector;
 
 struct AsmJSGlobalAccess
 {
--- a/js/src/jit/shared/Assembler-x86-shared.h
+++ b/js/src/jit/shared/Assembler-x86-shared.h
@@ -470,16 +470,19 @@ class AssemblerX86Shared : public Assemb
         MOZ_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::MEM_REG_DISP:
             masm.movaps_mr(src.disp(), src.base(), dest.code());
             break;
           case Operand::MEM_SCALE:
             masm.movaps_mr(src.disp(), src.base(), src.index(), src.scale(), dest.code());
             break;
+          case Operand::FPREG:
+            masm.movaps_rr(src.fpu(), dest.code());
+            break;
           default:
             MOZ_CRASH("unexpected operand kind");
         }
     }
     void movaps(FloatRegister src, const Operand &dest) {
         MOZ_ASSERT(HasSSE2());
         switch (dest.kind()) {
           case Operand::MEM_REG_DISP:
@@ -1683,16 +1686,31 @@ class AssemblerX86Shared : public Assemb
             break;
           case Operand::MEM_ADDRESS32:
             masm.cmpps_mr(src.address(), dest.code(), order);
             break;
           default:
             MOZ_CRASH("unexpected operand kind");
         }
     }
+    void cmpeqps(const Operand &src, FloatRegister dest) {
+        cmpps(src, dest, X86Assembler::ConditionCmp_EQ);
+    }
+    void cmpltps(const Operand &src, FloatRegister dest) {
+        cmpps(src, dest, X86Assembler::ConditionCmp_LT);
+    }
+    void cmpleps(const Operand &src, FloatRegister dest) {
+        cmpps(src, dest, X86Assembler::ConditionCmp_LE);
+    }
+    void cmpunordps(const Operand &src, FloatRegister dest) {
+        cmpps(src, dest, X86Assembler::ConditionCmp_UNORD);
+    }
+    void cmpneqps(const Operand &src, FloatRegister dest) {
+        cmpps(src, dest, X86Assembler::ConditionCmp_NEQ);
+    }
     void rcpps(const Operand &src, FloatRegister dest) {
         MOZ_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::FPREG:
             masm.rcpps_rr(src.fpu(), dest.code());
             break;
           case Operand::MEM_REG_DISP:
             masm.rcpps_mr(src.disp(), src.base(), dest.code());
@@ -1884,16 +1902,17 @@ class AssemblerX86Shared : public Assemb
           case Operand::MEM_ADDRESS32:
             masm.andps_mr(src.address(), dest.code());
             break;
           default:
             MOZ_CRASH("unexpected operand kind");
         }
     }
     void andnps(const Operand &src, FloatRegister dest) {
+        // Negates bits of dest and then applies AND
         MOZ_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::FPREG:
             masm.andnps_rr(src.fpu(), dest.code());
             break;
           case Operand::MEM_REG_DISP:
             masm.andnps_mr(src.disp(), src.base(), dest.code());
             break;
--- a/js/src/jit/shared/BaseAssembler-x86-shared.h
+++ b/js/src/jit/shared/BaseAssembler-x86-shared.h
@@ -192,16 +192,28 @@ public:
         ConditionGE,
         ConditionLE,
         ConditionG,
 
         ConditionC  = ConditionB,
         ConditionNC = ConditionAE
     } Condition;
 
+    // Conditions for CMP instructions (CMPSS, CMPSD, CMPPS, CMPPD, etc).
+    typedef enum {
+        ConditionCmp_EQ    = 0x0,
+        ConditionCmp_LT    = 0x1,
+        ConditionCmp_LE    = 0x2,
+        ConditionCmp_UNORD = 0x3,
+        ConditionCmp_NEQ   = 0x4,
+        ConditionCmp_NLT   = 0x5,
+        ConditionCmp_NLE   = 0x6,
+        ConditionCmp_ORD   = 0x7,
+    } ConditionCmp;
+
     static const char* nameCC(Condition cc)
     {
         static const char* const names[16]
           = { "o ", "no", "b ", "ae", "e ", "ne", "be", "a ",
               "s ", "ns", "p ", "np", "l ", "ge", "le", "g " };
         int ix = (int)cc;
         return (ix < 0 || ix > 15) ? "??" : names[ix];
     }
@@ -3222,16 +3234,29 @@ public:
 
     void movss_mr(const void* address, XMMRegisterID dst)
     {
         spew("movss      %p, %s", address, nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_F3);
         m_formatter.twoByteOp(OP2_MOVSD_VsdWsd, (RegisterID)dst, address);
     }
 
+    void movups_mr(const void* address, XMMRegisterID dst)
+    {
+        spew("movups     %p, %s", address, nameFPReg(dst));
+        m_formatter.twoByteOp(OP2_MOVPS_VpsWps, (RegisterID)dst, address);
+    }
+
+    void movdqu_mr(const void* address, XMMRegisterID dst)
+    {
+        spew("movdqu     %p, %s", address, nameFPReg(dst));
+        m_formatter.prefix(PRE_SSE_F3);
+        m_formatter.twoByteOp(OP2_MOVDQ_VdqWdq, (RegisterID)dst, address);
+    }
+
     void movsd_rm(XMMRegisterID src, const void* address)
     {
         spew("movsd      %s, %p", nameFPReg(src), address);
         m_formatter.prefix(PRE_SSE_F2);
         m_formatter.twoByteOp(OP2_MOVSD_WsdVsd, (RegisterID)src, address);
     }
 
     void movss_rm(XMMRegisterID src, const void* address)
@@ -3246,16 +3271,29 @@ public:
         spew("movdqa     %s, %p", nameFPReg(src), address);
         m_formatter.prefix(PRE_SSE_66);
         m_formatter.twoByteOp(OP2_MOVDQ_WdqVdq, (RegisterID)src, address);
     }
 
     void movaps_rm(XMMRegisterID src, const void* address)
     {
         spew("movaps     %s, %p", nameFPReg(src), address);
+        m_formatter.twoByteOp(OP2_MOVAPS_WsdVsd, (RegisterID)src, address);
+    }
+
+    void movdqu_rm(XMMRegisterID src, const void* address)
+    {
+        spew("movdqu     %s, %p", nameFPReg(src), address);
+        m_formatter.prefix(PRE_SSE_F3);
+        m_formatter.twoByteOp(OP2_MOVDQ_WdqVdq, (RegisterID)src, address);
+    }
+
+    void movups_rm(XMMRegisterID src, const void* address)
+    {
+        spew("movups     %s, %p", nameFPReg(src), address);
         m_formatter.twoByteOp(OP2_MOVPS_WpsVps, (RegisterID)src, address);
     }
 #ifdef JS_CODEGEN_X64
     JmpSrc movsd_ripr(XMMRegisterID dst)
     {
         spew("movsd      ?(%%rip), %s", nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_F2);
         m_formatter.twoByteRipOp(OP2_MOVSD_VsdWsd, (RegisterID)dst, 0);
@@ -3329,28 +3367,40 @@ public:
 
 
     void movups_rm(XMMRegisterID src, int offset, RegisterID base)
     {
         spew("movups     %s, %s0x%x(%s)",
              nameFPReg(src), PRETTY_PRINT_OFFSET(offset), nameIReg(base));
         m_formatter.twoByteOp(OP2_MOVPS_WpsVps, (RegisterID)src, base, offset);
     }
+    void movups_rm_disp32(XMMRegisterID src, int offset, RegisterID base)
+    {
+        spew("movups     %s, %s0x%x(%s)",
+             nameFPReg(src), PRETTY_PRINT_OFFSET(offset), nameIReg(base));
+        m_formatter.twoByteOp_disp32(OP2_MOVPS_WpsVps, (RegisterID)src, base, offset);
+    }
     void movups_rm(XMMRegisterID src, int offset, RegisterID base, RegisterID index, int scale)
     {
         spew("movups     %s, %d(%s,%s,%d)",
              nameFPReg(src), offset, nameIReg(base), nameIReg(index), 1<<scale);
         m_formatter.twoByteOp(OP2_MOVPS_WpsVps, (RegisterID)src, base, index, scale, offset);
     }
     void movups_mr(int offset, RegisterID base, XMMRegisterID dst)
     {
         spew("movups     %s0x%x(%s), %s",
              PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameFPReg(dst));
         m_formatter.twoByteOp(OP2_MOVPS_VpsWps, (RegisterID)dst, base, offset);
     }
+    void movups_mr_disp32(int offset, RegisterID base, XMMRegisterID dst)
+    {
+        spew("movups     %s0x%x(%s), %s",
+             PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameFPReg(dst));
+        m_formatter.twoByteOp_disp32(OP2_MOVPS_VpsWps, (RegisterID)dst, base, offset);
+    }
     void movups_mr(int offset, RegisterID base, RegisterID index, int scale, XMMRegisterID dst)
     {
         spew("movups     %d(%s,%s,%d), %s",
              offset, nameIReg(base), nameIReg(index), 1<<scale, nameFPReg(dst));
         m_formatter.twoByteOp(OP2_MOVPS_VpsWps, (RegisterID)dst, base, index, scale, offset);
     }
 
     void movapd_rr(XMMRegisterID src, XMMRegisterID dst)
@@ -3393,32 +3443,48 @@ public:
     void movdqu_rm(XMMRegisterID src, int offset, RegisterID base)
     {
         spew("movdqu     %s, %s0x%x(%s)",
              nameFPReg(src), PRETTY_PRINT_OFFSET(offset), nameIReg(base));
         m_formatter.prefix(PRE_SSE_F3);
         m_formatter.twoByteOp(OP2_MOVDQ_WdqVdq, (RegisterID)src, base, offset);
     }
 
+    void movdqu_rm_disp32(XMMRegisterID src, int offset, RegisterID base)
+    {
+        spew("movdqu     %s, %s0x%x(%s)",
+             nameFPReg(src), PRETTY_PRINT_OFFSET(offset), nameIReg(base));
+        m_formatter.prefix(PRE_SSE_F3);
+        m_formatter.twoByteOp_disp32(OP2_MOVDQ_WdqVdq, (RegisterID)src, base, offset);
+    }
+
     void movdqu_rm(XMMRegisterID src, int offset, RegisterID base, RegisterID index, int scale)
     {
         spew("movdqu     %s, %d(%s,%s,%d)",
              nameFPReg(src), offset, nameIReg(base), nameIReg(index), 1<<scale);
         m_formatter.prefix(PRE_SSE_F3);
         m_formatter.twoByteOp(OP2_MOVDQ_WdqVdq, (RegisterID)src, base, index, scale, offset);
     }
 
     void movdqu_mr(int offset, RegisterID base, XMMRegisterID dst)
     {
         spew("movdqu     %s0x%x(%s), %s",
              PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_F3);
         m_formatter.twoByteOp(OP2_MOVDQ_VdqWdq, (RegisterID)dst, base, offset);
     }
 
+    void movdqu_mr_disp32(int offset, RegisterID base, XMMRegisterID dst)
+    {
+        spew("movdqu     %s0x%x(%s), %s",
+             PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameFPReg(dst));
+        m_formatter.prefix(PRE_SSE_F3);
+        m_formatter.twoByteOp_disp32(OP2_MOVDQ_VdqWdq, (RegisterID)dst, base, offset);
+    }
+
     void movdqu_mr(int offset, RegisterID base, RegisterID index, int scale, XMMRegisterID dst)
     {
         spew("movdqu     %d(%s,%s,%d), %s",
              offset, nameIReg(base), nameIReg(index), 1<<scale, nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_F3);
         m_formatter.twoByteOp(OP2_MOVDQ_VdqWdq, (RegisterID)dst, base, index, scale, offset);
     }
 
--- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp
@@ -341,24 +341,39 @@ CodeGeneratorX86Shared::visitAsmJSPassSt
         }
     }
     return true;
 }
 
 bool
 CodeGeneratorX86Shared::visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds *ool)
 {
-    if (ool->dest().isFloat()) {
-        if (ool->isFloat32Load())
-            masm.loadConstantFloat32(float(GenericNaN()), ool->dest().fpu());
-        else
-            masm.loadConstantDouble(GenericNaN(), ool->dest().fpu());
-    } else {
+    switch (ool->viewType()) {
+      case AsmJSHeapAccess::Float32:
+        masm.loadConstantFloat32(float(GenericNaN()), ool->dest().fpu());
+        break;
+      case AsmJSHeapAccess::Float64:
+        masm.loadConstantDouble(GenericNaN(), ool->dest().fpu());
+        break;
+      case AsmJSHeapAccess::Float32x4:
+        masm.loadConstantFloat32x4(SimdConstant::SplatX4(float(GenericNaN())), ool->dest().fpu());
+        break;
+      case AsmJSHeapAccess::Int32x4:
+        masm.loadConstantInt32x4(SimdConstant::SplatX4(0), ool->dest().fpu());
+        break;
+      case AsmJSHeapAccess::Int8:
+      case AsmJSHeapAccess::Uint8:
+      case AsmJSHeapAccess::Int16:
+      case AsmJSHeapAccess::Uint16:
+      case AsmJSHeapAccess::Int32:
+      case AsmJSHeapAccess::Uint32:
+      case AsmJSHeapAccess::Uint8Clamped:
         Register destReg = ool->dest().gpr();
         masm.mov(ImmWord(0), destReg);
+        break;
     }
     masm.jmp(ool->rejoin());
     return true;
 }
 
 bool
 CodeGeneratorX86Shared::generateOutOfLineCode()
 {
@@ -2700,26 +2715,26 @@ CodeGeneratorX86Shared::visitSimdBinaryC
 {
     FloatRegister lhs = ToFloatRegister(ins->lhs());
     Operand rhs = ToOperand(ins->rhs());
     MOZ_ASSERT(ToFloatRegister(ins->output()) == lhs);
 
     MSimdBinaryComp::Operation op = ins->operation();
     switch (op) {
       case MSimdBinaryComp::equal:
-        masm.cmpps(rhs, lhs, 0x0);
+        masm.cmpeqps(rhs, lhs);
         return true;
       case MSimdBinaryComp::lessThan:
-        masm.cmpps(rhs, lhs, 0x1);
+        masm.cmpltps(rhs, lhs);
         return true;
       case MSimdBinaryComp::lessThanOrEqual:
-        masm.cmpps(rhs, lhs, 0x2);
+        masm.cmpleps(rhs, lhs);
         return true;
       case MSimdBinaryComp::notEqual:
-        masm.cmpps(rhs, lhs, 0x4);
+        masm.cmpneqps(rhs, lhs);
         return true;
       case MSimdBinaryComp::greaterThanOrEqual:
       case MSimdBinaryComp::greaterThan:
         // We reverse these before register allocation so that we don't have to
         // copy into and out of temporaries after codegen.
         MOZ_CRASH("lowering should have reversed this");
     }
     MOZ_CRASH("unexpected SIMD op");
@@ -2749,16 +2764,19 @@ CodeGeneratorX86Shared::visitSimdBinaryA
       case MSimdBinaryArith::Max:
         // we can do max with a single instruction only if we have SSE4.1
         // using the PMAXSD instruction.
         break;
       case MSimdBinaryArith::Min:
         // we can do max with a single instruction only if we have SSE4.1
         // using the PMINSD instruction.
         break;
+      case MSimdBinaryArith::MinNum:
+      case MSimdBinaryArith::MaxNum:
+        break;
     }
     MOZ_CRASH("unexpected SIMD op");
 }
 
 bool
 CodeGeneratorX86Shared::visitSimdBinaryArithFx4(LSimdBinaryArithFx4 *ins)
 {
     FloatRegister lhs = ToFloatRegister(ins->lhs());
@@ -2774,29 +2792,85 @@ CodeGeneratorX86Shared::visitSimdBinaryA
         masm.packedSubFloat32(rhs, lhs);
         return true;
       case MSimdBinaryArith::Mul:
         masm.packedMulFloat32(rhs, lhs);
         return true;
       case MSimdBinaryArith::Div:
         masm.packedDivFloat32(rhs, lhs);
         return true;
-      case MSimdBinaryArith::Max:
-        // TODO: standardization of float32x4.min/max needs to define the
-        // semantics for particular values: if both input operands are -0 or 0,
-        // or one operand is NaN, the return value will be the "second operand"
-        // in the instruction.
-        // See also bug 1068028, which is about fixing semantics once the
-        // specification is stable.
+      case MSimdBinaryArith::Max: {
+        masm.movaps(lhs, ScratchSimdReg);
+        masm.cmpunordps(rhs, ScratchSimdReg);
+
+        FloatRegister tmp = ToFloatRegister(ins->temp());
+        masm.movaps(rhs, tmp);
+        masm.maxps(Operand(lhs), tmp);
         masm.maxps(rhs, lhs);
+
+        masm.andps(tmp, lhs);
+        masm.orps(ScratchSimdReg, lhs); // or in the all-ones NaNs
         return true;
-      case MSimdBinaryArith::Min:
-        // See comment above.
+      }
+      case MSimdBinaryArith::Min: {
+        FloatRegister rhsCopy = ScratchSimdReg;
+        masm.movaps(rhs, rhsCopy);
+        masm.minps(Operand(lhs), rhsCopy);
         masm.minps(rhs, lhs);
+        masm.orps(rhsCopy, lhs); // NaN or'd with arbitrary bits is NaN
         return true;
+      }
+      case MSimdBinaryArith::MinNum: {
+        FloatRegister tmp = ToFloatRegister(ins->temp());
+        masm.loadConstantInt32x4(SimdConstant::SplatX4(int32_t(0x80000000)), ScratchSimdReg);
+        masm.movdqa(ScratchSimdReg, tmp);
+
+        FloatRegister mask = ScratchSimdReg;
+        masm.pcmpeqd(Operand(lhs), mask);
+        masm.andps(tmp, mask);
+
+        masm.movaps(lhs, tmp);
+        masm.minps(rhs, tmp);
+        masm.orps(mask, tmp);
+
+        masm.movaps(rhs, mask);
+        masm.cmpneqps(Operand(mask), mask);
+
+        // Emulates blendv
+        masm.andps(Operand(mask), lhs);
+        masm.andnps(Operand(tmp), mask);
+        masm.orps(Operand(mask), lhs);
+        return true;
+      }
+      case MSimdBinaryArith::MaxNum: {
+        FloatRegister mask = ScratchSimdReg;
+        masm.loadConstantInt32x4(SimdConstant::SplatX4(0), mask);
+        masm.pcmpeqd(Operand(lhs), mask);
+
+        FloatRegister tmp = ToFloatRegister(ins->temp());
+        masm.loadConstantInt32x4(SimdConstant::SplatX4(int32_t(0x80000000)), tmp);
+        masm.andps(tmp, mask);
+
+        masm.movaps(lhs, tmp);
+        masm.maxps(rhs, tmp);
+        masm.andnps(Operand(tmp), mask);
+
+        // Ensure tmp always contains the temporary result
+        mask = tmp;
+        tmp = ScratchSimdReg;
+
+        masm.movaps(rhs, mask);
+        masm.cmpneqps(Operand(mask), mask);
+
+        // Emulates blendv
+        masm.andps(Operand(mask), lhs);
+        masm.andnps(Operand(tmp), mask);
+        masm.orps(Operand(mask), lhs);
+        return true;
+      }
     }
     MOZ_CRASH("unexpected SIMD op");
 }
 
 bool
 CodeGeneratorX86Shared::visitSimdUnaryArithIx4(LSimdUnaryArithIx4 *ins)
 {
     Operand in = ToOperand(ins->input());
@@ -2937,18 +3011,16 @@ CodeGeneratorX86Shared::visitSimdShift(L
 bool
 CodeGeneratorX86Shared::visitSimdSelect(LSimdSelect *ins)
 {
     FloatRegister mask = ToFloatRegister(ins->mask());
     FloatRegister onTrue = ToFloatRegister(ins->lhs());
     FloatRegister onFalse = ToFloatRegister(ins->rhs());
 
     MOZ_ASSERT(onTrue == ToFloatRegister(ins->output()));
-    // The onFalse argument is not destroyed but due to limitations of the
-    // register allocator its life ends at the start of the operation.
     masm.bitwiseAndX4(Operand(mask), onTrue);
     masm.bitwiseAndNotX4(Operand(onFalse), mask);
     masm.bitwiseOrX4(Operand(mask), onTrue);
     return true;
 }
 
 bool
 CodeGeneratorX86Shared::visitForkJoinGetSlice(LForkJoinGetSlice *ins)
--- a/js/src/jit/shared/CodeGenerator-x86-shared.h
+++ b/js/src/jit/shared/CodeGenerator-x86-shared.h
@@ -33,24 +33,24 @@ class CodeGeneratorX86Shared : public Co
 
   protected:
 
     // Load a NaN or zero into a register for an out of bounds AsmJS or static
     // typed array load.
     class OutOfLineLoadTypedArrayOutOfBounds : public OutOfLineCodeBase<CodeGeneratorX86Shared>
     {
         AnyRegister dest_;
-        bool isFloat32Load_;
+        AsmJSHeapAccess::ViewType viewType_;
       public:
-        OutOfLineLoadTypedArrayOutOfBounds(AnyRegister dest, bool isFloat32Load)
-          : dest_(dest), isFloat32Load_(isFloat32Load)
+        OutOfLineLoadTypedArrayOutOfBounds(AnyRegister dest, AsmJSHeapAccess::ViewType viewType)
+          : dest_(dest), viewType_(viewType)
         {}
 
         AnyRegister dest() const { return dest_; }
-        bool isFloat32Load() const { return isFloat32Load_; }
+        AsmJSHeapAccess::ViewType viewType() const { return viewType_; }
         bool accept(CodeGeneratorX86Shared *codegen) {
             return codegen->visitOutOfLineLoadTypedArrayOutOfBounds(this);
         }
     };
 
     // Label for the common return path.
     NonAssertingLabel returnLabel_;
     NonAssertingLabel deoptLabel_;
--- a/js/src/jit/shared/Lowering-x86-shared.cpp
+++ b/js/src/jit/shared/Lowering-x86-shared.cpp
@@ -95,24 +95,30 @@ bool
 LIRGeneratorX86Shared::lowerForALU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir,
                                    MDefinition *lhs, MDefinition *rhs)
 {
     ins->setOperand(0, useRegisterAtStart(lhs));
     ins->setOperand(1, lhs != rhs ? useOrConstant(rhs) : useOrConstantAtStart(rhs));
     return defineReuseInput(ins, mir, 0);
 }
 
+template<size_t Temps>
 bool
-LIRGeneratorX86Shared::lowerForFPU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir, MDefinition *lhs, MDefinition *rhs)
+LIRGeneratorX86Shared::lowerForFPU(LInstructionHelper<1, 2, Temps> *ins, MDefinition *mir, MDefinition *lhs, MDefinition *rhs)
 {
     ins->setOperand(0, useRegisterAtStart(lhs));
     ins->setOperand(1, lhs != rhs ? use(rhs) : useAtStart(rhs));
     return defineReuseInput(ins, mir, 0);
 }
 
+template bool LIRGeneratorX86Shared::lowerForFPU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir,
+                                                 MDefinition *lhs, MDefinition *rhs);
+template bool LIRGeneratorX86Shared::lowerForFPU(LInstructionHelper<1, 2, 1> *ins, MDefinition *mir,
+                                                 MDefinition *lhs, MDefinition *rhs);
+
 bool
 LIRGeneratorX86Shared::lowerForCompIx4(LSimdBinaryCompIx4 *ins, MSimdBinaryComp *mir, MDefinition *lhs, MDefinition *rhs)
 {
     return lowerForALU(ins, mir, lhs, rhs);
 }
 
 bool
 LIRGeneratorX86Shared::lowerForCompFx4(LSimdBinaryCompFx4 *ins, MSimdBinaryComp *mir, MDefinition *lhs, MDefinition *rhs)
--- a/js/src/jit/shared/Lowering-x86-shared.h
+++ b/js/src/jit/shared/Lowering-x86-shared.h
@@ -26,17 +26,18 @@ class LIRGeneratorX86Shared : public LIR
     bool visitGuardShape(MGuardShape *ins);
     bool visitGuardObjectType(MGuardObjectType *ins);
     bool visitPowHalf(MPowHalf *ins);
     bool lowerForShift(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir, MDefinition *lhs,
                        MDefinition *rhs);
     bool lowerForALU(LInstructionHelper<1, 1, 0> *ins, MDefinition *mir, MDefinition *input);
     bool lowerForALU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir, MDefinition *lhs,
                      MDefinition *rhs);
-    bool lowerForFPU(LInstructionHelper<1, 2, 0> *ins, MDefinition *mir, MDefinition *lhs,
+    template<size_t Temps>
+    bool lowerForFPU(LInstructionHelper<1, 2, Temps> *ins, MDefinition *mir, MDefinition *lhs,
                      MDefinition *rhs);
     bool lowerForCompIx4(LSimdBinaryCompIx4 *ins, MSimdBinaryComp *mir,
                          MDefinition *lhs, MDefinition *rhs);
     bool lowerForCompFx4(LSimdBinaryCompFx4 *ins, MSimdBinaryComp *mir,
                          MDefinition *lhs, MDefinition *rhs);
     bool lowerForBitAndAndBranch(LBitAndAndBranch *baab, MInstruction *mir,
                                  MDefinition *lhs, MDefinition *rhs);
     bool visitConstant(MConstant *ins);
--- a/js/src/jit/shared/MacroAssembler-x86-shared.h
+++ b/js/src/jit/shared/MacroAssembler-x86-shared.h
@@ -839,19 +839,25 @@ class MacroAssemblerX86Shared : public A
         movdqa(src, Operand(dest));
     }
     void moveAlignedInt32x4(FloatRegister src, FloatRegister dest) {
         movdqa(src, dest);
     }
     void loadUnalignedInt32x4(const Address &src, FloatRegister dest) {
         movdqu(Operand(src), dest);
     }
+    void loadUnalignedInt32x4(const Operand &src, FloatRegister dest) {
+        movdqu(src, dest);
+    }
     void storeUnalignedInt32x4(FloatRegister src, const Address &dest) {
         movdqu(src, Operand(dest));
     }
+    void storeUnalignedInt32x4(FloatRegister src, const Operand &dest) {
+        movdqu(src, dest);
+    }
     void packedEqualInt32x4(const Operand &src, FloatRegister dest) {
         pcmpeqd(src, dest);
     }
     void packedGreaterThanInt32x4(const Operand &src, FloatRegister dest) {
         pcmpgtd(src, dest);
     }
     void packedAddInt32(const Operand &src, FloatRegister dest) {
         paddd(src, dest);
@@ -902,19 +908,25 @@ class MacroAssemblerX86Shared : public A
         movaps(src, Operand(dest));
     }
     void moveAlignedFloat32x4(FloatRegister src, FloatRegister dest) {
         movaps(src, dest);
     }
     void loadUnalignedFloat32x4(const Address &src, FloatRegister dest) {
         movups(Operand(src), dest);
     }
+    void loadUnalignedFloat32x4(const Operand &src, FloatRegister dest) {
+        movups(src, dest);
+    }
     void storeUnalignedFloat32x4(FloatRegister src, const Address &dest) {
         movups(src, Operand(dest));
     }
+    void storeUnalignedFloat32x4(FloatRegister src, const Operand &dest) {
+        movups(src, dest);
+    }
     void packedAddFloat32(const Operand &src, FloatRegister dest) {
         addps(src, dest);
     }
     void packedSubFloat32(const Operand &src, FloatRegister dest) {
         subps(src, dest);
     }
     void packedMulFloat32(const Operand &src, FloatRegister dest) {
         mulps(src, dest);
--- a/js/src/jit/x64/CodeGenerator-x64.cpp
+++ b/js/src/jit/x64/CodeGenerator-x64.cpp
@@ -264,68 +264,69 @@ CodeGeneratorX64::memoryBarrier(MemoryBa
     if (barrier & MembarStoreLoad)
         masm.storeLoadFence();
 }
 
 bool
 CodeGeneratorX64::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins)
 {
     MAsmJSLoadHeap *mir = ins->mir();
-    Scalar::Type vt = mir->viewType();
+    AsmJSHeapAccess::ViewType vt = mir->viewType();
     const LAllocation *ptr = ins->ptr();
     const LDefinition *out = ins->output();
     Operand srcAddr(HeapReg);
 
     if (ptr->isConstant()) {
         int32_t ptrImm = ptr->toConstant()->toInt32();
         MOZ_ASSERT(ptrImm >= 0);
         srcAddr = Operand(HeapReg, ptrImm);
     } else {
         srcAddr = Operand(HeapReg, ToRegister(ptr), TimesOne);
     }
 
     memoryBarrier(ins->mir()->barrierBefore());
     OutOfLineLoadTypedArrayOutOfBounds *ool = nullptr;
     uint32_t maybeCmpOffset = AsmJSHeapAccess::NoLengthCheck;
     if (mir->needsBoundsCheck()) {
-        bool isFloat32Load = vt == Scalar::Float32;
-        ool = new(alloc()) OutOfLineLoadTypedArrayOutOfBounds(ToAnyRegister(out), isFloat32Load);
+        ool = new(alloc()) OutOfLineLoadTypedArrayOutOfBounds(ToAnyRegister(out), vt);
         if (!addOutOfLineCode(ool, ins->mir()))
             return false;
 
         CodeOffsetLabel cmp = masm.cmplWithPatch(ToRegister(ptr), Imm32(0));
         masm.j(Assembler::AboveOrEqual, ool->entry());
         maybeCmpOffset = cmp.offset();
     }
 
     uint32_t before = masm.size();
     switch (vt) {
-      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;
-      default: MOZ_CRASH("unexpected array type");
+      case AsmJSHeapAccess::Int8:      masm.movsbl(srcAddr, ToRegister(out)); break;
+      case AsmJSHeapAccess::Uint8:     masm.movzbl(srcAddr, ToRegister(out)); break;
+      case AsmJSHeapAccess::Int16:     masm.movswl(srcAddr, ToRegister(out)); break;
+      case AsmJSHeapAccess::Uint16:    masm.movzwl(srcAddr, ToRegister(out)); break;
+      case AsmJSHeapAccess::Int32:
+      case AsmJSHeapAccess::Uint32:    masm.movl(srcAddr, ToRegister(out)); break;
+      case AsmJSHeapAccess::Float32:   masm.loadFloat32(srcAddr, ToFloatRegister(out)); break;
+      case AsmJSHeapAccess::Float64:   masm.loadDouble(srcAddr, ToFloatRegister(out)); break;
+      case AsmJSHeapAccess::Float32x4: masm.loadUnalignedFloat32x4(srcAddr, ToFloatRegister(out)); break;
+      case AsmJSHeapAccess::Int32x4:   masm.loadUnalignedInt32x4(srcAddr, ToFloatRegister(out)); break;
+      case AsmJSHeapAccess::Uint8Clamped: MOZ_CRASH("unexpected array type");
     }
     uint32_t after = masm.size();
     if (ool)
         masm.bind(ool->rejoin());
     memoryBarrier(ins->mir()->barrierAfter());
     masm.append(AsmJSHeapAccess(before, after, vt, ToAnyRegister(out), maybeCmpOffset));
     return true;
 }
 
 bool
 CodeGeneratorX64::visitAsmJSStoreHeap(LAsmJSStoreHeap *ins)
 {
     MAsmJSStoreHeap *mir = ins->mir();
-    Scalar::Type vt = mir->viewType();
+    AsmJSHeapAccess::ViewType vt = mir->viewType();
     const LAllocation *ptr = ins->ptr();
     Operand dstAddr(HeapReg);
 
     if (ptr->isConstant()) {
         int32_t ptrImm = ptr->toConstant()->toInt32();
         MOZ_ASSERT(ptrImm >= 0);
         dstAddr = Operand(HeapReg, ptrImm);
     } else {
@@ -339,50 +340,59 @@ CodeGeneratorX64::visitAsmJSStoreHeap(LA
         CodeOffsetLabel cmp = masm.cmplWithPatch(ToRegister(ptr), Imm32(0));
         masm.j(Assembler::AboveOrEqual, &rejoin);
         maybeCmpOffset = cmp.offset();
     }
 
     uint32_t before = masm.size();
     if (ins->value()->isConstant()) {
         switch (vt) {
-          case Scalar::Int8:
-          case Scalar::Uint8:   masm.movb(Imm32(ToInt32(ins->value())), dstAddr); break;
-          case Scalar::Int16:
-          case Scalar::Uint16:  masm.movw(Imm32(ToInt32(ins->value())), dstAddr); break;
-          case Scalar::Int32:
-          case Scalar::Uint32:  masm.movl(Imm32(ToInt32(ins->value())), dstAddr); break;
-          default: MOZ_CRASH("unexpected array type");
+          case AsmJSHeapAccess::Int8:
+          case AsmJSHeapAccess::Uint8:        masm.movb(Imm32(ToInt32(ins->value())), dstAddr); break;
+          case AsmJSHeapAccess::Int16:
+          case AsmJSHeapAccess::Uint16:       masm.movw(Imm32(ToInt32(ins->value())), dstAddr); break;
+          case AsmJSHeapAccess::Int32:
+          case AsmJSHeapAccess::Uint32:       masm.movl(Imm32(ToInt32(ins->value())), dstAddr); break;
+          case AsmJSHeapAccess::Float32:
+          case AsmJSHeapAccess::Float64:
+          case AsmJSHeapAccess::Float32x4:
+          case AsmJSHeapAccess::Int32x4:
+          case AsmJSHeapAccess::Uint8Clamped: MOZ_CRASH("unexpected array type");
         }
     } else {
         switch (vt) {
-          case Scalar::Int8:
-          case Scalar::Uint8:   masm.movb(ToRegister(ins->value()), dstAddr); break;
-          case Scalar::Int16:
-          case Scalar::Uint16:  masm.movw(ToRegister(ins->value()), dstAddr); break;
-          case Scalar::Int32:
-          case Scalar::Uint32:  masm.movl(ToRegister(ins->value()), dstAddr); break;
-          case Scalar::Float32: masm.storeFloat32(ToFloatRegister(ins->value()), dstAddr); break;
-          case Scalar::Float64: masm.storeDouble(ToFloatRegister(ins->value()), dstAddr); break;
-          default: MOZ_CRASH("unexpected array type");
+          case AsmJSHeapAccess::Int8:
+          case AsmJSHeapAccess::Uint8:        masm.movb(ToRegister(ins->value()), dstAddr); break;
+          case AsmJSHeapAccess::Int16:
+          case AsmJSHeapAccess::Uint16:       masm.movw(ToRegister(ins->value()), dstAddr); break;
+          case AsmJSHeapAccess::Int32:
+          case AsmJSHeapAccess::Uint32:       masm.movl(ToRegister(ins->value()), dstAddr); break;
+          case AsmJSHeapAccess::Float32:      masm.storeFloat32(ToFloatRegister(ins->value()), dstAddr); break;
+          case AsmJSHeapAccess::Float64:      masm.storeDouble(ToFloatRegister(ins->value()), dstAddr); break;
+          case AsmJSHeapAccess::Float32x4:    masm.storeUnalignedFloat32x4(ToFloatRegister(ins->value()), dstAddr); break;
+          case AsmJSHeapAccess::Int32x4:      masm.storeUnalignedInt32x4(ToFloatRegister(ins->value()), dstAddr); break;
+          case AsmJSHeapAccess::Uint8Clamped: MOZ_CRASH("unexpected array type");
         }
     }
     uint32_t after = masm.size();
     if (rejoin.used())
         masm.bind(&rejoin);
     memoryBarrier(ins->mir()->barrierAfter());
-    masm.append(AsmJSHeapAccess(before, after, maybeCmpOffset));
+    masm.append(AsmJSHeapAccess(before, after, vt, maybeCmpOffset));
     return true;
 }
 
 bool
 CodeGeneratorX64::visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap *ins)
 {
     MAsmJSCompareExchangeHeap *mir = ins->mir();
-    Scalar::Type vt = mir->viewType();
+
+    MOZ_ASSERT(mir->viewType() <= AsmJSHeapAccess::Uint32);
+    Scalar::Type vt = Scalar::Type(mir->viewType());
+
     const LAllocation *ptr = ins->ptr();
 
     MOZ_ASSERT(ptr->isRegister());
     BaseIndex srcAddr(HeapReg, ToRegister(ptr), TimesOne);
 
     Register oldval = ToRegister(ins->oldValue());
     Register newval = ToRegister(ins->newValue());
 
@@ -390,38 +400,41 @@ CodeGeneratorX64::visitAsmJSCompareExcha
     uint32_t maybeCmpOffset = AsmJSHeapAccess::NoLengthCheck;
     MOZ_ASSERT(mir->needsBoundsCheck());
     {
         maybeCmpOffset = masm.cmplWithPatch(ToRegister(ptr), Imm32(0)).offset();
         Label goahead;
         masm.j(Assembler::LessThan, &goahead);
         memoryBarrier(MembarFull);
         Register out = ToRegister(ins->output());
-        masm.xorl(out,out);
+        masm.xorl(out, out);
         masm.jmp(&rejoin);
         masm.bind(&goahead);
     }
     masm.compareExchangeToTypedIntArray(vt == Scalar::Uint32 ? Scalar::Int32 : vt,
                                         srcAddr,
                                         oldval,
                                         newval,
                                         InvalidReg,
                                         ToAnyRegister(ins->output()));
     uint32_t after = masm.size();
     if (rejoin.used())
         masm.bind(&rejoin);
-    masm.append(AsmJSHeapAccess(after, after, maybeCmpOffset));
+    masm.append(AsmJSHeapAccess(after, after, mir->viewType(), maybeCmpOffset));
     return true;
 }
 
 bool
 CodeGeneratorX64::visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap *ins)
 {
     MAsmJSAtomicBinopHeap *mir = ins->mir();
-    Scalar::Type vt = mir->viewType();
+
+    MOZ_ASSERT(mir->viewType() <= AsmJSHeapAccess::Uint32);
+    Scalar::Type vt = Scalar::Type(mir->viewType());
+
     const LAllocation *ptr = ins->ptr();
     Register temp = ins->temp()->isBogusTemp() ? InvalidReg : ToRegister(ins->temp());
     const LAllocation* value = ins->value();
     AtomicOp op = mir->operation();
 
     MOZ_ASSERT(ptr->isRegister());
     BaseIndex srcAddr(HeapReg, ToRegister(ptr), TimesOne);
 
@@ -451,17 +464,17 @@ CodeGeneratorX64::visitAsmJSAtomicBinopH
                                         srcAddr,
                                         temp,
                                         InvalidReg,
                                         ToAnyRegister(ins->output()));
     }
     uint32_t after = masm.size();
     if (rejoin.used())
         masm.bind(&rejoin);
-    masm.append(AsmJSHeapAccess(after, after, maybeCmpOffset));
+    masm.append(AsmJSHeapAccess(after, after, mir->viewType(), maybeCmpOffset));
     return true;
 }
 
 bool
 CodeGeneratorX64::visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar *ins)
 {
     MAsmJSLoadGlobalVar *mir = ins->mir();
 
--- a/js/src/jit/x64/Lowering-x64.cpp
+++ b/js/src/jit/x64/Lowering-x64.cpp
@@ -158,29 +158,31 @@ LIRGeneratorX64::visitAsmJSStoreHeap(MAs
     // getting maximum performance in these cases) only allow constant
     // opererands when skipping bounds checks.
     LAllocation ptrAlloc = ins->needsBoundsCheck()
                            ? useRegisterAtStart(ptr)
                            : useRegisterOrNonNegativeConstantAtStart(ptr);
 
     LAsmJSStoreHeap *lir;
     switch (ins->viewType()) {
-      case Scalar::Int8:
-      case Scalar::Uint8:
-      case Scalar::Int16:
-      case Scalar::Uint16:
-      case Scalar::Int32:
-      case Scalar::Uint32:
+      case AsmJSHeapAccess::Int8:
+      case AsmJSHeapAccess::Uint8:
+      case AsmJSHeapAccess::Int16:
+      case AsmJSHeapAccess::Uint16:
+      case AsmJSHeapAccess::Int32:
+      case AsmJSHeapAccess::Uint32:
         lir = new(alloc()) LAsmJSStoreHeap(ptrAlloc, useRegisterOrConstantAtStart(ins->value()));
         break;
-      case Scalar::Float32:
-      case Scalar::Float64:
+      case AsmJSHeapAccess::Float32:
+      case AsmJSHeapAccess::Float64:
+      case AsmJSHeapAccess::Float32x4:
+      case AsmJSHeapAccess::Int32x4:
         lir = new(alloc()) LAsmJSStoreHeap(ptrAlloc, useRegisterAtStart(ins->value()));
         break;
-      default:
+      case AsmJSHeapAccess::Uint8Clamped:
         MOZ_CRASH("unexpected array type");
     }
 
     return add(lir, ins);
 }
 
 bool
 LIRGeneratorX64::visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins)
--- a/js/src/jit/x86/Assembler-x86.h
+++ b/js/src/jit/x86/Assembler-x86.h
@@ -460,16 +460,26 @@ class Assembler : public AssemblerX86Sha
         masm.movss_mr_disp32(src.offset, src.base.code(), dest.code());
         return CodeOffsetLabel(masm.currentOffset());
     }
     CodeOffsetLabel movsdWithPatch(Address src, FloatRegister dest) {
         MOZ_ASSERT(HasSSE2());
         masm.movsd_mr_disp32(src.offset, src.base.code(), dest.code());
         return CodeOffsetLabel(masm.currentOffset());
     }
+    CodeOffsetLabel movupsWithPatch(Address src, FloatRegister dest) {
+        MOZ_ASSERT(HasSSE2());
+        masm.movups_mr_disp32(src.offset, src.base.code(), dest.code());
+        return CodeOffsetLabel(masm.currentOffset());
+    }
+    CodeOffsetLabel movdquWithPatch(Address src, FloatRegister dest) {
+        MOZ_ASSERT(HasSSE2());
+        masm.movdqu_mr_disp32(src.offset, src.base.code(), dest.code());
+        return CodeOffsetLabel(masm.currentOffset());
+    }
 
     // Store to *(base + disp32) where disp32 can be patched.
     CodeOffsetLabel movbWithPatch(Register src, Address dest) {
         masm.movb_rm_disp32(src.code(), dest.offset, dest.base.code());
         return CodeOffsetLabel(masm.currentOffset());
     }
     CodeOffsetLabel movwWithPatch(Register src, Address dest) {
         masm.movw_rm_disp32(src.code(), dest.offset, dest.base.code());
@@ -484,16 +494,26 @@ class Assembler : public AssemblerX86Sha
         masm.movss_rm_disp32(src.code(), dest.offset, dest.base.code());
         return CodeOffsetLabel(masm.currentOffset());
     }
     CodeOffsetLabel movsdWithPatch(FloatRegister src, Address dest) {
         MOZ_ASSERT(HasSSE2());
         masm.movsd_rm_disp32(src.code(), dest.offset, dest.base.code());
         return CodeOffsetLabel(masm.currentOffset());
     }
+    CodeOffsetLabel movupsWithPatch(FloatRegister src, Address dest) {
+        MOZ_ASSERT(HasSSE2());
+        masm.movups_rm_disp32(src.code(), dest.offset, dest.base.code());
+        return CodeOffsetLabel(masm.currentOffset());
+    }
+    CodeOffsetLabel movdquWithPatch(FloatRegister src, Address dest) {
+        MOZ_ASSERT(HasSSE2());
+        masm.movdqu_rm_disp32(src.code(), dest.offset, dest.base.code());
+        return CodeOffsetLabel(masm.currentOffset());
+    }
 
     // Load from *(addr + index*scale) where addr can be patched.
     CodeOffsetLabel movlWithPatch(PatchedAbsoluteAddress addr, Register index, Scale scale,
                                   Register dest)
     {
         masm.movl_mr(addr.addr, index.code(), scale, dest.code());
         return CodeOffsetLabel(masm.currentOffset());
     }
@@ -529,21 +549,31 @@ class Assembler : public AssemblerX86Sha
         masm.movsd_mr(src.addr, dest.code());
         return CodeOffsetLabel(masm.currentOffset());
     }
     CodeOffsetLabel movdqaWithPatch(PatchedAbsoluteAddress src, FloatRegister dest) {
         MOZ_ASSERT(HasSSE2());
         masm.movdqa_mr(src.addr, dest.code());
         return CodeOffsetLabel(masm.currentOffset());
     }
+    CodeOffsetLabel movdquWithPatch(PatchedAbsoluteAddress src, FloatRegister dest) {
+        MOZ_ASSERT(HasSSE2());
+        masm.movdqu_mr(src.addr, dest.code());
+        return CodeOffsetLabel(masm.currentOffset());
+    }
     CodeOffsetLabel movapsWithPatch(PatchedAbsoluteAddress src, FloatRegister dest) {
         MOZ_ASSERT(HasSSE2());
         masm.movaps_mr(src.addr, dest.code());
         return CodeOffsetLabel(masm.currentOffset());
     }
+    CodeOffsetLabel movupsWithPatch(PatchedAbsoluteAddress src, FloatRegister dest) {
+        MOZ_ASSERT(HasSSE2());
+        masm.movups_mr(src.addr, dest.code());
+        return CodeOffsetLabel(masm.currentOffset());
+    }
 
     // Store to *dest where dest can be patched.
     CodeOffsetLabel movbWithPatch(Register src, PatchedAbsoluteAddress dest) {
         masm.movb_rm(src.code(), dest.addr);
         return CodeOffsetLabel(masm.currentOffset());
     }
     CodeOffsetLabel movwWithPatch(Register src, PatchedAbsoluteAddress dest) {
         masm.movw_rm(src.code(), dest.addr);
@@ -568,16 +598,26 @@ class Assembler : public AssemblerX86Sha
         masm.movdqa_rm(src.code(), dest.addr);
         return CodeOffsetLabel(masm.currentOffset());
     }
     CodeOffsetLabel movapsWithPatch(FloatRegister src, PatchedAbsoluteAddress dest) {
         MOZ_ASSERT(HasSSE2());
         masm.movaps_rm(src.code(), dest.addr);
         return CodeOffsetLabel(masm.currentOffset());
     }
+    CodeOffsetLabel movdquWithPatch(FloatRegister src, PatchedAbsoluteAddress dest) {
+        MOZ_ASSERT(HasSSE2());
+        masm.movdqu_rm(src.code(), dest.addr);
+        return CodeOffsetLabel(masm.currentOffset());
+    }
+    CodeOffsetLabel movupsWithPatch(FloatRegister src, PatchedAbsoluteAddress dest) {
+        MOZ_ASSERT(HasSSE2());
+        masm.movups_rm(src.code(), dest.addr);
+        return CodeOffsetLabel(masm.currentOffset());
+    }
 
     void loadAsmJSActivation(Register dest) {
         CodeOffsetLabel label = movlWithPatch(PatchedAbsoluteAddress(), dest);
         append(AsmJSGlobalAccess(label, AsmJSActivationGlobalDataOffset));
     }
     void loadAsmJSHeapRegisterFromGlobalData() {
         // x86 doesn't have a pinned heap register.
     }
--- a/js/src/jit/x86/CodeGenerator-x86.cpp
+++ b/js/src/jit/x86/CodeGenerator-x86.cpp
@@ -262,74 +262,74 @@ CodeGeneratorX86::visitAsmJSUInt32ToFloa
 
     // Beware: convertUInt32ToFloat32 clobbers input.
     masm.convertUInt32ToFloat32(temp, output);
     return true;
 }
 
 template<typename T>
 void
-CodeGeneratorX86::loadViewTypeElement(Scalar::Type vt, const T &srcAddr,
+CodeGeneratorX86::loadViewTypeElement(AsmJSHeapAccess::ViewType vt, const T &srcAddr,
                                       const LDefinition *out)
 {
     switch (vt) {
-      case Scalar::Int8:    masm.movsblWithPatch(srcAddr, ToRegister(out)); break;
-      case Scalar::Uint8Clamped:
-      case Scalar::Uint8:   masm.movzblWithPatch(srcAddr, ToRegister(out)); break;
-      case Scalar::Int16:   masm.movswlWithPatch(srcAddr, ToRegister(out)); break;
-      case Scalar::Uint16:  masm.movzwlWithPatch(srcAddr, ToRegister(out)); break;
-      case Scalar::Int32:
-      case Scalar::Uint32:  masm.movlWithPatch(srcAddr, ToRegister(out)); break;
-      case Scalar::Float32: masm.movssWithPatch(srcAddr, ToFloatRegister(out)); break;
-      case Scalar::Float64: masm.movsdWithPatch(srcAddr, ToFloatRegister(out)); break;
-      default: MOZ_CRASH("unexpected array type");
+      case AsmJSHeapAccess::Int8:         masm.movsblWithPatch(srcAddr, ToRegister(out)); break;
+      case AsmJSHeapAccess::Uint8Clamped:
+      case AsmJSHeapAccess::Uint8:        masm.movzblWithPatch(srcAddr, ToRegister(out)); break;
+      case AsmJSHeapAccess::Int16:        masm.movswlWithPatch(srcAddr, ToRegister(out)); break;
+      case AsmJSHeapAccess::Uint16:       masm.movzwlWithPatch(srcAddr, ToRegister(out)); break;
+      case AsmJSHeapAccess::Int32:
+      case AsmJSHeapAccess::Uint32:       masm.movlWithPatch(srcAddr, ToRegister(out)); break;
+      case AsmJSHeapAccess::Float32:      masm.movssWithPatch(srcAddr, ToFloatRegister(out)); break;
+      case AsmJSHeapAccess::Float64:      masm.movsdWithPatch(srcAddr, ToFloatRegister(out)); break;
+      case AsmJSHeapAccess::Float32x4:    masm.movupsWithPatch(srcAddr, ToFloatRegister(out)); break;
+      case AsmJSHeapAccess::Int32x4:      masm.movdquWithPatch(srcAddr, ToFloatRegister(out)); break;
     }
 }
 
 template<typename T>
 bool
-CodeGeneratorX86::loadAndNoteViewTypeElement(Scalar::Type vt, const T &srcAddr,
+CodeGeneratorX86::loadAndNoteViewTypeElement(AsmJSHeapAccess::ViewType vt, const T &srcAddr,
                                              const LDefinition *out)
 {
     uint32_t before = masm.size();
     loadViewTypeElement(vt, srcAddr, out);
     uint32_t after = masm.size();
     masm.append(AsmJSHeapAccess(before, after, vt, ToAnyRegister(out)));
     return true;
 }
 
 bool
 CodeGeneratorX86::visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic *ins)
 {
     const MLoadTypedArrayElementStatic *mir = ins->mir();
-    Scalar::Type vt = mir->viewType();
-    MOZ_ASSERT_IF(vt == Scalar::Float32, mir->type() == MIRType_Float32);
+    AsmJSHeapAccess::ViewType vt = AsmJSHeapAccess::ViewType(mir->viewType());
+    MOZ_ASSERT_IF(vt == AsmJSHeapAccess::Float32, mir->type() == MIRType_Float32);
 
     Register ptr = ToRegister(ins->ptr());
     const LDefinition *out = ins->output();
 
     OutOfLineLoadTypedArrayOutOfBounds *ool = nullptr;
-    bool isFloat32Load = (vt == Scalar::Float32);
     if (!mir->fallible()) {
-        ool = new(alloc()) OutOfLineLoadTypedArrayOutOfBounds(ToAnyRegister(out), isFloat32Load);
+        ool = new(alloc()) OutOfLineLoadTypedArrayOutOfBounds(ToAnyRegister(out), vt);
         if (!addOutOfLineCode(ool, ins->mir()))
             return false;
     }
 
     masm.cmpl(ptr, Imm32(mir->length()));
     if (ool)
         masm.j(Assembler::AboveOrEqual, ool->entry());
     else if (!bailoutIf(Assembler::AboveOrEqual, ins->snapshot()))
         return false;
 
     Address srcAddr(ptr, (int32_t) mir->base());
     loadViewTypeElement(vt, srcAddr, out);
-    if (vt == Scalar::Float64)
+    if (vt == AsmJSHeapAccess::Float64)
         masm.canonicalizeDouble(ToFloatRegister(out));
-    if (vt == Scalar::Float32)
+    if (vt == AsmJSHeapAccess::Float32)
         masm.canonicalizeFloat(ToFloatRegister(out));
     if (ool)
         masm.bind(ool->rejoin());
     return true;
 }
 
 bool
 CodeGeneratorX86::visitAsmJSCall(LAsmJSCall *ins)
@@ -364,17 +364,17 @@ CodeGeneratorX86::memoryBarrier(MemoryBa
     if (barrier & MembarStoreLoad)
         masm.storeLoadFence();
 }
 
 bool
 CodeGeneratorX86::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins)
 {
     const MAsmJSLoadHeap *mir = ins->mir();
-    Scalar::Type vt = mir->viewType();
+    AsmJSHeapAccess::ViewType vt = mir->viewType();
     const LAllocation *ptr = ins->ptr();
     const LDefinition *out = ins->output();
 
     memoryBarrier(ins->mir()->barrierBefore());
 
     if (ptr->isConstant()) {
         // The constant displacement still needs to be added to the as-yet-unknown
         // base address of the heap. For now, embed the displacement as an
@@ -390,18 +390,17 @@ CodeGeneratorX86::visitAsmJSLoadHeap(LAs
     Address srcAddr(ptrReg, 0);
 
     if (!mir->needsBoundsCheck()) {
         loadAndNoteViewTypeElement(vt, srcAddr, out);
         memoryBarrier(ins->mir()->barrierAfter());
         return true;
     }
 
-    bool isFloat32Load = vt == Scalar::Float32;
-    OutOfLineLoadTypedArrayOutOfBounds *ool = new(alloc()) OutOfLineLoadTypedArrayOutOfBounds(ToAnyRegister(out), isFloat32Load);
+    OutOfLineLoadTypedArrayOutOfBounds *ool = new(alloc()) OutOfLineLoadTypedArrayOutOfBounds(ToAnyRegister(out), vt);
     if (!addOutOfLineCode(ool, mir))
         return false;
 
     CodeOffsetLabel cmp = masm.cmplWithPatch(ptrReg, Imm32(0));
     masm.j(Assembler::AboveOrEqual, ool->entry());
 
     uint32_t before = masm.size();
     loadViewTypeElement(vt, srcAddr, out);
@@ -409,49 +408,50 @@ CodeGeneratorX86::visitAsmJSLoadHeap(LAs
     masm.bind(ool->rejoin());
     memoryBarrier(ins->mir()->barrierAfter());
     masm.append(AsmJSHeapAccess(before, after, vt, ToAnyRegister(out), cmp.offset()));
     return true;
 }
 
 template<typename T>
 void
-CodeGeneratorX86::storeViewTypeElement(Scalar::Type vt, const LAllocation *value,
+CodeGeneratorX86::storeViewTypeElement(AsmJSHeapAccess::ViewType vt, const LAllocation *value,
                                        const T &dstAddr)
 {
     switch (vt) {
-      case Scalar::Int8:
-      case Scalar::Uint8Clamped:
-      case Scalar::Uint8:   masm.movbWithPatch(ToRegister(value), dstAddr); break;
-      case Scalar::Int16:
-      case Scalar::Uint16:  masm.movwWithPatch(ToRegister(value), dstAddr); break;
-      case Scalar::Int32:
-      case Scalar::Uint32:  masm.movlWithPatch(ToRegister(value), dstAddr); break;
-      case Scalar::Float32: masm.movssWithPatch(ToFloatRegister(value), dstAddr); break;
-      case Scalar::Float64: masm.movsdWithPatch(ToFloatRegister(value), dstAddr); break;
-      default: MOZ_CRASH("unexpected array type");
+      case AsmJSHeapAccess::Int8:
+      case AsmJSHeapAccess::Uint8Clamped:
+      case AsmJSHeapAccess::Uint8:        masm.movbWithPatch(ToRegister(value), dstAddr); break;
+      case AsmJSHeapAccess::Int16:
+      case AsmJSHeapAccess::Uint16:       masm.movwWithPatch(ToRegister(value), dstAddr); break;
+      case AsmJSHeapAccess::Int32:
+      case AsmJSHeapAccess::Uint32:       masm.movlWithPatch(ToRegister(value), dstAddr); break;
+      case AsmJSHeapAccess::Float32:      masm.movssWithPatch(ToFloatRegister(value), dstAddr); break;
+      case AsmJSHeapAccess::Float64:      masm.movsdWithPatch(ToFloatRegister(value), dstAddr); break;
+      case AsmJSHeapAccess::Float32x4:    masm.movupsWithPatch(ToFloatRegister(value), dstAddr); break;
+      case AsmJSHeapAccess::Int32x4:      masm.movdquWithPatch(ToFloatRegister(value), dstAddr); break;
     }
 }
 
 template<typename T>
 void
-CodeGeneratorX86::storeAndNoteViewTypeElement(Scalar::Type vt, const LAllocation *value,
-                                              const T &dstAddr)
+CodeGeneratorX86::storeAndNoteViewTypeElement(AsmJSHeapAccess::ViewType vt,
+                                              const LAllocation *value, const T &dstAddr)
 {
     uint32_t before = masm.size();
     storeViewTypeElement(vt, value, dstAddr);
     uint32_t after = masm.size();
-    masm.append(AsmJSHeapAccess(before, after));
+    masm.append(AsmJSHeapAccess(before, after, vt));
 }
 
 bool
 CodeGeneratorX86::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic *ins)
 {
     MStoreTypedArrayElementStatic *mir = ins->mir();
-    Scalar::Type vt = mir->viewType();
+    AsmJSHeapAccess::ViewType vt = AsmJSHeapAccess::ViewType(mir->viewType());
 
     Register ptr = ToRegister(ins->ptr());
     const LAllocation *value = ins->value();
 
     masm.cmpl(ptr, Imm32(mir->length()));
     Label rejoin;
     masm.j(Assembler::AboveOrEqual, &rejoin);
 
@@ -460,17 +460,17 @@ CodeGeneratorX86::visitStoreTypedArrayEl
     masm.bind(&rejoin);
     return true;
 }
 
 bool
 CodeGeneratorX86::visitAsmJSStoreHeap(LAsmJSStoreHeap *ins)
 {
     MAsmJSStoreHeap *mir = ins->mir();
-    Scalar::Type vt = mir->viewType();
+    AsmJSHeapAccess::ViewType vt = mir->viewType();
     const LAllocation *value = ins->value();
     const LAllocation *ptr = ins->ptr();
 
     memoryBarrier(ins->mir()->barrierBefore());
 
     if (ptr->isConstant()) {
         // The constant displacement still needs to be added to the as-yet-unknown
         // base address of the heap. For now, embed the displacement as an
@@ -495,25 +495,28 @@ CodeGeneratorX86::visitAsmJSStoreHeap(LA
     Label rejoin;
     masm.j(Assembler::AboveOrEqual, &rejoin);
 
     uint32_t before = masm.size();
     storeViewTypeElement(vt, value, dstAddr);
     uint32_t after = masm.size();
     masm.bind(&rejoin);
     memoryBarrier(ins->mir()->barrierAfter());
-    masm.append(AsmJSHeapAccess(before, after, cmp.offset()));
+    masm.append(AsmJSHeapAccess(before, after, vt, cmp.offset()));
     return true;
 }
 
 bool
 CodeGeneratorX86::visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap *ins)
 {
     MAsmJSCompareExchangeHeap *mir = ins->mir();
-    Scalar::Type vt = mir->viewType();
+
+    MOZ_ASSERT(mir->viewType() <= AsmJSHeapAccess::Uint32);
+    Scalar::Type vt = Scalar::Type(mir->viewType());
+
     const LAllocation *ptr = ins->ptr();
     Register oldval = ToRegister(ins->oldValue());
     Register newval = ToRegister(ins->newValue());
 
     MOZ_ASSERT(ptr->isRegister());
     // Set up the offset within the heap in the pointer reg.
     Register ptrReg = ToRegister(ptr);
 
@@ -531,17 +534,17 @@ CodeGeneratorX86::visitAsmJSCompareExcha
         masm.bind(&goahead);
     }
 
     // Add in the actual heap pointer explicitly, to avoid opening up
     // the abstraction that is compareExchangeToTypedIntArray at this time.
     uint32_t before = masm.size();
     masm.addl_wide(Imm32(0), ptrReg);
     uint32_t after = masm.size();
-    masm.append(AsmJSHeapAccess(before, after, maybeCmpOffset));
+    masm.append(AsmJSHeapAccess(before, after, mir->viewType(), maybeCmpOffset));
 
     Address memAddr(ToRegister(ptr), 0);
     masm.compareExchangeToTypedIntArray(vt == Scalar::Uint32 ? Scalar::Int32 : vt,
                                         memAddr,
                                         oldval,
                                         newval,
                                         InvalidReg,
                                         ToAnyRegister(ins->output()));
@@ -550,17 +553,20 @@ CodeGeneratorX86::visitAsmJSCompareExcha
 
     return true;
 }
 
 bool
 CodeGeneratorX86::visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap *ins)
 {
     MAsmJSAtomicBinopHeap *mir = ins->mir();
-    Scalar::Type vt = mir->viewType();
+
+    MOZ_ASSERT(mir->viewType() <= AsmJSHeapAccess::Uint32);
+    Scalar::Type vt = Scalar::Type(mir->viewType());
+
     const LAllocation *ptr = ins->ptr();
     Register temp = ins->temp()->isBogusTemp() ? InvalidReg : ToRegister(ins->temp());
     const LAllocation* value = ins->value();
     AtomicOp op = mir->operation();
 
     MOZ_ASSERT(ptr->isRegister());
     // Set up the offset within the heap in the pointer reg.
     Register ptrReg = ToRegister(ptr);
@@ -579,17 +585,17 @@ CodeGeneratorX86::visitAsmJSAtomicBinopH
         masm.bind(&goahead);
     }
 
     // Add in the actual heap pointer explicitly, to avoid opening up
     // the abstraction that is atomicBinopToTypedIntArray at this time.
     uint32_t before = masm.size();
     masm.addl_wide(Imm32(0), ptrReg);
     uint32_t after = masm.size();
-    masm.append(AsmJSHeapAccess(before, after, maybeCmpOffset));
+    masm.append(AsmJSHeapAccess(before, after, mir->viewType(), maybeCmpOffset));
 
     Address memAddr(ptrReg, 0);
     if (value->isConstant()) {
         masm.atomicBinopToTypedIntArray(op, vt == Scalar::Uint32 ? Scalar::Int32 : vt,
                                         Imm32(ToInt32(value)),
                                         memAddr,
                                         temp,
                                         InvalidReg,
--- a/js/src/jit/x86/CodeGenerator-x86.h
+++ b/js/src/jit/x86/CodeGenerator-x86.h
@@ -24,27 +24,28 @@ class CodeGeneratorX86 : public CodeGene
     }
 
   protected:
     ValueOperand ToValue(LInstruction *ins, size_t pos);
     ValueOperand ToOutValue(LInstruction *ins);
     ValueOperand ToTempValue(LInstruction *ins, size_t pos);
 
     template<typename T>
-    bool loadAndNoteViewTypeElement(Scalar::Type vt, const T &srcAddr,
+    bool loadAndNoteViewTypeElement(AsmJSHeapAccess::ViewType vt, const T &srcAddr,
+                                    const LDefinition *out);
+    template<typename T>
+    void loadViewTypeElement(AsmJSHeapAccess::ViewType vt, const T &srcAddr,
                              const LDefinition *out);
     template<typename T>
-    void loadViewTypeElement(Scalar::Type vt, const T &srcAddr,
-                                       const LDefinition *out);
-    template<typename T>
-    void storeAndNoteViewTypeElement(Scalar::Type vt, const LAllocation *value,
+    void storeAndNoteViewTypeElement(AsmJSHeapAccess::ViewType vt, const LAllocation *value,
                                      const T &dstAddr);
     template<typename T>
-    void storeViewTypeElement(Scalar::Type vt, const LAllocation *value,
+    void storeViewTypeElement(AsmJSHeapAccess::ViewType vt, const LAllocation *value,
                               const T &dstAddr);
+
     void memoryBarrier(MemoryBarrierBits barrier);
 
   public:
     CodeGeneratorX86(MIRGenerator *gen, LIRGraph *graph, MacroAssembler *masm);
 
   public:
     bool visitBox(LBox *box);
     bool visitBoxFloatingPoint(LBoxFloatingPoint *box);
--- a/js/src/jit/x86/Lowering-x86.cpp
+++ b/js/src/jit/x86/Lowering-x86.cpp
@@ -238,44 +238,48 @@ LIRGeneratorX86::visitAsmJSStoreHeap(MAs
     LAsmJSStoreHeap *lir;
     MOZ_ASSERT(ptr->type() == MIRType_Int32);
 
     if (ptr->isConstant() && !ins->needsBoundsCheck()) {
         int32_t ptrValue = ptr->toConstant()->value().toInt32();
         MOZ_ASSERT(ptrValue >= 0);
         LAllocation ptrAlloc = LAllocation(ptr->toConstant()->vp());
         switch (ins->viewType()) {
-          case Scalar::Int8: case Scalar::Uint8:
+          case AsmJSHeapAccess::Int8: case AsmJSHeapAccess::Uint8:
             // See comment below.
             lir = new(alloc()) LAsmJSStoreHeap(ptrAlloc, useFixed(ins->value(), eax));
             break;
-          case Scalar::Int16: case Scalar::Uint16:
-          case Scalar::Int32: case Scalar::Uint32:
-          case Scalar::Float32: case Scalar::Float64:
+          case AsmJSHeapAccess::Int16: case AsmJSHeapAccess::Uint16:
+          case AsmJSHeapAccess::Int32: case AsmJSHeapAccess::Uint32:
+          case AsmJSHeapAccess::Float32: case AsmJSHeapAccess::Float64:
+          case AsmJSHeapAccess::Float32x4: case AsmJSHeapAccess::Int32x4:
             // See comment below.
             lir = new(alloc()) LAsmJSStoreHeap(ptrAlloc, useRegisterAtStart(ins->value()));
             break;
-          default: MOZ_CRASH("unexpected array type");
+          case AsmJSHeapAccess::Uint8Clamped:
+            MOZ_CRASH("unexpected array type");
         }
         return add(lir, ins);
     }
 
     switch (ins->viewType()) {
-      case Scalar::Int8: case Scalar::Uint8:
+      case AsmJSHeapAccess::Int8: case AsmJSHeapAccess::Uint8:
         // See comment for LIRGeneratorX86::useByteOpRegister.
         lir = new(alloc()) LAsmJSStoreHeap(useRegister(ins->ptr()), useFixed(ins->value(), eax));
         break;
-      case Scalar::Int16: case Scalar::Uint16:
-      case Scalar::Int32: case Scalar::Uint32:
-      case Scalar::Float32: case Scalar::Float64:
+      case AsmJSHeapAccess::Int16: case AsmJSHeapAccess::Uint16:
+      case AsmJSHeapAccess::Int32: case AsmJSHeapAccess::Uint32:
+      case AsmJSHeapAccess::Float32: case AsmJSHeapAccess::Float64:
+      case AsmJSHeapAccess::Float32x4: case AsmJSHeapAccess::Int32x4:
         // For now, don't allow constant values. The immediate operand
         // affects instruction layout which affects patching.
         lir = new(alloc()) LAsmJSStoreHeap(useRegisterAtStart(ptr), useRegisterAtStart(ins->value()));
         break;
-      default: MOZ_CRASH("unexpected array type");
+      case AsmJSHeapAccess::Uint8Clamped:
+        MOZ_CRASH("unexpected array type");
     }
 
     return add(lir, ins);
 }
 
 bool
 LIRGeneratorX86::visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins)
 {
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3224,17 +3224,19 @@ static const JSFunctionSpec array_method
 
     JS_SELF_HOSTED_FN("fill",        "ArrayFill",        3,0),
 
     JS_SELF_HOSTED_SYM_FN(iterator,  "ArrayValues",      0,0),
     JS_SELF_HOSTED_FN("entries",     "ArrayEntries",     0,0),
     JS_SELF_HOSTED_FN("keys",        "ArrayKeys",        0,0),
 
     /* ES7 additions */
+#ifdef NIGHTLY_BUILD
     JS_SELF_HOSTED_FN("includes",    "ArrayIncludes",    2,0),
+#endif
 
     JS_FS_END
 };
 
 static const JSFunctionSpec array_static_methods[] = {
     JS_FN("isArray",            array_isArray,      1,0),
     JS_SELF_HOSTED_FN("lastIndexOf", "ArrayStaticLastIndexOf", 2,0),
     JS_SELF_HOSTED_FN("indexOf",     "ArrayStaticIndexOf", 2,0),
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -560,18 +560,18 @@ js::math_log(JSContext *cx, unsigned arg
     if (!mathCache)
         return false;
 
     double z = math_log_impl(mathCache, x);
     args.rval().setNumber(z);
     return true;
 }
 
-static double
-max_double(double x, double y)
+double
+js::math_max_impl(double x, double y)
 {
     // Math.max(num, NaN) => NaN, Math.max(-0, +0) => +0
     if (x > y || IsNaN(x) || (x == y && IsNegative(y)))
         return x;
     return y;
 }
 
 bool
@@ -579,24 +579,24 @@ js::math_max(JSContext *cx, unsigned arg
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     double maxval = NegativeInfinity<double>();
     for (unsigned i = 0; i < args.length(); i++) {
         double x;
         if (!ToNumber(cx, args[i], &x))
             return false;
-        maxval = max_double(x, maxval);
+        maxval = math_max_impl(x, maxval);
     }
     args.rval().setNumber(maxval);
     return true;
 }
 
-static double
-min_double(double x, double y)
+double
+js::math_min_impl(double x, double y)
 {
     // Math.min(num, NaN) => NaN, Math.min(-0, +0) => -0
     if (x < y || IsNaN(x) || (x == y && IsNegativeZero(x)))
         return x;
     return y;
 }
 
 bool
@@ -604,36 +604,36 @@ js::math_min(JSContext *cx, unsigned arg
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     double minval = PositiveInfinity<double>();
     for (unsigned i = 0; i < args.length(); i++) {
         double x;
         if (!ToNumber(cx, args[i], &x))
             return false;
-        minval = min_double(x, minval);
+        minval = math_min_impl(x, minval);
     }
     args.rval().setNumber(minval);
     return true;
 }
 
 bool
 js::minmax_impl(JSContext *cx, bool max, HandleValue a, HandleValue b, MutableHandleValue res)
 {
     double x, y;
 
     if (!ToNumber(cx, a, &x))
         return false;
     if (!ToNumber(cx, b, &y))
         return false;
 
     if (max)
-        res.setNumber(max_double(x, y));
+        res.setNumber(math_max_impl(x, y));
     else
-        res.setNumber(min_double(x, y));
+        res.setNumber(math_min_impl(x, y));
 
     return true;
 }
 
 double
 js::powi(double x, int y)
 {
     unsigned n = (y < 0) ? -y : y;
--- a/js/src/jsmath.h
+++ b/js/src/jsmath.h
@@ -115,19 +115,25 @@ extern bool
 math_random(JSContext *cx, unsigned argc, js::Value *vp);
 
 extern bool
 math_abs_handle(JSContext *cx, js::HandleValue v, js::MutableHandleValue r);
 
 extern bool
 math_abs(JSContext *cx, unsigned argc, js::Value *vp);
 
+extern double
+math_max_impl(double x, double y);
+
 extern bool
 math_max(JSContext *cx, unsigned argc, js::Value *vp);
 
+extern double
+math_min_impl(double x, double y);
+
 extern bool
 math_min(JSContext *cx, unsigned argc, js::Value *vp);
 
 extern bool
 math_sqrt(JSContext *cx, unsigned argc, js::Value *vp);
 
 extern bool
 math_pow_handle(JSContext *cx, js::HandleValue base, js::HandleValue power,
--- a/js/src/shell/OSObject.cpp
+++ b/js/src/shell/OSObject.cpp
@@ -3,17 +3,28 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // OSObject.h - os object for exposing posix system calls in the JS shell
 
 #include "shell/OSObject.h"
 
+#include <errno.h>
 #include <stdlib.h>
+#ifdef XP_WIN
+#include <process.h>
+#include <string.h>
+#else
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+
+// For JSFunctionSpecWithHelp
+#include "jsfriendapi.h"
 
 using namespace JS;
 
 static bool
 os_getenv(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() < 1) {
@@ -33,21 +44,251 @@ os_getenv(JSContext* cx, unsigned argc, 
             return false;
         args.rval().setString(value);
     } else {
         args.rval().setUndefined();
     }
     return true;
 }
 
-static const JSFunctionSpec os_functions[] = {
-    JS_FS("getenv", os_getenv, 1, 0),
-    JS_FS_END
+static bool
+os_getpid(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (args.length() != 0) {
+        JS_ReportError(cx, "os.getpid takes no arguments");
+        return false;
+    }
+    args.rval().setInt32(getpid());
+    return true;
+}
+
+// There are two possible definitions of strerror_r floating around. The GNU
+// one returns a char* which may or may not be the buffer you passed in. The
+// other one returns an integer status code, and always writes the result into
+// the provided buffer.
+
+static inline char *
+strerror_message(int result, char *buffer)
+{
+    return result == 0 ? buffer : nullptr;
+}
+
+static inline char *
+strerror_message(char *result, char *buffer)
+{
+    return result;
+}
+
+static void
+ReportSysError(JSContext *cx, const char *prefix)
+{
+    static char buffer[200];
+#if defined(XP_WIN)
+    strerror_s(buffer, sizeof(buffer), errno);
+    const char *errstr = buffer;
+#else
+    const char *errstr = strerror_message(strerror_r(errno, buffer, sizeof(buffer)), buffer);
+#endif
+
+    if (!errstr)
+        errstr = "unknown error";
+
+    size_t nbytes = strlen(prefix) + strlen(errstr) + 3;
+    char *final = (char*) js_malloc(nbytes);
+    if (!final) {
+        JS_ReportOutOfMemory(cx);
+        return;
+    }
+
+#ifdef XP_WIN
+    _snprintf(final, nbytes, "%s: %s", prefix, errstr);
+#else
+    snprintf(final, nbytes, "%s: %s", prefix, errstr);
+#endif
+    JS_ReportError(cx, final);
+    js_free(final);
+}
+
+static bool
+os_system(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() == 0) {
+        JS_ReportError(cx, "os.system requires 1 argument");
+        return false;
+    }
+
+    JSString *str = JS::ToString(cx, args[0]);
+    if (!str)
+        return false;
+
+    JSAutoByteString command(cx, str);
+    if (!command)
+        return false;
+
+    int result = system(command.ptr());
+    if (result == -1) {
+        ReportSysError(cx, "system call failed");
+        return false;
+    }
+
+    args.rval().setInt32(result);
+    return true;
+}
+
+#ifndef XP_WIN
+static bool
+os_spawn(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() == 0) {
+        JS_ReportError(cx, "os.spawn requires 1 argument");
+        return false;
+    }
+
+    JSString *str = JS::ToString(cx, args[0]);
+    if (!str)
+        return false;
+
+    JSAutoByteString command(cx, str);
+    if (!command)
+        return false;
+
+    int32_t childPid = fork();
+    if (childPid) {
+        args.rval().setInt32(childPid);
+        return true;
+    }
+
+    if (childPid == -1) {
+        ReportSysError(cx, "fork failed");
+        return false;
+    }
+
+    // We are in the child
+
+    const char *cmd[] = {"sh", "-c", nullptr, nullptr};
+    cmd[2] = command.ptr();
+
+    execvp("sh", (char * const*)cmd);
+    exit(1);
+}
+
+static bool
+os_kill(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    int32_t pid;
+    if (args.length() < 1) {
+        JS_ReportError(cx, "os.kill requires 1 argument");
+        return false;
+    }
+    if (!JS::ToInt32(cx, args[0], &pid))
+        return false;
+
+    // It is too easy to kill yourself accidentally with os.kill("goose").
+    if (pid == 0 && !args[0].isInt32()) {
+        JS_ReportError(cx, "os.kill requires numeric pid");
+        return false;
+    }
+
+    int signal = SIGINT;
+    if (args.length() > 1) {
+        if (!JS::ToInt32(cx, args[1], &signal))
+            return false;
+    }
+
+    int status = kill(pid, signal);
+    if (status == -1)
+        ReportSysError(cx, "kill failed");
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
+os_waitpid(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    int32_t pid;
+    if (args.length() == 0) {
+        pid = -1;
+    } else {
+        if (!JS::ToInt32(cx, args[0], &pid))
+            return false;
+    }
+
+    bool nohang = false;
+    if (args.length() >= 2)
+        nohang = JS::ToBoolean(args[1]);
+
+    int status;
+    pid_t result = waitpid(pid, &status, nohang ? WNOHANG : 0);
+    if (result == -1) {
+        ReportSysError(cx, "os.waitpid failed");
+        return false;
+    }
+
+    RootedObject info(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
+    if (!info)
+        return false;
+
+    RootedValue v(cx);
+    if (result != 0) {
+        v.setInt32(result);
+        if (!JS_DefineProperty(cx, info, "pid", v, JSPROP_ENUMERATE))
+            return false;
+    }
+    if (WIFEXITED(status)) {
+        v.setInt32(WEXITSTATUS(status));
+        if (!JS_DefineProperty(cx, info, "exitStatus", v, JSPROP_ENUMERATE))
+            return false;
+    }
+
+    args.rval().setObject(*info);
+    return true;
+}
+#endif
+
+static const JSFunctionSpecWithHelp os_functions[] = {
+    JS_FN_HELP("getenv", os_getenv, 1, 0,
+"getenv(variable)",
+"  Get the value of an environment variable."),
+
+    JS_FN_HELP("getpid", os_getpid, 0, 0,
+"getpid()",
+"  Return the current process id."),
+
+    JS_FN_HELP("system", os_system, 1, 0,
+"system(command)",
+"  Execute command on the current host, returning result code or throwing an\n"
+"  exception on failure."),
+
+#ifndef XP_WIN
+    JS_FN_HELP("spawn", os_spawn, 1, 0,
+"spawn(command)",
+"  Start up a separate process running the given command. Returns the pid."),
+
+    JS_FN_HELP("kill", os_kill, 1, 0,
+"kill(pid[, signal])",
+"  Send a signal to the given pid. The default signal is SIGINT. The signal\n"
+"  passed in must be numeric, if given."),
+
+    JS_FN_HELP("waitpid", os_waitpid, 1, 0,
+"waitpid(pid[, nohang])",
+"  Calls waitpid(). 'nohang' is a boolean indicating whether to pass WNOHANG.\n"
+"  The return value is an object containing a 'pid' field, if a process was waitable\n"
+"  and an 'exitStatus' field if a pid exited."),
+#endif
+    JS_FS_HELP_END
 };
 
 bool
 js::DefineOS(JSContext *cx, HandleObject global)
 {
     RootedObject obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
     return obj &&
-           JS_DefineFunctions(cx, obj, os_functions) &&
+           JS_DefineFunctionsWithHelp(cx, obj, os_functions) &&
            JS_DefineProperty(cx, global, "os", obj, 0);
 }
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3570,40 +3570,16 @@ RedirectOutput(JSContext *cx, unsigned a
         if (!redirect(cx, stderr, stderrPath))
             return false;
     }
 
     args.rval().setUndefined();
     return true;
 }
 
-static bool
-System(JSContext *cx, unsigned argc, jsval *vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    if (args.length() == 0) {
-        JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
-                             "system");
-        return false;
-    }
-
-    JSString *str = JS::ToString(cx, args[0]);
-    if (!str)
-        return false;
-
-    JSAutoByteString command(cx, str);
-    if (!command)
-        return false;
-
-    int result = system(command.ptr());
-    args.rval().setInt32(result);
-    return true;
-}
-
 static int sArgc;
 static char **sArgv;
 
 class AutoCStringVector
 {
     Vector<char *> argv_;
   public:
     explicit AutoCStringVector(JSContext *cx) : argv_(cx) {}
@@ -4560,20 +4536,16 @@ static const JSFunctionSpecWithHelp fuzz
 "pc2line(fun[, pc])",
 "  Map PC to line number."),
 
     JS_FN_HELP("redirect", RedirectOutput, 2, 0,
 "redirect(stdoutFilename[, stderrFilename])",
 "  Redirect stdout and/or stderr to the named file. Pass undefined to avoid\n"
 "   redirecting. Filenames are relative to the current working directory."),
 
-    JS_FN_HELP("system", System, 1, 0,
-"system(command)",
-"  Execute command on the current host, returning result code."),
-
     JS_FN_HELP("nestedShell", NestedShell, 0, 0,
 "nestedShell(shellArgs...)",
 "  Execute the given code in a new JS shell process, passing this nested shell\n"
 "  the arguments passed to nestedShell. argv[0] of the nested shell will be argv[0]\n"
 "  of the current shell (which is assumed to be the actual path to the shell.\n"
 "  arguments[0] (of the call to nestedShell) will be argv[1], arguments[1] will\n"
 "  be argv[2], etc."),
 
rename from js/src/tests/ecma_6/TypedObject/simd/float32x4max.js
rename to js/src/tests/ecma_6/TypedObject/simd/float32x4-minmax.js
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4max.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/float32x4-minmax.js
@@ -1,35 +1,54 @@
 // |reftest| skip-if(!this.hasOwnProperty("SIMD"))
 var BUGNUMBER = 946042;
+
 var float32x4 = SIMD.float32x4;
-var int32x4 = SIMD.int32x4;
+
+function testMaxFloat32(v, w) {
+    return testBinaryFunc(v, w, float32x4.max, (x, y) => Math.fround(Math.max(x, y)));
+}
+function testMinFloat32(v, w) {
+    return testBinaryFunc(v, w, float32x4.min, (x, y) => Math.fround(Math.min(x, y)));
+}
 
-var summary = 'float32x4 max';
+function maxNum(x, y) {
+    if (x != x)
+        return y;
+    if (y != y)
+        return x;
+    return Math.max(x, y);
+}
+
+function minNum(x, y) {
+    if (x != x)
+        return y;
+    if (y != y)
+        return x;
+    return Math.min(x, y);
+}
+
+function testMaxNumFloat32(v, w) {
+    return testBinaryFunc(v, w, float32x4.maxNum, maxNum);
+}
+function testMinNumFloat32(v, w) {
+    return testBinaryFunc(v, w, float32x4.minNum, minNum);
+}
 
 function test() {
   print(BUGNUMBER + ": " + summary);
 
-  // FIXME -- Bug 1081697: Amend to check for correctness of -0/Infinity/-Infinity border cases.
-  // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined.
-
-  var a = float32x4(1, 20, 30, 4);
-  var b = float32x4(10, 2, 3, 40);
-  var c = SIMD.float32x4.max(a, b);
-  assertEq(c.x, 10);
-  assertEq(c.y, 20);
-  assertEq(c.z, 30);
-  assertEq(c.w, 40);
-
-  var d = float32x4(9.999, 2.1234, 30.4443, 4);
-  var e = float32x4(10, 2.1233, 30.4444, 4.0001);
-  var f = float32x4.max(d, e);
-  assertEq(f.x, 10);
-  assertEq(f.y, Math.fround(2.1234));
-  assertEq(f.z, Math.fround(30.4444));
-  assertEq(f.w, Math.fround(4.0001));
+  for ([v, w] of [[float32x4(1, 20, 30, 4), float32x4(10, 2, 3, 40)],
+                  [float32x4(9.999, 2.1234, 30.4443, 4), float32x4(10, 2.1233, 30.4444, 4.0001)],
+                  [float32x4(NaN, -Infinity, +Infinity, -0), float32x4(13.37, 42.42, NaN, 0)]])
+  {
+      testMinFloat32(v, w);
+      testMaxFloat32(v, w);
+      testMinNumFloat32(v, w);
+      testMaxNumFloat32(v, w);
+  }
 
   if (typeof reportCompare === "function")
     reportCompare(true, true);
 }
 
 test();
 
deleted file mode 100644
--- a/js/src/tests/ecma_6/TypedObject/simd/float32x4min.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
-var BUGNUMBER = 946042;
-var float32x4 = SIMD.float32x4;
-var int32x4 = SIMD.int32x4;
-
-var summary = 'float32x4 min';
-
-function test() {
-  print(BUGNUMBER + ": " + summary);
-
-  // FIXME -- Bug 1081697: Amend to check for correctness of -0/Infinity/-Infinity border cases.
-  // FIXME -- Bug 1068028: Amend to check for correctness of NaN border cases once the semantics are defined.
-
-  var a = float32x4(1, 20, 3, 40);
-  var b = float32x4(10, 2, 30, 4);
-  var c = SIMD.float32x4.min(a, b);
-  assertEq(c.x, 1);
-  assertEq(c.y, 2);
-  assertEq(c.z, 3);
-  assertEq(c.w, 4);
-
-  var d = float32x4(1.4321, 20.5567, 30.8999, 4.0002);
-  var e = float32x4(1.432, 20.5568, 30.8998, 4.0001);
-  var f = float32x4.min(d, e);
-  assertEq(f.x, Math.fround(1.432));
-  assertEq(f.y, Math.fround(20.5567));
-  assertEq(f.z, Math.fround(30.8998));
-  assertEq(f.w, Math.fround(4.0001));
-
-  if (typeof reportCompare === "function")
-    reportCompare(true, true);
-}
-
-test();
-
--- a/js/src/tests/ecma_6/TypedObject/simd/int32x4select.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/int32x4select.js
@@ -1,17 +1,14 @@
 // |reftest| skip-if(!this.hasOwnProperty("SIMD"))
 var BUGNUMBER = 1060437;
 var int32x4 = SIMD.int32x4;
 
 var summary = 'int32x4 select';
 
-const INT32_MAX = Math.pow(2, 31) - 1;
-const INT32_MIN = INT32_MAX + 1 | 0;
-
 function test() {
   print(BUGNUMBER + ": " + summary);
 
   var a = int32x4(0,4,9,16)
   var b = int32x4(1,2,3,4)
   var sel_ttff = int32x4.bool(true, true, false, false);
   var c = SIMD.int32x4.select(sel_ttff,a,b);
   assertEq(c.x, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedObject/simd/load.js
@@ -0,0 +1,154 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Our float32array will have 16 elements
+const SIZE_ARRAY = 16;
+
+// 1 float32 == 4 bytes
+const SIZE_BYTES = SIZE_ARRAY * 4;
+
+function MakeComparator(kind, arr) {
+    var bpe = arr.BYTES_PER_ELEMENT;
+    var uint8 = (bpe != 1) ? new Uint8Array(arr.buffer) : arr;
+
+    // Size in bytes of a single element in the SIMD vector.
+    var sizeOfLaneElem;
+    // Typed array constructor corresponding to the SIMD kind.
+    var typedArrayCtor;
+    switch (kind) {
+      case 'int32x4':
+        sizeOfLaneElem = 4;
+        typedArrayCtor = Int32Array;
+        break;
+      case 'float32x4':
+        sizeOfLaneElem = 4;
+        typedArrayCtor = Float32Array;
+        break;
+      default:
+        assertEq(true, false, "unknown SIMD kind");
+    }
+
+    // Reads (numElemToRead * sizeOfLaneElem) bytes in arr, and reinterprets
+    // these bytes as a typed array equivalent to the typed SIMD vector.
+    var slice = function(start, numElemToRead) {
+        // Read enough bytes
+        var startBytes = start * bpe;
+        var endBytes = startBytes + numElemToRead * sizeOfLaneElem;
+        var asArray = Array.prototype.slice.call(uint8, startBytes, endBytes);
+
+        // If length is less than SIZE_BYTES bytes, fill with 0.
+        // This is needed for loadX, loadXY, loadXYZ which do only partial
+        // reads.
+        for (var i = asArray.length; i < SIZE_BYTES; i++) asArray[i] = 0;
+        assertEq(asArray.length, SIZE_BYTES);
+
+        return new typedArrayCtor(new Uint8Array(asArray).buffer);
+    }
+
+    return {
+        loadX: function(index) {
+            var v = SIMD[kind].loadX(arr, index);
+            assertEqX4(v, slice(index, 1));
+        },
+
+        loadXY: function(index) {
+            var v = SIMD[kind].loadXY(arr, index);
+            assertEqX4(v, slice(index, 2));
+        },
+
+        loadXYZ: function(index) {
+            var v = SIMD[kind].loadXYZ(arr, index);
+            assertEqX4(v, slice(index, 3));
+        },
+
+        load: function(index) {
+            var v = SIMD[kind].load(arr, index);
+            assertEqX4(v, slice(index, 4));
+        }
+    }
+}
+
+function testLoad(kind, TA) {
+    for (var i = SIZE_ARRAY; i--;)
+        TA[i] = i;
+
+    for (var ta of [
+                    new Uint8Array(TA.buffer),
+                    new Int8Array(TA.buffer),
+                    new Uint16Array(TA.buffer),
+                    new Int16Array(TA.buffer),
+                    new Uint32Array(TA.buffer),
+                    new Int32Array(TA.buffer),
+                    new Float32Array(TA.buffer),
+                    new Float64Array(TA.buffer)
+                   ])
+    {
+        // Invalid args
+        assertThrowsInstanceOf(() => SIMD[kind].load(), TypeError);
+        assertThrowsInstanceOf(() => SIMD[kind].load(ta), TypeError);
+        assertThrowsInstanceOf(() => SIMD[kind].load("hello", 0), TypeError);
+        assertThrowsInstanceOf(() => SIMD[kind].load(ta, -1), TypeError);
+
+        // Valid and invalid reads
+        var C = MakeComparator(kind, ta);
+        var bpe = ta.BYTES_PER_ELEMENT;
+
+        var lastValidArgLoadX   = (SIZE_BYTES - 4)  / bpe | 0;
+        var lastValidArgLoadXY  = (SIZE_BYTES - 8)  / bpe | 0;
+        var lastValidArgLoadXYZ = (SIZE_BYTES - 12) / bpe | 0;
+        var lastValidArgLoad    = (SIZE_BYTES - 16) / bpe | 0;
+
+        C.load(0);
+        C.load(1);
+        C.load(2);
+        C.load(3);
+        C.load(lastValidArgLoad);
+        assertThrowsInstanceOf(() => SIMD[kind].load(ta, lastValidArgLoad + 1), TypeError);
+
+        C.loadX(0);
+        C.loadX(1);
+        C.loadX(2);
+        C.loadX(3);
+        C.loadX(lastValidArgLoadX);
+        assertThrowsInstanceOf(() => SIMD[kind].loadX(ta, lastValidArgLoadX + 1), TypeError);
+
+        C.loadXY(0);
+        C.loadXY(1);
+        C.loadXY(2);
+        C.loadXY(3);
+        C.loadXY(lastValidArgLoadXY);
+        assertThrowsInstanceOf(() => SIMD[kind].loadXY(ta, lastValidArgLoadXY + 1), TypeError);
+
+        C.loadXYZ(0);
+        C.loadXYZ(1);
+        C.loadXYZ(2);
+        C.loadXYZ(3);
+        C.loadXYZ(lastValidArgLoadXYZ);
+        assertThrowsInstanceOf(() => SIMD[kind].loadXYZ(ta, lastValidArgLoadXYZ + 1), TypeError);
+    }
+
+    // Test ToInt32 behavior
+    var v = SIMD[kind].load(TA, 12.5);
+    assertEqX4(v, [12, 13, 14, 15]);
+
+    var obj = {
+        valueOf: function() { return 12 }
+    }
+    var v = SIMD[kind].load(TA, obj);
+    assertEqX4(v, [12, 13, 14, 15]);
+
+    var obj = {
+        valueOf: function() { throw new TypeError("i ain't a number"); }
+    }
+    assertThrowsInstanceOf(() => SIMD[kind].load(TA, obj), TypeError);
+}
+
+testLoad('float32x4', new Float32Array(SIZE_ARRAY));
+testLoad('int32x4', new Int32Array(SIZE_ARRAY));
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/tests/ecma_6/TypedObject/simd/shell.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/shell.js
@@ -0,0 +1,30 @@
+function assertEqX4(v, arr) {
+    try {
+        assertEq(v.x, arr[0]);
+        assertEq(v.y, arr[1]);
+        assertEq(v.z, arr[2]);
+        assertEq(v.w, arr[3]);
+    } catch (e) {
+        print("stack trace:", e.stack);
+        throw e;
+    }
+}
+
+function simdToArray(v) {
+    return [v.x, v.y, v.z, v.w];
+}
+
+const INT32_MAX = Math.pow(2, 31) - 1;
+const INT32_MIN = -Math.pow(2, 31);
+assertEq(INT32_MAX + 1 | 0, INT32_MIN);
+
+function testBinaryFunc(v, w, simdFunc, func) {
+    var varr = simdToArray(v);
+    var warr = simdToArray(w);
+
+    var observed = simdToArray(simdFunc(v, w));
+    var expected = varr.map(function(v, i) { return func(varr[i], warr[i]); });
+
+    for (var i = 0; i < observed.length; i++)
+        assertEq(observed[i], expected[i]);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedObject/simd/store.js
@@ -0,0 +1,85 @@
+// |reftest| skip-if(!this.hasOwnProperty("SIMD"))
+
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// As SIMD.*.store is entirely symmetric to SIMD.*.load, this file just
+// contains basic tests to store on one single TypedArray kind, while load is
+// exhaustively tested. See load.js for more details.
+
+const POISON = 42;
+
+function reset(ta) {
+    for (var i = 0; i < ta.length; i++)
+        ta[i] = POISON + i;
+}
+
+function assertChanged(ta, from, expected) {
+    var i = 0;
+    for (; i < from; i++)
+        assertEq(ta[i], POISON + i);
+    for (; i < from + expected.length; i++)
+        assertEq(ta[i], expected[i - from]);
+    for (; i < ta.length; i++)
+        assertEq(ta[i], POISON + i);
+}
+
+function testStore(ta, kind, i, v) {
+    reset(ta);
+    SIMD[kind].storeX(ta, i, v);
+    assertChanged(ta, i, [v.x]);
+
+    reset(ta);
+    SIMD[kind].storeXY(ta, i, v);
+    assertChanged(ta, i, [v.x, v.y]);
+
+    reset(ta);
+    SIMD[kind].storeXYZ(ta, i, v);
+    assertChanged(ta, i, [v.x, v.y, v.z]);
+
+    reset(ta);
+    SIMD[kind].store(ta, i, v);
+    assertChanged(ta, i, [v.x, v.y, v.z, v.w]);
+}
+
+function testStoreInt32x4() {
+    var I32 = new Int32Array(16);
+
+    var v = SIMD.int32x4(0, 1, Math.pow(2,31) - 1, -Math.pow(2, 31));
+    testStore(I32, 'int32x4', 0, v);
+    testStore(I32, 'int32x4', 1, v);
+    testStore(I32, 'int32x4', 2, v);
+    testStore(I32, 'int32x4', 12, v);
+
+    assertThrowsInstanceOf(() => SIMD.int32x4.store(I32), TypeError);
+    assertThrowsInstanceOf(() => SIMD.int32x4.store(I32, 0), TypeError);
+    assertThrowsInstanceOf(() => SIMD.float32x4.store(I32, 0, v), TypeError);
+}
+
+function testStoreFloat32x4() {
+    var F32 = new Float32Array(16);
+
+    var v = SIMD.float32x4(1,2,3,4);
+    testStore(F32, 'float32x4', 0, v);
+    testStore(F32, 'float32x4', 1, v);
+    testStore(F32, 'float32x4', 2, v);
+    testStore(F32, 'float32x4', 12, v);
+
+    var v = SIMD.float32x4(NaN, -0, -Infinity, 5e-324);
+    testStore(F32, 'float32x4', 0, v);
+    testStore(F32, 'float32x4', 1, v);
+    testStore(F32, 'float32x4', 2, v);
+    testStore(F32, 'float32x4', 12, v);
+
+    assertThrowsInstanceOf(() => SIMD.float32x4.store(F32), TypeError);
+    assertThrowsInstanceOf(() => SIMD.float32x4.store(F32, 0), TypeError);
+    assertThrowsInstanceOf(() => SIMD.int32x4.store(F32, 0, v), TypeError);
+}
+
+testStoreInt32x4();
+testStoreFloat32x4();
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/tests/ecma_6/TypedObject/simd/swizzle-shuffle.js
+++ b/js/src/tests/ecma_6/TypedObject/simd/swizzle-shuffle.js
@@ -3,27 +3,16 @@
 /*
  * Any copyright is dedicated to the Public Domain.
  * https://creativecommons.org/publicdomain/zero/1.0/
  */
 
 var float32x4 = SIMD.float32x4;
 var int32x4 = SIMD.int32x4;
 
-function assertEq4(v, arr) {
-    assertEq(v.x, arr[0]);
-    assertEq(v.y, arr[1]);
-    assertEq(v.z, arr[2]);
-    assertEq(v.w, arr[3]);
-}
-
-function simdToArray(v) {
-    return [v.x, v.y, v.z, v.w];
-}
-
 function swizzle(arr, x, y, z, w) {
     return [arr[x], arr[y], arr[z], arr[w]];
 }
 
 function testSwizzleForType(type) {
     var v = type(1,2,3,4);
 
     assertThrowsInstanceOf(() => type.swizzle()               , TypeError);
@@ -33,26 +22,26 @@ function testSwizzleForType(type) {
     assertThrowsInstanceOf(() => type.swizzle(v, 0, 1, 2, 4)  , TypeError);
     assertThrowsInstanceOf(() => type.swizzle(v, 0, 1, 2, -1) , TypeError);
     assertThrowsInstanceOf(() => type.swizzle(0, 1, 2, 3, v)  , TypeError);
 
     // Test all possible swizzles.
     var x, y, z, w;
     for (var i = 0; i < Math.pow(4, 4); i++) {
         [x, y, z, w] = [i & 3, (i >> 2) & 3, (i >> 4) & 3, (i >> 6) & 3];
-        assertEq4(type.swizzle(v, x, y, z, w), swizzle(simdToArray(v), x, y, z, w));
+        assertEqX4(type.swizzle(v, x, y, z, w), swizzle(simdToArray(v), x, y, z, w));
     }
 
     // Test that the lane inputs are converted into an int32.
     // n.b, order of evaluation of args is left-to-right.
     var obj = {
         x: 0,
         valueOf: function() { return this.x++ }
     };
-    assertEq4(type.swizzle(v, obj, obj, obj, obj), swizzle(simdToArray(v), 0, 1, 2, 3));
+    assertEqX4(type.swizzle(v, obj, obj, obj, obj), swizzle(simdToArray(v), 0, 1, 2, 3));
 
     // Object for which ToInt32 will fail.
     obj = {
         valueOf: function() { throw new Error; }
     };
     assertThrowsInstanceOf(() => type.swizzle(v, 0, 1, 2, obj), Error);
 }
 
@@ -95,28 +84,28 @@ function testShuffleForType(type) {
     assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 1, 2, -1) , TypeError);
     assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 1, 2, 8)  , TypeError);
     assertThrowsInstanceOf(() => type.shuffle(lhs, 0, 1, 2, 7, rhs)  , TypeError);
 
     // Test all possible shuffles.
     var x, y, z, w;
     for (var i = 0; i < Math.pow(8, 4); i++) {
         [x, y, z, w] = [i & 7, (i >> 3) & 7, (i >> 6) & 7, (i >> 9) & 7];
-        assertEq4(type.shuffle(lhs, rhs, x, y, z, w),
-                  shuffle(simdToArray(lhs), simdToArray(rhs), x, y, z, w));
+        assertEqX4(type.shuffle(lhs, rhs, x, y, z, w),
+                   shuffle(simdToArray(lhs), simdToArray(rhs), x, y, z, w));
     }
 
     // Test that the lane inputs are converted into an int32.
     // n.b, order of evaluation of args is left-to-right.
     var obj = {
         x: 0,
         valueOf: function() { return this.x++ }
     };
-    assertEq4(type.shuffle(lhs, rhs, obj, obj, obj, obj),
-              shuffle(simdToArray(lhs),simdToArray(rhs), 0, 1, 2, 3));
+    assertEqX4(type.shuffle(lhs, rhs, obj, obj, obj, obj),
+               shuffle(simdToArray(lhs),simdToArray(rhs), 0, 1, 2, 3));
 
     // Object for which ToInt32 will fail.
     obj = {
         valueOf: function() { throw new Error; }
     };
     assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 1, 2, obj), Error);
 }
 
--- a/js/src/tests/ecma_7/Array/includes.js
+++ b/js/src/tests/ecma_7/Array/includes.js
@@ -3,50 +3,52 @@
  * http://creativecommons.org/licenses/publicdomain/
  */
 
 var BUGNUMBER = 1069063;
 var summary = "Implement Array.prototype.includes";
 
 print(BUGNUMBER + ": " + summary);
 
-assertEq(typeof [].includes, "function");
-assertEq([].includes.length, 1);
+if ('includes' in []) {
+    assertEq(typeof [].includes, "function");
+    assertEq([].includes.length, 1);
 
-assertTrue([1, 2, 3].includes(2));
-assertTrue([1,,2].includes(2));
-assertTrue([1, 2, 3].includes(2, 1));
-assertTrue([1, 2, 3].includes(2, -2));
-assertTrue([1, 2, 3].includes(2, -100));
-assertTrue([Object, Function, Array].includes(Function));
-assertTrue([-0].includes(0));
-assertTrue([NaN].includes(NaN));
-assertTrue([,].includes());
-assertTrue(staticIncludes("123", "2"));
-assertTrue(staticIncludes({length: 3, 1: 2}, 2));
-assertTrue(staticIncludes({length: 3, 1: 2, get 3(){throw ""}}, 2));
-assertTrue(staticIncludes({length: 3, get 1() {return 2}}, 2));
-assertTrue(staticIncludes({__proto__: {1: 2}, length: 3}, 2));
-assertTrue(staticIncludes(new Proxy([1], {get(){return 2}}), 2));
+    assertTrue([1, 2, 3].includes(2));
+    assertTrue([1,,2].includes(2));
+    assertTrue([1, 2, 3].includes(2, 1));
+    assertTrue([1, 2, 3].includes(2, -2));
+    assertTrue([1, 2, 3].includes(2, -100));
+    assertTrue([Object, Function, Array].includes(Function));
+    assertTrue([-0].includes(0));
+    assertTrue([NaN].includes(NaN));
+    assertTrue([,].includes());
+    assertTrue(staticIncludes("123", "2"));
+    assertTrue(staticIncludes({length: 3, 1: 2}, 2));
+    assertTrue(staticIncludes({length: 3, 1: 2, get 3(){throw ""}}, 2));
+    assertTrue(staticIncludes({length: 3, get 1() {return 2}}, 2));
+    assertTrue(staticIncludes({__proto__: {1: 2}, length: 3}, 2));
+    assertTrue(staticIncludes(new Proxy([1], {get(){return 2}}), 2));
 
-assertFalse([1, 2, 3].includes("2"));
-assertFalse([1, 2, 3].includes(2, 2));
-assertFalse([1, 2, 3].includes(2, -1));
-assertFalse([undefined].includes(NaN));
-assertFalse([{}].includes({}));
-assertFalse(staticIncludes({length: 3, 1: 2}, 2, 2));
-assertFalse(staticIncludes({length: 3, get 0(){delete this[1]}, 1: 2}, 2));
-assertFalse(staticIncludes({length: -100, 0: 1}, 1));
+    assertFalse([1, 2, 3].includes("2"));
+    assertFalse([1, 2, 3].includes(2, 2));
+    assertFalse([1, 2, 3].includes(2, -1));
+    assertFalse([undefined].includes(NaN));
+    assertFalse([{}].includes({}));
+    assertFalse(staticIncludes({length: 3, 1: 2}, 2, 2));
+    assertFalse(staticIncludes({length: 3, get 0(){delete this[1]}, 1: 2}, 2));
+    assertFalse(staticIncludes({length: -100, 0: 1}, 1));
 
-assertThrowsInstanceOf(() => staticIncludes(), TypeError);
-assertThrowsInstanceOf(() => staticIncludes(null), TypeError);
-assertThrowsInstanceOf(() => staticIncludes({get length(){throw TypeError()}}), TypeError);
-assertThrowsInstanceOf(() => staticIncludes({length: 3, get 1() {throw TypeError()}}, 2), TypeError);
-assertThrowsInstanceOf(() => staticIncludes({__proto__: {get 1() {throw TypeError()}}, length: 3}, 2), TypeError);
-assertThrowsInstanceOf(() => staticIncludes(new Proxy([1], {get(){throw TypeError()}})), TypeError);
+    assertThrowsInstanceOf(() => staticIncludes(), TypeError);
+    assertThrowsInstanceOf(() => staticIncludes(null), TypeError);
+    assertThrowsInstanceOf(() => staticIncludes({get length(){throw TypeError()}}), TypeError);
+    assertThrowsInstanceOf(() => staticIncludes({length: 3, get 1() {throw TypeError()}}, 2), TypeError);
+    assertThrowsInstanceOf(() => staticIncludes({__proto__: {get 1() {throw TypeError()}}, length: 3}, 2), TypeError);
+    assertThrowsInstanceOf(() => staticIncludes(new Proxy([1], {get(){throw TypeError()}})), TypeError);
+}
 
 function assertTrue(v) {
     assertEq(v, true);
 }
 
 function assertFalse(v) {
     assertEq(v, false);
 }
new file mode 100644
--- /dev/null
+++ b/js/src/tests/shell/README
@@ -0,0 +1,1 @@
+Tests for JS shell-only functions
new file mode 100644
--- /dev/null
+++ b/js/src/tests/shell/os.js
@@ -0,0 +1,39 @@
+// |reftest| skip-if(!xulRuntime.shell||xulRuntime.OS=="WINNT")
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var pid = os.getpid();
+assertEq(pid > 0, true);
+
+var PATH = os.getenv("PATH");
+assertEq(PATH.indexOf("bin") > 0, true);
+assertEq(os.getenv("SQUAMMISH_HILLBILLY_GOAT_SQUEEZERS"), undefined);
+
+assertEq(os.system("true"), 0, "/bin/true should exit 0");
+assertEq(os.system("false") != 0, true, "/bin/false should exit nonzero");
+
+var kidpid = os.spawn("sleep 3600");
+assertEq(kidpid > 0, true, "spawning sleep");
+var info = os.waitpid(kidpid, true);
+assertEq(info.hasOwnProperty("pid"), false);
+assertEq(info.hasOwnProperty("exitStatus"), false);
+
+os.kill(kidpid);
+
+info = os.waitpid(kidpid);
+assertEq(info.hasOwnProperty("pid"), true, "waiting on dead process should return pid");
+assertEq(info.pid, kidpid);
+assertEq(info.hasOwnProperty("exitStatus"), false, "killed process should not have exitStatus");
+
+kidpid = os.spawn("false");
+assertEq(kidpid > 0, true, "spawning /bin/false");
+info = os.waitpid(kidpid);
+assertEq(info.hasOwnProperty("pid"), true, "waiting on dead process should return pid");
+assertEq(info.pid, kidpid);
+assertEq(info.hasOwnProperty("exitStatus"), true, "process should have exitStatus");
+assertEq(info.exitStatus, 1, "/bin/false should exit 1");
+
+reportCompare(true, true);
new file mode 100644
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -164,18 +164,19 @@ https://bugzilla.mozilla.org/show_bug.cg
                                       the JS engine filters it out of getOwnPropertyNames */
     ["constructor", "toSource", "toString", "toLocaleString", "valueOf", "watch",
      "unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable",
      "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__"];
   gPrototypeProperties['Array'] =
     ["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push",
       "pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf",
       "forEach", "map", "reduce", "reduceRight", "filter", "some", "every", "find",
-      "findIndex", "copyWithin", "fill", "includes", kIteratorSymbol, "entries", "keys", "constructor"];
+      "findIndex", "copyWithin", "fill", kIteratorSymbol, "entries", "keys", "constructor"];
   if (isNightlyBuild) {
+    gPrototypeProperties['Array'].push('includes');
     let pjsMethods = ['mapPar', 'reducePar', 'scanPar', 'scatterPar', 'filterPar'];
     gPrototypeProperties['Array'] = gPrototypeProperties['Array'].concat(pjsMethods);
   }
   for (var c of typedArrayClasses) {
     gPrototypeProperties[c] = ["constructor", "BYTES_PER_ELEMENT"];
   }
   gPrototypeProperties['TypedArray'] =
     ["length", "buffer", "byteLength", "byteOffset", kIteratorSymbol, "subarray",
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -5655,16 +5655,40 @@ nsDisplaySVGEffects::nsDisplaySVGEffects
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplaySVGEffects::~nsDisplaySVGEffects()
 {
   MOZ_COUNT_DTOR(nsDisplaySVGEffects);
 }
 #endif
 
+nsDisplayVR::nsDisplayVR(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+                         nsDisplayList* aList, mozilla::gfx::VRHMDInfo* aHMD)
+  : nsDisplayOwnLayer(aBuilder, aFrame, aList)
+  , mHMD(aHMD)
+{
+}
+
+already_AddRefed<Layer>
+nsDisplayVR::BuildLayer(nsDisplayListBuilder* aBuilder,
+                        LayerManager* aManager,
+                        const ContainerLayerParameters& aContainerParameters)
+{
+  ContainerLayerParameters newContainerParameters = aContainerParameters;
+  uint32_t flags = FrameLayerBuilder::CONTAINER_NOT_CLIPPED_BY_ANCESTORS;
+  nsRefPtr<ContainerLayer> container = aManager->GetLayerBuilder()->
+    BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
+                           newContainerParameters, nullptr, flags);
+
+  container->SetVRHMDInfo(mHMD);
+  container->SetUserData(nsIFrame::LayerIsPrerenderedDataKey(),
+                         /*the value is irrelevant*/nullptr);
+
+  return container.forget();
+}
 nsRegion nsDisplaySVGEffects::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                               bool* aSnap)
 {
   *aSnap = false;
   return nsRegion();
 }
 
 void
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -23,16 +23,17 @@
 #include "Layers.h"
 #include "nsRegion.h"
 #include "nsDisplayListInvalidation.h"
 #include "DisplayListClipState.h"
 #include "LayerState.h"
 #include "FrameMetrics.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/gfx/UserData.h"
+#include "gfxVR.h"
 
 #include <stdint.h>
 #include "nsTHashtable.h"
 
 #include <stdlib.h>
 #include <algorithm>
 
 class nsIContent;
@@ -3611,9 +3612,34 @@ public:
             t == nsDisplayItem::TYPE_TEXT_SHADOW)
       ? static_cast<nsCharClipDisplayItem*>(aItem) : nullptr;
   }
 
   nscoord mLeftEdge;  // length from the left side
   nscoord mRightEdge; // length from the right side
 };
 
+/**
+ * A wrapper layer that wraps its children in a container, then renders
+ * everything with an appropriate VR effect based on the HMDInfo.
+ */
+
+class nsDisplayVR : public nsDisplayOwnLayer {
+public:
+  nsDisplayVR(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+              nsDisplayList* aList, mozilla::gfx::VRHMDInfo* aHMD);
+
+  virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
+                                   LayerManager* aManager,
+                                   const ContainerLayerParameters& aParameters) MOZ_OVERRIDE
+  {
+    return mozilla::LAYER_ACTIVE;
+  }
+
+  virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
+                                             LayerManager* aManager,
+                                             const ContainerLayerParameters& aContainerParameters) MOZ_OVERRIDE;
+
+protected:
+  nsRefPtr<mozilla::gfx::VRHMDInfo> mHMD;
+};
+
 #endif /*NSDISPLAYLIST_H_*/
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -7188,16 +7188,17 @@ PresShell::HandleKeyboardEvent(nsINode* 
   // after event.
   size_t chainIndex;
   bool defaultPrevented = false;
   DispatchBeforeKeyboardEventInternal(chain, aEvent, chainIndex,
                                       defaultPrevented);
 
   // Dispatch after events to partial items.
   if (defaultPrevented) {
+    *aStatus = nsEventStatus_eConsumeNoDefault;
     DispatchAfterKeyboardEventInternal(chain, aEvent,
                                        aEvent.mFlags.mDefaultPrevented, chainIndex);
 
     // No need to forward the event to child process.
     aEvent.mFlags.mNoCrossProcessBoundaryForwarding = true;
     return;
   }
 
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -97,16 +97,22 @@
 
 using namespace mozilla;
 using namespace mozilla::css;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 
+namespace mozilla {
+namespace gfx {
+class VRHMDInfo;
+}
+}
+
 // Struct containing cached metrics for box-wrapped frames.
 struct nsBoxLayoutMetrics
 {
   nsSize mPrefSize;
   nsSize mMinSize;
   nsSize mMaxSize;
 
   nsSize mBlockMinSize;
@@ -585,16 +591,20 @@ nsFrame::Init(nsIContent*       aContent
         AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
       }
     }
     NS_ASSERTION(GetParent() ||
                  (GetStateBits() & NS_FRAME_FONT_INFLATION_CONTAINER),
                  "root frame should always be a container");
   }
 
+  if (aContent && aContent->GetProperty(nsGkAtoms::vr_state) != nullptr) {
+    AddStateBits(NS_FRAME_HAS_VR_CONTENT);
+  }
+
   DidSetStyleContext(nullptr);
 
   if (::IsBoxWrapped(this))
     ::InitBoxMetrics(this, false);
 }
 
 void
 nsFrame::DestroyFrom(nsIFrame* aDestructRoot)
@@ -1990,16 +2000,22 @@ nsIFrame::BuildDisplayListForStackingCon
   bool useStickyPosition = disp->mPosition == NS_STYLE_POSITION_STICKY &&
     IsScrollFrameActive(aBuilder,
                         nsLayoutUtils::GetNearestScrollableFrame(GetParent(),
                         nsLayoutUtils::SCROLLABLE_SAME_DOC |
                         nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN));
 
   nsDisplayListBuilder::AutoBuildingDisplayList
     buildingDisplayList(aBuilder, this, dirtyRect, true);
+
+  mozilla::gfx::VRHMDInfo* vrHMDInfo = nullptr;
+  if ((GetStateBits() & NS_FRAME_HAS_VR_CONTENT)) {
+    vrHMDInfo = static_cast<mozilla::gfx::VRHMDInfo*>(mContent->GetProperty(nsGkAtoms::vr_state));
+  }
+
   DisplayListClipState::AutoSaveRestore clipState(aBuilder);
 
   if (isTransformed || useOpacity || useBlendMode || usingSVGEffects || useStickyPosition) {
     // We don't need to pass ancestor clipping down to our children;
     // everything goes inside a display item's child list, and the display
     // item itself will be clipped.
     // For transforms we also need to clear ancestor clipping because it's
     // relative to the wrong display item reference frame anyway.
@@ -2150,16 +2166,23 @@ nsIFrame::BuildDisplayListForStackingCon
     if (Preserves3DChildren()) {
       WrapPreserve3DList(this, aBuilder, &resultList);
     } else {
       resultList.AppendNewToTop(
         new (aBuilder) nsDisplayTransform(aBuilder, this, &resultList, dirtyRect));
     }
   }
 
+  /* If we're doing VR rendering, then we need to wrap everything in a nsDisplayVR
+   */
+  if (vrHMDInfo && !resultList.IsEmpty()) {
+    resultList.AppendNewToTop(
+      new (aBuilder) nsDisplayVR(aBuilder, this, &resultList, vrHMDInfo));
+  }
+
   /* If adding both a nsDisplayBlendContainer and a nsDisplayMixBlendMode to the
    * same list, the nsDisplayBlendContainer should be added first. This only
    * happens when the element creating this stacking context has mix-blend-mode
    * and also contains a child which has mix-blend-mode.
    * The nsDisplayBlendContainer must be added to the list first, so it does not
    * isolate the containing element blending as well.
    */
 
@@ -2320,17 +2343,18 @@ nsIFrame::BuildDisplayListForChild(nsDis
   const nsStyleDisplay* disp = child->StyleDisplay();
   const nsStylePosition* pos = child->StylePosition();
   bool isVisuallyAtomic = child->HasOpacity()
     || child->IsTransformed()
     // strictly speaking, 'perspective' doesn't require visual atomicity,
     // but the spec says it acts like the rest of these
     || disp->mChildPerspective.GetUnit() == eStyleUnit_Coord
     || disp->mMixBlendMode != NS_STYLE_BLEND_NORMAL
-    || nsSVGIntegrationUtils::UsingEffectsForFrame(child);
+    || nsSVGIntegrationUtils::UsingEffectsForFrame(child)
+    || (child->GetStateBits() & NS_FRAME_HAS_VR_CONTENT);
 
   bool isPositioned = disp->IsPositioned(child);
   bool isStackingContext =
     (isPositioned && (disp->mPosition == NS_STYLE_POSITION_STICKY ||
                       pos->mZIndex.GetUnit() == eStyleUnit_Integer)) ||
      (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_STACKING_CONTEXT) ||
      disp->mIsolation != NS_STYLE_ISOLATION_AUTO ||
      isVisuallyAtomic || (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
--- a/layout/generic/nsFrameStateBits.h
+++ b/layout/generic/nsFrameStateBits.h
@@ -244,16 +244,19 @@ FRAME_STATE_BIT(Generic, 52, NS_FRAME_HA
 
 // Frame is not displayed directly due to it being, or being under, an SVG
 // <defs> element or an SVG resource element (<mask>, <pattern>, etc.)
 FRAME_STATE_BIT(Generic, 53, NS_FRAME_IS_NONDISPLAY)
 
 // Frame has a LayerActivityProperty property
 FRAME_STATE_BIT(Generic, 54, NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)
 
+// Frame has VR content, and needs VR display items created
+FRAME_STATE_BIT(Generic, 57, NS_FRAME_HAS_VR_CONTENT)
+
 // Set for all descendants of MathML sub/supscript elements (other than the
 // base frame) to indicate that the SSTY font feature should be used.
 FRAME_STATE_BIT(Generic, 58, NS_FRAME_MATHML_SCRIPT_DESCENDANT)
 
 // This state bit is set on frames within token MathML elements if the
 // token represents an <mi> tag whose inner HTML consists of a single
 // non-whitespace character to allow special rendering behaviour.
 FRAME_STATE_BIT(Generic, 59, NS_FRAME_IS_IN_SINGLE_CHAR_MI)
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -17,17 +17,16 @@ import signal
 import subprocess
 import sys
 import threading
 
 SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
 sys.path.insert(0, SCRIPT_DIRECTORY)
 
 from automationutils import (
-    addCommonOptions,
     dumpScreen,
     environment,
     processLeakLog
 )
 import mozcrash
 import mozdebug
 import mozinfo
 import mozprocess
@@ -613,18 +612,36 @@ class RefTest(object):
         continue
 
 
 class ReftestOptions(OptionParser):
 
   def __init__(self):
     OptionParser.__init__(self)
     defaults = {}
-    addCommonOptions(self)
-
+    self.add_option("--xre-path",
+                    action = "store", type = "string", dest = "xrePath",
+                    # individual scripts will set a sane default
+                    default = None,
+                    help = "absolute path to directory containing XRE (probably xulrunner)")
+    self.add_option("--symbols-path",
+                    action = "store", type = "string", dest = "symbolsPath",
+                    default = None,
+                    help = "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols")
+    self.add_option("--debugger",
+                    action = "store", dest = "debugger",
+                    help = "use the given debugger to launch the application")
+    self.add_option("--debugger-args",
+                    action = "store", dest = "debuggerArgs",
+                    help = "pass the given args to the debugger _before_ "
+                           "the application on the command line")
+    self.add_option("--debugger-interactive",
+                    action = "store_true", dest = "debuggerInteractive",
+                    help = "prevents the test harness from redirecting "
+                        "stdout and stderr for interactive debuggers")
     self.add_option("--appname",
                     action = "store", type = "string", dest = "app",
                     help = "absolute path to application, overriding default")
     # Certain paths do not make sense when we're cross compiling Fennec.  This
     # logic is cribbed from the example in
     # python/mozbuild/mozbuild/mach_commands.py.
     defaults['app'] = build_obj.get_binary_path() if \
         build_obj and build_obj.substs['MOZ_BUILD_APP'] != 'mobile/android' else None
--- a/memory/mozjemalloc/jemalloc.c
+++ b/memory/mozjemalloc/jemalloc.c
@@ -6743,23 +6743,25 @@ jemalloc_purge_freed_pages_impl()
 {
 	size_t i;
 	for (i = 0; i < narenas; i++) {
 		arena_t *arena = arenas[i];
 		if (arena != NULL)
 			hard_purge_arena(arena);
 	}
 	if (!config_munmap || config_recycle) {
+		malloc_mutex_lock(&chunks_mtx);
 		extent_node_t *node = extent_tree_szad_first(&chunks_szad_mmap);
 		while (node) {
 			pages_decommit(node->addr, node->size);
 			pages_commit(node->addr, node->size);
 			node->zeroed = true;
 			node = extent_tree_szad_next(&chunks_szad_mmap, node);
 		}
+		malloc_mutex_unlock(&chunks_mtx);
 	}
 }
 
 #else /* !defined MALLOC_DOUBLE_PURGE */
 
 MOZ_JEMALLOC_API void
 jemalloc_purge_freed_pages_impl()
 {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4216,16 +4216,20 @@ pref("jsloader.reuseGlobal", false);
 
 // When we're asked to take a screenshot, don't wait more than 2000ms for the
 // event loop to become idle before actually taking the screenshot.
 pref("dom.browserElement.maxScreenshotDelayMS", 2000);
 
 // Whether we should show the placeholder when the element is focused but empty.
 pref("dom.placeholder.show_on_focus", true);
 
+pref("dom.vr.enabled", false);
+// 0 = never; 1 = only if real devices aren't there; 2 = always
+pref("dom.vr.add-test-devices", 1);
+
 // MMS UA Profile settings
 pref("wap.UAProf.url", "");
 pref("wap.UAProf.tagname", "x-wap-profile");
 
 // MMS version 1.1 = 0x11 (or decimal 17)
 // MMS version 1.3 = 0x13 (or decimal 19)
 // @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.34
 pref("dom.mms.version", 19);
--- a/python/mozboot/mozboot/bootstrap.py
+++ b/python/mozboot/mozboot/bootstrap.py
@@ -54,17 +54,17 @@ class Bootstrapper(object):
                 # Most Linux Mint editions are based on Ubuntu; one is based on
                 # Debian, and reports this in dist_id
                 if dist_id == 'debian':
                     cls = DebianBootstrapper
                 else:
                     cls = UbuntuBootstrapper
             elif distro == 'Ubuntu':
                 cls = UbuntuBootstrapper
-            elif distro in ('Elementary OS', 'Elementary'):
+            elif distro in ('Elementary OS', 'Elementary', '"elementary OS"'):
                 cls = UbuntuBootstrapper
             else:
                 raise NotImplementedError('Bootstrap support for this Linux '
                                           'distro not yet available.')
 
             args['version'] = version
             args['dist_id'] = dist_id
 
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -4,17 +4,16 @@
 
 from urlparse import urlparse
 import mozinfo
 import moznetwork
 import optparse
 import os
 import tempfile
 
-from automationutils import addCommonOptions
 from mozprofile import DEFAULT_PORTS
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 try:
     from mozbuild.base import MozbuildObject
     build_obj = MozbuildObject.from_environment(cwd=here)
 except ImportError:
@@ -430,28 +429,56 @@ class MochitestOptions(optparse.OptionPa
           "help": "Use test media device drivers for media testing.",
         }],
         [["--gmp-path"],
         { "action": "store",
           "default": None,
           "dest": "gmp_path",
           "help": "Path to fake GMP plugin. Will be deduced from the binary if not passed.",
         }],
+        [["--xre-path"],
+        { "action": "store",
+          "type": "string", 
+          "dest": "xrePath",
+          "default": None,    # individual scripts will set a sane default
+          "help": "absolute path to directory containing XRE (probably xulrunner)",
+        }],
+        [["--symbols-path"],
+        { "action": "store", 
+          "type": "string", 
+          "dest": "symbolsPath",
+          "default": None,
+          "help": "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols",
+        }],
+        [["--debugger"],
+        { "action": "store", 
+          "dest": "debugger",
+          "help": "use the given debugger to launch the application",
+        }],
+        [["--debugger-args"],
+        { "action": "store",
+          "dest": "debuggerArgs",
+          "help": "pass the given args to the debugger _before_ the application on the command line",
+        }],
+        [["--debugger-interactive"],
+        { "action": "store_true",
+          "dest": "debuggerInteractive",
+          "help": "prevents the test harness from redirecting stdout and stderr for interactive debuggers",
+        }],
     ]
 
     def __init__(self, **kwargs):
 
         optparse.OptionParser.__init__(self, **kwargs)
         for option, value in self.mochitest_options:
             # Allocate new lists so references to original don't get mutated.
             # allowing multiple uses within a single process.
             if "default" in value and isinstance(value["default"], list):
                 value["default"] = []
             self.add_option(*option, **value)
-        addCommonOptions(self)
         self.set_usage(self.__doc__)
 
     def verifyOptions(self, options, mochitest):
         """ verify correct options and cleanup paths """
 
         if options.contentSandbox != 'off':
             options.e10s = True
 
--- a/testing/mozbase/mozinstall/mozinstall/mozinstall.py
+++ b/testing/mozbase/mozinstall/mozinstall/mozinstall.py
@@ -232,17 +232,17 @@ def _install_dmg(src, dest):
     """Extract a dmg file into the destination folder and return the
     application folder.
 
     src -- DMG image which has to be extracted
     dest -- the path to extract to
 
     """
     try:
-        proc = subprocess.Popen('hdiutil attach %s' % src,
+        proc = subprocess.Popen('hdiutil attach -nobrowse -noautoopen %s' % src,
                                 shell=True,
                                 stdout=subprocess.PIPE)
 
         for data in proc.communicate()[0].split():
             if data.find('/Volumes/') != -1:
                 appDir = data
                 break
 
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -34,17 +34,16 @@ from threading import (
 
 try:
     import psutil
     HAVE_PSUTIL = True
 except ImportError:
     HAVE_PSUTIL = False
 
 from automation import Automation
-from automationutils import addCommonOptions
 
 HARNESS_TIMEOUT = 5 * 60
 
 # benchmarking on tbpl revealed that this works best for now
 NUM_THREADS = int(cpu_count() * 4)
 
 EXPECTED_LOG_ACTIONS = set([
     "test_status",
@@ -1354,18 +1353,16 @@ class XPCShellTests(object):
 
         self.log.suite_end()
         return self.failCount == 0
 
 class XPCShellOptions(OptionParser):
     def __init__(self):
         """Process command line arguments and call runTests() to do the real work."""
         OptionParser.__init__(self)
-
-        addCommonOptions(self)
         self.add_option("--app-path",
                         type="string", dest="appPath", default=None,
                         help="application directory (as opposed to XRE directory)")
         self.add_option("--interactive",
                         action="store_true", dest="interactive", default=False,
                         help="don't automatically run tests, drop to an xpcshell prompt")
         self.add_option("--verbose",
                         action="store_true", dest="verbose", default=False,
@@ -1412,16 +1409,36 @@ class XPCShellOptions(OptionParser):
                         type = "string", dest="mozInfo", default=None,
                         help="path to a mozinfo.json including information about the build configuration. defaults to looking for mozinfo.json next to the script.")
         self.add_option("--shuffle",
                         action="store_true", dest="shuffle", default=False,
                         help="Execute tests in random order")
         self.add_option("--failure-manifest", dest="failureManifest",
                         action="store",
                         help="path to file where failure manifest will be written.")
+        self.add_option("--xre-path",
+                        action = "store", type = "string", dest = "xrePath",
+                        # individual scripts will set a sane default
+                        default = None,
+                        help = "absolute path to directory containing XRE (probably xulrunner)")
+        self.add_option("--symbols-path",
+                        action = "store", type = "string", dest = "symbolsPath",
+                        default = None,
+                        help = "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols")
+        self.add_option("--debugger",
+                        action = "store", dest = "debugger",
+                        help = "use the given debugger to launch the application")
+        self.add_option("--debugger-args",
+                        action = "store", dest = "debuggerArgs",
+                        help = "pass the given args to the debugger _before_ "
+                           "the application on the command line")
+        self.add_option("--debugger-interactive",
+                        action = "store_true", dest = "debuggerInteractive",
+                        help = "prevents the test harness from redirecting "
+                          "stdout and stderr for interactive debuggers")
 
 def main():
     parser = XPCShellOptions()
     structured.commandline.add_logging_group(parser)
     options, args = parser.parse_args()
 
 
     log = structured.commandline.setup_logging("XPCShell",
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -687,17 +687,17 @@ nsWindow::DispatchEvent(WidgetGUIEvent* 
             break;
         }
         return status;
     }
     return nsEventStatus_eIgnore;
 }
 
 NS_IMETHODIMP
-nsWindow::MakeFullScreen(bool aFullScreen)
+nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen*)
 {
     mozilla::widget::android::GeckoAppShell::SetFullScreen(aFullScreen);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::SetWindowClass(const nsAString& xulWinType)
 {
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -99,17 +99,17 @@ public:
     virtual bool IsEnabled() const;
     NS_IMETHOD Invalidate(const nsIntRect &aRect);
     NS_IMETHOD SetFocus(bool aRaise = false);
     NS_IMETHOD GetScreenBounds(nsIntRect &aRect);
     virtual nsIntPoint WidgetToScreenOffset();
     NS_IMETHOD DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
                              nsEventStatus& aStatus);
     nsEventStatus DispatchEvent(mozilla::WidgetGUIEvent* aEvent);
-    NS_IMETHOD MakeFullScreen(bool aFullScreen);
+    NS_IMETHOD MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen = nullptr);
     NS_IMETHOD SetWindowClass(const nsAString& xulWinType);
 
 
 
     NS_IMETHOD SetCursor(nsCursor aCursor) { return NS_ERROR_NOT_IMPLEMENTED; }
     NS_IMETHOD SetCursor(imgIContainer* aCursor,
                          uint32_t aHotspotX,
                          uint32_t aHotspotY) { return NS_ERROR_NOT_IMPLEMENTED; }
--- a/widget/cocoa/nsCocoaWindow.h
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -270,17 +270,17 @@ public:
                                               int32_t *aX, int32_t *aY);
     virtual void            SetSizeConstraints(const SizeConstraints& aConstraints);
     NS_IMETHOD              Move(double aX, double aY);
     NS_IMETHOD              PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
                                         nsIWidget *aWidget, bool aActivate);
     NS_IMETHOD              SetSizeMode(int32_t aMode);
     NS_IMETHOD              HideWindowChrome(bool aShouldHide);
     void                    EnteredFullScreen(bool aFullScreen);
-    NS_IMETHOD              MakeFullScreen(bool aFullScreen);
+    NS_IMETHOD              MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen = nullptr);
     NS_IMETHOD              Resize(double aWidth, double aHeight, bool aRepaint);
     NS_IMETHOD              Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint);
     NS_IMETHOD              GetClientBounds(nsIntRect &aRect);
     NS_IMETHOD              GetScreenBounds(nsIntRect &aRect);
     void                    ReportMoveEvent();
     void                    ReportSizeEvent();
     NS_IMETHOD              SetCursor(nsCursor aCursor);
     NS_IMETHOD              SetCursor(imgIContainer* aCursor, uint32_t aHotspotX, uint32_t aHotspotY);
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -1275,17 +1275,17 @@ NS_IMETHODIMP nsCocoaWindow::HideWindowC
 
 void nsCocoaWindow::EnteredFullScreen(bool aFullScreen)
 {
   mInFullScreenTransition = false;
   mFullScreen = aFullScreen;
   DispatchSizeModeEvent();
 }
 
-NS_METHOD nsCocoaWindow::MakeFullScreen(bool aFullScreen)
+NS_METHOD nsCocoaWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   if (!mWindow) {
     return NS_OK;
   }
 
   // We will call into MakeFullScreen redundantly when entering/exiting
--- a/widget/gonk/nsWindow.cpp
+++ b/widget/gonk/nsWindow.cpp
@@ -450,17 +450,17 @@ nsWindow::GetInputContext()
 
 NS_IMETHODIMP
 nsWindow::ReparentNativeWidget(nsIWidget* aNewParent)
 {
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsWindow::MakeFullScreen(bool aFullScreen)
+nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen*)
 {
     if (mWindowType != eWindowType_toplevel) {
         // Ignore fullscreen request for non-toplevel windows.
         NS_WARNING("MakeFullScreen() on a dialog or child widget?");
         return nsBaseWidget::MakeFullScreen(aFullScreen);
     }
 
     if (aFullScreen) {
--- a/widget/gonk/nsWindow.h
+++ b/widget/gonk/nsWindow.h
@@ -90,17 +90,17 @@ public:
                              nsEventStatus& aStatus);
     NS_IMETHOD CaptureRollupEvents(nsIRollupListener *aListener,
                                    bool aDoCapture)
     {
         return NS_ERROR_NOT_IMPLEMENTED;
     }
     NS_IMETHOD ReparentNativeWidget(nsIWidget* aNewParent);
 
-    NS_IMETHOD MakeFullScreen(bool aFullScreen) /*MOZ_OVERRIDE*/;
+    NS_IMETHOD MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen = nullptr) /*MOZ_OVERRIDE*/;
 
     virtual mozilla::TemporaryRef<mozilla::gfx::DrawTarget>
         StartRemoteDrawing() MOZ_OVERRIDE;
     virtual void EndRemoteDrawing() MOZ_OVERRIDE;
 
     virtual float GetDPI();
     virtual double GetDefaultScaleInternal();
     virtual mozilla::layers::LayerManager*
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -4699,17 +4699,17 @@ nsWindow::ConvertBorderStyles(nsBorderSt
         w |= GDK_DECOR_MINIMIZE;
     if (aStyle & eBorderStyle_maximize)
         w |= GDK_DECOR_MAXIMIZE;
 
     return w;
 }
 
 NS_IMETHODIMP
-nsWindow::MakeFullScreen(bool aFullScreen)
+nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen)
 {
     LOG(("nsWindow::MakeFullScreen [%p] aFullScreen %d\n",
          (void *)this, aFullScreen));
 
     if (aFullScreen) {
         if (mSizeMode != nsSizeMode_Fullscreen)
             mLastSizeMode = mSizeMode;
 
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -141,17 +141,17 @@ public:
     NS_IMETHOD         CaptureMouse(bool aCapture);
     NS_IMETHOD         CaptureRollupEvents(nsIRollupListener *aListener,
                                            bool aDoCapture);
     NS_IMETHOD         GetAttention(int32_t aCycleCount);
     virtual nsresult   SetWindowClipRegion(const nsTArray<nsIntRect>& aRects,
                                            bool aIntersectWithExisting) MOZ_OVERRIDE;
     virtual bool       HasPendingInputEvent();
 
-    NS_IMETHOD         MakeFullScreen(bool aFullScreen);
+    NS_IMETHOD         MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen = nullptr);
     NS_IMETHOD         HideWindowChrome(bool aShouldHide);
 
     /**
      * GetLastUserInputTime returns a timestamp for the most recent user input
      * event.  This is intended for pointer grab requests (including drags).
      */
     static guint32     GetLastUserInputTime();
 
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -37,17 +37,16 @@
 #include "prenv.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/unused.h"
 #include "nsContentUtils.h"
 #include "gfxPrefs.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/MouseEvents.h"
 #include "GLConsts.h"
-#include "LayerScope.h"
 #include "mozilla/unused.h"
 
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
 
 #ifdef DEBUG
 #include "nsIObserver.h"
@@ -167,18 +166,16 @@ static void DeferredDestroyCompositor(Co
     // we can close the channel properly
     //aCompositorChild->Close();
     aCompositorParent->Release();
     aCompositorChild->Release();
 }