Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 17 Sep 2015 14:59:11 -0700
changeset 295691 3929b8fc6c33
parent 295601 214fe6c504ec (current diff)
parent 295690 9c45ef119f96 (diff)
child 295718 01a75ffad102
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge
configure.in
dom/inputmethod/mochitest/file_inputmethod_1043828.html
testing/web-platform/mozilla/meta/service-workers/service-worker/unregister-then-register.https.html.ini
toolkit/components/telemetry/Histograms.json
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -158,17 +158,17 @@
 
     <!-- for select dropdowns. The menupopup is what shows the list of options,
          and the popuponly menulist makes things like the menuactive attributes
          work correctly on the menupopup. ContentSelectDropdown expects the
          popuponly menulist to be its immediate parent. -->
     <menulist popuponly="true" id="ContentSelectDropdown" hidden="true">
       <menupopup rolluponmousewheel="true"
 #ifdef XP_WIN
-                 consumeoutsideclicks="false"
+                 consumeoutsideclicks="false" ignorekeys="handled"
 #endif
         />
     </menulist>
 
     <!-- for invalid form error message -->
     <panel id="invalid-form-popup" type="arrow" orient="vertical" noautofocus="true" hidden="true" level="parent">
       <description/>
     </panel>
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -18,17 +18,17 @@ const PAGECONTENT =
   "  <optgroup label='Second Group' disabled='true'>" +
   "    <option value='Four'>Four</option>" +
   "    <option value='Five'>Five</option>" +
   "  </optgroup>" +
   "  <option value='Six' disabled='true'>Six</option>" +
   "  <optgroup label='Third Group'>" +
   "    <option value='Seven'>   Seven  </option>" +
   "    <option value='Eight'>&nbsp;&nbsp;Eight&nbsp;&nbsp;</option>" +
-  "  </optgroup></select><input />" +
+  "  </optgroup></select><input />Text" +
   "</body></html>";
 
 function openSelectPopup(selectPopup, withMouse)
 {
   let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
 
   if (withMouse) {
     return Promise.all([popupShownPromise,
@@ -103,16 +103,22 @@ function doSelectTests(contentType, dtd)
   }
 
   EventUtils.synthesizeKey("KEY_ArrowUp", { code: "ArrowUp" });
   is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3 again");
   is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex");
 
   is((yield getChangeEvents()), 0, "Before closed - number of change events");
 
+  EventUtils.synthesizeKey("a", { accelKey: true });
+  let selection = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+    return String(content.getSelection());
+  });
+  is(selection, isWindows ? "Text" : "", "Select all while popup is open");
+
   yield hideSelectPopup(selectPopup);
 
   is(menulist.selectedIndex, 3, "Item 3 still selected");
   is((yield getChangeEvents()), 1, "After closed - number of change events");
 
   // Opening and closing the popup without changing the value should not fire a change event.
   yield openSelectPopup(selectPopup, true);
   yield hideSelectPopup(selectPopup, true);
--- a/config/check_vanilla_allocations.py
+++ b/config/check_vanilla_allocations.py
@@ -70,18 +70,18 @@ def main():
                         help='also check for malloc, calloc, realloc and free')
     parser.add_argument('file', type=str,
                         help='name of the file to check')
     args = parser.parse_args()
 
     # Run |nm|.  Options:
     # -u: show only undefined symbols
     # -C: demangle symbol names
-    # -l: show a filename and line number for each undefined symbol
-    cmd = ['nm', '-u', '-C', '-l', args.file]
+    # -A: show an object filename for each undefined symbol
+    cmd = ['nm', '-u', '-C', '-A', args.file]
     lines = subprocess.check_output(cmd, universal_newlines=True,
                                     stderr=subprocess.PIPE).split('\n')
 
     # alloc_fns contains all the vanilla allocation/free functions that we look
     # for. Regexp chars are escaped appropriately.
 
     alloc_fns = [
         # Matches |operator new(unsigned T)|, where |T| is |int| or |long|.
@@ -107,51 +107,83 @@ def main():
         ]
 
     # This is like alloc_fns, but regexp chars are not escaped.
     alloc_fns_unescaped = [fn.translate(None, r'\\') for fn in alloc_fns]
 
     # This regexp matches the relevant lines in the output of |nm|, which look
     # like the following.
     #
-    #   U malloc  /path/to/objdir/dist/include/js/Utility.h:142
+    #   js/src/libjs_static.a:jsutil.o:              U malloc
     #
-    alloc_fns_re = r'U (' + r'|'.join(alloc_fns) + r').*\/([\w\.]+):(\d+)$'
+    alloc_fns_re = r'([^:/ ]+):\s+U (' + r'|'.join(alloc_fns) + r')'
 
     # This tracks which allocation/free functions have been seen in jsutil.cpp.
     jsutil_cpp = set([])
 
+    # Would it be helpful to emit detailed line number information after a failure?
+    emit_line_info = False
+
     for line in lines:
         m = re.search(alloc_fns_re, line)
         if m is None:
             continue
 
-        fn = m.group(1)
-        filename = m.group(2)
-        linenum = m.group(3)
-        if filename == 'jsutil.cpp':
+        filename = m.group(1)
+        fn = m.group(2)
+        if filename == 'jsutil.o':
             jsutil_cpp.add(fn)
         else:
             # An allocation is present in a non-special file.  Fail!
-            fail("'" + fn + "' present at " + filename + ':' + linenum)
+            fail("'" + fn + "' present in " + filename)
+            # Try to give more precise information about the offending code.
+            emit_line_info = True
 
 
     # Check that all functions we expect are used in jsutil.cpp.  (This will
     # fail if the function-detection code breaks at any point.)
     for fn in alloc_fns_unescaped:
         if fn not in jsutil_cpp:
             fail("'" + fn + "' isn't used as expected in jsutil.cpp")
         else:
             jsutil_cpp.remove(fn)
 
     # This should never happen, but check just in case.
     if jsutil_cpp:
         fail('unexpected allocation fns used in jsutil.cpp: ' +
              ', '.join(jsutil_cpp))
 
+    # If we found any improper references to allocation functions, try to use
+    # DWARF debug info to get more accurate line number information about the
+    # bad calls. This is a lot slower than 'nm -A', and it is not always
+    # precise when building with --enable-optimized.
+    if emit_line_info:
+        print('check_vanilla_allocations.py: Source lines with allocation calls:')
+        print('check_vanilla_allocations.py: Accurate in unoptimized builds; jsutil.cpp expected.')
+
+        # Run |nm|.  Options:
+        # -u: show only undefined symbols
+        # -C: demangle symbol names
+        # -l: show line number information for each undefined symbol
+        cmd = ['nm', '-u', '-C', '-l', args.file]
+        lines = subprocess.check_output(cmd, universal_newlines=True,
+                                        stderr=subprocess.PIPE).split('\n')
+
+        # This regexp matches the relevant lines in the output of |nm -l|,
+        # which look like the following.
+        #
+        #       U malloc jsutil.cpp:117
+        #
+        alloc_lines_re = r'U ((' + r'|'.join(alloc_fns) + r').*)\s+(\S+:\d+)$'
+
+        for line in lines:
+            m = re.search(alloc_lines_re, line)
+            if m:
+                print('check_vanilla_allocations.py:', m.group(1), 'called at', m.group(3))
+
     if has_failed:
         sys.exit(1)
 
     print('TEST-PASS | check_vanilla_allocations.py | ok')
     sys.exit(0)
 
 
 if __name__ == '__main__':
--- a/configure.in
+++ b/configure.in
@@ -7246,19 +7246,16 @@ if test -z "$MOZ_MEMORY"; then
     *-mingw*)
       if test -z "$WIN32_REDIST_DIR" -a -z "$MOZ_DEBUG"; then
         AC_MSG_WARN([When not building jemalloc, you need to set WIN32_REDIST_DIR to the path to the Visual C++ Redist (usually VCINSTALLDIR\redist\x86\Microsoft.VC80.CRT, for VC++ v8) if you intend to distribute your build.])
       fi
       ;;
   esac
 else
   AC_DEFINE(MOZ_MEMORY)
-  if test -n "$NIGHTLY_BUILD"; then
-    MOZ_JEMALLOC4=1
-  fi
   if test -n "$MOZ_JEMALLOC4"; then
     AC_DEFINE(MOZ_JEMALLOC4)
   fi
   if test "x$MOZ_DEBUG" = "x1"; then
     AC_DEFINE(MOZ_MEMORY_DEBUG)
   fi
   dnl The generic feature tests that determine how to compute ncpus are long and
   dnl complicated.  Therefore, simply define special cpp variables for the
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -10866,20 +10866,17 @@ nsDocShell::DoChannelLoad(nsIChannel* aC
   if (!aBypassClassifier) {
     loadFlags |= nsIChannel::LOAD_CLASSIFY_URI;
   }
 
   (void)aChannel->SetLoadFlags(loadFlags);
 
   // If the user pressed shift-reload, then do not allow ServiceWorker
   // interception to occur. See step 12.1 of the SW HandleFetch algorithm.
-  if (mLoadType == LOAD_RELOAD_BYPASS_CACHE ||
-      mLoadType == LOAD_RELOAD_BYPASS_PROXY ||
-      mLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE ||
-      mLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
+  if (IsForceReloadType(mLoadType)) {
     nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aChannel);
     if (internal) {
       internal->ForceNoIntercept();
     }
   }
 
   uint32_t openFlags = 0;
   if (mLoadType == LOAD_LINK) {
@@ -11158,21 +11155,17 @@ nsDocShell::OnNewURI(nsIURI* aURI, nsICh
   if (mLoadType == LOAD_REFRESH && !inputStream && equalUri) {
     SetHistoryEntry(&mLSHE, mOSHE);
   }
 
   /* If the user pressed shift-reload, cache will create a new cache key
    * for the page. Save the new cacheKey in Session History.
    * see bug 90098
    */
-  if (aChannel &&
-      (aLoadType == LOAD_RELOAD_BYPASS_CACHE ||
-       aLoadType == LOAD_RELOAD_BYPASS_PROXY ||
-       aLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE ||
-       aLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT)) {
+  if (aChannel && IsForceReloadType(aLoadType)) {
     NS_ASSERTION(!updateSHistory,
                  "We shouldn't be updating session history for forced"
                  " reloads!");
 
     nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(aChannel));
     nsCOMPtr<nsISupports> cacheKey;
     // Get the Cache Key and store it in SH.
     if (cacheChannel) {
--- a/docshell/base/nsDocShellLoadTypes.h
+++ b/docshell/base/nsDocShellLoadTypes.h
@@ -96,10 +96,22 @@ IsValidLoadType(uint32_t aLoadType)
     case LOAD_PUSHSTATE:
     case LOAD_REPLACE_BYPASS_CACHE:
     case LOAD_ERROR_PAGE:
       return true;
   }
   return false;
 }
 
+static inline bool
+IsForceReloadType(uint32_t aLoadType) {
+  switch (aLoadType) {
+    case LOAD_RELOAD_BYPASS_CACHE:
+    case LOAD_RELOAD_BYPASS_PROXY:
+    case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+    case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
+      return true;
+  }
+  return false;
+}
+
 #endif // MOZILLA_INTERNAL_API
 #endif
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -555,17 +555,17 @@ Animation::CanThrottle() const
   // special handling for newly-idle animations or animations that are newly
   // yet-to-start since any operation that would cause that change (e.g. a call
   // to cancel() on the animation, or seeking its current time) will trigger an
   // unthrottled sample.
   if (!IsInEffect()) {
     return true;
   }
 
-  return mIsRunningOnCompositor;
+  return IsRunningOnCompositor();
 }
 
 void
 Animation::ComposeStyle(nsRefPtr<AnimValuesStyleRule>& aStyleRule,
                         nsCSSPropertySet& aSetProperties,
                         bool& aNeedsRefreshes)
 {
   if (!mEffect) {
@@ -758,21 +758,16 @@ Animation::DoPause(ErrorResult& aRv)
   }
 
   bool reuseReadyPromise = false;
   if (mPendingState == PendingState::PlayPending) {
     CancelPendingTasks();
     reuseReadyPromise = true;
   }
 
-  // Mark this as no longer running on the compositor so that next time
-  // we update animations we won't throttle them and will have a chance
-  // to remove the animation from any layer it might be on.
-  mIsRunningOnCompositor = false;
-
   if (!reuseReadyPromise) {
     // Clear ready promise. We'll create a new one lazily.
     mReady = nullptr;
   }
 
   mPendingState = PendingState::PausePending;
 
   nsIDocument* doc = GetRenderedDocument();
@@ -1197,10 +1192,16 @@ Animation::DispatchPlaybackEvent(const n
     AnimationPlaybackEvent::Constructor(this, aName, init);
   event->SetTrusted(true);
 
   nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
     new AsyncEventDispatcher(this, event);
   asyncDispatcher->PostDOMEvent();
 }
 
+bool
+Animation::IsRunningOnCompositor() const
+{
+  return mEffect && mEffect->IsRunningOnCompositor();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -53,17 +53,16 @@ protected:
   virtual ~Animation() {}
 
 public:
   explicit Animation(nsIGlobalObject* aGlobal)
     : DOMEventTargetHelper(aGlobal)
     , mPlaybackRate(1.0)
     , mPendingState(PendingState::NotPending)
     , mAnimationIndex(sNextAnimationIndex++)
-    , mIsRunningOnCompositor(false)
     , mFinishedAtLastComposeStyle(false)
     , mIsRelevant(false)
     , mFinishedIsResolved(false)
   {
   }
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Animation,
@@ -104,17 +103,17 @@ public:
   AnimationPlayState PlayState() const;
   virtual Promise* GetReady(ErrorResult& aRv);
   virtual Promise* GetFinished(ErrorResult& aRv);
   void Cancel();
   virtual void Finish(ErrorResult& aRv);
   virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior);
   virtual void Pause(ErrorResult& aRv);
   virtual void Reverse(ErrorResult& aRv);
-  bool IsRunningOnCompositor() const { return mIsRunningOnCompositor; }
+  bool IsRunningOnCompositor() const;
   IMPL_EVENT_HANDLER(finish);
   IMPL_EVENT_HANDLER(cancel);
 
   // Wrapper functions for Animation DOM methods when called
   // from script.
   //
   // We often use the same methods internally and from script but when called
   // from script we (or one of our subclasses) perform extra steps such as
@@ -264,18 +263,16 @@ public:
   bool IsRelevant() const { return mIsRelevant; }
   void UpdateRelevance();
 
   /**
    * Returns true if this Animation has a lower composite order than aOther.
    */
   virtual bool HasLowerCompositeOrderThan(const Animation& aOther) const;
 
-  void SetIsRunningOnCompositor() { mIsRunningOnCompositor = true; }
-  void ClearIsRunningOnCompositor() { mIsRunningOnCompositor = false; }
   /**
    * Returns true if this animation does not currently need to update
    * style on the main thread (e.g. because it is empty, or is
    * running on the compositor).
    */
   bool CanThrottle() const;
   /**
    * Updates |aStyleRule| with the animation values of this animation's effect,
@@ -404,17 +401,16 @@ protected:
   // This is kNoIndex while the animation is in the idle state and is updated
   // each time the animation transitions out of the idle state.
   //
   // Note that subclasses such as CSSTransition and CSSAnimation may repurpose
   // this member to implement their own brand of sorting. As a result, it is
   // possible for two different objects to have the same index.
   uint64_t mAnimationIndex;
 
-  bool mIsRunningOnCompositor;
   bool mFinishedAtLastComposeStyle;
   // Indicates that the animation should be exposed in an element's
   // getAnimations() list.
   bool mIsRelevant;
 
   nsRevocableEventPtr<nsRunnableMethod<Animation>> mFinishNotificationTask;
   // True if mFinished is resolved or would be resolved if mFinished has
   // yet to be created. This is not set when mFinished is rejected since
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/KeyframeEffect.h"
 #include "mozilla/dom/KeyframeEffectBinding.h"
 #include "mozilla/FloatingPoint.h"
 #include "AnimationCommon.h"
 #include "nsCSSPropertySet.h"
+#include "nsCSSProps.h" // For nsCSSProps::PropHasFlags
 
 namespace mozilla {
 
 void
 ComputedTimingFunction::Init(const nsTimingFunction &aFunction)
 {
   mType = aFunction.mType;
   if (mType == nsTimingFunction::Function) {
@@ -69,16 +70,30 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INH
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(KeyframeEffectReadOnly)
 NS_INTERFACE_MAP_END_INHERITING(AnimationEffectReadOnly)
 
 NS_IMPL_ADDREF_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
 NS_IMPL_RELEASE_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
 
+KeyframeEffectReadOnly::KeyframeEffectReadOnly(
+  nsIDocument* aDocument,
+  Element* aTarget,
+  nsCSSPseudoElements::Type aPseudoType,
+  const AnimationTiming& aTiming)
+  : AnimationEffectReadOnly(aDocument)
+  , mTarget(aTarget)
+  , mTiming(aTiming)
+  , mPseudoType(aPseudoType)
+{
+  MOZ_ASSERT(aTarget, "null animation target is not yet supported");
+  ResetIsRunningOnCompositor();
+}
+
 JSObject*
 KeyframeEffectReadOnly::WrapObject(JSContext* aCx,
                                    JS::Handle<JSObject*> aGivenProto)
 {
   return KeyframeEffectReadOnlyBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
@@ -391,10 +406,55 @@ KeyframeEffectReadOnly::ComposeStyle(nsR
       StyleAnimationValue::Interpolate(prop.mProperty,
                                        segment->mFromValue,
                                        segment->mToValue,
                                        valuePosition, *val);
     MOZ_ASSERT(result, "interpolate must succeed now");
   }
 }
 
+bool
+KeyframeEffectReadOnly::IsRunningOnCompositor() const
+{
+  // We consider animation is running on compositor if there is at least
+  // one property running on compositor.
+  // Animation.IsRunningOnCompotitor will return more fine grained
+  // information in bug 1196114.
+  for (bool isPropertyRunningOnCompositor : mIsPropertyRunningOnCompositor) {
+    if (isPropertyRunningOnCompositor) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void
+KeyframeEffectReadOnly::SetIsRunningOnCompositor(nsCSSProperty aProperty,
+                                                 bool aIsRunning)
+{
+  static_assert(
+    MOZ_ARRAY_LENGTH(LayerAnimationInfo::sRecords) ==
+      MOZ_ARRAY_LENGTH(mIsPropertyRunningOnCompositor),
+    "The length of mIsPropertyRunningOnCompositor should equal to"
+    "the length of LayserAnimationInfo::sRecords");
+  MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
+                                      CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
+             "Property being animated on compositor is a recognized "
+             "compositor-animatable property");
+  const auto& info = LayerAnimationInfo::sRecords;
+  for (size_t i = 0; i < ArrayLength(mIsPropertyRunningOnCompositor); i++) {
+    if (info[i].mProperty == aProperty) {
+      mIsPropertyRunningOnCompositor[i] = aIsRunning;
+      return;
+    }
+  }
+}
+
+void
+KeyframeEffectReadOnly::ResetIsRunningOnCompositor()
+{
+  for (bool& isPropertyRunningOnCompositor : mIsPropertyRunningOnCompositor) {
+    isPropertyRunningOnCompositor = false;
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -8,16 +8,17 @@
 #define mozilla_dom_KeyframeEffect_h
 
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCSSPseudoElements.h"
 #include "nsIDocument.h"
 #include "nsWrapperCache.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/LayerAnimationInfo.h" // LayerAnimations::kRecords
 #include "mozilla/StickyTimeDuration.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/AnimationEffectReadOnly.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Nullable.h"
 #include "nsSMILKeySpline.h"
 #include "nsStyleStruct.h" // for nsTimingFunction
@@ -194,24 +195,17 @@ struct ElementPropertyTransition;
 namespace dom {
 
 class KeyframeEffectReadOnly : public AnimationEffectReadOnly
 {
 public:
   KeyframeEffectReadOnly(nsIDocument* aDocument,
                          Element* aTarget,
                          nsCSSPseudoElements::Type aPseudoType,
-                         const AnimationTiming &aTiming)
-    : AnimationEffectReadOnly(aDocument)
-    , mTarget(aTarget)
-    , mTiming(aTiming)
-    , mPseudoType(aPseudoType)
-  {
-    MOZ_ASSERT(aTarget, "null animation target is not yet supported");
-  }
+                         const AnimationTiming& aTiming);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(KeyframeEffectReadOnly,
                                                         AnimationEffectReadOnly)
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
@@ -303,25 +297,39 @@ public:
   }
 
   // Updates |aStyleRule| with the animation values produced by this
   // Animation for the current time except any properties already contained
   // in |aSetProperties|.
   // Any updated properties are added to |aSetProperties|.
   void ComposeStyle(nsRefPtr<AnimValuesStyleRule>& aStyleRule,
                     nsCSSPropertySet& aSetProperties);
+  bool IsRunningOnCompositor() const;
+  void SetIsRunningOnCompositor(nsCSSProperty aProperty, bool aIsRunning);
 
 protected:
   virtual ~KeyframeEffectReadOnly() { }
+  void ResetIsRunningOnCompositor();
 
   nsCOMPtr<Element> mTarget;
   Nullable<TimeDuration> mParentTime;
 
   AnimationTiming mTiming;
   nsCSSPseudoElements::Type mPseudoType;
 
   InfallibleTArray<AnimationProperty> mProperties;
+
+  // Parallel array corresponding to CommonAnimationManager::sLayerAnimationInfo
+  // such that mIsPropertyRunningOnCompositor[x] is true only if this effect has
+  // an animation of CommonAnimationManager::sLayerAnimationInfo[x].mProperty
+  // that is currently running on the compositor.
+  //
+  // Note that when the owning Animation requests a non-throttled restyle, in
+  // between calling RequestRestyle on its AnimationCollection and when the
+  // restyle is performed, this member may temporarily become false even if
+  // the animation remains on the layer after the restyle.
+  bool mIsPropertyRunningOnCompositor[LayerAnimationInfo::kRecords];
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_KeyframeEffect_h
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -1,3 +1,8 @@
+[DEFAULT]
+support-files =
+  testcommon.js
+  ../../imptests/testharness.js
+  ../../imptests/testharnessreport.js
 [chrome/test_animation_observers.html]
 [chrome/test_running_on_compositor.html]
-skip-if = buildapp == 'b2g'
\ No newline at end of file
+skip-if = buildapp == 'b2g'
--- a/dom/animation/test/chrome/test_running_on_compositor.html
+++ b/dom/animation/test/chrome/test_running_on_compositor.html
@@ -1,72 +1,271 @@
 <!doctype html>
 <head>
 <meta charset=utf-8>
 <title>Bug 1045994 - Add a chrome-only property to inspect if an animation is
        running on the compositor or not</title>
-<script type="application/javascript"
-  src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-<script type="application/javascript"
-  src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
-<script type="application/javascript"
-  src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
-<link rel="stylesheet" type="text/css"
-  href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<script type="application/javascript" src="../testharness.js"></script>
+<script type="application/javascript" src="../testharnessreport.js"></script>
+<script type="application/javascript" src="../testcommon.js"></script>
 <style>
 @keyframes anim {
   to { transform: translate(100px) }
 }
+@keyframes background_and_translate {
+  to { background-color: red; transform: translate(100px); }
+}
+@keyframes background {
+  to { background-color: red; }
+}
+@keyframes rotate {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
+}
 div {
   /* Element needs geometry to be eligible for layerization */
   width: 100px;
   height: 100px;
   background-color: white;
 }
 </style>
 </head>
 <body>
 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1045994"
   target="_blank">Mozilla Bug 1045994</a>
+<div id="log"></div>
 <script>
 'use strict';
 
 /** Test for bug 1045994 - Add a chrome-only property to inspect if an
     animation is running on the compositor or not **/
 
-function addDiv(attrs) {
-  var div = document.createElement('div');
-  if (attrs) {
-    for (var attrName in attrs) {
-      div.setAttribute(attrName, attrs[attrName]);
-    }
-  }
-  document.body.appendChild(div);
-  return div;
-}
-
 const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
 var omtaEnabled = SpecialPowers.DOMWindowUtils.layerManagerRemote &&
                   SpecialPowers.getBoolPref(OMTAPrefKey);
 
-add_task(function* play_and_pause_from_style() {
+promise_test(function(t) {
   // FIXME: When we implement Element.animate, use that here instead of CSS
   // so that we remove any dependency on the CSS mapping.
-  var div = addDiv({ style: 'animation: anim 100s' });
+  var div = addDiv(t, { style: 'animation: anim 100s' });
+  var animation = div.getAnimations()[0];
+
+  return animation.ready.then(t.step_func(function() {
+    assert_equals(animation.isRunningOnCompositor, omtaEnabled,
+       'Animation reports that it is running on the compositor'
+       + ' during playback');
+
+    div.style.animationPlayState = 'paused';
+
+    return animation.ready;
+  })).then(t.step_func(function() {
+    assert_equals(animation.isRunningOnCompositor, false,
+       'Animation reports that it is NOT running on the compositor'
+       + ' when paused');
+  }));
+}, '');
+
+promise_test(function(t) {
+  var div = addDiv(t, { style: 'animation: background 100s' });
+  var animation = div.getAnimations()[0];
+
+  return animation.ready.then(t.step_func(function() {
+    assert_equals(animation.isRunningOnCompositor, false,
+       'Animation reports that it is NOT running on the compositor'
+       + ' for animation of "background"');
+  }));
+}, 'isRunningOnCompositor is false for animation of "background"');
+
+promise_test(function(t) {
+  var div = addDiv(t, { style: 'animation: background_and_translate 100s' });
+  var animation = div.getAnimations()[0];
+
+  return animation.ready.then(t.step_func(function() {
+    assert_equals(animation.isRunningOnCompositor, omtaEnabled,
+       'Animation reports that it is running on the compositor'
+        + ' when the animation has two properties, where one can run'
+        + ' on the compositor, the other cannot');
+  }));
+}, 'isRunningOnCompositor is true if the animation has at least one ' +
+   'property can run on compositor');
+
+promise_test(function(t) {
+  var div = addDiv(t, { style: 'animation: anim 100s' });
+  var animation = div.getAnimations()[0];
+
+  return animation.ready.then(t.step_func(function() {
+    animation.pause();
+    return animation.ready;
+  })).then(t.step_func(function() {
+    assert_equals(animation.isRunningOnCompositor, false,
+       'Animation reports that it is NOT running on the compositor'
+       + ' when animation.pause() is called');
+  }));
+}, 'isRunningOnCompositor is false when the animation.pause() is called');
+
+promise_test(function(t) {
+  var div = addDiv(t, { style: 'animation: anim 100s' });
+  var animation = div.getAnimations()[0];
+
+  return animation.ready.then(t.step_func(function() {
+    animation.finish();
+    // We need to wait for two frames here to ensure isRunningOnCompositor
+    // flag is cleared. The first frame is for waking root refresh driver up,
+    // the second one is for clearing the flag.
+    // In most cases root refresh driver has been dormant because there is
+    // nothing to do for the root refresh driver (e.g. nothing change on
+    // chrome window). In the first frame document's refresh driver wakes the
+    // root refresh driver up as a result of animation's style changes.
+    // In the second frame the root refresh driver clears the flag as a
+    // result of a paint.
+    return waitForAnimationFrames(2);
+  })).then(t.step_func(function() {
+    assert_equals(animation.isRunningOnCompositor, false,
+       'Animation reports that it is NOT running on the compositor'
+       + ' when animation.finish() is called');
+  }));
+}, 'isRunningOnCompositor is false when the animation.finish() is called');
+
+promise_test(function(t) {
+  var div = addDiv(t, { style: 'animation: anim 100s' });
+  var animation = div.getAnimations()[0];
+
+  return animation.ready.then(t.step_func(function() {
+    animation.currentTime = 100000; // 100s
+    // We need to wait for up to two frames here to ensure the "is running on
+    // compositor" flag is cleared.
+    // See the description in the 'isRunningOnCompositor is false when the
+    // animation.finish() is called' test for the rationale.
+    return waitForAnimationFrames(2);
+  })).then(t.step_func(function() {
+    assert_equals(animation.isRunningOnCompositor, false,
+       'Animation reports that it is NOT running on the compositor'
+       + ' when manually seeking the animation to the end');
+  }));
+}, 'isRunningOnCompositor is false when manually seeking the animation to ' +
+   'the end');
+
+promise_test(function(t) {
+  var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
 
-  yield animation.ready;
+  return animation.ready.then(t.step_func(function() {
+    animation.cancel();
+    // We need to wait for up to two frames here to ensure the "is running on
+    // compositor" flag is cleared.
+    // See the description in the 'isRunningOnCompositor is false when the
+    // animation.finish() is called' test for the rationale.
+    return waitForAnimationFrames(2);
+  })).then(t.step_func(function() {
+    assert_equals(animation.isRunningOnCompositor, false,
+       'Animation reports that it is NOT running on the compositor'
+       + ' when animation.cancel() is called');
+  }));
+}, 'isRunningOnCompositor is false when animation.cancel() is called');
+
+promise_test(function(t) {
+  var div = addDiv(t, { style: 'animation: anim 100s 100s' });
+  var animation = div.getAnimations()[0];
 
-  is(animation.isRunningOnCompositor, omtaEnabled,
-     'Animation reports that it is running on the compositor'
-     + ' during playback');
+  return animation.ready.then(t.step_func(function() {
+    assert_equals(animation.isRunningOnCompositor, false,
+       'Animation reports that it is NOT running on the compositor'
+       + ' while in the delay phase');
+  }));
+}, 'isRunningOnCompositor is false while in the delay phase');
+
+// This is to test that we don't simply clobber the flag when ticking
+// animations and then set it again during painting.
+promise_test(function(t) {
+  var div = addDiv(t, { style: 'animation: anim 100s' });
+  var animation = div.getAnimations()[0];
 
-  div.style.animationPlayState = 'paused';
+  return animation.ready.then(t.step_func(function() {
+    return new Promise(t.step_func(function(resolve) {
+      window.requestAnimationFrame(t.step_func(function() {
+        assert_equals(animation.isRunningOnCompositor, omtaEnabled,
+           'Animation reports that it is running on the compositor'
+            + ' in requestAnimationFrame callback');
+        resolve();
+      }));
+    }));
+  }));
+}, 'isRunningOnCompositor is true in requestAnimationFrame callback');
+
+promise_test(function(t) {
+  var div = addDiv(t, { style: 'animation: anim 100s' });
+  var animation = div.getAnimations()[0];
 
-  yield animation.ready;
+  return animation.ready.then(t.step_func(function() {
+    return new Promise(t.step_func(function(resolve) {
+      var observer = new MutationObserver(t.step_func(function(records) {
+        var changedAnimation;
+        records.forEach(function(record) {
+          changedAnimation =
+            record.changedAnimations.find(function(changedAnim) {
+              return changedAnim == animation;
+            });
+        });
+        assert_true(!!changedAnimation, 'The animation should be recorded '
+          + 'as one of the changedAnimations');
+        assert_equals(animation.isRunningOnCompositor, omtaEnabled,
+          'Animation reports that it is running on the compositor'
+           + ' in MutationObserver callback');
+        resolve();
+      }));
+      observer.observe(div, { animations: true, subtree: false });
+      div.style.animationDuration = "200s";
+    }));
+  }));
+}, 'isRunningOnCompositor is true in MutationObserver callback');
+
+// This is to test that we don't temporarily clear the flag when forcing
+// an unthrottled sample.
+promise_test(function(t) {
+  return new Promise(function(resolve) {
+    // Needs scrollbars to cause overflow.
+    SpecialPowers.pushPrefEnv({ set: [["ui.showHideScrollbars", 1]] },
+                              resolve);
+  }).then(t.step_func(function() {
+    var div = addDiv(t, { style: 'animation: rotate 100s' });
+    var animation = div.getAnimations()[0];
 
-  is(animation.isRunningOnCompositor, false,
-     'Animation reports that it is NOT running on the compositor'
-     + ' when paused');
-  div.parentNode.removeChild(div);
-});
+    return animation.ready.then(t.step_func(function() {
+      return new Promise(t.step_func(function(resolve) {
+        var timeAtStart = window.performance.now();
+        function handleFrame() {
+          assert_equals(animation.isRunningOnCompositor, omtaEnabled,
+             'Animation reports that it is running on the compositor'
+              + ' in requestAnimationFrame callback');
+
+          // we have to wait at least 200ms because this animation is
+          // unthrottled on every 200ms.
+          // See http://hg.mozilla.org/mozilla-central/file/cafb1c90f794/layout/style/AnimationCommon.cpp#l863
+          if (window.performance.now() - timeAtStart > 200) {
+            resolve();
+            return;
+          }
+          window.requestAnimationFrame(handleFrame);
+        }
+        window.requestAnimationFrame(handleFrame);
+      }));
+    }));
+  }));
+}, 'isRunningOnCompositor remains true in requestAnimationFrameCallback for ' +
+   'overflow animation');
+
+promise_test(function(t) {
+  var div = addDiv(t, { style: 'transition: opacity 100s; opacity: 1' });
+
+  getComputedStyle(div).opacity;
+
+  div.style.opacity = 0;
+  var animation = div.getAnimations()[0];
+
+  return animation.ready.then(t.step_func(function() {
+    assert_equals(animation.isRunningOnCompositor, omtaEnabled,
+       'Transition reports that it is running on the compositor'
+       + ' during playback for opacity transition');
+  }));
+}, 'isRunningOnCompositor for transitions');
+
 </script>
 </body>
--- a/dom/animation/test/testcommon.js
+++ b/dom/animation/test/testcommon.js
@@ -84,25 +84,27 @@ function waitForAllAnimations(animations
  * to be computed so that when we synchronouslyet set it to a different value
  * we actually get a transition instead of that being the initial value.
  */
 function flushComputedStyle(elem) {
   var cs = window.getComputedStyle(elem);
   cs.marginLeft;
 }
 
-for (var funcName of ["async_test", "assert_not_equals", "assert_equals",
-                      "assert_approx_equals", "assert_less_than",
-                      "assert_less_than_equal", "assert_between_inclusive",
-                      "assert_true", "assert_false",
-                      "assert_class_string", "assert_throws",
-                      "assert_unreached", "test"]) {
-  window[funcName] = opener[funcName].bind(opener);
-}
+if (opener) {
+  for (var funcName of ["async_test", "assert_not_equals", "assert_equals",
+                        "assert_approx_equals", "assert_less_than",
+                        "assert_less_than_equal", "assert_between_inclusive",
+                        "assert_true", "assert_false",
+                        "assert_class_string", "assert_throws",
+                        "assert_unreached", "test"]) {
+    window[funcName] = opener[funcName].bind(opener);
+  }
 
-window.EventWatcher = opener.EventWatcher;
+  window.EventWatcher = opener.EventWatcher;
 
-function done() {
-  opener.add_completion_callback(function() {
-    self.close();
-  });
-  opener.done();
+  function done() {
+    opener.add_completion_callback(function() {
+      self.close();
+    });
+    opener.done();
+  }
 }
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -130,18 +130,18 @@ class Animation;
 class Link;
 class UndoManager;
 class DOMRect;
 class DOMRectList;
 class DestinationInsertionPointList;
 
 // IID for the dom::Element interface
 #define NS_ELEMENT_IID \
-{ 0x31d3f3fb, 0xcdf8, 0x4e40, \
- { 0xb7, 0x09, 0x1a, 0x11, 0x43, 0x93, 0x61, 0x71 } }
+{ 0xc67ed254, 0xfd3b, 0x4b10, \
+  { 0x96, 0xa2, 0xc5, 0x8b, 0x7b, 0x64, 0x97, 0xd1 } }
 
 class Element : public FragmentOrElement
 {
 public:
 #ifdef MOZILLA_INTERNAL_API
   explicit Element(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo) :
     FragmentOrElement(aNodeInfo),
     mState(NS_EVENT_STATE_MOZ_READONLY)
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -28,16 +28,17 @@
 #include "nsILoadContext.h"
 #include "nsUnicharUtils.h"
 #include "nsContentList.h"
 #include "nsIObserver.h"
 #include "nsIBaseWindow.h"
 #include "mozilla/css/Loader.h"
 #include "mozilla/css/ImageLoader.h"
 #include "nsDocShell.h"
+#include "nsDocShellLoadTypes.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsCOMArray.h"
 #include "nsQueryObject.h"
 #include "nsDOMClassInfo.h"
 #include "mozilla/Services.h"
 #include "nsScreen.h"
 
 #include "mozilla/AsyncEventDispatcher.h"
@@ -4725,25 +4726,23 @@ nsDocument::SetScriptGlobalObject(nsIScr
     EnumerateActivityObservers(NotifyActivityChanged, nullptr);
   }
 
   // The global in the template contents owner document should be the same.
   if (mTemplateContentsOwner && mTemplateContentsOwner != this) {
     mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject);
   }
 
-  nsCOMPtr<nsIChannel> channel = GetChannel();
-  if (!mMaybeServiceWorkerControlled && channel) {
-    nsLoadFlags loadFlags = 0;
-    channel->GetLoadFlags(&loadFlags);
+  if (!mMaybeServiceWorkerControlled && mDocumentContainer && mScriptGlobalObject && GetChannel()) {
+    nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+    uint32_t loadType;
+    docShell->GetLoadType(&loadType);
+
     // If we are shift-reloaded, don't associate with a ServiceWorker.
-    // TODO: This should check the nsDocShell definition of shift-reload instead
-    //       of trying to infer it from LOAD_BYPASS_CACHE.  The current code
-    //       will probably cause problems once bug 1120715 lands.
-    if (loadFlags & nsIRequest::LOAD_BYPASS_CACHE) {
+    if (IsForceReloadType(loadType)) {
       NS_WARNING("Page was shift reloaded, skipping ServiceWorker control");
       return;
     }
 
     nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
     if (swm) {
       swm->MaybeStartControlling(this);
       mMaybeServiceWorkerControlled = true;
--- a/dom/base/nsIAttribute.h
+++ b/dom/base/nsIAttribute.h
@@ -7,18 +7,18 @@
 #ifndef nsIAttribute_h___
 #define nsIAttribute_h___
 
 #include "nsINode.h"
 
 class nsDOMAttributeMap;
 
 #define NS_IATTRIBUTE_IID  \
-{ 0x233a9c4d, 0xb27f, 0x4662, \
-    { 0xbd, 0x90, 0xba, 0xd6, 0x2e, 0x76, 0xc8, 0xe1 } }
+{ 0x84d43da7, 0xb45d, 0x47ae, \
+  { 0x8f, 0xbf, 0x95, 0x26, 0x78, 0x4d, 0x5e, 0x47 } }
 
 class nsIAttribute : public nsINode
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IATTRIBUTE_IID)
 
   virtual void SetMap(nsDOMAttributeMap *aMap) = 0;
   
--- a/dom/base/nsIContent.h
+++ b/dom/base/nsIContent.h
@@ -35,18 +35,18 @@ struct IMEState;
 enum nsLinkState {
   eLinkState_Unvisited  = 1,
   eLinkState_Visited    = 2,
   eLinkState_NotLink    = 3 
 };
 
 // IID for the nsIContent interface
 #define NS_ICONTENT_IID \
-{ 0x70f7e9ea, 0xa9bf, 0x48cc, \
-  { 0xad, 0x9d, 0x8a, 0xca, 0xee, 0xd2, 0x9b, 0x68 } }
+{ 0x52cebfc8, 0x79ba, 0x4e38, \
+  { 0x8a, 0x4c, 0x7f, 0x9d, 0xb1, 0xa2, 0xb6, 0x1d } }
 
 /**
  * A node of content in a document's content model. This interface
  * is supported by all content objects.
  */
 class nsIContent : public nsINode {
 public:
   typedef mozilla::widget::IMEState IMEState;
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -150,18 +150,18 @@ template<typename> class Sequence;
 
 template<typename, typename> class CallbackObjectHolder;
 typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
 
 } // namespace dom
 } // namespace mozilla
 
 #define NS_IDOCUMENT_IID \
-{ 0x292450a1, 0x285e, 0x4a09, \
-  { 0x9a, 0xf9, 0x61, 0xf9, 0xb1, 0xbd, 0x27, 0xcc } }
+{ 0x72391609, 0x673d, 0x4bec, \
+  { 0xbd, 0x75, 0x64, 0xbf, 0x1f, 0x6a, 0x6b, 0x5e } }
 
 // Enum for requesting a particular type of document when creating a doc
 enum DocumentFlavor {
   DocumentFlavorLegacyGuess, // compat with old code until made HTML5-compliant
   DocumentFlavorHTML, // HTMLDocument with HTMLness bit set to true
   DocumentFlavorSVG, // SVGDocument
   DocumentFlavorPlain, // Just a Document
 };
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -2828,8 +2828,17 @@ nsINode::AddAnimationObserver(nsIAnimati
 
 void
 nsINode::AddAnimationObserverUnlessExists(
                                nsIAnimationObserver* aAnimationObserver)
 {
   AddMutationObserverUnlessExists(aAnimationObserver);
   OwnerDoc()->SetMayHaveAnimationObservers();
 }
+
+bool
+nsINode::HasApzAwareListeners() const
+{
+  if (NodeMayHaveApzAwareListeners()) {
+    return EventTarget::HasApzAwareListeners();
+  }
+  return false;
+}
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -245,18 +245,18 @@ private:
 
 // Categories of node properties
 // 0 is global.
 #define DOM_USER_DATA         1
 #define SMIL_MAPPED_ATTR_ANIMVAL 2
 
 // IID for the nsINode interface
 #define NS_INODE_IID \
-{ 0xe8fdd227, 0x27da, 0x46ee, \
-  { 0xbe, 0xf3, 0x1a, 0xef, 0x5a, 0x8f, 0xc5, 0xb4 } }
+{ 0x70ba4547, 0x7699, 0x44fc, \
+  { 0xb3, 0x20, 0x52, 0xdb, 0xe3, 0xd1, 0xf9, 0x0a } }
 
 /**
  * An internal interface that abstracts some DOMNode-related parts that both
  * nsIContent and nsIDocument share.  An instance of this interface has a list
  * of nsIContent children and provides access to them.
  */
 class nsINode : public mozilla::dom::EventTarget
 {
@@ -944,16 +944,19 @@ public:
   using mozilla::dom::EventTarget::RemoveEventListener;
   using nsIDOMEventTarget::AddEventListener;
   virtual void AddEventListener(const nsAString& aType,
                                 mozilla::dom::EventListener* aListener,
                                 bool aUseCapture,
                                 const mozilla::dom::Nullable<bool>& aWantsUntrusted,
                                 mozilla::ErrorResult& aRv) override;
   using nsIDOMEventTarget::AddSystemEventListener;
+
+  virtual bool HasApzAwareListeners() const override;
+
   virtual nsIDOMWindow* GetOwnerGlobalForBindings() override;
   virtual nsIGlobalObject* GetOwnerGlobal() const override;
 
   /**
    * Adds a mutation observer to be notified when this node, or any of its
    * descendants, are modified. The node will hold a weak reference to the
    * observer, which means that it is the responsibility of the observer to
    * remove itself in case it dies before the node.  If an observer is added
@@ -1490,16 +1493,18 @@ private:
     NodeHandlingClick,
     // Set if the node has had :hover selectors matched against it
     NodeHasRelevantHoverRules,
     // Set if the element has a parser insertion mode other than "in body",
     // per the HTML5 "Parse state" section.
     ElementHasWeirdParserInsertionMode,
     // Parser sets this flag if it has notified about the node.
     ParserHasNotified,
+    // EventListenerManager sets this flag in case we have apz aware listeners.
+    MayHaveApzAwareListeners,
     // Guard value
     BooleanFlagCount
   };
 
   void SetBoolFlag(BooleanFlag name, bool value) {
     static_assert(BooleanFlagCount <= 8*sizeof(mBoolFlags),
                   "Too many boolean flags");
     mBoolFlags = (mBoolFlags & ~(1 << name)) | (value << name);
@@ -1632,16 +1637,22 @@ public:
 
   void SetIsScopedStyleRoot() { SetBoolFlag(ElementIsScopedStyleRoot); }
   void ClearIsScopedStyleRoot() { ClearBoolFlag(ElementIsScopedStyleRoot); }
   bool IsScopedStyleRoot() { return GetBoolFlag(ElementIsScopedStyleRoot); }
   bool HasRelevantHoverRules() const { return GetBoolFlag(NodeHasRelevantHoverRules); }
   void SetHasRelevantHoverRules() { SetBoolFlag(NodeHasRelevantHoverRules); }
   void SetParserHasNotified() { SetBoolFlag(ParserHasNotified); };
   bool HasParserNotified() { return GetBoolFlag(ParserHasNotified); }
+
+  void SetMayHaveApzAwareListeners() { SetBoolFlag(MayHaveApzAwareListeners); }
+  bool NodeMayHaveApzAwareListeners() const
+  {
+    return GetBoolFlag(MayHaveApzAwareListeners);
+  }
 protected:
   void SetParentIsContent(bool aValue) { SetBoolFlag(ParentIsContent, aValue); }
   void SetInDocument() { SetBoolFlag(IsInDocument); }
   void SetNodeIsContent() { SetBoolFlag(NodeIsContent); }
   void ClearInDocument() { ClearBoolFlag(IsInDocument); }
   void SetIsElement() { SetBoolFlag(NodeIsElement); }
   void SetHasID() { SetBoolFlag(ElementHasID); }
   void ClearHasID() { ClearBoolFlag(ElementHasID); }
--- a/dom/base/nsPIWindowRoot.h
+++ b/dom/base/nsPIWindowRoot.h
@@ -17,18 +17,18 @@ class nsIController;
 
 namespace mozilla {
 namespace dom {
 class TabParent;
 } // namespace dom
 } // namespace mozilla
 
 #define NS_IWINDOWROOT_IID \
-{ 0x238edca0, 0xb30d, 0x46d3, \
- { 0xb2, 0x6a, 0x17, 0xb6, 0x21, 0x28, 0x89, 0x7e } }
+{ 0xb8724c49, 0xc398, 0x4f9b, \
+  { 0x82, 0x59, 0x87, 0x27, 0xa6, 0x47, 0xdd, 0x0f } }
 
 class nsPIWindowRoot : public mozilla::dom::EventTarget
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IWINDOWROOT_IID)
 
   virtual nsPIDOMWindow* GetWindow()=0;
 
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -400,16 +400,23 @@ EventListenerManager::AddEventListenerIn
              aTypeAtom == nsGkAtoms::oncompositionstart ||
              aTypeAtom == nsGkAtoms::oncompositionupdate ||
              aTypeAtom == nsGkAtoms::oninput) {
     if (!aFlags.mInSystemGroup) {
       mMayHaveInputOrCompositionEventListener = true;
     }
   }
 
+  if (IsApzAwareEvent(aTypeAtom)) {
+    nsCOMPtr<nsINode> node = do_QueryInterface(mTarget);
+    if (node) {
+      node->SetMayHaveApzAwareListeners();
+    }
+  }
+
   if (aTypeAtom && mTarget) {
     mTarget->EventListenerAdded(aTypeAtom);
   }
 
   if (mIsMainThreadELM && mTarget) {
     EventListenerService::NotifyAboutMainThreadListenerChange(mTarget,
                                                               aTypeAtom);
   }
@@ -1485,16 +1492,40 @@ EventListenerManager::TraceListeners(JST
     } else if (listener.mListenerType == Listener::eWebIDLListener) {
       mozilla::TraceScriptHolder(listener.mListener.GetWebIDLCallback(), aTrc);
     }
     // We might have eWrappedJSListener, but that is the legacy type for
     // JS implemented event listeners, and trickier to handle here.
   }
 }
 
+bool
+EventListenerManager::HasApzAwareListeners()
+{
+  uint32_t count = mListeners.Length();
+  for (uint32_t i = 0; i < count; ++i) {
+    Listener* listener = &mListeners.ElementAt(i);
+    if (IsApzAwareEvent(listener->mTypeAtom)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool
+EventListenerManager::IsApzAwareEvent(nsIAtom* aEvent)
+{
+  return aEvent == nsGkAtoms::ontouchstart ||
+         aEvent == nsGkAtoms::ontouchmove ||
+         aEvent == nsGkAtoms::onwheel ||
+         aEvent == nsGkAtoms::onDOMMouseScroll ||
+         aEvent == nsHtml5Atoms::onmousewheel ||
+         aEvent == nsGkAtoms::onMozMousePixelScroll;
+}
+
 already_AddRefed<nsIScriptGlobalObject>
 EventListenerManager::GetScriptGlobalAndDocument(nsIDocument** aDoc)
 {
   nsCOMPtr<nsINode> node(do_QueryInterface(mTarget));
   nsCOMPtr<nsIDocument> doc;
   nsCOMPtr<nsIScriptGlobalObject> global;
   if (node) {
     // Try to get context from doc
--- a/dom/events/EventListenerManager.h
+++ b/dom/events/EventListenerManager.h
@@ -438,16 +438,20 @@ public:
   }
 
   void MarkForCC();
 
   void TraceListeners(JSTracer* aTrc);
 
   dom::EventTarget* GetTarget() { return mTarget; }
 
+  bool HasApzAwareListeners();
+
+  bool IsApzAwareEvent(nsIAtom* aEvent);
+
 protected:
   void HandleEventInternal(nsPresContext* aPresContext,
                            WidgetEvent* aEvent,
                            nsIDOMEvent** aDOMEvent,
                            dom::EventTarget* aCurrentTarget,
                            nsEventStatus* aEventStatus);
 
   nsresult HandleEventSubType(Listener* aListener,
--- a/dom/events/EventTarget.cpp
+++ b/dom/events/EventTarget.cpp
@@ -51,10 +51,17 @@ EventTarget::SetEventHandler(const nsASt
 
 void
 EventTarget::SetEventHandler(nsIAtom* aType, const nsAString& aTypeString,
                              EventHandlerNonNull* aHandler)
 {
   GetOrCreateListenerManager()->SetEventHandler(aType, aTypeString, aHandler);
 }
 
+bool
+EventTarget::HasApzAwareListeners() const
+{
+  EventListenerManager* elm = GetExistingListenerManager();
+  return elm && elm->HasApzAwareListeners();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/events/EventTarget.h
+++ b/dom/events/EventTarget.h
@@ -23,18 +23,18 @@ namespace dom {
 
 class Event;
 class EventListener;
 class EventHandlerNonNull;
 template <class T> struct Nullable;
 
 // IID for the dom::EventTarget interface
 #define NS_EVENTTARGET_IID \
-{ 0x605158a9, 0xe229, 0x45b1, \
- { 0xbc, 0x12, 0x02, 0x9f, 0xa3, 0xa9, 0x3f, 0xcb } }
+{ 0xde651c36, 0x0053, 0x4c67, \
+  { 0xb1, 0x3d, 0x67, 0xb9, 0x40, 0xfc, 0x82, 0xe4 } }
 
 class EventTarget : public nsIDOMEventTarget,
                     public nsWrapperCache
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_EVENTTARGET_IID)
 
   // WebIDL API
@@ -83,16 +83,18 @@ public:
   virtual EventListenerManager* GetOrCreateListenerManager() = 0;
 
   /**
    * Get the event listener manager, returning null if it does not already
    * exist.
    */
   virtual EventListenerManager* GetExistingListenerManager() const = 0;
 
+  virtual bool HasApzAwareListeners() const;
+
 protected:
   EventHandlerNonNull* GetEventHandler(nsIAtom* aType,
                                        const nsAString& aTypeString);
   void SetEventHandler(nsIAtom* aType, const nsAString& aTypeString,
                        EventHandlerNonNull* aHandler);
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(EventTarget, NS_EVENTTARGET_IID)
--- a/dom/html/HTMLVideoElement.cpp
+++ b/dom/html/HTMLVideoElement.cpp
@@ -227,17 +227,17 @@ HTMLVideoElement::GetVideoPlaybackQualit
     if (window) {
       nsPerformance* perf = window->GetPerformance();
       if (perf) {
         creationTime = perf->Now();
       }
     }
 
     if (mDecoder) {
-      MediaDecoder::FrameStatistics& stats = mDecoder->GetFrameStatistics();
+      FrameStatistics& stats = mDecoder->GetFrameStatistics();
       totalFrames = stats.GetParsedFrames();
       droppedFrames = stats.GetDroppedFrames();
       corruptedFrames = 0;
     }
   }
 
   nsRefPtr<VideoPlaybackQuality> playbackQuality =
     new VideoPlaybackQuality(this, creationTime, totalFrames, droppedFrames,
--- a/dom/inputmethod/Keyboard.jsm
+++ b/dom/inputmethod/Keyboard.jsm
@@ -40,29 +40,31 @@ var Utils = {
   }
 };
 
 this.Keyboard = {
   _formMM: null,      // The current web page message manager.
   _keyboardMM: null,  // The keyboard app message manager.
   _keyboardID: -1,    // The keyboard app's ID number. -1 = invalid
   _nextKeyboardID: 0, // The ID number counter.
+  _systemMMs: [],     // The message managers registered to handle system async
+                      // messages.
   _supportsSwitchingTypes: [],
   _systemMessageNames: [
     'SetValue', 'RemoveFocus', 'SetSelectedOption', 'SetSelectedOptions',
-    'SetSupportsSwitchingTypes'
+    'SetSupportsSwitchingTypes', 'RegisterSync', 'Unregister'
   ],
 
   _messageNames: [
     'RemoveFocus',
     'SetSelectionRange', 'ReplaceSurroundingText', 'ShowInputMethodPicker',
     'SwitchToNextInputMethod', 'HideInputMethod',
     'GetText', 'SendKey', 'GetContext',
     'SetComposition', 'EndComposition',
-    'Register', 'Unregister'
+    'RegisterSync', 'Unregister'
   ],
 
   get formMM() {
     if (this._formMM && !Cu.isDeadWrapper(this._formMM))
       return this._formMM;
 
     return null;
   },
@@ -84,16 +86,30 @@ this.Keyboard = {
   },
 
   sendToKeyboard: function(name, data) {
     try {
       this._keyboardMM.sendAsyncMessage(name, data);
     } catch(e) { }
   },
 
+  sendToSystem: function(name, data) {
+    if (!this._systemMMs.length) {
+      dump("Keyboard.jsm: Attempt to send message " + name +
+        " to system but no message manager registered.\n");
+
+      return;
+    }
+
+    this._systemMMs.forEach((mm, i) => {
+      data.inputManageId = i;
+      mm.sendAsyncMessage(name, data);
+    });
+  },
+
   init: function keyboardInit() {
     Services.obs.addObserver(this, 'inprocess-browser-shown', false);
     Services.obs.addObserver(this, 'remote-browser-shown', false);
     Services.obs.addObserver(this, 'oop-frameloader-crashed', false);
     Services.obs.addObserver(this, 'message-manager-close', false);
 
     for (let name of this._messageNames) {
       ppmm.addMessageListener('Keyboard:' + name, this);
@@ -119,20 +135,24 @@ this.Keyboard = {
 
     if (topic == 'oop-frameloader-crashed' ||
 	      topic == 'message-manager-close') {
       if (this.formMM == mm) {
         // The application has been closed unexpectingly. Let's tell the
         // keyboard app that the focus has been lost.
         this.sendToKeyboard('Keyboard:Blur', {});
         // Notify system app to hide keyboard.
+        this.sendToSystem('System:Blur', {});
+        // XXX: To be removed when content migrate away from mozChromeEvents.
         SystemAppProxy.dispatchEvent({
           type: 'inputmethod-contextchange',
           inputType: 'blur'
         });
+
+        this.formMM = null;
       }
     } else {
       // Ignore notifications that aren't from a BrowserOrApp
       if (!frameLoader.ownerIsBrowserOrAppFrame) {
         return;
       }
       this.initFormsFrameScript(mm);
     }
@@ -188,17 +208,17 @@ this.Keyboard = {
     // if they come from a kb that we're currently not regsitered for.
     // this decision is made with the kbID kept by us and kb app
     let kbID = null;
     if ('kbID' in msg.data) {
       kbID = msg.data.kbID;
     }
 
     if (0 === msg.name.indexOf('Keyboard:') &&
-        ('Keyboard:Register' !== msg.name && this._keyboardID !== kbID)
+        ('Keyboard:RegisterSync' !== msg.name && this._keyboardID !== kbID)
        ) {
       return;
     }
 
     switch (msg.name) {
       case 'Forms:Focus':
         this.handleFocus(msg);
         break;
@@ -224,16 +244,34 @@ this.Keyboard = {
 
       case 'System:SetValue':
         this.setValue(msg);
         break;
       case 'Keyboard:RemoveFocus':
       case 'System:RemoveFocus':
         this.removeFocus();
         break;
+      case 'System:RegisterSync': {
+        if (this._systemMMs.length !== 0) {
+          dump('Keyboard.jsm Warning: There are more than one content page ' +
+            'with input-manage permission. There will be undeterministic ' +
+            'responses to addInput()/removeInput() if both content pages are ' +
+            'trying to respond to the same request event.\n');
+        }
+
+        let id = this._systemMMs.length;
+        this._systemMMs.push(mm);
+
+        return id;
+      }
+
+      case 'System:Unregister':
+        this._systemMMs.splice(msg.data.id, 1);
+
+        break;
       case 'System:SetSelectedOption':
         this.setSelectedOption(msg);
         break;
       case 'System:SetSelectedOptions':
         this.setSelectedOption(msg);
         break;
       case 'System:SetSupportsSwitchingTypes':
         this.setSupportsSwitchingTypes(msg);
@@ -260,17 +298,17 @@ this.Keyboard = {
         this.getContext(msg);
         break;
       case 'Keyboard:SetComposition':
         this.setComposition(msg);
         break;
       case 'Keyboard:EndComposition':
         this.endComposition(msg);
         break;
-      case 'Keyboard:Register':
+      case 'Keyboard:RegisterSync':
         this._keyboardMM = mm;
         if (kbID) {
           // keyboard identifies itself, use its kbID
           // this msg would be async, so no need to return
           this._keyboardID = kbID;
         }else{
           // generate the id for the keyboard
           this._keyboardID = this._nextKeyboardID;
@@ -288,20 +326,24 @@ this.Keyboard = {
   },
 
   handleFocus: function keyboardHandleFocus(msg) {
     // Set the formMM to the new message manager received.
     let mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
                 .frameLoader.messageManager;
     this.formMM = mm;
 
+    // Notify the current active input app to gain focus.
     this.forwardEvent('Keyboard:Focus', msg);
 
-    // Chrome event, used also to render value selectors; that's why we need
-    // the info about choices / min / max here as well...
+    // Notify System app, used also to render value selectors for now;
+    // that's why we need the info about choices / min / max here as well...
+    this.sendToSystem('System:Focus', msg.data);
+
+    // XXX: To be removed when content migrate away from mozChromeEvents.
     SystemAppProxy.dispatchEvent({
       type: 'inputmethod-contextchange',
       inputType: msg.data.inputType,
       value: msg.data.value,
       choices: JSON.stringify(msg.data.choices),
       min: msg.data.min,
       max: msg.data.max
     });
@@ -317,17 +359,19 @@ this.Keyboard = {
     if (mm !== this.formMM) {
       return;
     }
 
     // unset formMM
     this.formMM = null;
 
     this.forwardEvent('Keyboard:Blur', msg);
+    this.sendToSystem('System:Blur', {});
 
+    // XXX: To be removed when content migrate away from mozChromeEvents.
     SystemAppProxy.dispatchEvent({
       type: 'inputmethod-contextchange',
       inputType: 'blur'
     });
   },
 
   forwardEvent: function keyboardForwardEvent(newEventName, msg) {
     this.sendToKeyboard(newEventName, msg.data);
@@ -357,22 +401,28 @@ this.Keyboard = {
     this.sendToForm('Forms:Select:Blur', {});
   },
 
   replaceSurroundingText: function keyboardReplaceSurroundingText(msg) {
     this.sendToForm('Forms:ReplaceSurroundingText', msg.data);
   },
 
   showInputMethodPicker: function keyboardShowInputMethodPicker() {
+    this.sendToSystem('System:ShowAll', {});
+
+    // XXX: To be removed with mozContentEvent support from shell.js
     SystemAppProxy.dispatchEvent({
       type: "inputmethod-showall"
     });
   },
 
   switchToNextInputMethod: function keyboardSwitchToNextInputMethod() {
+    this.sendToSystem('System:Next', {});
+
+    // XXX: To be removed with mozContentEvent support from shell.js
     SystemAppProxy.dispatchEvent({
       type: "inputmethod-next"
     });
   },
 
   getText: function keyboardGetText(msg) {
     this.sendToForm('Forms:GetText', msg.data);
   },
@@ -427,49 +477,66 @@ this.Keyboard = {
 };
 
 function InputRegistryGlue() {
   this._messageId = 0;
   this._msgMap = new Map();
 
   ppmm.addMessageListener('InputRegistry:Add', this);
   ppmm.addMessageListener('InputRegistry:Remove', this);
+  ppmm.addMessageListener('System:InputRegistry:Add:Done', this);
+  ppmm.addMessageListener('System:InputRegistry:Remove:Done', this);
 };
 
 InputRegistryGlue.prototype.receiveMessage = function(msg) {
   let mm = Utils.getMMFromMessage(msg);
 
-  if (!Utils.checkPermissionForMM(mm, 'input')) {
+  let permName = msg.name.startsWith("System:") ? "input-mgmt" : "input";
+  if (!Utils.checkPermissionForMM(mm, permName)) {
     dump("InputRegistryGlue message " + msg.name +
-      " from a content process with no 'input' privileges.");
+      " from a content process with no " + permName + " privileges.");
     return;
   }
 
   switch (msg.name) {
     case 'InputRegistry:Add':
       this.addInput(msg, mm);
 
       break;
 
     case 'InputRegistry:Remove':
       this.removeInput(msg, mm);
 
       break;
+
+    case 'System:InputRegistry:Add:Done':
+    case 'System:InputRegistry:Remove:Done':
+      this.returnMessage(msg.data);
+
+      break;
   }
 };
 
 InputRegistryGlue.prototype.addInput = function(msg, mm) {
   let msgId = this._messageId++;
   this._msgMap.set(msgId, {
     mm: mm,
     requestId: msg.data.requestId
   });
 
   let manifestURL = appsService.getManifestURLByLocalId(msg.data.appId);
 
+  Keyboard.sendToSystem('System:InputRegistry:Add', {
+    id: msgId,
+    manifestURL: manifestURL,
+    inputId: msg.data.inputId,
+    inputManifest: msg.data.inputManifest
+  });
+
+  // XXX: To be removed when content migrate away from mozChromeEvents.
   SystemAppProxy.dispatchEvent({
     type: 'inputregistry-add',
     id: msgId,
     manifestURL: manifestURL,
     inputId: msg.data.inputId,
     inputManifest: msg.data.inputManifest
   });
 };
@@ -478,33 +545,43 @@ InputRegistryGlue.prototype.removeInput 
   let msgId = this._messageId++;
   this._msgMap.set(msgId, {
     mm: mm,
     requestId: msg.data.requestId
   });
 
   let manifestURL = appsService.getManifestURLByLocalId(msg.data.appId);
 
+  Keyboard.sendToSystem('System:InputRegistry:Remove', {
+    id: msgId,
+    manifestURL: manifestURL,
+    inputId: msg.data.inputId
+  });
+
+  // XXX: To be removed when content migrate away from mozChromeEvents.
   SystemAppProxy.dispatchEvent({
     type: 'inputregistry-remove',
     id: msgId,
     manifestURL: manifestURL,
     inputId: msg.data.inputId
   });
 };
 
 InputRegistryGlue.prototype.returnMessage = function(detail) {
   if (!this._msgMap.has(detail.id)) {
+    dump('InputRegistryGlue: Ignoring already handled message response. ' +
+         'id=' + detail.id + '\n');
     return;
   }
 
   let { mm, requestId } = this._msgMap.get(detail.id);
   this._msgMap.delete(detail.id);
 
   if (Cu.isDeadWrapper(mm)) {
+    dump('InputRegistryGlue: Message manager has already died.\n');
     return;
   }
 
   if (!('error' in detail)) {
     mm.sendAsyncMessage('InputRegistry:Result:OK', {
       requestId: requestId
     });
   } else {
--- a/dom/inputmethod/MozKeyboard.js
+++ b/dom/inputmethod/MozKeyboard.js
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
+const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
   "@mozilla.org/childprocessmessagemanager;1", "nsISyncMessageSender");
 
@@ -138,16 +139,64 @@ function MozInputMethodManager(win) {
 MozInputMethodManager.prototype = {
   supportsSwitchingForCurrentInputContext: false,
   _window: null,
 
   classID: Components.ID("{7e9d7280-ef86-11e2-b778-0800200c9a66}"),
 
   QueryInterface: XPCOMUtils.generateQI([]),
 
+  set oninputcontextfocus(handler) {
+    this.__DOM_IMPL__.setEventHandler("oninputcontextfocus", handler);
+  },
+
+  get oninputcontextfocus() {
+    return this.__DOM_IMPL__.getEventHandler("oninputcontextfocus");
+  },
+
+  set oninputcontextblur(handler) {
+    this.__DOM_IMPL__.setEventHandler("oninputcontextblur", handler);
+  },
+
+  get oninputcontextblur() {
+    return this.__DOM_IMPL__.getEventHandler("oninputcontextblur");
+  },
+
+  set onshowallrequest(handler) {
+    this.__DOM_IMPL__.setEventHandler("onshowallrequest", handler);
+  },
+
+  get onshowallrequest() {
+    return this.__DOM_IMPL__.getEventHandler("onshowallrequest");
+  },
+
+  set onnextrequest(handler) {
+    this.__DOM_IMPL__.setEventHandler("onnextrequest", handler);
+  },
+
+  get onnextrequest() {
+    return this.__DOM_IMPL__.getEventHandler("onnextrequest");
+  },
+
+  set onaddinputrequest(handler) {
+    this.__DOM_IMPL__.setEventHandler("onaddinputrequest", handler);
+  },
+
+  get onaddinputrequest() {
+    return this.__DOM_IMPL__.getEventHandler("onaddinputrequest");
+  },
+
+  set onremoveinputrequest(handler) {
+    this.__DOM_IMPL__.setEventHandler("onremoveinputrequest", handler);
+  },
+
+  get onremoveinputrequest() {
+    return this.__DOM_IMPL__.getEventHandler("onremoveinputrequest");
+  },
+
   showAll: function() {
     if (!WindowMap.isActive(this._window)) {
       return;
     }
     cpmmSendAsyncMessageWithKbID(this, 'Keyboard:ShowInputMethodPicker', {});
   },
 
   next: function() {
@@ -170,81 +219,278 @@ MozInputMethodManager.prototype = {
     }
     cpmmSendAsyncMessageWithKbID(this, 'Keyboard:RemoveFocus', {});
   },
 
   setSupportsSwitchingTypes: function(types) {
     cpmm.sendAsyncMessage('System:SetSupportsSwitchingTypes', {
       types: types
     });
+  },
+
+  handleFocus: function(data) {
+    let detail = new MozInputContextFocusEventDetail(this._window, data);
+    let wrappedDetail =
+      this._window.MozInputContextFocusEventDetail._create(this._window, detail);
+    let event = new this._window.CustomEvent('inputcontextfocus',
+      { cancelable: true, detail: wrappedDetail });
+
+    let handled = !this.__DOM_IMPL__.dispatchEvent(event);
+
+    // A gentle warning if the event is not preventDefault() by the content.
+    if (!handled) {
+      dump('MozKeyboard.js: A frame with input-manage permission did not' +
+        ' handle the inputcontextfocus event dispatched.\n');
+    }
+  },
+
+  handleBlur: function(data) {
+    let event =
+      new this._window.Event('inputcontextblur', { cancelable: true });
+
+    let handled = !this.__DOM_IMPL__.dispatchEvent(event);
+
+    // A gentle warning if the event is not preventDefault() by the content.
+    if (!handled) {
+      dump('MozKeyboard.js: A frame with input-manage permission did not' +
+        ' handle the inputcontextblur event dispatched.\n');
+    }
+  },
+
+  dispatchShowAllRequestEvent: function() {
+    this._fireSimpleEvent('showallrequest');
+  },
+
+  dispatchNextRequestEvent: function() {
+    this._fireSimpleEvent('nextrequest');
+  },
+
+  _fireSimpleEvent: function(eventType) {
+    let event = new this._window.Event(eventType);
+    let handled = !this.__DOM_IMPL__.dispatchEvent(event, { cancelable: true });
+
+    // A gentle warning if the event is not preventDefault() by the content.
+    if (!handled) {
+      dump('MozKeyboard.js: A frame with input-manage permission did not' +
+        ' handle the ' + eventType + ' event dispatched.\n');
+    }
+  },
+
+  handleAddInput: function(data) {
+    let p = this._fireInputRegistryEvent('addinputrequest', data);
+    if (!p) {
+      return;
+    }
+
+    p.then(() => {
+      cpmm.sendAsyncMessage('System:InputRegistry:Add:Done', {
+        id: data.id
+      });
+    }, (error) => {
+      cpmm.sendAsyncMessage('System:InputRegistry:Add:Done', {
+        id: data.id,
+        error: error || 'Unknown Error'
+      });
+    });
+  },
+
+  handleRemoveInput: function(data) {
+    let p = this._fireInputRegistryEvent('removeinputrequest', data);
+    if (!p) {
+      return;
+    }
+
+    p.then(() => {
+      cpmm.sendAsyncMessage('System:InputRegistry:Remove:Done', {
+        id: data.id
+      });
+    }, (error) => {
+      cpmm.sendAsyncMessage('System:InputRegistry:Remove:Done', {
+        id: data.id,
+        error: error || 'Unknown Error'
+      });
+    });
+  },
+
+  _fireInputRegistryEvent: function(eventType, data) {
+    let detail = new MozInputRegistryEventDetail(this._window, data);
+    let wrappedDetail =
+      this._window.MozInputRegistryEventDetail._create(this._window, detail);
+    let event = new this._window.CustomEvent(eventType,
+      { cancelable: true, detail: wrappedDetail });
+    let handled = !this.__DOM_IMPL__.dispatchEvent(event);
+
+    // A gentle warning if the event is not preventDefault() by the content.
+    if (!handled) {
+      dump('MozKeyboard.js: A frame with input-manage permission did not' +
+        ' handle the ' + eventType + ' event dispatched.\n');
+
+      return null;
+    }
+    return detail.takeChainedPromise();
+  }
+};
+
+function MozInputContextFocusEventDetail(win, data) {
+  this.type = data.type;
+  this.inputType = data.inputType;
+  this.value = data.value;
+  // Exposed as MozInputContextChoicesInfo dictionary defined in WebIDL
+  this.choices = data.choices;
+  this.min = data.min;
+  this.max = data.max;
+}
+MozInputContextFocusEventDetail.prototype = {
+  classID: Components.ID("{e0794208-ac50-40e8-b22e-6ee0b4c4e6e8}"),
+  QueryInterface: XPCOMUtils.generateQI([]),
+
+  type: undefined,
+  inputType: undefined,
+  value: '',
+  choices: null,
+  min: undefined,
+  max: undefined
+};
+
+function MozInputRegistryEventDetail(win, data) {
+  this._window = win;
+
+  this.manifestURL = data.manifestURL;
+  this.inputId = data.inputId;
+  // Exposed as MozInputMethodInputManifest dictionary defined in WebIDL
+  this.inputManifest = data.inputManifest;
+
+  this._chainedPromise = Promise.resolve();
+}
+MozInputRegistryEventDetail.prototype = {
+  classID: Components.ID("{02130070-9b3e-4f38-bbd9-f0013aa36717}"),
+  QueryInterface: XPCOMUtils.generateQI([]),
+
+  _window: null,
+
+  manifestURL: undefined,
+  inputId: undefined,
+  inputManifest: null,
+
+  waitUntil: function(p) {
+    // Need an extra protection here since waitUntil will be an no-op
+    // when chainedPromise is already returned.
+    if (!this._chainedPromise) {
+      throw new this._window.DOMException(
+        'Must call waitUntil() within the event handling loop.',
+        'InvalidStateError');
+    }
+
+    this._chainedPromise = this._chainedPromise
+      .then(function() { return p; });
+  },
+
+  takeChainedPromise: function() {
+    var p = this._chainedPromise;
+    this._chainedPromise = null;
+    return p;
   }
 };
 
 /**
  * ==============================================
  * InputMethod
  * ==============================================
  */
 function MozInputMethod() { }
 
 MozInputMethod.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
+  _window: null,
   _inputcontext: null,
   _wrappedInputContext: null,
+  _mgmt: null,
+  _wrappedMgmt: null,
   _supportsSwitchingTypes: [],
-  _window: null,
+  _inputManageId: undefined,
 
   classID: Components.ID("{4607330d-e7d2-40a4-9eb8-43967eae0142}"),
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIDOMGlobalPropertyInitializer,
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference
   ]),
 
   init: function mozInputMethodInit(win) {
     this._window = win;
     this._mgmt = new MozInputMethodManager(win);
+    this._wrappedMgmt = win.MozInputMethodManager._create(win, this._mgmt);
     this.innerWindowID = win.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindowUtils)
                             .currentInnerWindowID;
 
     Services.obs.addObserver(this, "inner-window-destroyed", false);
 
     cpmm.addWeakMessageListener('Keyboard:Focus', this);
     cpmm.addWeakMessageListener('Keyboard:Blur', this);
     cpmm.addWeakMessageListener('Keyboard:SelectionChange', this);
     cpmm.addWeakMessageListener('Keyboard:GetContext:Result:OK', this);
     cpmm.addWeakMessageListener('Keyboard:SupportsSwitchingTypesChange', this);
     cpmm.addWeakMessageListener('InputRegistry:Result:OK', this);
     cpmm.addWeakMessageListener('InputRegistry:Result:Error', this);
+
+    if (this._hasInputManagePerm(win)) {
+      this._inputManageId = cpmm.sendSyncMessage('System:RegisterSync', {})[0];
+      cpmm.addWeakMessageListener('System:Focus', this);
+      cpmm.addWeakMessageListener('System:Blur', this);
+      cpmm.addWeakMessageListener('System:ShowAll', this);
+      cpmm.addWeakMessageListener('System:Next', this);
+      cpmm.addWeakMessageListener('System:InputRegistry:Add', this);
+      cpmm.addWeakMessageListener('System:InputRegistry:Remove', this);
+    }
   },
 
   uninit: function mozInputMethodUninit() {
     this._window = null;
     this._mgmt = null;
+    this._wrappedMgmt = null;
 
     cpmm.removeWeakMessageListener('Keyboard:Focus', this);
     cpmm.removeWeakMessageListener('Keyboard:Blur', this);
     cpmm.removeWeakMessageListener('Keyboard:SelectionChange', this);
     cpmm.removeWeakMessageListener('Keyboard:GetContext:Result:OK', this);
     cpmm.removeWeakMessageListener('Keyboard:SupportsSwitchingTypesChange', this);
     cpmm.removeWeakMessageListener('InputRegistry:Result:OK', this);
     cpmm.removeWeakMessageListener('InputRegistry:Result:Error', this);
     this.setActive(false);
+
+    if (typeof this._inputManageId === 'number') {
+      cpmm.sendAsyncMessage('System:Unregister', {
+        'id': this._inputManageId
+      });
+      cpmm.removeWeakMessageListener('System:Focus', this);
+      cpmm.removeWeakMessageListener('System:Blur', this);
+      cpmm.removeWeakMessageListener('System:ShowAll', this);
+      cpmm.removeWeakMessageListener('System:Next', this);
+      cpmm.removeWeakMessageListener('System:InputRegistry:Add', this);
+      cpmm.removeWeakMessageListener('System:InputRegistry:Remove', this);
+    }
   },
 
   receiveMessage: function mozInputMethodReceiveMsg(msg) {
-    if (!msg.name.startsWith('InputRegistry') &&
+    if (msg.name.startsWith('Keyboard') &&
         !WindowMap.isActive(this._window)) {
       return;
     }
 
     let data = msg.data;
+
+    if (msg.name.startsWith('System') &&
+      this._inputManageId !== data.inputManageId) {
+      return;
+    }
+    delete data.inputManageId;
+
     let resolver = ('requestId' in data) ?
       this.takePromiseResolver(data.requestId) : null;
 
     switch(msg.name) {
       case 'Keyboard:Focus':
         // XXX Bug 904339 could receive 'text' event twice
         this.setInputContext(data);
         break;
@@ -267,27 +513,51 @@ MozInputMethod.prototype = {
         resolver.resolve();
 
         break;
 
       case 'InputRegistry:Result:Error':
         resolver.reject(data.error);
 
         break;
+
+      case 'System:Focus':
+        this._mgmt.handleFocus(data);
+        break;
+
+      case 'System:Blur':
+        this._mgmt.handleBlur(data);
+        break;
+
+      case 'System:ShowAll':
+        this._mgmt.dispatchShowAllRequestEvent();
+        break;
+
+      case 'System:Next':
+        this._mgmt.dispatchNextRequestEvent();
+        break;
+
+      case 'System:InputRegistry:Add':
+        this._mgmt.handleAddInput(data);
+        break;
+
+      case 'System:InputRegistry:Remove':
+        this._mgmt.handleRemoveInput(data);
+        break;
     }
   },
 
   observe: function mozInputMethodObserve(subject, topic, data) {
     let wId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
     if (wId == this.innerWindowID)
       this.uninit();
   },
 
   get mgmt() {
-    return this._mgmt;
+    return this._wrappedMgmt;
   },
 
   get inputcontext() {
     if (!WindowMap.isActive(this._window)) {
       return null;
     }
     return this._wrappedInputContext;
   },
@@ -315,18 +585,17 @@ MozInputMethod.prototype = {
       this._inputcontext = new MozInputContext(data);
       this._inputcontext.init(this._window);
       // inputcontext will be exposed as a WebIDL object. Create its
       // content-side object explicitly to avoid Bug 1001325.
       this._wrappedInputContext =
         this._window.MozInputContext._create(this._window, this._inputcontext);
     }
 
-    let event = new this._window.Event("inputcontextchange",
-                                       Cu.cloneInto({}, this._window));
+    let event = new this._window.Event("inputcontextchange");
     this.__DOM_IMPL__.dispatchEvent(event);
   },
 
   setActive: function mozInputMethodSetActive(isActive) {
     if (WindowMap.isActive(this._window) === isActive) {
       return;
     }
 
@@ -339,19 +608,19 @@ MozInputMethod.prototype = {
       // Otherwise silently ignored.
 
       // get keyboard ID from Keyboard.jsm,
       // or if we already have it, get it from our map
       // Note: if we need to get it from Keyboard.jsm,
       // we have to use a synchronous message
       var kbID = WindowMap.getKbID(this._window);
       if (kbID) {
-        cpmmSendAsyncMessageWithKbID(this, 'Keyboard:Register', {});
+        cpmmSendAsyncMessageWithKbID(this, 'Keyboard:RegisterSync', {});
       } else {
-        let res = cpmm.sendSyncMessage('Keyboard:Register', {});
+        let res = cpmm.sendSyncMessage('Keyboard:RegisterSync', {});
         WindowMap.setKbID(this._window, res[0]);
       }
 
       cpmmSendAsyncMessageWithKbID(this, 'Keyboard:GetContext', {});
     } else {
       // Deactive current input method.
       cpmmSendAsyncMessageWithKbID(this, 'Keyboard:Unregister', {});
       if (this._inputcontext) {
@@ -400,16 +669,23 @@ MozInputMethod.prototype = {
   setSelectedOptions: function(indexes) {
     cpmm.sendAsyncMessage('System:SetSelectedOptions', {
       'indexes': indexes
     });
   },
 
   removeFocus: function() {
     cpmm.sendAsyncMessage('System:RemoveFocus', {});
+  },
+
+  _hasInputManagePerm: function(win) {
+    let principal = win.document.nodePrincipal;
+    let perm = Services.perms.testExactPermissionFromPrincipal(principal,
+                                                               "input-manage");
+    return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
   }
 };
 
  /**
  * ==============================================
  * InputContextDOMRequestIpcHelper
  * ==============================================
  */
--- a/dom/inputmethod/forms.js
+++ b/dom/inputmethod/forms.js
@@ -1130,16 +1130,18 @@ function getJSON(element, focusCounter) 
   // let's return their real type even if the platform returns 'text'
   let attributeInputType = element.getAttribute("type") || "";
 
   if (attributeInputType) {
     let inputTypeLowerCase = attributeInputType.toLowerCase();
     switch (inputTypeLowerCase) {
       case "datetime":
       case "datetime-local":
+      case "month":
+      case "week":
       case "range":
         inputType = inputTypeLowerCase;
         break;
     }
   }
 
   // Gecko has some support for @inputmode but behind a preference and
   // it is disabled by default.
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/file_blank.html
@@ -0,0 +1,4 @@
+<html>
+<body>
+</body>
+</html>
deleted file mode 100644
--- a/dom/inputmethod/mochitest/file_inputmethod_1043828.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<html>
-<body>
-</body>
-</html>
--- a/dom/inputmethod/mochitest/mochitest.ini
+++ b/dom/inputmethod/mochitest/mochitest.ini
@@ -1,15 +1,15 @@
 [DEFAULT]
 # Not supported on Android, bug 983015 for B2G emulator
 skip-if = (toolkit == 'android' || toolkit == 'gonk') || e10s
 support-files =
   inputmethod_common.js
   file_inputmethod.html
-  file_inputmethod_1043828.html
+  file_blank.html
   file_test_app.html
   file_test_sendkey_cancel.html
   file_test_sms_app.html
   file_test_sms_app_1066515.html
 
 [test_basic.html]
 [test_bug944397.html]
 [test_bug949059.html]
@@ -17,14 +17,17 @@ support-files =
 [test_bug960946.html]
 [test_bug978918.html]
 [test_bug1026997.html]
 [test_bug1043828.html]
 [test_bug1059163.html]
 [test_bug1066515.html]
 [test_bug1175399.html]
 [test_bug1137557.html]
+[test_focus_blur_manage_events.html]
+[test_input_registry_events.html]
 [test_sendkey_cancel.html]
 [test_setSupportsSwitching.html]
+[test_simple_manage_events.html]
 [test_sync_edit.html]
 [test_two_inputs.html]
 [test_two_selects.html]
 [test_unload.html]
--- a/dom/inputmethod/mochitest/test_bug1043828.html
+++ b/dom/inputmethod/mochitest/test_bug1043828.html
@@ -79,17 +79,17 @@ function runTest() {
     keyboardA.setAttribute('mozbrowser', true);
     document.body.appendChild(keyboardA);
 
     keyboardB = document.createElement('iframe');
     keyboardB.setAttribute('mozbrowser', true);
     document.body.appendChild(keyboardB);
 
     // simulate two different keyboard apps
-    let imeUrl = basePath + '/file_inputmethod_1043828.html';
+    let imeUrl = basePath + '/file_blank.html';
 
     SpecialPowers.pushPermissions([{
       type: 'input',
       allow: true,
       context: {
         url: imeUrl,
         appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
         isInBrowserElement: true
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/test_focus_blur_manage_events.html
@@ -0,0 +1,230 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1201407
+-->
+<head>
+  <title>Test inputcontextfocus and inputcontextblur event</title>
+  <script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1201407">Mozilla Bug 1201407</a>
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.7">
+
+let contentFrameMM;
+
+function setupTestRunner() {
+  info('setupTestRunner');
+  let im = navigator.mozInputMethod;
+
+  let expectedEventDetails = [
+    { type: 'input', inputType: 'text' },
+    { type: 'input', inputType: 'search' },
+    { type: 'textarea', inputType: 'textarea' },
+    { type: 'contenteditable', inputType: 'textarea' },
+    { type: 'input', inputType: 'number' },
+    { type: 'input', inputType: 'tel' },
+    { type: 'input', inputType: 'url' },
+    { type: 'input', inputType: 'email' },
+    { type: 'input', inputType: 'password' },
+    { type: 'input', inputType: 'datetime' },
+    { type: 'input', inputType: 'date',
+      value: '2015-08-03', min: '1990-01-01', max: '2020-01-01' },
+    { type: 'input', inputType: 'month' },
+    { type: 'input', inputType: 'week' },
+    { type: 'input', inputType: 'time' },
+    { type: 'input', inputType: 'datetime-local' },
+    { type: 'input', inputType: 'color' },
+    { type: 'select', inputType: 'select-one',
+      choices: {
+        multiple: false,
+        choices: [
+          { group: false, inGroup: false, text: 'foo',
+            disabled: false, selected: true, optionIndex: 0 },
+          { group: false, inGroup: false, text: 'bar',
+            disabled: true, selected: false, optionIndex: 1 },
+          { group: true, text: 'group', disabled: false },
+          { group: false, inGroup: true, text: 'baz',
+            disabled: false, selected: false, optionIndex: 2 } ] }
+    },
+    { type: 'select', inputType: 'select-multiple',
+      choices: {
+        multiple: true,
+        choices: [
+          { group: false, inGroup: false, text: 'foo',
+            disabled: false, selected: true, optionIndex: 0 },
+          { group: false, inGroup: false, text: 'bar',
+            disabled: true, selected: false, optionIndex: 1 },
+          { group: true, text: 'group', disabled: false },
+          { group: false, inGroup: true, text: 'baz',
+            disabled: false, selected: false, optionIndex: 2 } ] }
+    }
+  ];
+
+  let expectBlur = false;
+
+  function deepAssertObject(obj, expectedObj, desc) {
+    for (let prop in expectedObj) {
+      if (typeof expectedObj[prop] === 'object') {
+        deepAssertObject(obj[prop], expectedObj[prop], desc + '.' + prop);
+      } else {
+        is(obj[prop], expectedObj[prop], desc + '.' + prop);
+      }
+    }
+  }
+
+  im.mgmt.oninputcontextfocus =
+  im.mgmt.oninputcontextblur = function(evt) {
+    if (expectBlur) {
+      is(evt.type, 'inputcontextblur', 'evt.type');
+      evt.preventDefault();
+      expectBlur = false;
+
+      return;
+    }
+
+    let expectedEventDetail = expectedEventDetails.shift();
+
+    if (!expectedEventDetail) {
+        ok(false, 'Receving extra events');
+        inputmethod_cleanup();
+
+      return;
+    }
+
+    is(evt.type, 'inputcontextfocus', 'evt.type');
+    evt.preventDefault();
+    expectBlur = true;
+
+    let detail = evt.detail;
+    deepAssertObject(detail, expectedEventDetail, 'detail');
+
+    if (expectedEventDetails.length) {
+      contentFrameMM.sendAsyncMessage('test:next');
+    } else {
+      im.mgmt.oninputcontextfocus = im.mgmt.oninputcontextblur = null;
+      inputmethod_cleanup();
+    }
+  };
+}
+
+function setupInputAppFrame() {
+  info('setupInputAppFrame');
+  return new Promise((resolve, reject) => {
+    let appFrameScript = function appFrameScript() {
+      let im = content.navigator.mozInputMethod;
+
+      im.mgmt.oninputcontextfocus =
+      im.mgmt.oninputcontextblur = function(evt) {
+        sendAsyncMessage('text:appEvent', { type: evt.type });
+      };
+
+      content.document.body.textContent = 'I am a input app';
+    };
+
+    let path = location.pathname;
+    let basePath = location.protocol + '//' + location.host +
+                 path.substring(0, path.lastIndexOf('/'));
+    let imeUrl = basePath + '/file_blank.html';
+
+    let inputAppFrame = document.createElement('iframe');
+    inputAppFrame.setAttribute('mozbrowser', true);
+    inputAppFrame.src = imeUrl;
+    document.body.appendChild(inputAppFrame);
+
+    SpecialPowers.pushPermissions([{
+      type: 'input',
+      allow: true,
+      context: {
+        url: imeUrl,
+        appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
+        isInBrowserElement: true
+      }
+    }], function() {
+      let mm = SpecialPowers.getBrowserFrameMessageManager(inputAppFrame);
+      inputAppFrame.addEventListener('mozbrowserloadend', function() {
+        mm.addMessageListener('text:appEvent', function(msg) {
+          ok(false, 'Input app should not receive ' + msg.data.type + ' event.');
+        });
+        mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
+
+        // Set the input app frame to be active
+        let req = inputAppFrame.setInputMethodActive(true);
+        resolve(req);
+      });
+    });
+  });
+}
+
+function setupContentFrame() {
+  info('setupContentFrame');
+  return new Promise((resolve, reject) => {
+    let contentFrameScript = function contentFrameScript() {
+      let input = content.document.body.firstElementChild;
+
+      let i = 0;
+
+      input.focus();
+
+      addMessageListener('test:next', function() {
+        content.document.body.children[++i].focus();
+      });
+    };
+
+    let iframe = document.createElement('iframe');
+    iframe.src = 'data:text/html,<html><body>' +
+      '<input type="text">' +
+      '<input type="search">' +
+      '<textarea></textarea>' +
+      '<p contenteditable></p>' +
+      '<input type="number">' +
+      '<input type="tel">' +
+      '<input type="url">' +
+      '<input type="email">' +
+      '<input type="password">' +
+      '<input type="datetime">' +
+      '<input type="date" value="2015-08-03" min="1990-01-01" max="2020-01-01">' +
+      '<input type="month">' +
+      '<input type="week">' +
+      '<input type="time">' +
+      '<input type="datetime-local">' +
+      '<input type="color">' +
+      '<select><option selected>foo</option><option disabled>bar</option>' +
+        '<optgroup label="group"><option>baz</option></optgroup></select>' +
+      '<select multiple><option selected>foo</option><option disabled>bar</option>' +
+        '<optgroup label="group"><option>baz</option></optgroup></select>' +
+      '</body></html>';
+    iframe.setAttribute('mozbrowser', true);
+    document.body.appendChild(iframe);
+
+    let mm = contentFrameMM =
+      SpecialPowers.getBrowserFrameMessageManager(iframe);
+
+    iframe.addEventListener('mozbrowserloadend', function() {
+      mm.loadFrameScript('data:,(' + encodeURIComponent(contentFrameScript.toString()) + ')();', false);
+
+      resolve();
+    });
+  });
+}
+
+inputmethod_setup(function() {
+  Promise.resolve()
+    .then(() => setupTestRunner())
+    .then(() => setupContentFrame())
+    .then(() => setupInputAppFrame())
+    .catch((e) => {
+      ok(false, 'Error' + e.toString());
+      console.error(e);
+    });
+});
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/test_input_registry_events.html
@@ -0,0 +1,259 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1201407
+-->
+<head>
+  <title>Test addinputrequest and removeinputrequest event</title>
+  <script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1201407">Mozilla Bug 1201407</a>
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.7">
+
+let appFrameMM;
+let nextStep;
+
+function setupInputAppFrame() {
+  info('setupInputAppFrame');
+  return new Promise((resolve, reject) => {
+    let appFrameScript = function appFrameScript() {
+      let im = content.navigator.mozInputMethod;
+
+      addMessageListener('test:callAddInput', function() {
+        im.addInput('foo', {
+            launch_path: 'bar.html',
+            name: 'Foo',
+            description: 'foobar',
+            types: ['text', 'password']
+          })
+          .then((r) => {
+              sendAsyncMessage('test:resolved', { resolved: true, result: r });
+            }, (e) => {
+              sendAsyncMessage('test:rejected', { rejected: true, error: e });
+            });
+      });
+
+      addMessageListener('test:callRemoveInput', function() {
+        im.removeInput('foo')
+          .then((r) => {
+              sendAsyncMessage('test:resolved', { resolved: true, result: r });
+            }, (e) => {
+              sendAsyncMessage('test:rejected', { rejected: true, error: e });
+            });
+      });
+
+      im.mgmt.onaddinputrequest =
+      im.mgmt.onremoveinputrequest = function(evt) {
+        sendAsyncMessage('test:appEvent', { type: evt.type });
+      };
+
+      content.document.body.textContent = 'I am a input app';
+    };
+
+    let path = location.pathname;
+    let basePath = location.protocol + '//' + location.host +
+                 path.substring(0, path.lastIndexOf('/'));
+    let imeUrl = basePath + '/file_blank.html';
+
+    let inputAppFrame = document.createElement('iframe');
+    inputAppFrame.setAttribute('mozbrowser', true);
+    inputAppFrame.src = imeUrl;
+    document.body.appendChild(inputAppFrame);
+
+    SpecialPowers.pushPermissions([{
+      type: 'input',
+      allow: true,
+      context: {
+        url: imeUrl,
+        appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
+        isInBrowserElement: true
+      }
+    }], function() {
+      let mm = appFrameMM =
+        SpecialPowers.getBrowserFrameMessageManager(inputAppFrame);
+
+      inputAppFrame.addEventListener('mozbrowserloadend', function() {
+        mm.addMessageListener('test:appEvent', function(msg) {
+          ok(false, 'Input app should not receive ' + msg.data.type + ' event.');
+        });
+        mm.addMessageListener('test:resolved', function(msg) {
+          nextStep && nextStep(msg.data);
+        });
+        mm.addMessageListener('test:rejected', function(msg) {
+          nextStep && nextStep(msg.data);
+        });
+        mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
+
+        resolve();
+      });
+    });
+  });
+}
+
+function Deferred() {
+  this.promise = new Promise((res, rej) => {
+    this.resolve = res;
+    this.reject = rej;
+  });
+  return this;
+}
+
+function deepAssertObject(obj, expectedObj, desc) {
+  for (let prop in expectedObj) {
+    if (typeof expectedObj[prop] === 'object') {
+      deepAssertObject(obj[prop], expectedObj[prop], desc + '.' + prop);
+    } else {
+      is(obj[prop], expectedObj[prop], desc + '.' + prop);
+    }
+  }
+}
+
+function setupTestRunner() {
+  let im = navigator.mozInputMethod;
+  let d;
+
+  let i = -1;
+  nextStep = function next(evt) {
+    i++;
+    info('Step ' + i);
+
+    switch (i) {
+      case 0:
+        appFrameMM.sendAsyncMessage('test:callAddInput');
+
+        break;
+
+      case 1:
+        is(evt.type, 'addinputrequest', 'evt.type');
+        deepAssertObject(evt.detail, {
+          inputId: 'foo',
+          manifestURL: null, // todo
+          inputManifest: {
+            launch_path: 'bar.html',
+            name: 'Foo',
+            description: 'foobar',
+            types: ['text', 'password']
+          }
+        }, 'detail');
+
+        d = new Deferred();
+        evt.detail.waitUntil(d.promise);
+        evt.preventDefault();
+
+        Promise.resolve().then(next);
+        break;
+
+      case 2:
+        d.resolve();
+        d = null;
+        break;
+
+      case 3:
+        ok(evt.resolved, 'resolved');
+        appFrameMM.sendAsyncMessage('test:callAddInput');
+
+        break;
+
+      case 4:
+        is(evt.type, 'addinputrequest', 'evt.type');
+
+        d = new Deferred();
+        evt.detail.waitUntil(d.promise);
+        evt.preventDefault();
+
+        Promise.resolve().then(next);
+        break;
+
+      case 5:
+        d.reject('Foo Error');
+        d = null;
+        break;
+
+      case 6:
+        ok(evt.rejected, 'rejected');
+        is(evt.error, 'Foo Error', 'rejected');
+
+
+        appFrameMM.sendAsyncMessage('test:callRemoveInput');
+
+        break;
+
+      case 7:
+        is(evt.type, 'removeinputrequest', 'evt.type');
+        deepAssertObject(evt.detail, {
+          inputId: 'foo',
+          manifestURL: null // todo
+        }, 'detail');
+
+        d = new Deferred();
+        evt.detail.waitUntil(d.promise);
+        evt.preventDefault();
+
+        Promise.resolve().then(next);
+        break;
+
+      case 8:
+        d.resolve();
+        d = null;
+        break;
+
+      case 9:
+        ok(evt.resolved, 'resolved');
+        appFrameMM.sendAsyncMessage('test:callRemoveInput');
+
+        break;
+
+      case 10:
+        is(evt.type, 'removeinputrequest', 'evt.type');
+
+        d = new Deferred();
+        evt.detail.waitUntil(d.promise);
+        evt.preventDefault();
+
+        Promise.resolve().then(next);
+        break;
+
+      case 11:
+        d.reject('Foo Error');
+        d = null;
+        break;
+
+      case 12:
+        ok(evt.rejected, 'rejected');
+        is(evt.error, 'Foo Error', 'rejected');
+        inputmethod_cleanup();
+
+        break;
+
+      default:
+        ok(false, 'received extra call.');
+        inputmethod_cleanup();
+
+        break;
+    }
+  }
+
+  im.mgmt.onaddinputrequest =
+  im.mgmt.onremoveinputrequest = nextStep;
+}
+
+inputmethod_setup(function() {
+  Promise.resolve()
+    .then(() => setupTestRunner())
+    .then(() => setupInputAppFrame())
+    .then(() => nextStep())
+    .catch((e) => {
+      ok(false, 'Error' + e.toString());
+      console.error(e);
+    });
+});
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/test_simple_manage_events.html
@@ -0,0 +1,164 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1201407
+-->
+<head>
+  <title>Test simple manage notification events on MozInputMethodManager</title>
+  <script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1201407">Mozilla Bug 1201407</a>
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.7">
+
+let appFrameMM;
+let nextStep;
+
+function setupTestRunner() {
+  info('setupTestRunner');
+  let im = navigator.mozInputMethod;
+
+  let i = 0;
+  im.mgmt.onshowallrequest =
+  im.mgmt.onnextrequest = nextStep = function(evt) {
+    i++;
+    switch (i) {
+      case 1:
+        is(evt.type, 'inputcontextchange', '1) inputcontextchange event');
+        appFrameMM.sendAsyncMessage('test:callShowAll');
+
+        break;
+
+      case 2:
+        is(evt.type, 'showallrequest', '2) showallrequest event');
+        ok(evt.target, im.mgmt, '2) evt.target');
+        evt.preventDefault();
+
+        appFrameMM.sendAsyncMessage('test:callNext');
+
+        break;
+
+      case 3:
+        is(evt.type, 'nextrequest', '3) nextrequest event');
+        ok(evt.target, im.mgmt, '3) evt.target');
+        evt.preventDefault();
+
+        im.mgmt.onshowallrequest =
+        im.mgmt.onnextrequest = nextStep = null;
+
+        inputmethod_cleanup();
+        break;
+
+      default:
+        ok(false, 'Receving extra events');
+        inputmethod_cleanup();
+
+        break;
+    }
+  };
+}
+
+function setupInputAppFrame() {
+  info('setupInputAppFrame');
+  return new Promise((resolve, reject) => {
+    let appFrameScript = function appFrameScript() {
+      let im = content.navigator.mozInputMethod;
+
+      addMessageListener('test:callShowAll', function() {
+        im.mgmt.showAll();
+      });
+
+      addMessageListener('test:callNext', function() {
+        im.mgmt.next();
+      });
+
+      im.mgmt.onshowallrequest =
+      im.mgmt.onnextrequest = function(evt) {
+        sendAsyncMessage('test:appEvent', { type: evt.type });
+      };
+
+      im.oninputcontextchange = function(evt) {
+        sendAsyncMessage('test:inputcontextchange', {});
+      };
+
+      content.document.body.textContent = 'I am a input app';
+    };
+
+    let path = location.pathname;
+    let basePath = location.protocol + '//' + location.host +
+                 path.substring(0, path.lastIndexOf('/'));
+    let imeUrl = basePath + '/file_blank.html';
+
+    let inputAppFrame = document.createElement('iframe');
+    inputAppFrame.setAttribute('mozbrowser', true);
+    inputAppFrame.src = imeUrl;
+    document.body.appendChild(inputAppFrame);
+
+    SpecialPowers.pushPermissions([{
+      type: 'input',
+      allow: true,
+      context: {
+        url: imeUrl,
+        appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
+        isInBrowserElement: true
+      }
+    }], function() {
+      let mm = appFrameMM =
+        SpecialPowers.getBrowserFrameMessageManager(inputAppFrame);
+
+      inputAppFrame.addEventListener('mozbrowserloadend', function() {
+        mm.addMessageListener('test:appEvent', function(msg) {
+          ok(false, 'Input app should not receive ' + msg.data.type + ' event.');
+        });
+        mm.addMessageListener('test:inputcontextchange', function(msg) {
+          nextStep && nextStep({ type: 'inputcontextchange' });
+        });
+        mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
+
+        // Set the input app frame to be active
+        let req = inputAppFrame.setInputMethodActive(true);
+        resolve(req);
+      });
+    });
+  });
+}
+
+function setupContentFrame() {
+  let contentFrameScript = function contentFrameScript() {
+    let input = content.document.body.firstElementChild;
+
+    input.focus();
+  };
+
+  let iframe = document.createElement('iframe');
+  iframe.src = 'data:text/html,<html><body><input type="text"></body></html>';
+  iframe.setAttribute('mozbrowser', true);
+  document.body.appendChild(iframe);
+
+  let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
+
+  iframe.addEventListener('mozbrowserloadend', function() {
+    mm.loadFrameScript('data:,(' + encodeURIComponent(contentFrameScript.toString()) + ')();', false);
+  });
+}
+
+inputmethod_setup(function() {
+  Promise.resolve()
+    .then(() => setupTestRunner())
+    .then(() => setupContentFrame())
+    .then(() => setupInputAppFrame())
+    .catch((e) => {
+      ok(false, 'Error' + e.toString());
+      console.error(e);
+    });
+});
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/media/FrameStatistics.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FrameStatistics_h_
+#define FrameStatistics_h_
+
+namespace mozilla {
+
+// Frame decoding/painting related performance counters.
+// Threadsafe.
+class FrameStatistics {
+public:
+  FrameStatistics() :
+      mReentrantMonitor("FrameStats"),
+      mParsedFrames(0),
+      mDecodedFrames(0),
+      mPresentedFrames(0),
+      mDroppedFrames(0),
+      mCorruptFrames(0) {}
+
+  // Returns number of frames which have been parsed from the media.
+  // Can be called on any thread.
+  uint32_t GetParsedFrames() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return mParsedFrames;
+  }
+
+  // Returns the number of parsed frames which have been decoded.
+  // Can be called on any thread.
+  uint32_t GetDecodedFrames() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return mDecodedFrames;
+  }
+
+  // Returns the number of decoded frames which have been sent to the rendering
+  // pipeline for painting ("presented").
+  // Can be called on any thread.
+  uint32_t GetPresentedFrames() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return mPresentedFrames;
+  }
+
+  // Number of frames that have been skipped because they have missed their
+  // compoisition deadline.
+  uint32_t GetDroppedFrames() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return mDroppedFrames + mCorruptFrames;
+  }
+
+  uint32_t GetCorruptedFrames() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return mCorruptFrames;
+  }
+
+  // Increments the parsed and decoded frame counters by the passed in counts.
+  // Can be called on any thread.
+  void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
+                           uint32_t aDropped) {
+    if (aParsed == 0 && aDecoded == 0 && aDropped == 0)
+      return;
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    mParsedFrames += aParsed;
+    mDecodedFrames += aDecoded;
+    mDroppedFrames += aDropped;
+  }
+
+  // Increments the presented frame counters.
+  // Can be called on any thread.
+  void NotifyPresentedFrame() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    ++mPresentedFrames;
+  }
+
+  void NotifyCorruptFrame() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    ++mCorruptFrames;
+  }
+
+private:
+
+  // ReentrantMonitor to protect access of playback statistics.
+  ReentrantMonitor mReentrantMonitor;
+
+  // Number of frames parsed and demuxed from media.
+  // Access protected by mReentrantMonitor.
+  uint32_t mParsedFrames;
+
+  // Number of parsed frames which were actually decoded.
+  // Access protected by mReentrantMonitor.
+  uint32_t mDecodedFrames;
+
+  // Number of decoded frames which were actually sent down the rendering
+  // pipeline to be painted ("presented"). Access protected by mReentrantMonitor.
+  uint32_t mPresentedFrames;
+
+  uint32_t mDroppedFrames;
+
+  uint32_t mCorruptFrames;
+};
+
+} // namespace mozilla
+
+#endif
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -198,16 +198,17 @@ destroying the MediaDecoder object.
 #include "necko-config.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
 #include "nsISupports.h"
 #include "nsITimer.h"
 
 #include "AbstractMediaDecoder.h"
+#include "FrameStatistics.h"
 #include "MediaDecoderOwner.h"
 #include "MediaEventSource.h"
 #include "MediaMetadataManager.h"
 #include "MediaResource.h"
 #include "MediaStatistics.h"
 #include "MediaStreamGraph.h"
 #include "TimeUnits.h"
 
@@ -716,109 +717,16 @@ public:
 #endif
 
   // Return statistics. This is used for progress events and other things.
   // This can be called from any thread. It's only a snapshot of the
   // current state, since other threads might be changing the state
   // at any time.
   MediaStatistics GetStatistics();
 
-  // Frame decoding/painting related performance counters.
-  // Threadsafe.
-  class FrameStatistics {
-  public:
-
-    FrameStatistics() :
-        mReentrantMonitor("MediaDecoder::FrameStats"),
-        mParsedFrames(0),
-        mDecodedFrames(0),
-        mPresentedFrames(0),
-        mDroppedFrames(0),
-        mCorruptFrames(0) {}
-
-    // Returns number of frames which have been parsed from the media.
-    // Can be called on any thread.
-    uint32_t GetParsedFrames() {
-      ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-      return mParsedFrames;
-    }
-
-    // Returns the number of parsed frames which have been decoded.
-    // Can be called on any thread.
-    uint32_t GetDecodedFrames() {
-      ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-      return mDecodedFrames;
-    }
-
-    // Returns the number of decoded frames which have been sent to the rendering
-    // pipeline for painting ("presented").
-    // Can be called on any thread.
-    uint32_t GetPresentedFrames() {
-      ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-      return mPresentedFrames;
-    }
-
-    // Number of frames that have been skipped because they have missed their
-    // compoisition deadline.
-    uint32_t GetDroppedFrames() {
-      ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-      return mDroppedFrames + mCorruptFrames;
-    }
-
-    uint32_t GetCorruptedFrames() {
-      ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-      return mCorruptFrames;
-    }
-
-    // Increments the parsed and decoded frame counters by the passed in counts.
-    // Can be called on any thread.
-    void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
-                             uint32_t aDropped) {
-      if (aParsed == 0 && aDecoded == 0 && aDropped == 0)
-        return;
-      ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-      mParsedFrames += aParsed;
-      mDecodedFrames += aDecoded;
-      mDroppedFrames += aDropped;
-    }
-
-    // Increments the presented frame counters.
-    // Can be called on any thread.
-    void NotifyPresentedFrame() {
-      ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-      ++mPresentedFrames;
-    }
-
-    void NotifyCorruptFrame() {
-      ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-      ++mCorruptFrames;
-    }
-
-  private:
-
-    // ReentrantMonitor to protect access of playback statistics.
-    ReentrantMonitor mReentrantMonitor;
-
-    // Number of frames parsed and demuxed from media.
-    // Access protected by mReentrantMonitor.
-    uint32_t mParsedFrames;
-
-    // Number of parsed frames which were actually decoded.
-    // Access protected by mReentrantMonitor.
-    uint32_t mDecodedFrames;
-
-    // Number of decoded frames which were actually sent down the rendering
-    // pipeline to be painted ("presented"). Access protected by mReentrantMonitor.
-    uint32_t mPresentedFrames;
-
-    uint32_t mDroppedFrames;
-
-    uint32_t mCorruptFrames;
-  };
-
   // Return the frame decode/paint related statistics.
   FrameStatistics& GetFrameStatistics() { return mFrameStats; }
 
   // Increments the parsed and decoded frame counters by the passed in counts.
   // Can be called on any thread.
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped) override
   {
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -2493,17 +2493,17 @@ bool MediaDecoderStateMachine::CheckFram
   // If we've sent this frame before then only return the valid state,
   // don't update the statistics.
   if (aData->mSentToCompositor) {
     return !aData->mImage || aData->mImage->IsValid();
   }
 
   // Update corrupt-frames statistics
   if (aData->mImage && !aData->mImage->IsValid()) {
-    MediaDecoder::FrameStatistics& frameStats = mDecoder->GetFrameStatistics();
+    FrameStatistics& frameStats = mDecoder->GetFrameStatistics();
     frameStats.NotifyCorruptFrame();
     // If more than 10% of the last 30 frames have been corrupted, then try disabling
     // hardware acceleration. We use 10 as the corrupt value because RollingMean<>
     // only supports integer types.
     mCorruptFrames.insert(10);
     if (mReader->VideoIsHardwareAccelerated() &&
         frameStats.GetPresentedFrames() > 60 &&
         mCorruptFrames.mean() >= 2 /* 20% */) {
@@ -2630,17 +2630,17 @@ void MediaDecoderStateMachine::UpdateRen
                     currentFrame->mTime, clockTime);
       }
       currentFrame = VideoQueue().PopFront();
 
     }
     VideoQueue().PushFront(currentFrame);
     if (framesRemoved > 0) {
       mVideoFrameEndTime = currentFrame->GetEndTime();
-      MediaDecoder::FrameStatistics& frameStats = mDecoder->GetFrameStatistics();
+      FrameStatistics& frameStats = mDecoder->GetFrameStatistics();
       frameStats.NotifyPresentedFrame();
     }
   }
 
   RenderVideoFrames(sVideoQueueSendToCompositorSize, clockTime, nowTime);
 
   // Cap the current time to the larger of the audio and video end time.
   // This ensures that if we're running off the system clock, we don't
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -28,16 +28,17 @@
 #include "nsNetUtil.h"
 #include "nsPrincipal.h"
 #include "nsICryptoHash.h"
 #include "nsICryptoHMAC.h"
 #include "nsIKeyModule.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsIInputStream.h"
 #include "nsILineInputStream.h"
+#include "mozilla/Telemetry.h"
 #include "mozilla/Types.h"
 #include "mozilla/PeerIdentity.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "mozilla/dom/GetUserMediaRequestBinding.h"
 #include "mozilla/Preferences.h"
@@ -1574,16 +1575,26 @@ nsresult MediaManager::GenerateUUID(nsAS
   NS_ENSURE_SUCCESS(rv, rv);
 
   char buffer[NSID_LENGTH];
   id.ToProvidedString(buffer);
   aResult.Assign(NS_ConvertUTF8toUTF16(buffer));
   return NS_OK;
 }
 
+enum class GetUserMediaSecurityState {
+  Other = 0,
+  HTTPS = 1,
+  File = 2,
+  App = 3,
+  Localhost = 4,
+  Loop = 5,
+  Privileged = 6
+};
+
 /**
  * The entry point for this file. A call from Navigator::mozGetUserMedia
  * will end up here. MediaManager is a singleton that is responsible
  * for handling all incoming getUserMedia calls from every window.
  */
 nsresult
 MediaManager::GetUserMedia(nsPIDOMWindow* aWindow,
                            const MediaStreamConstraints& aConstraintsPassedIn,
@@ -1625,19 +1636,57 @@ MediaManager::GetUserMedia(nsPIDOMWindow
   nsIURI* docURI = aWindow->GetDocumentURI();
   if (!docURI) {
     return NS_ERROR_UNEXPECTED;
   }
   bool loop = IsLoop(docURI);
   bool privileged = loop || IsPrivileged();
   bool isHTTPS = false;
   docURI->SchemeIs("https", &isHTTPS);
+  nsCString host;
+  nsresult rv = docURI->GetHost(host);
+  // Test for some other schemes that ServiceWorker recognizes
+  bool isFile;
+  docURI->SchemeIs("file", &isFile);
+  bool isApp;
+  docURI->SchemeIs("app", &isApp);
+  // Same localhost check as ServiceWorkers uses
+  // (see IsFromAuthenticatedOriginInternal())
+  bool isLocalhost = NS_SUCCEEDED(rv) &&
+                     (host.LowerCaseEqualsLiteral("localhost") ||
+                      host.LowerCaseEqualsLiteral("127.0.0.1") ||
+                      host.LowerCaseEqualsLiteral("::1"));
+
+  // Record telemetry about whether the source of the call was secure, i.e.,
+  // privileged or HTTPS.  We may handle other cases
+  if (loop) {
+    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
+                          (uint32_t) GetUserMediaSecurityState::Loop);
+  } else if (privileged) {
+    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
+                          (uint32_t) GetUserMediaSecurityState::Privileged);
+  } else if (isHTTPS) {
+    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
+                          (uint32_t) GetUserMediaSecurityState::HTTPS);
+  } else if (isFile) {
+    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
+                          (uint32_t) GetUserMediaSecurityState::File);
+  } else if (isApp) {
+    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
+                          (uint32_t) GetUserMediaSecurityState::App);
+  } else if (isLocalhost) {
+    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
+                          (uint32_t) GetUserMediaSecurityState::Localhost);
+  } else {
+    Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
+                          (uint32_t) GetUserMediaSecurityState::Other);
+  }
 
   nsCString origin;
-  nsresult rv = nsPrincipal::GetOriginForURI(docURI, origin);
+  rv = nsPrincipal::GetOriginForURI(docURI, origin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
     c.mVideo.SetAsBoolean() = false;
   }
 
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -107,16 +107,17 @@ EXPORTS += [
     'AudioStream.h',
     'BufferMediaResource.h',
     'CubebUtils.h',
     'DecoderTraits.h',
     'DOMMediaStream.h',
     'EncodedBufferCache.h',
     'FileBlockCache.h',
     'FlushableTaskQueue.h',
+    'FrameStatistics.h',
     'Intervals.h',
     'Latency.h',
     'MediaCache.h',
     'MediaData.h',
     'MediaDataDemuxer.h',
     'MediaDecoder.h',
     'MediaDecoderOwner.h',
     'MediaDecoderReader.h',
--- a/dom/media/ogg/OggReader.cpp
+++ b/dom/media/ogg/OggReader.cpp
@@ -467,19 +467,19 @@ nsresult OggReader::ReadMetadata(MediaIn
   }
 
   SetupTargetSkeleton(mSkeletonState);
   SetupMediaTracksInfo(serials);
 
   if (HasAudio() || HasVideo()) {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
-    if (mInfo.mMetadataDuration.isNothing() && !mDecoder->IsOggDecoderShutdown() &&
-        mResource.GetLength() >= 0 && mDecoder->IsMediaSeekable())
-    {
+    if (mInfo.mMetadataDuration.isNothing() &&
+        !mDecoder->IsOggDecoderShutdown() &&
+        mResource.GetLength() >= 0) {
       // We didn't get a duration from the index or a Content-Duration header.
       // Seek to the end of file to find the end time.
       int64_t length = mResource.GetLength();
 
       NS_ASSERTION(length > 0, "Must have a content length to get end time");
 
       int64_t endTime = 0;
       {
--- a/dom/media/systemservices/MediaParent.cpp
+++ b/dom/media/systemservices/MediaParent.cpp
@@ -540,16 +540,17 @@ AllocPMediaParent()
   return sIPCServingParent;
 }
 
 bool
 DeallocPMediaParent(media::PMediaParent *aActor)
 {
   MOZ_ASSERT(sIPCServingParent == static_cast<Parent<PMediaParent>*>(aActor));
   delete sIPCServingParent;
+  sIPCServingParent = nullptr;
   return true;
 }
 
 } // namespace media
 } // namespace mozilla
 
 // Instantiate templates to satisfy linker
 template class mozilla::media::Parent<mozilla::media::NonE10s>;
--- a/dom/media/tests/mochitest/test_peerConnection_addIceCandidate.html
+++ b/dom/media/tests/mochitest/test_peerConnection_addIceCandidate.html
@@ -3,17 +3,17 @@
 <head>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1087551",
-    title: "addCandidate behavior in different states"
+    title: "addIceCandidate behavior (local and remote) including invalid data"
   });
 
   var test;
   runNetworkTest(function () {
     test = new PeerConnectionTest();
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.chain.removeAfter("PC_LOCAL_GET_ANSWER");
 
@@ -66,16 +66,38 @@
         );
       },
       function PC_REMOTE_ADD_VALID_CANDIDATE(test) {
         var candidate = new mozRTCIceCandidate(
           {candidate:"candidate:1 1 UDP 2130706431 192.168.2.1 50005 typ host",
            sdpMLineIndex: 0});
         return test.pcRemote._pc.addIceCandidate(candidate)
         .then(ok(true, "Successfully added valid ICE candidate"));
+      },
+      // bug 1095793
+      function PC_REMOTE_ADD_MISMATCHED_MID_AND_LEVEL_CANDIDATE(test) {
+        var bogus = new mozRTCIceCandidate(
+          {candidate:"candidate:1 1 UDP 2130706431 192.168.2.1 50005 typ host",
+           sdpMLineIndex: 0,
+           sdpMid: "sdparta_1"});
+        return test.pcRemote._pc.addIceCandidate(bogus)
+        .then(
+          generateErrorCallback("addIceCandidate should have failed."),
+          err => {
+            is(err.name, "InvalidCandidateError", "Error is InvalidCandidateError");
+          }
+        );
+      },
+      function PC_REMOTE_ADD_MATCHING_MID_AND_LEVEL_CANDIDATE(test) {
+        var candidate = new mozRTCIceCandidate(
+          {candidate:"candidate:1 1 UDP 2130706431 192.168.2.1 50005 typ host",
+           sdpMLineIndex: 0,
+           sdpMid: "sdparta_0"});
+        return test.pcRemote._pc.addIceCandidate(candidate)
+        .then(ok(true, "Successfully added valid ICE candidate with matching mid and level"));
       }
     ]);
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/webidl/InputMethod.webidl
+++ b/dom/webidl/InputMethod.webidl
@@ -115,17 +115,17 @@ interface MozInputMethod : EventTarget {
 };
 
 /**
  * InputMethodManager contains a few of the global methods for the input app.
  */
 [JSImplementation="@mozilla.org/b2g-imm;1",
  Pref="dom.mozInputMethod.enabled",
  CheckAnyPermissions="input input-manage"]
-interface MozInputMethodManager {
+interface MozInputMethodManager : EventTarget {
   /**
    * Ask the OS to show a list of available inputs for users to switch from.
    * OS should sliently ignore this request if the app is currently not the
    * active one.
    */
   [CheckAllPermissions="input"]
   void showAll();
 
@@ -160,16 +160,159 @@ interface MozInputMethodManager {
    * Update Gecko with information on the input types which supportsSwitching()
    * should return ture.
    *
    * @param types Array of input types in which supportsSwitching() should
    *              return true.
    */
   [CheckAllPermissions="input-manage"]
   void setSupportsSwitchingTypes(sequence<MozInputMethodInputContextInputTypes> types);
+
+  /**
+   * CustomEvent dispatches to System when there is an input to handle.
+   * If the API consumer failed to handle and call preventDefault(),
+   * there will be a message printed on the console.
+   *
+   * evt.detail is defined by MozInputContextFocusEventDetail.
+   */
+  [CheckAnyPermissions="input-manage"]
+  attribute EventHandler oninputcontextfocus;
+
+  /**
+   * Event dispatches to System when there is no longer an input to handle.
+   * If the API consumer failed to handle and call preventDefault(),
+   * there will be a message printed on the console.
+   */
+  [CheckAnyPermissions="input-manage"]
+  attribute EventHandler oninputcontextblur;
+
+  /**
+   * Event dispatches to System when there is a showAll() call.
+   * If the API consumer failed to handle and call preventDefault(),
+   * there will be a message printed on the console.
+   */
+  [CheckAnyPermissions="input-manage"]
+  attribute EventHandler onshowallrequest;
+
+  /**
+   * Event dispatches to System when there is a next() call.
+   * If the API consumer failed to handle and call preventDefault(),
+   * there will be a message printed on the console.
+   */
+  [CheckAnyPermissions="input-manage"]
+  attribute EventHandler onnextrequest;
+
+  /**
+   * Event dispatches to System when there is a addInput() call.
+   * The API consumer must call preventDefault() to indicate the event is
+   * consumed, otherwise the request is not considered handled even if
+   * waitUntil() was called.
+   *
+   * evt.detail is defined by MozInputRegistryEventDetail.
+   */
+  [CheckAnyPermissions="input-manage"]
+  attribute EventHandler onaddinputrequest;
+
+  /**
+   * Event dispatches to System when there is a removeInput() call.
+   * The API consumer must call preventDefault() to indicate the event is
+   * consumed, otherwise the request is not considered handled even if
+   * waitUntil() was called.
+   *
+   * evt.detail is defined by MozInputRegistryEventDetail.
+   */
+  [CheckAnyPermissions="input-manage"]
+  attribute EventHandler onremoveinputrequest;
+};
+
+/**
+ * Detail of the inputcontextfocus event.
+ */
+[JSImplementation="@mozilla.org/b2g-imm-focus;1",
+ Pref="dom.mozInputMethod.enabled",
+ CheckAnyPermissions="input-manage"]
+interface MozInputContextFocusEventDetail {
+  /**
+   * The type of the focused input.
+   */
+  readonly attribute MozInputMethodInputContextTypes type;
+  /**
+   * The input type of the focused input.
+   */
+  readonly attribute MozInputMethodInputContextInputTypes inputType;
+
+  /**
+   * The following is only needed for rendering and handling "option" input types,
+   * in System app.
+   */
+
+  /**
+   * Current value of the input/select element.
+   */
+  readonly attribute DOMString? value;
+  /**
+   * An object representing all the <optgroup> and <option> elements
+   * in the <select> element.
+   */
+  [Pure, Cached, Frozen]
+  readonly attribute MozInputContextChoicesInfo? choices;
+  /**
+   * Max/min value of <input>
+   */
+  readonly attribute DOMString? min;
+  readonly attribute DOMString? max;
+};
+
+/**
+ * Information about the options within the <select> element.
+ */
+dictionary MozInputContextChoicesInfo {
+  boolean multiple;
+  sequence<MozInputMethodChoiceDict> choices;
+};
+
+/**
+ * Content the header (<optgroup>) or an option (<option>).
+ */
+dictionary MozInputMethodChoiceDict {
+  boolean group;
+  DOMString text;
+  boolean disabled;
+  boolean? inGroup;
+  boolean? selected;
+  long? optionIndex;
+};
+
+/**
+ * detail of addinputrequest or removeinputrequest event.
+ */
+[JSImplementation="@mozilla.org/b2g-imm-input-registry;1",
+ Pref="dom.mozInputMethod.enabled",
+ CheckAnyPermissions="input-manage"]
+interface MozInputRegistryEventDetail {
+  /**
+   * Manifest URL of the requesting app.
+   */
+  readonly attribute DOMString manifestURL;
+  /**
+   * ID of the input
+   */
+  readonly attribute DOMString inputId;
+  /**
+   * Input manifest of the input to add.
+   * Null for removeinputrequest event.
+   */
+  [Pure, Cached, Frozen]
+  readonly attribute MozInputMethodInputManifest? inputManifest;
+  /**
+   * Resolve or Reject the addInput() or removeInput() call when the passed
+   * promises are resolved.
+   */
+  [Throws]
+  void waitUntil(Promise<any> p);
 };
 
 /**
  * The input context, which consists of attributes and information of current
  * input field. It also hosts the methods available to the keyboard app to
  * mutate the input field represented. An "input context" gets void when the
  * app is no longer allowed to interact with the text field,
  * e.g., the text field does no longer exist, the app is being switched to
@@ -392,29 +535,30 @@ dictionary CompositionClauseParameters {
   long length;
 };
 
 /**
  * Types are HTML tag names of the inputs that is explosed with InputContext,
  * *and* the special keyword "contenteditable" for contenteditable element.
  */
 enum MozInputMethodInputContextTypes {
-  "input", "textarea", "contenteditable"
+  "input", "textarea", "contenteditable",
   /**
-   * <select> is managed by the API but it's not exposed through InputContext
-   * yet.
+   * <select> is managed by the API but it is handled by the System app only,
+   * so this value is only accessible by System app from inputcontextfocus event.
    */
-  // "select"
+  "select"
 };
 
 /**
  * InputTypes of the input that InputContext is representing. The value
- * is inferred from the type attribute of input element.
+ * is inferred from the type attribute of element.
  *
  * See https://html.spec.whatwg.org/multipage/forms.html#states-of-the-type-attribute
+ * for types of HTMLInputElement.
  *
  * They are divided into groups -- an layout/input capable of handling one type
  * in the group is considered as capable of handling all of the types in the
  * same group.
  * The layout/input that could handle type "text" is considered as the fallback
  * if none of layout/input installed can handle a specific type.
  *
  * Groups and fallbacks is enforced in Gaia System app currently.
@@ -439,22 +583,25 @@ enum MozInputMethodInputContextInputType
   /**
    * Group "email"
    */
   "email",
   /**
    * Group "password".
    * An non-Latin alphabet layout/input should not be able to handle this type.
    */
-  "password"
+  "password",
   /**
-   * Group "option". These types are handled by System app itself currently, and
-   * not exposed and allowed to handled with input context.
+   * Group "option". These types are handled by System app itself currently, so
+   * no input app will be set to active for these input types.
+   * System app access these types from inputcontextfocus event.
+   * ("select-one" and "select-multiple" are valid HTMLSelectElement#type.)
    */
-  //"datetime", "date", "month", "week", "time", "datetime-local", "color",
+  "datetime", "date", "month", "week", "time", "datetime-local", "color",
+  "select-one", "select-multiple"
   /**
    * These types are ignored by the API even though they are valid
    * HTMLInputElement#type.
    */
   //"checkbox", "radio", "file", "submit", "image", "range", "reset", "button"
 };
 
 /**
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -157,16 +157,18 @@ support-files =
   test_request_context.js
   fetch_event_client.js
   sw_clients/dummy.html
   fetch/plugin/worker.js
   fetch/plugin/plugins.html
   eventsource/*
   sw_clients/file_blob_upload_frame.html
   redirect_post.sjs
+  xslt_worker.js
+  xslt/*
 
 [test_app_protocol.html]
 skip-if = release_build
 [test_bug1151916.html]
 [test_claim.html]
 [test_claim_fetch.html]
 [test_claim_oninstall.html]
 [test_close.html]
@@ -245,12 +247,13 @@ skip-if = toolkit == "android" || toolki
 [test_strict_mode_warning.html]
 [test_third_party_iframes.html]
 [test_unregister.html]
 [test_workerUnregister.html]
 [test_workerUpdate.html]
 [test_workerupdatefoundevent.html]
 [test_opaque_intercept.html]
 [test_fetch_event_client_postmessage.html]
+[test_xslt.html]
 [test_escapedSlashes.html]
 [test_eventsource_intercept.html]
 [test_not_intercept_plugin.html]
 [test_file_blob_upload.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_xslt.html
@@ -0,0 +1,113 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1182113 - Test service worker XSLT interception</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+  var registration;
+  var worker;
+
+  function start() {
+    return navigator.serviceWorker.register("xslt_worker.js",
+                                            { scope: "./" })
+      .then((swr) => registration = swr);
+  }
+
+  function unregister() {
+    return registration.unregister().then(function(result) {
+      ok(result, "Unregister should return true.");
+    }, function(e) {
+      dump("Unregistering the SW failed with " + e + "\n");
+    });
+  }
+
+  function getXmlString(xmlObject) {
+    serializer = new XMLSerializer();
+    return serializer.serializeToString(iframe.contentDocument);
+  }
+
+  function synthetic() {
+    content = document.getElementById("content");
+    ok(content, "parent exists.");
+
+    iframe = document.createElement("iframe");
+    content.appendChild(iframe);
+
+    iframe.setAttribute('src', "xslt/test.xml");
+
+    var p = new Promise(function(res, rej) {
+      iframe.onload = function(e) {
+        dump("Set request mode\n");
+        registration.active.postMessage("synthetic");
+        xmlString = getXmlString(iframe.contentDocument);
+        ok(!xmlString.includes("Error"), "Load synthetic cross origin XSLT should be allowed");
+        res();
+      };
+    });
+
+    return p;
+  }
+
+  function cors() {
+    var p = new Promise(function(res, rej) {
+      iframe.onload = function(e) {
+        xmlString = getXmlString(iframe.contentDocument);
+        ok(!xmlString.includes("Error"), "Load CORS cross origin XSLT should be allowed");
+        res();
+      };
+    });
+
+    registration.active.postMessage("cors");
+    iframe.setAttribute('src', "xslt/test.xml");
+
+    return p;
+  }
+
+  function opaque() {
+    var p = new Promise(function(res, rej) {
+      iframe.onload = function(e) {
+        xmlString = getXmlString(iframe.contentDocument);
+        ok(xmlString.includes("Error"), "Load opaque cross origin XSLT should not be allowed");
+        res();
+      };
+    });
+
+    registration.active.postMessage("opaque");
+    iframe.setAttribute('src', "xslt/test.xml");
+
+    return p;
+  }
+
+  function runTest() {
+     start()
+      .then(synthetic)
+      .then(opaque)
+      .then(cors)
+      .then(unregister)
+      .catch(function(e) {
+        ok(false, "Some test failed with error " + e);
+      }).then(SimpleTest.finish);
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true],
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/xslt/test.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="test.xsl"?>
+<result>
+  <Title>Example</Title>
+  <Error>Error</Error>
+</result>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/xslt/xslt.sjs
@@ -0,0 +1,12 @@
+function handleRequest(request, response) {
+  response.setHeader("Content-Type", "application/xslt+xml", false);
+  response.setHeader("Access-Control-Allow-Origin", "*");
+
+  var body = request.queryString;
+  if (!body) {
+    response.setStatusLine(null, 500, "Invalid querystring");
+    return;
+  }
+
+  response.write(unescape(body));
+}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/xslt_worker.js
@@ -0,0 +1,52 @@
+var testType = 'synthetic';
+
+var xslt = "<?xml version=\"1.0\"?> " +
+           "<xsl:stylesheet version=\"1.0\"" +
+           "   xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">" +
+           "  <xsl:template match=\"node()|@*\">" +
+           "    <xsl:copy>" +
+           "      <xsl:apply-templates select=\"node()|@*\"/>" +
+           "    </xsl:copy>" +
+           "  </xsl:template>" +
+           "  <xsl:template match=\"Error\"/>" +
+           "</xsl:stylesheet>";
+
+onfetch = function(event) {
+  if (event.request.url.includes('test.xsl')) {
+    if (testType == 'synthetic') {
+      if (event.request.mode != 'cors') {
+        event.respondWith(Response.error());
+        return;
+      }
+
+      event.respondWith(Promise.resolve(
+        new Response(xslt, { headers: {'Content-Type': 'application/xslt+xml'}})
+      ));
+    }
+    else if (testType == 'cors') {
+      if (event.request.mode != 'cors') {
+        event.respondWith(Response.error());
+        return;
+      }
+
+      var url = "http://example.com/tests/dom/workers/test/serviceworkers/xslt/xslt.sjs?" + escape(xslt);
+      event.respondWith(fetch(url, { mode: 'cors' }));
+    }
+    else if (testType == 'opaque') {
+      if (event.request.mode != 'cors') {
+        event.respondWith(Response.error());
+        return;
+      }
+
+      var url = "http://example.com/tests/dom/workers/test/serviceworkers/xslt/xslt.sjs?" + escape(xslt);
+      event.respondWith(fetch(url, { mode: 'no-cors' }));
+    }
+    else {
+      event.respondWith(Response.error());
+    }
+  }
+};
+
+onmessage = function(event) {
+  testType = event.data;
+};
--- a/embedding/browser/nsWebBrowser.cpp
+++ b/embedding/browser/nsWebBrowser.cpp
@@ -1683,17 +1683,17 @@ nsWebBrowser::EnsureDocShellTreeOwner()
 
   return NS_OK;
 }
 
 static void
 DrawPaintedLayer(PaintedLayer* aLayer,
                  gfxContext* aContext,
                  const nsIntRegion& aRegionToDraw,
-                 const nsIntRegion* aDirtyRegion,
+                 const nsIntRegion& aDirtyRegion,
                  DrawRegionClip aClip,
                  const nsIntRegion& aRegionToInvalidate,
                  void* aCallbackData)
 {
   DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
 
   ColorPattern color(ToDeviceColor(*static_cast<nscolor*>(aCallbackData)));
   nsIntRect dirtyRect = aRegionToDraw.GetBounds();
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -311,16 +311,17 @@ GLContext::GLContext(const SurfaceCaps& 
     mCaps(caps),
     mScreen(nullptr),
     mLockedSurface(nullptr),
     mMaxTextureSize(0),
     mMaxCubeMapTextureSize(0),
     mMaxTextureImageSize(0),
     mMaxRenderbufferSize(0),
     mNeedsTextureSizeChecks(false),
+    mNeedsFlushBeforeDeleteFB(false),
     mWorkAroundDriverBugs(true),
     mHeavyGLCallsSinceLastFlush(false)
 {
     mOwningThreadId = PlatformThread::CurrentId();
 }
 
 GLContext::~GLContext() {
     NS_ASSERTION(IsDestroyed(), "GLContext implementation must call MarkDestroyed in destructor!");
@@ -611,16 +612,17 @@ GLContext::InitWithPrefix(const char *pr
             mInitialized = false;
 
         const char *rendererMatchStrings[size_t(GLRenderer::Other)] = {
                 "Adreno 200",
                 "Adreno 205",
                 "Adreno (TM) 200",
                 "Adreno (TM) 205",
                 "Adreno (TM) 320",
+                "Adreno (TM) 420",
                 "PowerVR SGX 530",
                 "PowerVR SGX 540",
                 "NVIDIA Tegra",
                 "Android Emulator",
                 "Gallium 0.4 on llvmpipe",
                 "Intel HD Graphics 3000 OpenGL Engine",
                 "Microsoft Basic Render Driver"
         };
@@ -1602,16 +1604,22 @@ GLContext::InitWithPrefix(const char *pr
 #ifdef MOZ_X11
         if (mWorkAroundDriverBugs &&
             mVendor == GLVendor::Nouveau) {
             // see bug 814716. Clamp MaxCubeMapTextureSize at 2K for Nouveau.
             mMaxCubeMapTextureSize = std::min(mMaxCubeMapTextureSize, 2048);
             mNeedsTextureSizeChecks = true;
         }
 #endif
+        if (mWorkAroundDriverBugs &&
+            Renderer() == GLRenderer::AdrenoTM420) {
+            // see bug 1194923. Calling glFlush before glDeleteFramebuffers
+            // prevents occasional driver crash.
+            mNeedsFlushBeforeDeleteFB = true;
+        }
 
         mMaxTextureImageSize = mMaxTextureSize;
 
         mMaxSamples = 0;
         if (IsSupported(GLFeature::framebuffer_multisample)) {
             fGetIntegerv(LOCAL_GL_MAX_SAMPLES, (GLint*)&mMaxSamples);
         }
 
@@ -2846,16 +2854,21 @@ GLContext::fDeleteFramebuffers(GLsizei n
     if (mScreen) {
         // Notify mScreen which framebuffers we're deleting.
         // Otherwise, we will get framebuffer binding mispredictions.
         for (int i = 0; i < n; i++) {
             mScreen->DeletingFB(names[i]);
         }
     }
 
+    // Avoid crash by flushing before glDeleteFramebuffers. See bug 1194923.
+    if (mNeedsFlushBeforeDeleteFB) {
+        fFlush();
+    }
+
     if (n == 1 && *names == 0) {
         // Deleting framebuffer 0 causes hangs on the DROID. See bug 623228.
     } else {
         raw_fDeleteFramebuffers(n, names);
     }
     TRACKING_CONTEXT(DeletedFramebuffers(this, n, names));
 }
 
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -164,16 +164,17 @@ enum class GLVendor {
 };
 
 enum class GLRenderer {
     Adreno200,
     Adreno205,
     AdrenoTM200,
     AdrenoTM205,
     AdrenoTM320,
+    AdrenoTM420,
     SGX530,
     SGX540,
     Tegra,
     AndroidEmulator,
     GalliumLlvmpipe,
     IntelHD3000,
     MicrosoftBasicRenderDriver,
     Other
@@ -3499,16 +3500,17 @@ protected:
 
     GLint mMaxTextureSize;
     GLint mMaxCubeMapTextureSize;
     GLint mMaxTextureImageSize;
     GLint mMaxRenderbufferSize;
     GLint mMaxViewportDims[2];
     GLsizei mMaxSamples;
     bool mNeedsTextureSizeChecks;
+    bool mNeedsFlushBeforeDeleteFB;
     bool mWorkAroundDriverBugs;
 
     bool IsTextureSizeSafeToPassToDriver(GLenum target, GLsizei width, GLsizei height) const {
         if (mNeedsTextureSizeChecks) {
             // some drivers incorrectly handle some large texture sizes that are below the
             // max texture size that they report. So we check ourselves against our own values
             // (mMax[CubeMap]TextureSize).
             // see bug 737182 for Mac Intel 2D textures
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -254,21 +254,22 @@ public:
    * Function called to draw the contents of each PaintedLayer.
    * aRegionToDraw contains the region that needs to be drawn.
    * This would normally be a subregion of the visible region.
    * The callee must draw all of aRegionToDraw. Drawing outside
    * aRegionToDraw will be clipped out or ignored.
    * The callee must draw all of aRegionToDraw.
    * This region is relative to 0,0 in the PaintedLayer.
    *
-   * aDirtyRegion, if non-null, contains the total region that is due to be
-   * painted during the transaction, even though only aRegionToDraw should
-   * be drawn during this call. The sum of every aRegionToDraw over the
-   * course of the transaction must equal aDirtyRegion. aDirtyRegion can be
-   * null if the total dirty region is unknown.
+   * aDirtyRegion should contain the total region that is be due to be painted
+   * during the transaction, even though only aRegionToDraw should be drawn
+   * during this call. aRegionToDraw must be entirely contained within
+   * aDirtyRegion. If the total dirty region is unknown it is okay to pass a
+   * subregion of the total dirty region, e.g. just aRegionToDraw, though it
+   * may not be as efficient.
    *
    * aRegionToInvalidate contains a region whose contents have been
    * changed by the layer manager and which must therefore be invalidated.
    * For example, this could be non-empty if a retained layer internally
    * switches from RGBA to RGB or back ... we might want to repaint it to
    * consistently use subpixel-AA or not.
    * This region is relative to 0,0 in the PaintedLayer.
    * aRegionToInvalidate may contain areas that are outside
@@ -280,17 +281,17 @@ public:
    * We guarantee that buffered contents in the visible
    * region are valid once drawing is complete.
    *
    * The origin of aContext is 0,0 in the PaintedLayer.
    */
   typedef void (* DrawPaintedLayerCallback)(PaintedLayer* aLayer,
                                            gfxContext* aContext,
                                            const nsIntRegion& aRegionToDraw,
-                                           const nsIntRegion* aDirtyRegion,
+                                           const nsIntRegion& aDirtyRegion,
                                            DrawRegionClip aClip,
                                            const nsIntRegion& aRegionToInvalidate,
                                            void* aCallbackData);
 
   /**
    * Finish the construction phase of the transaction, perform the
    * drawing phase, and end the transaction.
    * During the drawing phase, all PaintedLayers in the tree are
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1222,16 +1222,20 @@ APZCTreeManager::GetRootNode() const
  * @return true on success, false if aStartPoint or aEndPoint cannot be transformed into target's coordinate space
  */
 static bool
 TransformDisplacement(APZCTreeManager* aTreeManager,
                       AsyncPanZoomController* aSource,
                       AsyncPanZoomController* aTarget,
                       ParentLayerPoint& aStartPoint,
                       ParentLayerPoint& aEndPoint) {
+  if (aSource == aTarget) {
+    return true;
+  }
+
   // Convert start and end points to Screen coordinates.
   Matrix4x4 untransformToApzc = aTreeManager->GetScreenToApzcTransform(aSource).Inverse();
   ScreenPoint screenStart = TransformTo<ScreenPixel>(untransformToApzc, aStartPoint);
   ScreenPoint screenEnd = TransformTo<ScreenPixel>(untransformToApzc, aEndPoint);
 
 
   // Convert start and end points to aTarget's ParentLayer coordinates.
   Matrix4x4 transformToApzc = aTreeManager->GetScreenToApzcTransform(aTarget);
@@ -1241,118 +1245,137 @@ TransformDisplacement(APZCTreeManager* a
     return false;
   }
   aEndPoint = *endPoint;
   aStartPoint = *startPoint;
 
   return true;
 }
 
-bool
+void
 APZCTreeManager::DispatchScroll(AsyncPanZoomController* aPrev,
-                                ParentLayerPoint aStartPoint,
-                                ParentLayerPoint aEndPoint,
+                                ParentLayerPoint& aStartPoint,
+                                ParentLayerPoint& aEndPoint,
                                 OverscrollHandoffState& aOverscrollHandoffState)
 {
   const OverscrollHandoffChain& overscrollHandoffChain = aOverscrollHandoffState.mChain;
   uint32_t overscrollHandoffChainIndex = aOverscrollHandoffState.mChainIndex;
   nsRefPtr<AsyncPanZoomController> next;
   // If we have reached the end of the overscroll handoff chain, there is
   // nothing more to scroll, so we ignore the rest of the pan gesture.
   if (overscrollHandoffChainIndex >= overscrollHandoffChain.Length()) {
     // Nothing more to scroll - ignore the rest of the pan gesture.
-    return false;
+    return;
   }
 
   next = overscrollHandoffChain.GetApzcAtIndex(overscrollHandoffChainIndex);
 
   if (next == nullptr || next->IsDestroyed()) {
-    return false;
+    return;
   }
 
   // Convert the start and end points from |aPrev|'s coordinate space to
-  // |next|'s coordinate space. Since |aPrev| may be the same as |next|
-  // (if |aPrev| is the APZC that is initiating the scroll and there is no
-  // scroll grabbing to grab the scroll from it), don't bother doing the
-  // transformations in that case.
-  if (next != aPrev) {
-    if (!TransformDisplacement(this, aPrev, next, aStartPoint, aEndPoint)) {
-      return false;
-    }
+  // |next|'s coordinate space.
+  if (!TransformDisplacement(this, aPrev, next, aStartPoint, aEndPoint)) {
+    return;
   }
 
   // Scroll |next|. If this causes overscroll, it will call DispatchScroll()
   // again with an incremented index.
-  return next->AttemptScroll(aStartPoint, aEndPoint, aOverscrollHandoffState);
+  if (!next->AttemptScroll(aStartPoint, aEndPoint, aOverscrollHandoffState)) {
+    // Transform |aStartPoint| and |aEndPoint| (which now represent the
+    // portion of the displacement that wasn't consumed by APZCs later
+    // in the handoff chain) back into |aPrev|'s coordinate space. This
+    // allows the caller (which is |aPrev|) to interpret the unconsumed
+    // displacement in its own coordinate space, and make use of it
+    // (e.g. by going into overscroll).
+    if (!TransformDisplacement(this, next, aPrev, aStartPoint, aEndPoint)) {
+      NS_WARNING("Failed to untransform scroll points during dispatch");
+    }
+  }
 }
 
-bool
+void
 APZCTreeManager::DispatchFling(AsyncPanZoomController* aPrev,
-                               ParentLayerPoint aVelocity,
+                               ParentLayerPoint& aVelocity,
                                nsRefPtr<const OverscrollHandoffChain> aOverscrollHandoffChain,
                                bool aHandoff)
 {
   nsRefPtr<AsyncPanZoomController> current;
   uint32_t aOverscrollHandoffChainLength = aOverscrollHandoffChain->Length();
   uint32_t startIndex;
 
   // The fling's velocity needs to be transformed from the screen coordinates
   // of |aPrev| to the screen coordinates of |next|. To transform a velocity
   // correctly, we need to convert it to a displacement. For now, we do this
   // by anchoring it to a start point of (0, 0).
   // TODO: For this to be correct in the presence of 3D transforms, we should
   // use the end point of the touch that started the fling as the start point
   // rather than (0, 0).
   ParentLayerPoint startPoint;  // (0, 0)
   ParentLayerPoint endPoint;
-  ParentLayerPoint transformedVelocity = aVelocity;
+  ParentLayerPoint usedTransformedVelocity = aVelocity;
 
   if (aHandoff) {
     startIndex = aOverscrollHandoffChain->IndexOf(aPrev) + 1;
 
     // IndexOf will return aOverscrollHandoffChain->Length() if
     // |aPrev| is not found.
     if (startIndex >= aOverscrollHandoffChainLength) {
-      return false;
+      return;
     }
   } else {
     startIndex = 0;
   }
 
   for (; startIndex < aOverscrollHandoffChainLength; startIndex++) {
     current = aOverscrollHandoffChain->GetApzcAtIndex(startIndex);
 
     // Make sure the apcz about to be handled can be handled
     if (current == nullptr || current->IsDestroyed()) {
-      return false;
+      return;
     }
 
-    endPoint = startPoint + transformedVelocity;
+    endPoint = startPoint + usedTransformedVelocity;
 
     // Only transform when current apcz can be transformed with previous
     if (startIndex > 0) {
       if (!TransformDisplacement(this,
                             aOverscrollHandoffChain->GetApzcAtIndex(startIndex - 1),
                             current,
                             startPoint,
                             endPoint)) {
-          return false;
+        return;
       }
     }
 
-    transformedVelocity = endPoint - startPoint;
+    ParentLayerPoint transformedVelocity = endPoint - startPoint;
+    usedTransformedVelocity = transformedVelocity;
 
-    if (current->AttemptFling(transformedVelocity,
+    if (current->AttemptFling(usedTransformedVelocity,
                               aOverscrollHandoffChain,
                               aHandoff)) {
-      return true;
+      if (IsZero(usedTransformedVelocity)) {
+        aVelocity = ParentLayerPoint();
+        return;
+      }
+
+      // Subtract the proportion of used velocity from aVelocity
+      if (!FuzzyEqualsAdditive(transformedVelocity.x,
+                               usedTransformedVelocity.x, COORDINATE_EPSILON)) {
+        aVelocity.x = aVelocity.x *
+          (usedTransformedVelocity.x / transformedVelocity.x);
+      }
+      if (!FuzzyEqualsAdditive(transformedVelocity.y,
+                               usedTransformedVelocity.y, COORDINATE_EPSILON)) {
+        aVelocity.y = aVelocity.y *
+          (usedTransformedVelocity.y / transformedVelocity.y);
+      }
     }
   }
-
-  return false;
 }
 
 bool
 APZCTreeManager::HitTestAPZC(const ScreenIntPoint& aPoint)
 {
   nsRefPtr<AsyncPanZoomController> target = GetTargetAPZC(aPoint, nullptr);
   return target != nullptr;
 }
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -322,19 +322,20 @@ public:
    *   displacement between different points in transformed coordinates can
    *   represent different displacements in untransformed coordinates.
    * |aOverscrollHandoffChain| is the overscroll handoff chain used for
    *   determining the order in which scroll should be handed off between
    *   APZCs
    * |aOverscrollHandoffChainIndex| is the next position in the overscroll
    *   handoff chain that should be scrolled.
    *
-   * Returns true iff. some APZC accepted the scroll and scrolled.
-   * This is to allow the sending APZC to go into an overscrolled state if
-   * no APZC further up in the handoff chain accepted the overscroll.
+   * aStartPoint and aEndPoint will be modified depending on how much of the
+   * scroll each APZC consumes. This is to allow the sending APZC to go into
+   * an overscrolled state if no APZC further up in the handoff chain accepted
+   * the entire scroll.
    *
    * The way this method works is best illustrated with an example.
    * Consider three nested APZCs, A, B, and C, with C being the innermost one.
    * Say B is scroll-grabbing.
    * The touch events go to C because it's the innermost one (so e.g. taps
    * should go through C), but the overscroll handoff chain is B -> C -> A
    * because B is scroll-grabbing.
    * For convenience I'll refer to the three APZC objects as A, B, and C, and
@@ -347,19 +348,19 @@ public:
    *   - C.AttemptScroll() scrolls C. If there is overscroll, it calls TM.DispatchScroll() with index = 2.
    *   - TM.DispatchScroll() calls A.AttemptScroll() (since A is at index 2 in the chain)
    *   - A.AttemptScroll() scrolls A. If there is overscroll, it calls TM.DispatchScroll() with index = 3.
    *   - TM.DispatchScroll() discards the rest of the scroll as there are no more elements in the chain.
    *
    * Note: this should be used for panning only. For handing off overscroll for
    *       a fling, use DispatchFling().
    */
-  bool DispatchScroll(AsyncPanZoomController* aApzc,
-                      ParentLayerPoint aStartPoint,
-                      ParentLayerPoint aEndPoint,
+  void DispatchScroll(AsyncPanZoomController* aApzc,
+                      ParentLayerPoint& aStartPoint,
+                      ParentLayerPoint& aEndPoint,
                       OverscrollHandoffState& aOverscrollHandoffState);
 
   /**
    * This is a callback for AsyncPanZoomController to call when it wants to
    * start a fling in response to a touch-end event, or when it needs to hand
    * off a fling to the next APZC. Note that because of scroll grabbing, the
    * first APZC to fling may not be the one that is receiving the touch events.
    *
@@ -369,22 +370,23 @@ public:
    * @param aOverscrollHandoffChain the chain of APZCs along which the fling
    *                                should be handed off
    * @param aHandoff is true if |aApzc| is handing off an existing fling (in
    *                 this case the fling is given to the next APZC in the
    *                 handoff chain after |aApzc|), and false is |aApzc| wants
    *                 start a fling (in this case the fling is given to the
    *                 first APZC in the chain)
    *
-   * Returns true iff. an APZC accepted the fling. In the case of fling handoff,
-   * the caller uses this return value to determine whether it should consume
-   * the excess fling itself by going into an overscroll fling.
+   * aVelocity will be modified depending on how much of that velocity has
+   * been consumed by APZCs in the overscroll hand-off chain. The caller can
+   * use this value to determine whether it should consume the excess velocity
+   * by going into an overscroll fling.
    */
-  bool DispatchFling(AsyncPanZoomController* aApzc,
-                     ParentLayerPoint aVelocity,
+  void DispatchFling(AsyncPanZoomController* aApzc,
+                     ParentLayerPoint& aVelocity,
                      nsRefPtr<const OverscrollHandoffChain> aOverscrollHandoffChain,
                      bool aHandoff);
 
   /*
    * Build the chain of APZCs that will handle overscroll for a pan starting at |aInitialTarget|.
    */
   nsRefPtr<const OverscrollHandoffChain> BuildOverscrollHandoffChain(const nsRefPtr<AsyncPanZoomController>& aInitialTarget);
 
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1573,19 +1573,19 @@ nsEventStatus AsyncPanZoomController::On
 
       CancelAnimation();
       SetState(WHEEL_SCROLL);
 
       OverscrollHandoffState handoffState(
           *mInputQueue->CurrentWheelBlock()->GetOverscrollHandoffChain(),
           distance,
           ScrollSource::Wheel);
-      CallDispatchScroll(aEvent.mLocalOrigin,
-                         aEvent.mLocalOrigin - delta,
-                         handoffState);
+      ParentLayerPoint startPoint = aEvent.mLocalOrigin;
+      ParentLayerPoint endPoint = aEvent.mLocalOrigin - delta;
+      CallDispatchScroll(startPoint, endPoint, handoffState);
 
       SetState(NOTHING);
       RequestContentRepaint();
 
       break;
     }
 
     case ScrollWheelInput::SCROLLMODE_SMOOTH: {
@@ -1721,19 +1721,19 @@ nsEventStatus AsyncPanZoomController::On
 
   HandlePanningUpdate(aEvent.mPanDisplacement);
 
   ScreenPoint panDistance(fabs(aEvent.mPanDisplacement.x), fabs(aEvent.mPanDisplacement.y));
   OverscrollHandoffState handoffState(
       *CurrentPanGestureBlock()->GetOverscrollHandoffChain(),
       panDistance,
       ScrollSource::Wheel);
-  CallDispatchScroll(aEvent.mLocalPanStartPoint,
-                     aEvent.mLocalPanStartPoint + aEvent.mLocalPanDisplacement,
-                     handoffState);
+  ParentLayerPoint startPoint = aEvent.mLocalPanStartPoint;
+  ParentLayerPoint endPoint = aEvent.mLocalPanStartPoint + aEvent.mLocalPanDisplacement;
+  CallDispatchScroll(startPoint, endPoint, handoffState);
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnPanEnd(const PanGestureInput& aEvent) {
   APZC_LOG("%p got a pan-end in state %d\n", this, mState);
 
   // Call into OnPan in order to process any delta included in this event.
@@ -1978,29 +1978,37 @@ void AsyncPanZoomController::HandlePanni
     // touchmoves.
     mX.SetVelocity(0);
     mY.SetVelocity(0);
   }
 }
 
 void AsyncPanZoomController::HandlePanning(double aAngle) {
   ReentrantMonitorAutoEnter lock(mMonitor);
-  if (!gfxPrefs::APZCrossSlideEnabled() && (!mX.CanScrollNow() || !mY.CanScrollNow())) {
+  nsRefPtr<const OverscrollHandoffChain> overscrollHandoffChain =
+    GetInputQueue()->CurrentBlock()->GetOverscrollHandoffChain();
+  bool canScrollHorizontal = !mX.IsAxisLocked() &&
+    overscrollHandoffChain->CanScrollInDirection(this, Layer::HORIZONTAL);
+  bool canScrollVertical = !mY.IsAxisLocked() &&
+    overscrollHandoffChain->CanScrollInDirection(this, Layer::VERTICAL);
+
+  if (!gfxPrefs::APZCrossSlideEnabled() &&
+      (!canScrollHorizontal || !canScrollVertical)) {
     SetState(PANNING);
   } else if (IsCloseToHorizontal(aAngle, gfxPrefs::APZAxisLockAngle())) {
     mY.SetAxisLocked(true);
-    if (mX.CanScrollNow()) {
+    if (canScrollHorizontal) {
       SetState(PANNING_LOCKED_X);
     } else {
       SetState(CROSS_SLIDING_X);
       mX.SetAxisLocked(true);
     }
   } else if (IsCloseToVertical(aAngle, gfxPrefs::APZAxisLockAngle())) {
     mX.SetAxisLocked(true);
-    if (mY.CanScrollNow()) {
+    if (canScrollVertical) {
       SetState(PANNING_LOCKED_Y);
     } else {
       SetState(CROSS_SLIDING_Y);
       mY.SetAxisLocked(true);
     }
   } else {
     SetState(PANNING);
   }
@@ -2062,18 +2070,18 @@ nsEventStatus AsyncPanZoomController::St
 }
 
 void AsyncPanZoomController::UpdateWithTouchAtDevicePoint(const MultiTouchInput& aEvent) {
   ParentLayerPoint point = GetFirstTouchPoint(aEvent);
   mX.UpdateWithTouchAtDevicePoint(point.x, 0, aEvent.mTime);
   mY.UpdateWithTouchAtDevicePoint(point.y, 0, aEvent.mTime);
 }
 
-bool AsyncPanZoomController::AttemptScroll(const ParentLayerPoint& aStartPoint,
-                                           const ParentLayerPoint& aEndPoint,
+bool AsyncPanZoomController::AttemptScroll(ParentLayerPoint& aStartPoint,
+                                           ParentLayerPoint& aEndPoint,
                                            OverscrollHandoffState& aOverscrollHandoffState) {
 
   // "start - end" rather than "end - start" because e.g. moving your finger
   // down (*positive* direction along y axis) causes the vertical scroll offset
   // to *decrease* as the page follows your finger.
   ParentLayerPoint displacement = aStartPoint - aEndPoint;
 
   ParentLayerPoint overscroll;  // will be used outside monitor block
@@ -2094,103 +2102,124 @@ bool AsyncPanZoomController::AttemptScro
 
     if (!IsZero(adjustedDisplacement)) {
       ScrollBy(adjustedDisplacement / mFrameMetrics.GetZoom());
       ScheduleCompositeAndMaybeRepaint();
       UpdateSharedCompositorFrameMetrics();
     }
   }
 
+  // Adjust the start point to reflect the consumed portion of the scroll.
+  aStartPoint = aEndPoint + overscroll;
+
   // If we consumed the entire displacement as a normal scroll, great.
   if (IsZero(overscroll)) {
     return true;
   }
 
   if (AllowScrollHandoffInCurrentBlock()) {
     // If there is overscroll, first try to hand it off to an APZC later
     // in the handoff chain to consume (either as a normal scroll or as
     // overscroll).
     // Note: "+ overscroll" rather than "- overscroll" because "overscroll"
     // is what's left of "displacement", and "displacement" is "start - end".
     ++aOverscrollHandoffState.mChainIndex;
-    if (CallDispatchScroll(aEndPoint + overscroll, aEndPoint,
-                           aOverscrollHandoffState)) {
+    CallDispatchScroll(aStartPoint, aEndPoint, aOverscrollHandoffState);
+
+    overscroll = aStartPoint - aEndPoint;
+    if (IsZero(overscroll)) {
       return true;
     }
   }
 
   // If there is no APZC later in the handoff chain that accepted the
   // overscroll, try to accept it ourselves. We only accept it if we
   // are pannable.
   APZC_LOG("%p taking overscroll during panning\n", this);
-  return OverscrollForPanning(overscroll, aOverscrollHandoffState.mPanDistance);
+  OverscrollForPanning(overscroll, aOverscrollHandoffState.mPanDistance);
+  aStartPoint = aEndPoint + overscroll;
+
+  return IsZero(overscroll);
 }
 
-bool AsyncPanZoomController::OverscrollForPanning(ParentLayerPoint aOverscroll,
+void AsyncPanZoomController::OverscrollForPanning(ParentLayerPoint& aOverscroll,
                                                   const ScreenPoint& aPanDistance) {
   // Only allow entering overscroll along an axis if the pan distance along
   // that axis is greater than the pan distance along the other axis by a
   // configurable factor. If we are already overscrolled, don't check this.
   if (!IsOverscrolled()) {
     if (aPanDistance.x < gfxPrefs::APZMinPanDistanceRatio() * aPanDistance.y) {
       aOverscroll.x = 0;
     }
     if (aPanDistance.y < gfxPrefs::APZMinPanDistanceRatio() * aPanDistance.x) {
       aOverscroll.y = 0;
     }
   }
 
-  return OverscrollBy(aOverscroll);
+  OverscrollBy(aOverscroll);
 }
 
-bool AsyncPanZoomController::OverscrollBy(const ParentLayerPoint& aOverscroll) {
+void AsyncPanZoomController::OverscrollBy(ParentLayerPoint& aOverscroll) {
   if (!gfxPrefs::APZOverscrollEnabled()) {
-    return false;
+    return;
   }
 
   ReentrantMonitorAutoEnter lock(mMonitor);
   // Do not go into overscroll in a direction in which we have no room to
   // scroll to begin with.
   bool xCanScroll = mX.CanScroll();
   bool yCanScroll = mY.CanScroll();
-  if (xCanScroll) {
+
+  bool xConsumed = FuzzyEqualsAdditive(aOverscroll.x, 0.0f, COORDINATE_EPSILON);
+  if (xCanScroll && !xConsumed) {
     mX.OverscrollBy(aOverscroll.x);
-  }
-  if (yCanScroll) {
-    mY.OverscrollBy(aOverscroll.y);
+    aOverscroll.x = 0;
+    xConsumed = true;
   }
-  if (xCanScroll || yCanScroll) {
-    ScheduleComposite();
-    return true;
+
+  bool yConsumed = FuzzyEqualsAdditive(aOverscroll.y, 0.0f, COORDINATE_EPSILON);
+  if (yCanScroll && !yConsumed) {
+    mY.OverscrollBy(aOverscroll.y);
+    aOverscroll.y = 0;
+    yConsumed = true;
   }
-  // TODO(botond): If one of the x- or y-overscroll was not accepted, we
-  // may want to propagate that one to an APZC earlier in the handoff chain.
-  return false;
+
+  if ((xCanScroll && xConsumed) || (yCanScroll && yConsumed)) {
+    ScheduleComposite();
+  }
 }
 
 nsRefPtr<const OverscrollHandoffChain> AsyncPanZoomController::BuildOverscrollHandoffChain() {
   if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
     return treeManagerLocal->BuildOverscrollHandoffChain(this);
   }
 
   // This APZC IsDestroyed(). To avoid callers having to special-case this
   // scenario, just build a 1-element chain containing ourselves.
   OverscrollHandoffChain* result = new OverscrollHandoffChain;
   result->Add(this);
   return result;
 }
 
-void AsyncPanZoomController::AcceptFling(const ParentLayerPoint& aVelocity,
+void AsyncPanZoomController::AcceptFling(ParentLayerPoint& aVelocity,
                                          const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                                          bool aHandoff) {
+  ReentrantMonitorAutoEnter lock(mMonitor);
+
   // We may have a pre-existing velocity for whatever reason (for example,
   // a previously handed off fling). We don't want to clobber that.
   APZC_LOG("%p accepting fling with velocity %s\n", this, Stringify(aVelocity).c_str());
-  mX.SetVelocity(mX.GetVelocity() + aVelocity.x);
-  mY.SetVelocity(mY.GetVelocity() + aVelocity.y);
+  if (mX.CanScroll()) {
+    mX.SetVelocity(mX.GetVelocity() + aVelocity.x);
+    aVelocity.x = 0;
+  }
+  if (mY.CanScroll()) {
+    mY.SetVelocity(mY.GetVelocity() + aVelocity.y);
+    aVelocity.y = 0;
+  }
   SetState(FLING);
   FlingAnimation *fling = new FlingAnimation(*this,
       aOverscrollHandoffChain,
       !aHandoff);  // only apply acceleration if this is an initial fling
 
   float friction = gfxPrefs::APZFlingFriction();
   ParentLayerPoint velocity(mX.GetVelocity(), mY.GetVelocity());
   ParentLayerPoint predictedDelta;
@@ -2222,41 +2251,39 @@ void AsyncPanZoomController::AcceptFling
       controller->RequestFlingSnap(mFrameMetrics.GetScrollId(),
                                    predictedDestination);
     }
   }
 
   StartAnimation(fling);
 }
 
-bool AsyncPanZoomController::AttemptFling(ParentLayerPoint aVelocity,
+bool AsyncPanZoomController::AttemptFling(ParentLayerPoint& aVelocity,
                                           const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                                           bool aHandoff) {
   // If we are pannable, take over the fling ourselves.
   if (IsPannable()) {
     AcceptFling(aVelocity,
                 aOverscrollHandoffChain,
                 aHandoff);
     return true;
   }
 
   return false;
 }
 
 void AsyncPanZoomController::HandleFlingOverscroll(const ParentLayerPoint& aVelocity,
                                                    const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain) {
   APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
-  if (!(treeManagerLocal && treeManagerLocal->DispatchFling(this,
-                                                            aVelocity,
-                                                            aOverscrollHandoffChain,
-                                                            true /* handoff */))) {
-    // No one wanted the fling, so we "take it" ourselves by entering an
-    // overscroll animation starting with the fling's velocity.
-    if (IsPannable() && gfxPrefs::APZOverscrollEnabled()) {
-      StartOverscrollAnimation(aVelocity);
+  ParentLayerPoint velocity = aVelocity;
+  if (treeManagerLocal) {
+    treeManagerLocal->DispatchFling(this, velocity, aOverscrollHandoffChain,
+                                    true /* handoff */);
+    if (!IsZero(velocity) && IsPannable() && gfxPrefs::APZOverscrollEnabled()) {
+      StartOverscrollAnimation(velocity);
     }
   }
 }
 
 void AsyncPanZoomController::HandleSmoothScrollOverscroll(const ParentLayerPoint& aVelocity) {
   // We must call BuildOverscrollHandoffChain from this deferred callback
   // function in order to avoid a deadlock when acquiring the tree lock.
   HandleFlingOverscroll(aVelocity, BuildOverscrollHandoffChain());
@@ -2279,29 +2306,29 @@ void AsyncPanZoomController::StartSmooth
                                            gfxPrefs::ScrollBehaviorDampingRatio()));
 }
 
 void AsyncPanZoomController::StartOverscrollAnimation(const ParentLayerPoint& aVelocity) {
   SetState(OVERSCROLL_ANIMATION);
   StartAnimation(new OverscrollAnimation(*this, aVelocity));
 }
 
-bool AsyncPanZoomController::CallDispatchScroll(const ParentLayerPoint& aStartPoint,
-                                                const ParentLayerPoint& aEndPoint,
+void AsyncPanZoomController::CallDispatchScroll(ParentLayerPoint& aStartPoint,
+                                                ParentLayerPoint& aEndPoint,
                                                 OverscrollHandoffState& aOverscrollHandoffState) {
   // Make a local copy of the tree manager pointer and check if it's not
   // null before calling DispatchScroll(). This is necessary because
   // Destroy(), which nulls out mTreeManager, could be called concurrently.
   APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
   if (!treeManagerLocal) {
-    return false;
+    return;
   }
-  return treeManagerLocal->DispatchScroll(this,
-                                          aStartPoint, aEndPoint,
-                                          aOverscrollHandoffState);
+  treeManagerLocal->DispatchScroll(this,
+                                   aStartPoint, aEndPoint,
+                                   aOverscrollHandoffState);
 }
 
 void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) {
   ParentLayerPoint prevTouchPoint(mX.GetPos(), mY.GetPos());
   ParentLayerPoint touchPoint = GetFirstTouchPoint(aEvent);
 
   ScreenPoint panDistance = ToScreenCoordinates(
       ParentLayerPoint(mX.PanDistance(touchPoint.x),
@@ -3098,23 +3125,26 @@ void AsyncPanZoomController::ZoomToRect(
     CSSToParentLayerScale localMaxZoom = mZoomConstraints.mMaxZoom;
 
     if (!aRect.IsEmpty()) {
       // Intersect the zoom-to-rect to the CSS rect to make sure it fits.
       aRect = aRect.Intersect(cssPageRect);
       targetZoom = CSSToParentLayerScale(std::min(compositionBounds.width / aRect.width,
                                                   compositionBounds.height / aRect.height));
     }
-    // 1. If the rect is empty, request received from browserElementScrolling.js
+    // 1. If the rect is empty, the content-side logic for handling a double-tap
+    //    requested that we zoom out.
     // 2. currentZoom is equal to mZoomConstraints.mMaxZoom and user still double-tapping it
     // 3. currentZoom is equal to localMinZoom and user still double-tapping it
     // Treat these three cases as a request to zoom out as much as possible.
+    bool zoomOut = false;
     if (aRect.IsEmpty() ||
         (currentZoom == localMaxZoom && targetZoom >= localMaxZoom) ||
         (currentZoom == localMinZoom && targetZoom <= localMinZoom)) {
+      zoomOut = true;
       CSSSize compositedSize = mFrameMetrics.CalculateCompositedSizeInCssPixels();
       float y = scrollOffset.y;
       float newHeight =
         cssPageRect.width * (compositedSize.height / compositedSize.width);
       float dh = compositedSize.height - newHeight;
 
       aRect = CSSRect(0.0f,
                       y + dh/2,
@@ -3138,16 +3168,24 @@ void AsyncPanZoomController::ZoomToRect(
       aRect.y = cssPageRect.height - sizeAfterZoom.height;
       aRect.y = aRect.y > 0 ? aRect.y : 0;
     }
     if (aRect.x + sizeAfterZoom.width > cssPageRect.width) {
       aRect.x = cssPageRect.width - sizeAfterZoom.width;
       aRect.x = aRect.x > 0 ? aRect.x : 0;
     }
 
+    // Vertically center the zoomed element in the screen.
+    if (!zoomOut && (sizeAfterZoom.height > aRect.height)) {
+      aRect.y -= (sizeAfterZoom.height - aRect.height) * 0.5f;
+      if (aRect.y < 0.0f) {
+        aRect.y = 0.0f;
+      }
+    }
+
     endZoomToMetrics.SetScrollOffset(aRect.TopLeft());
     endZoomToMetrics.SetDisplayPortMargins(
       CalculatePendingDisplayPort(endZoomToMetrics,
                                   ParentLayerPoint(0,0),
                                   0));
     endZoomToMetrics.SetUseDisplayPortMargins();
 
     StartAnimation(new ZoomAnimation(
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -850,25 +850,26 @@ private:
 private:
   /* ===================================================================
    * The functions and members in this section are used to manage
    * fling animations, smooth scroll animations, and overscroll
    * during a fling or smooth scroll.
    */
 public:
   /**
-   * Attempt a fling with the given velocity. If we are not pannable, tehe fling
+   * Attempt a fling with the given velocity. If we are not pannable, the fling
    * is handed off to the next APZC in the handoff chain via
-   * mTreeManager->DspatchFling(). Returns true iff. any APZC (whether this
-   * one or one further in the handoff chain) accepted the fling.
+   * mTreeManager->DispatchFling(). Returns true iff. the entire velocity of
+   * the fling was consumed by this APZC. aVelocity is modified to contain any
+   * unused, residual velocity.
    * |aHandoff| should be true iff. the fling was handed off from a previous
    *            APZC, and determines whether acceleration is applied to the
    *            fling.
    */
-  bool AttemptFling(ParentLayerPoint aVelocity,
+  bool AttemptFling(ParentLayerPoint& aVelocity,
                     const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                     bool aHandoff);
 
 private:
   friend class FlingAnimation;
   friend class OverscrollAnimation;
   friend class SmoothScrollAnimation;
   friend class WheelScrollAnimation;
@@ -884,17 +885,17 @@ private:
   // later in the handoff chain, or if there are no takers, continuing the
   // fling and entering an overscrolled state.
   void HandleFlingOverscroll(const ParentLayerPoint& aVelocity,
                              const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain);
 
   void HandleSmoothScrollOverscroll(const ParentLayerPoint& aVelocity);
 
   // Helper function used by TakeOverFling() and HandleFlingOverscroll().
-  void AcceptFling(const ParentLayerPoint& aVelocity,
+  void AcceptFling(ParentLayerPoint& aVelocity,
                    const nsRefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain,
                    bool aHandoff);
 
   // Start an overscroll animation with the given initial velocity.
   void StartOverscrollAnimation(const ParentLayerPoint& aVelocity);
 
   void StartSmoothScroll(ScrollSource aSource);
 
@@ -962,19 +963,21 @@ public:
    * |aOverscrollHandoffChain| and |aOverscrollHandoffChainIndex| are used by
    * the tree manager to keep track of which APZC to hand off the overscroll
    * to; this function increments the chain and the index and passes it on to
    * APZCTreeManager::DispatchScroll() in the event of overscroll.
    * Returns true iff. this APZC, or an APZC further down the
    * handoff chain, accepted the scroll (possibly entering an overscrolled
    * state). If this returns false, the caller APZC knows that it should enter
    * an overscrolled state itself if it can.
+   * aStartPoint and aEndPoint are modified depending on how much of the
+   * scroll gesture was consumed by APZCs in the handoff chain.
    */
-  bool AttemptScroll(const ParentLayerPoint& aStartPoint,
-                     const ParentLayerPoint& aEndPoint,
+  bool AttemptScroll(ParentLayerPoint& aStartPoint,
+                     ParentLayerPoint& aEndPoint,
                      OverscrollHandoffState& aOverscrollHandoffState);
 
   void FlushRepaintForOverscrollHandoff();
 
   /**
    * If overscrolled, start a snap-back animation and return true.
    * Otherwise return false.
    */
@@ -1002,35 +1005,34 @@ public:
   nsRefPtr<const OverscrollHandoffChain> BuildOverscrollHandoffChain();
 
 private:
   /**
    * A helper function for calling APZCTreeManager::DispatchScroll().
    * Guards against the case where the APZC is being concurrently destroyed
    * (and thus mTreeManager is being nulled out).
    */
-  bool CallDispatchScroll(const ParentLayerPoint& aStartPoint,
-                          const ParentLayerPoint& aEndPoint,
+  void CallDispatchScroll(ParentLayerPoint& aStartPoint,
+                          ParentLayerPoint& aEndPoint,
                           OverscrollHandoffState& aOverscrollHandoffState);
 
   /**
    * A helper function for overscrolling during panning. This is a wrapper
    * around OverscrollBy() that also implements restrictions on entering
    * overscroll based on the pan angle.
    */
-  bool OverscrollForPanning(ParentLayerPoint aOverscroll,
+  void OverscrollForPanning(ParentLayerPoint& aOverscroll,
                             const ScreenPoint& aPanDistance);
 
   /**
    * Try to overscroll by 'aOverscroll'.
-   * If we are pannable, 'aOverscroll' is added to any existing overscroll,
-   * and the function returns true.
-   * Otherwise, nothing happens and the function return false.
+   * If we are pannable on a particular axis, that component of 'aOverscroll'
+   * is transferred to any existing overscroll.
    */
-  bool OverscrollBy(const ParentLayerPoint& aOverscroll);
+  void OverscrollBy(ParentLayerPoint& aOverscroll);
 
 
   /* ===================================================================
    * The functions and members in this section are used to maintain the
    * area that this APZC instance is responsible for. This is used when
    * hit-testing to see which APZC instance should handle touch events.
    */
 public:
--- a/gfx/layers/apz/src/Axis.cpp
+++ b/gfx/layers/apz/src/Axis.cpp
@@ -505,16 +505,20 @@ CSSCoord Axis::ScaleWillOverscrollAmount
     return (originAfterScale - GetPageStart()) / zoom;
   }
   if (plus) {
     return (originAfterScale + (GetCompositionLength() / aScale) - GetPageEnd()) / zoom;
   }
   return 0;
 }
 
+bool Axis::IsAxisLocked() const {
+  return mAxisLocked;
+}
+
 float Axis::GetVelocity() const {
   return mAxisLocked ? 0 : mVelocity;
 }
 
 void Axis::SetVelocity(float aVelocity) {
   AXIS_LOG("%p|%s direct-setting velocity to %f\n",
     mAsyncPanZoomController, Name(), aVelocity);
   mVelocity = aVelocity;
--- a/gfx/layers/apz/src/Axis.h
+++ b/gfx/layers/apz/src/Axis.h
@@ -218,16 +218,21 @@ public:
    * Checks if an axis will overscroll in both directions by computing the
    * content rect and checking that its height/width (depending on the axis)
    * does not overextend past the viewport.
    *
    * This gets called by ScaleWillOverscroll().
    */
   bool ScaleWillOverscrollBothSides(float aScale) const;
 
+  /**
+   * Returns true if movement on this axis is locked.
+   */
+  bool IsAxisLocked() const;
+
   ParentLayerCoord GetOrigin() const;
   ParentLayerCoord GetCompositionLength() const;
   ParentLayerCoord GetPageStart() const;
   ParentLayerCoord GetPageLength() const;
   ParentLayerCoord GetCompositionEnd() const;
   ParentLayerCoord GetPageEnd() const;
 
   ParentLayerCoord GetPos() const { return mPos; }
--- a/gfx/layers/apz/src/OverscrollHandoffState.cpp
+++ b/gfx/layers/apz/src/OverscrollHandoffState.cpp
@@ -102,33 +102,20 @@ OverscrollHandoffChain::ClearOverscroll(
 }
 
 void
 OverscrollHandoffChain::SnapBackOverscrolledApzc(const AsyncPanZoomController* aStart) const
 {
   uint32_t i = IndexOf(aStart);
   for (; i < Length(); ++i) {
     AsyncPanZoomController* apzc = mChain[i];
-    if (!apzc->IsDestroyed() && apzc->SnapBackIfOverscrolled()) {
-      // At most one APZC from |aStart| onwards can be overscrolled.
-      break;
+    if (!apzc->IsDestroyed()) {
+      apzc->SnapBackIfOverscrolled();
     }
   }
-
-  // In debug builds, verify our assumption that only one APZC from |aStart|
-  // onwards is overscrolled.
-#ifdef DEBUG
-  ++i;
-  for (; i < Length(); ++i) {
-    AsyncPanZoomController* apzc = mChain[i];
-    if (!apzc->IsDestroyed()) {
-      MOZ_ASSERT(!apzc->IsOverscrolled());
-    }
-  }
-#endif
 }
 
 bool
 OverscrollHandoffChain::CanBePanned(const AsyncPanZoomController* aApzc) const
 {
   // Find |aApzc| in the handoff chain.
   uint32_t i = IndexOf(aApzc);
 
--- a/gfx/layers/apz/util/DoubleTapToZoom.cpp
+++ b/gfx/layers/apz/util/DoubleTapToZoom.cpp
@@ -66,33 +66,54 @@ ShouldZoomToElement(const nsCOMPtr<dom::
   }
   if (aElement->IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::q)) {
     return false;
   }
   return true;
 }
 
 // Calculate the bounding rect of |aElement|, relative to the origin
-// of the document associated with |aShell|.
-// |aRootScrollFrame| should be the root scroll frame of the document in
-// question.
-// The implementation is adapted from Element::GetBoundingClientRect().
+// of the scrolled content of |aRootScrollFrame|.
+// The implementation of this calculation is adapted from
+// Element::GetBoundingClientRect().
+//
+// Where the element is contained inside a scrollable subframe, the
+// bounding rect is clipped to the bounds of the subframe.
 static CSSRect
-GetBoundingContentRect(const nsCOMPtr<nsIPresShell>& aShell,
-                       const nsCOMPtr<dom::Element>& aElement,
+GetBoundingContentRect(const nsCOMPtr<dom::Element>& aElement,
                        const nsIScrollableFrame* aRootScrollFrame) {
+
+  CSSRect result;
   if (nsIFrame* frame = aElement->GetPrimaryFrame()) {
-    return CSSRect::FromAppUnits(
+    nsIFrame* relativeTo = aRootScrollFrame->GetScrolledFrame();
+    result = CSSRect::FromAppUnits(
         nsLayoutUtils::GetAllInFlowRectsUnion(
             frame,
-            aShell->GetRootFrame(),
-            nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS)
-      + aRootScrollFrame->GetScrollPosition());
+            relativeTo,
+            nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS));
+
+    // If the element is contained in a scrollable frame that is not
+    // the root scroll frame, make sure to clip the result so that it is
+    // not larger than the containing scrollable frame's bounds.
+    nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(frame);
+    if (scrollFrame && scrollFrame != aRootScrollFrame) {
+      nsIFrame* subFrame = do_QueryFrame(scrollFrame);
+      MOZ_ASSERT(subFrame);
+      // Get the bounds of the scroll frame in the same coordinate space
+      // as |result|.
+      CSSRect subFrameRect = CSSRect::FromAppUnits(
+          nsLayoutUtils::TransformFrameRectToAncestor(
+              subFrame,
+              subFrame->GetRectRelativeToSelf(),
+              relativeTo));
+
+      result = subFrameRect.Intersect(result);
+    }
   }
-  return CSSRect();
+  return result;
 }
 
 static bool
 IsRectZoomedIn(const CSSRect& aRect, const CSSRect& aCompositedArea)
 {
   // This functions checks to see if the area of the rect visible in the
   // composition bounds (i.e. the overlapArea variable below) is approximately
   // the max area of the rect we can show.
@@ -138,17 +159,37 @@ CalculateRectToZoomTo(const nsCOMPtr<nsI
 
   if (!element) {
     return zoomOut;
   }
 
   FrameMetrics metrics = nsLayoutUtils::CalculateBasicFrameMetrics(rootScrollFrame);
   CSSRect compositedArea(metrics.GetScrollOffset(), metrics.CalculateCompositedSizeInCssPixels());
   const CSSCoord margin = 15;
-  CSSRect rect = GetBoundingContentRect(shell, element, rootScrollFrame);
+  CSSRect rect = GetBoundingContentRect(element, rootScrollFrame);
+
+  // If the element is taller than the visible area of the page scale
+  // the height of the |rect| so that it has the same aspect ratio as
+  // the root frame.  The clipped |rect| is centered on the y value of
+  // the touch point. This allows tall narrow elements to be zoomed.
+  if (!rect.IsEmpty() && compositedArea.width > 0.0f) {
+    const float widthRatio = rect.width / compositedArea.width;
+    float targetHeight = compositedArea.height * widthRatio;
+    if (widthRatio < 0.9 && targetHeight < rect.height) {
+      const CSSPoint scrollPoint = CSSPoint::FromAppUnits(rootScrollFrame->GetScrollPosition());
+      float newY = aPoint.y + scrollPoint.y - (targetHeight * 0.5f);
+      if ((newY + targetHeight) > (rect.y + rect.height)) {
+        rect.y += rect.height - targetHeight;
+      } else if (newY > rect.y) {
+        rect.y = newY;
+      }
+      rect.height = targetHeight;
+    }
+  }
+
   rect = CSSRect(std::max(metrics.GetScrollableRect().x, rect.x - margin),
                  rect.y,
                  rect.width + 2 * margin,
                  rect.height);
   // Constrict the rect to the screen's right edge
   rect.width = std::min(rect.width, metrics.GetScrollableRect().XMost() - rect.x);
 
   // If the rect is already taking up most of the visible area and is
--- a/gfx/layers/basic/BasicPaintedLayer.cpp
+++ b/gfx/layers/basic/BasicPaintedLayer.cpp
@@ -86,17 +86,17 @@ BasicPaintedLayer::PaintThebes(gfxContex
                                             &needsClipToVisibleRegion);
         if (effectiveOperator != CompositionOp::OP_OVER) {
           needsClipToVisibleRegion = true;
         }
       } else {
         groupContext = aContext;
       }
       SetAntialiasingFlags(this, groupContext->GetDrawTarget());
-      aCallback(this, groupContext, toDraw, &toDraw,
+      aCallback(this, groupContext, toDraw, toDraw,
                 DrawRegionClip::NONE, nsIntRegion(), aCallbackData);
       if (needsGroup) {
         aContext->PopGroupToSource();
         if (needsClipToVisibleRegion) {
           gfxUtils::ClipToRegion(aContext, toDraw);
         }
         AutoSetOperator setOptimizedOperator(aContext, ThebesOp(effectiveOperator));
         PaintWithMask(aContext, opacity, aMaskLayer);
--- a/gfx/layers/basic/BasicPaintedLayer.h
+++ b/gfx/layers/basic/BasicPaintedLayer.h
@@ -107,17 +107,17 @@ protected:
               DrawRegionClip aClip,
               LayerManager::DrawPaintedLayerCallback aCallback,
               void* aCallbackData)
   {
     if (!aCallback) {
       BasicManager()->SetTransactionIncomplete();
       return;
     }
-    aCallback(this, aContext, aExtendedRegionToDraw, &aExtendedRegionToDraw,
+    aCallback(this, aContext, aExtendedRegionToDraw, aExtendedRegionToDraw,
               aClip, aRegionToInvalidate, aCallbackData);
     // Everything that's visible has been validated. Do this instead of just
     // OR-ing with aRegionToDraw, since that can lead to a very complex region
     // here (OR doesn't automatically simplify to the simplest possible
     // representation of a region.)
     nsIntRegion tmp;
     tmp.Or(mVisibleRegion, aExtendedRegionToDraw);
     mValidRegion.Or(mValidRegion, tmp);
--- a/gfx/layers/client/ClientPaintedLayer.cpp
+++ b/gfx/layers/client/ClientPaintedLayer.cpp
@@ -82,17 +82,17 @@ ClientPaintedLayer::PaintThebes()
   while (DrawTarget* target = mContentClient->BorrowDrawTargetForPainting(state, &iter)) {
     SetAntialiasingFlags(this, target);
 
     nsRefPtr<gfxContext> ctx = gfxContext::ContextForDrawTarget(target);
 
     ClientManager()->GetPaintedLayerCallback()(this,
                                               ctx,
                                               iter.mDrawRegion,
-                                              nullptr,
+                                              iter.mDrawRegion,
                                               state.mClip,
                                               state.mRegionToInvalidate,
                                               ClientManager()->GetPaintedLayerCallbackData());
 
     ctx = nullptr;
     mContentClient->ReturnDrawTargetToBuffer(target);
     didUpdate = true;
   }
--- a/gfx/layers/client/CompositableClient.cpp
+++ b/gfx/layers/client/CompositableClient.cpp
@@ -265,10 +265,31 @@ CompositableClient::DumpTextureClient(st
   }
   RefPtr<gfx::DataSourceSurface> dSurf = aTexture->GetAsSurface();
   if (!dSurf) {
     return;
   }
   aStream << gfxUtils::GetAsLZ4Base64Str(dSurf).get();
 }
 
+AutoRemoveTexture::~AutoRemoveTexture()
+{
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
+  if (mCompositable && mTexture && mCompositable->GetForwarder()) {
+    // remove old buffer from CompositableHost
+    RefPtr<AsyncTransactionWaiter> waiter = new AsyncTransactionWaiter();
+    RefPtr<AsyncTransactionTracker> tracker =
+        new RemoveTextureFromCompositableTracker(waiter);
+    // Hold TextureClient until transaction complete.
+    tracker->SetTextureClient(mTexture);
+    mTexture->SetRemoveFromCompositableWaiter(waiter);
+    // RemoveTextureFromCompositableAsync() expects CompositorChild's presence.
+    mCompositable->GetForwarder()->RemoveTextureFromCompositableAsync(tracker, mCompositable, mTexture);
+  }
+#else
+  if (mCompositable && mTexture) {
+    mCompositable->RemoveTexture(mTexture);
+  }
+#endif
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/client/CompositableClient.h
+++ b/gfx/layers/client/CompositableClient.h
@@ -243,22 +243,17 @@ protected:
 struct AutoRemoveTexture
 {
   explicit AutoRemoveTexture(CompositableClient* aCompositable,
                              TextureClient* aTexture = nullptr)
     : mTexture(aTexture)
     , mCompositable(aCompositable)
   {}
 
-  ~AutoRemoveTexture()
-  {
-    if (mCompositable && mTexture) {
-      mCompositable->RemoveTexture(mTexture);
-    }
-  }
+  ~AutoRemoveTexture();
 
   RefPtr<TextureClient> mTexture;
 private:
   CompositableClient* mCompositable;
 };
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/client/ImageClient.cpp
+++ b/gfx/layers/client/ImageClient.cpp
@@ -355,17 +355,16 @@ ImageClientOverlay::UpdateImage(ImageCon
     return false;
   }
 
   if (mLastUpdateGenerationCounter == (uint32_t)image->GetSerial()) {
     return true;
   }
   mLastUpdateGenerationCounter = (uint32_t)image->GetSerial();
 
-  AutoRemoveTexture autoRemoveTexture(this);
   if (image->GetFormat() == ImageFormat::OVERLAY_IMAGE) {
     OverlayImage* overlayImage = static_cast<OverlayImage*>(image);
     uint32_t overlayId = overlayImage->GetOverlayId();
     gfx::IntSize size = overlayImage->GetSize();
 
     OverlaySource source;
     source.handle() = OverlayHandle(overlayId);
     source.size() = size;
--- a/gfx/layers/client/SingleTiledContentClient.cpp
+++ b/gfx/layers/client/SingleTiledContentClient.cpp
@@ -176,17 +176,17 @@ ClientSingleTiledLayerBuffer::PaintThebe
     dt = Factory::CreateDualDrawTarget(dt, dtOnWhite);
     dtOnWhite = nullptr;
   }
 
   {
     nsRefPtr<gfxContext> ctx = new gfxContext(dt);
     ctx->SetMatrix(ctx->CurrentMatrix().Translate(-mTilingOrigin.x, -mTilingOrigin.y));
 
-    aCallback(mPaintedLayer, ctx, paintRegion, &paintRegion, DrawRegionClip::DRAW, nsIntRegion(), aCallbackData);
+    aCallback(mPaintedLayer, ctx, paintRegion, paintRegion, DrawRegionClip::DRAW, nsIntRegion(), aCallbackData);
   }
 
   // Mark the area we just drew into the back buffer as invalid in the front buffer as they're
   // now out of sync.
   mTile.mInvalidFront.OrWith(tileDirtyRegion);
 
   // The new buffer is now validated, remove the dirty region from it.
   mTile.mInvalidBack.SubOut(tileDirtyRegion);
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -959,17 +959,17 @@ ClientMultiTiledLayerBuffer::PaintThebes
     if (PR_IntervalNow() - start > 3) {
       printf_stderr("Slow alloc %i\n", PR_IntervalNow() - start);
     }
     start = PR_IntervalNow();
 #endif
     PROFILER_LABEL("ClientMultiTiledLayerBuffer", "PaintThebesSingleBufferDraw",
       js::ProfileEntry::Category::GRAPHICS);
 
-    mCallback(mPaintedLayer, ctxt, aPaintRegion, &aDirtyRegion,
+    mCallback(mPaintedLayer, ctxt, aPaintRegion, aDirtyRegion,
               DrawRegionClip::NONE, nsIntRegion(), mCallbackData);
   }
 
 #ifdef GFX_TILEDLAYER_PREF_WARNINGS
   if (PR_IntervalNow() - start > 30) {
     const IntRect bounds = aPaintRegion.GetBounds();
     printf_stderr("Time to draw %i: %i, %i, %i, %i\n", PR_IntervalNow() - start, bounds.x, bounds.y, bounds.width, bounds.height);
     if (aPaintRegion.IsComplex()) {
@@ -1166,17 +1166,17 @@ void ClientMultiTiledLayerBuffer::Update
     tileset.mTileCount = mMoz2DTiles.size();
     RefPtr<DrawTarget> drawTarget = gfx::Factory::CreateTiledDrawTarget(tileset);
     drawTarget->SetTransform(Matrix());
 
     RefPtr<gfxContext> ctx = new gfxContext(drawTarget);
     ctx->SetMatrix(
       ctx->CurrentMatrix().Scale(mResolution, mResolution).Translate(ThebesPoint(-mTilingOrigin)));
 
-    mCallback(mPaintedLayer, ctx, aPaintRegion, &aDirtyRegion,
+    mCallback(mPaintedLayer, ctx, aPaintRegion, aDirtyRegion,
               DrawRegionClip::DRAW, nsIntRegion(), mCallbackData);
     mMoz2DTiles.clear();
     // Reset:
     mTilingOrigin = IntPoint(std::numeric_limits<int32_t>::max(),
                              std::numeric_limits<int32_t>::max());
   }
 
   bool edgePaddingEnabled = gfxPrefs::TileEdgePaddingEnabled();
new file mode 100644
--- /dev/null
+++ b/js/src/asmjs/AsmJSCompile.cpp
@@ -0,0 +1,3172 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Copyright 2015 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "asmjs/AsmJSCompile.h"
+#include "asmjs/AsmJSGlobals.h"
+
+#include "jit/CodeGenerator.h"
+
+using namespace js;
+using namespace js::jit;
+using namespace js::wasm;
+
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+
+namespace js {
+// ModuleCompiler encapsulates the compilation of an entire asm.js module. Over
+// the course of an ModuleCompiler object's lifetime, many FunctionCompiler
+// objects will be created and destroyed in sequence, one for each function in
+// the module.
+//
+// *** asm.js FFI calls ***
+//
+// asm.js allows calling out to non-asm.js via "FFI calls". The asm.js type
+// system does not place any constraints on the FFI call. In particular:
+//  - an FFI call's target is not known or speculated at module-compile time;
+//  - a single external function can be called with different signatures.
+//
+// If performance didn't matter, all FFI calls could simply box their arguments
+// and call js::Invoke. However, we'd like to be able to specialize FFI calls
+// to be more efficient in several cases:
+//
+//  - for calls to JS functions which have been jitted, we'd like to call
+//    directly into JIT code without going through C++.
+//
+//  - for calls to certain builtins, we'd like to be call directly into the C++
+//    code for the builtin without going through the general call path.
+//
+// All of this requires dynamic specialization techniques which must happen
+// after module compilation. To support this, at module-compilation time, each
+// FFI call generates a call signature according to the system ABI, as if the
+// callee was a C++ function taking/returning the same types as the caller was
+// passing/expecting. The callee is loaded from a fixed offset in the global
+// data array which allows the callee to change at runtime. Initially, the
+// callee is stub which boxes its arguments and calls js::Invoke.
+//
+// To do this, we need to generate a callee stub for each pairing of FFI callee
+// and signature. We call this pairing an "exit". For example, this code has
+// two external functions and three exits:
+//
+//  function f(global, imports) {
+//    "use asm";
+//    var foo = imports.foo;
+//    var bar = imports.bar;
+//    function g() {
+//      foo(1);      // Exit #1: (int) -> void
+//      foo(1.5);    // Exit #2: (double) -> void
+//      bar(1)|0;    // Exit #3: (int) -> int
+//      bar(2)|0;    // Exit #3: (int) -> int
+//    }
+//  }
+//
+// The ModuleCompiler maintains a hash table (ExitMap) which allows a call site
+// to add a new exit or reuse an existing one. The key is an index into the
+// Vector<Exit> stored in the AsmJSModule and the value is the signature of
+// that exit's variant.
+//
+// Although ModuleCompiler isn't a MOZ_STACK_CLASS, it has the same rooting
+// properties as the ModuleValidator, and a shorter lifetime: so it is marked
+// as rooted in the in the rooting analysis. Don't add non-JSATom pointers, or
+// this will break!
+class ModuleCompiler
+{
+    ModuleCompileInputs                     compileInputs_;
+    ScopedJSDeletePtr<ModuleCompileResults> compileResults_;
+
+  public:
+    explicit ModuleCompiler(const ModuleCompileInputs& inputs)
+      : compileInputs_(inputs)
+    {}
+
+    bool init() {
+        compileResults_.reset(js_new<ModuleCompileResults>());
+        return !!compileResults_;
+    }
+
+    /*************************************************** Read-only interface */
+
+    MacroAssembler& masm()          { return compileResults_->masm(); }
+    Label& stackOverflowLabel()     { return compileResults_->stackOverflowLabel(); }
+    Label& asyncInterruptLabel()    { return compileResults_->asyncInterruptLabel(); }
+    Label& syncInterruptLabel()     { return compileResults_->syncInterruptLabel(); }
+    Label& onOutOfBoundsLabel()     { return compileResults_->onOutOfBoundsLabel(); }
+    Label& onConversionErrorLabel() { return compileResults_->onConversionErrorLabel(); }
+    int64_t usecBefore()            { return compileResults_->usecBefore(); }
+
+    bool usesSignalHandlersForOOB() const   { return compileInputs_.usesSignalHandlersForOOB; }
+    CompileRuntime* runtime() const         { return compileInputs_.runtime; }
+    CompileCompartment* compartment() const { return compileInputs_.compartment; }
+
+    /***************************************************** Mutable interface */
+
+    bool getOrCreateFunctionEntry(uint32_t funcIndex, Label** label)
+    {
+        return compileResults_->getOrCreateFunctionEntry(funcIndex, label);
+    }
+
+    bool finishGeneratingFunction(AsmFunction& func, CodeGenerator& codegen,
+                                  const AsmJSFunctionLabels& labels)
+    {
+        // Code range
+        unsigned line = func.lineno();
+        unsigned column = func.column();
+        PropertyName* funcName = func.name();
+        if (!compileResults_->addCodeRange(AsmJSModule::FunctionCodeRange(funcName, line, labels)))
+            return false;
+
+        // Script counts
+        jit::IonScriptCounts* counts = codegen.extractScriptCounts();
+        if (counts && !compileResults_->addFunctionCounts(counts)) {
+            js_delete(counts);
+            return false;
+        }
+
+        // Slow functions
+        if (func.compileTime() >= 250) {
+            ModuleCompileResults::SlowFunction sf(funcName, func.compileTime(), line, column);
+            if (!compileResults_->slowFunctions().append(Move(sf)))
+                return false;
+        }
+
+#if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
+        // Perf and profiling information
+        unsigned begin = labels.begin.offset();
+        unsigned end = labels.end.offset();
+        AsmJSModule::ProfiledFunction profiledFunc(funcName, begin, end, line, column);
+        if (!compileResults_->addProfiledFunction(profiledFunc))
+            return false;
+#endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF)
+        return true;
+    }
+
+    void finish(ScopedJSDeletePtr<ModuleCompileResults>* results) {
+        *results = compileResults_.forget();
+    }
+};
+
+} // namespace js
+
+enum class AsmType : uint8_t {
+    Int32,
+    Float32,
+    Float64,
+    Int32x4,
+    Float32x4
+};
+
+typedef Vector<size_t, 1, SystemAllocPolicy> LabelVector;
+typedef Vector<MBasicBlock*, 8, SystemAllocPolicy> BlockVector;
+
+// Encapsulates the compilation of a single function in an asm.js module. The
+// function compiler handles the creation and final backend compilation of the
+// MIR graph. Also see ModuleCompiler comment.
+class FunctionCompiler
+{
+  private:
+    typedef HashMap<uint32_t, BlockVector, DefaultHasher<uint32_t>, SystemAllocPolicy> LabeledBlockMap;
+    typedef HashMap<size_t, BlockVector, DefaultHasher<uint32_t>, SystemAllocPolicy> UnlabeledBlockMap;
+    typedef Vector<size_t, 4, SystemAllocPolicy> PositionStack;
+    typedef Vector<Type, 4, SystemAllocPolicy> LocalVarTypes;
+
+    ModuleCompiler &         m_;
+    LifoAlloc &              lifo_;
+    RetType                  retType_;
+
+    const AsmFunction &      func_;
+    size_t                   pc_;
+
+    TempAllocator *          alloc_;
+    MIRGraph *               graph_;
+    CompileInfo *            info_;
+    MIRGenerator *           mirGen_;
+    Maybe<JitContext>        jitContext_;
+
+    MBasicBlock *            curBlock_;
+
+    PositionStack            loopStack_;
+    PositionStack            breakableStack_;
+    UnlabeledBlockMap        unlabeledBreaks_;
+    UnlabeledBlockMap        unlabeledContinues_;
+    LabeledBlockMap          labeledBreaks_;
+    LabeledBlockMap          labeledContinues_;
+
+    LocalVarTypes            localVarTypes_;
+
+  public:
+    FunctionCompiler(ModuleCompiler& m, const AsmFunction& func, LifoAlloc& lifo)
+      : m_(m),
+        lifo_(lifo),
+        retType_(func.returnedType()),
+        func_(func),
+        pc_(0),
+        alloc_(nullptr),
+        graph_(nullptr),
+        info_(nullptr),
+        mirGen_(nullptr),
+        curBlock_(nullptr)
+    {}
+
+    ModuleCompiler &        m() const            { return m_; }
+    TempAllocator &         alloc() const        { return *alloc_; }
+    LifoAlloc &             lifo() const         { return lifo_; }
+    RetType                 returnedType() const { return retType_; }
+
+    bool init()
+    {
+        return unlabeledBreaks_.init() &&
+               unlabeledContinues_.init() &&
+               labeledBreaks_.init() &&
+               labeledContinues_.init();
+    }
+
+    void checkPostconditions()
+    {
+        MOZ_ASSERT(loopStack_.empty());
+        MOZ_ASSERT(unlabeledBreaks_.empty());
+        MOZ_ASSERT(unlabeledContinues_.empty());
+        MOZ_ASSERT(labeledBreaks_.empty());
+        MOZ_ASSERT(labeledContinues_.empty());
+        MOZ_ASSERT(inDeadCode());
+        MOZ_ASSERT(pc_ == func_.size(), "all bytecode must be consumed");
+    }
+
+    /************************* Read-only interface (after local scope setup) */
+
+    MIRGenerator & mirGen() const     { MOZ_ASSERT(mirGen_); return *mirGen_; }
+    MIRGraph &     mirGraph() const   { MOZ_ASSERT(graph_); return *graph_; }
+    CompileInfo &  info() const       { MOZ_ASSERT(info_); return *info_; }
+
+    MDefinition* getLocalDef(unsigned slot)
+    {
+        if (inDeadCode())
+            return nullptr;
+        return curBlock_->getSlot(info().localSlot(slot));
+    }
+
+    /***************************** Code generation (after local scope setup) */
+
+    MDefinition* constant(const SimdConstant& v, MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+        MInstruction* constant;
+        constant = MSimdConstant::New(alloc(), v, type);
+        curBlock_->add(constant);
+        return constant;
+    }
+
+    MDefinition* constant(Value v, MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+        MConstant* constant = MConstant::NewAsmJS(alloc(), v, type);
+        curBlock_->add(constant);
+        return constant;
+    }
+
+    template <class T>
+    MDefinition* unary(MDefinition* op)
+    {
+        if (inDeadCode())
+            return nullptr;
+        T* ins = T::NewAsmJS(alloc(), op);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    template <class T>
+    MDefinition* unary(MDefinition* op, MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+        T* ins = T::NewAsmJS(alloc(), op, type);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    template <class T>
+    MDefinition* binary(MDefinition* lhs, MDefinition* rhs)
+    {
+        if (inDeadCode())
+            return nullptr;
+        T* ins = T::New(alloc(), lhs, rhs);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    template <class T>
+    MDefinition* binary(MDefinition* lhs, MDefinition* rhs, MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+        T* ins = T::NewAsmJS(alloc(), lhs, rhs, type);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* unarySimd(MDefinition* input, MSimdUnaryArith::Operation op, MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MOZ_ASSERT(IsSimdType(input->type()) && input->type() == type);
+        MInstruction* ins = MSimdUnaryArith::NewAsmJS(alloc(), input, op, type);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* binarySimd(MDefinition* lhs, MDefinition* rhs, MSimdBinaryArith::Operation op,
+                            MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MOZ_ASSERT(IsSimdType(lhs->type()) && rhs->type() == lhs->type());
+        MOZ_ASSERT(lhs->type() == type);
+        MSimdBinaryArith* ins = MSimdBinaryArith::NewAsmJS(alloc(), lhs, rhs, op, type);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* binarySimd(MDefinition* lhs, MDefinition* rhs, MSimdBinaryBitwise::Operation op,
+                            MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MOZ_ASSERT(IsSimdType(lhs->type()) && rhs->type() == lhs->type());
+        MOZ_ASSERT(lhs->type() == type);
+        MSimdBinaryBitwise* ins = MSimdBinaryBitwise::NewAsmJS(alloc(), lhs, rhs, op, type);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    template<class T>
+    MDefinition* binarySimd(MDefinition* lhs, MDefinition* rhs, typename T::Operation op)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        T* ins = T::NewAsmJS(alloc(), lhs, rhs, op);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* swizzleSimd(MDefinition* vector, int32_t X, int32_t Y, int32_t Z, int32_t W,
+                             MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MSimdSwizzle* ins = MSimdSwizzle::New(alloc(), vector, type, X, Y, Z, W);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* shuffleSimd(MDefinition* lhs, MDefinition* rhs, int32_t X, int32_t Y,
+                             int32_t Z, int32_t W, MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MInstruction* ins = MSimdShuffle::New(alloc(), lhs, rhs, type, X, Y, Z, W);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* insertElementSimd(MDefinition* vec, MDefinition* val, SimdLane lane, MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MOZ_ASSERT(IsSimdType(vec->type()) && vec->type() == type);
+        MOZ_ASSERT(!IsSimdType(val->type()));
+        MSimdInsertElement* ins = MSimdInsertElement::NewAsmJS(alloc(), vec, val, type, lane);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* selectSimd(MDefinition* mask, MDefinition* lhs, MDefinition* rhs, MIRType type,
+                            bool isElementWise)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MOZ_ASSERT(IsSimdType(mask->type()));
+        MOZ_ASSERT(mask->type() == MIRType_Int32x4);
+        MOZ_ASSERT(IsSimdType(lhs->type()) && rhs->type() == lhs->type());
+        MOZ_ASSERT(lhs->type() == type);
+        MSimdSelect* ins = MSimdSelect::NewAsmJS(alloc(), mask, lhs, rhs, type, isElementWise);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    template<class T>
+    MDefinition* convertSimd(MDefinition* vec, MIRType from, MIRType to)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MOZ_ASSERT(IsSimdType(from) && IsSimdType(to) && from != to);
+        T* ins = T::NewAsmJS(alloc(), vec, from, to);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* splatSimd(MDefinition* v, MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MOZ_ASSERT(IsSimdType(type));
+        MSimdSplatX4* ins = MSimdSplatX4::NewAsmJS(alloc(), v, type);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* minMax(MDefinition* lhs, MDefinition* rhs, MIRType type, bool isMax) {
+        if (inDeadCode())
+            return nullptr;
+        MMinMax* ins = MMinMax::New(alloc(), lhs, rhs, type, isMax);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* mul(MDefinition* lhs, MDefinition* rhs, MIRType type, MMul::Mode mode)
+    {
+        if (inDeadCode())
+            return nullptr;
+        MMul* ins = MMul::New(alloc(), lhs, rhs, type, mode);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* div(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd)
+    {
+        if (inDeadCode())
+            return nullptr;
+        MDiv* ins = MDiv::NewAsmJS(alloc(), lhs, rhs, type, unsignd);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* mod(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd)
+    {
+        if (inDeadCode())
+            return nullptr;
+        MMod* ins = MMod::NewAsmJS(alloc(), lhs, rhs, type, unsignd);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    template <class T>
+    MDefinition* bitwise(MDefinition* lhs, MDefinition* rhs)
+    {
+        if (inDeadCode())
+            return nullptr;
+        T* ins = T::NewAsmJS(alloc(), lhs, rhs);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    template <class T>
+    MDefinition* bitwise(MDefinition* op)
+    {
+        if (inDeadCode())
+            return nullptr;
+        T* ins = T::NewAsmJS(alloc(), op);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* compare(MDefinition* lhs, MDefinition* rhs, JSOp op, MCompare::CompareType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+        MCompare* ins = MCompare::NewAsmJS(alloc(), lhs, rhs, op, type);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    void assign(unsigned slot, MDefinition* def)
+    {
+        if (inDeadCode())
+            return;
+        curBlock_->setSlot(info().localSlot(slot), def);
+    }
+
+    MDefinition* loadHeap(Scalar::Type accessType, MDefinition* ptr, NeedsBoundsCheck chk)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
+        MOZ_ASSERT(!Scalar::isSimdType(accessType), "SIMD loads should use loadSimdHeap");
+        MAsmJSLoadHeap* load = MAsmJSLoadHeap::New(alloc(), accessType, ptr, needsBoundsCheck);
+        curBlock_->add(load);
+        return load;
+    }
+
+    MDefinition* loadSimdHeap(Scalar::Type accessType, MDefinition* ptr, NeedsBoundsCheck chk,
+                              unsigned numElems)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
+        MOZ_ASSERT(Scalar::isSimdType(accessType), "loadSimdHeap can only load from a SIMD view");
+        MAsmJSLoadHeap* load = MAsmJSLoadHeap::New(alloc(), accessType, ptr, needsBoundsCheck,
+                                                   numElems);
+        curBlock_->add(load);
+        return load;
+    }
+
+    void storeHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* v, NeedsBoundsCheck chk)
+    {
+        if (inDeadCode())
+            return;
+
+        bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
+        MOZ_ASSERT(!Scalar::isSimdType(accessType), "SIMD stores should use loadSimdHeap");
+        MAsmJSStoreHeap* store = MAsmJSStoreHeap::New(alloc(), accessType, ptr, v, needsBoundsCheck);
+        curBlock_->add(store);
+    }
+
+    void storeSimdHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* v,
+                       NeedsBoundsCheck chk, unsigned numElems)
+    {
+        if (inDeadCode())
+            return;
+
+        bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
+        MOZ_ASSERT(Scalar::isSimdType(accessType), "storeSimdHeap can only load from a SIMD view");
+        MAsmJSStoreHeap* store = MAsmJSStoreHeap::New(alloc(), accessType, ptr, v, needsBoundsCheck,
+                                                      numElems);
+        curBlock_->add(store);
+    }
+
+    void memoryBarrier(MemoryBarrierBits type)
+    {
+        if (inDeadCode())
+            return;
+        MMemoryBarrier* ins = MMemoryBarrier::New(alloc(), type);
+        curBlock_->add(ins);
+    }
+
+    MDefinition* atomicLoadHeap(Scalar::Type accessType, MDefinition* ptr, NeedsBoundsCheck chk)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
+        MAsmJSLoadHeap* load = MAsmJSLoadHeap::New(alloc(), accessType, ptr, needsBoundsCheck,
+                                                   /* numElems */ 0,
+                                                   MembarBeforeLoad, MembarAfterLoad);
+        curBlock_->add(load);
+        return load;
+    }
+
+    void atomicStoreHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* v, NeedsBoundsCheck chk)
+    {
+        if (inDeadCode())
+            return;
+
+        bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
+        MAsmJSStoreHeap* store = MAsmJSStoreHeap::New(alloc(), accessType, ptr, v, needsBoundsCheck,
+                                                      /* numElems = */ 0,
+                                                      MembarBeforeStore, MembarAfterStore);
+        curBlock_->add(store);
+    }
+
+    MDefinition* atomicCompareExchangeHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* oldv,
+                                           MDefinition* newv, NeedsBoundsCheck chk)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
+        MAsmJSCompareExchangeHeap* cas =
+            MAsmJSCompareExchangeHeap::New(alloc(), accessType, ptr, oldv, newv, needsBoundsCheck);
+        curBlock_->add(cas);
+        return cas;
+    }
+
+    MDefinition* atomicExchangeHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* value,
+                                    NeedsBoundsCheck chk)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
+        MAsmJSAtomicExchangeHeap* cas =
+            MAsmJSAtomicExchangeHeap::New(alloc(), accessType, ptr, value, needsBoundsCheck);
+        curBlock_->add(cas);
+        return cas;
+    }
+
+    MDefinition* atomicBinopHeap(js::jit::AtomicOp op, Scalar::Type accessType, MDefinition* ptr,
+                                 MDefinition* v, NeedsBoundsCheck chk)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
+        MAsmJSAtomicBinopHeap* binop =
+            MAsmJSAtomicBinopHeap::New(alloc(), op, accessType, ptr, v, needsBoundsCheck);
+        curBlock_->add(binop);
+        return binop;
+    }
+
+    MDefinition* loadGlobalVar(unsigned globalDataOffset, bool isConst, MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+        MAsmJSLoadGlobalVar* load = MAsmJSLoadGlobalVar::New(alloc(), type, globalDataOffset,
+                                                             isConst);
+        curBlock_->add(load);
+        return load;
+    }
+
+    void storeGlobalVar(uint32_t globalDataOffset, MDefinition* v)
+    {
+        if (inDeadCode())
+            return;
+        curBlock_->add(MAsmJSStoreGlobalVar::New(alloc(), globalDataOffset, v));
+    }
+
+    void addInterruptCheck(unsigned lineno, unsigned column)
+    {
+        if (inDeadCode())
+            return;
+
+        CallSiteDesc callDesc(lineno, column, CallSiteDesc::Relative);
+        curBlock_->add(MAsmJSInterruptCheck::New(alloc(), &m().syncInterruptLabel(), callDesc));
+    }
+
+    MDefinition* extractSimdElement(SimdLane lane, MDefinition* base, MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MOZ_ASSERT(IsSimdType(base->type()));
+        MOZ_ASSERT(!IsSimdType(type));
+        MSimdExtractElement* ins = MSimdExtractElement::NewAsmJS(alloc(), base, type, lane);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    MDefinition* extractSignMask(MDefinition* base)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MOZ_ASSERT(IsSimdType(base->type()));
+        MSimdSignMask* ins = MSimdSignMask::NewAsmJS(alloc(), base);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    template<typename T>
+    MDefinition* constructSimd(MDefinition* x, MDefinition* y, MDefinition* z, MDefinition* w,
+                               MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MOZ_ASSERT(IsSimdType(type));
+        T* ins = T::NewAsmJS(alloc(), type, x, y, z, w);
+        curBlock_->add(ins);
+        return ins;
+    }
+
+    /***************************************************************** Calls */
+
+    // The IonMonkey backend maintains a single stack offset (from the stack
+    // pointer to the base of the frame) by adding the total amount of spill
+    // space required plus the maximum stack required for argument passing.
+    // Since we do not use IonMonkey's MPrepareCall/MPassArg/MCall, we must
+    // manually accumulate, for the entire function, the maximum required stack
+    // space for argument passing. (This is passed to the CodeGenerator via
+    // MIRGenerator::maxAsmJSStackArgBytes.) Naively, this would just be the
+    // maximum of the stack space required for each individual call (as
+    // determined by the call ABI). However, as an optimization, arguments are
+    // stored to the stack immediately after evaluation (to decrease live
+    // ranges and reduce spilling). This introduces the complexity that,
+    // between evaluating an argument and making the call, another argument
+    // evaluation could perform a call that also needs to store to the stack.
+    // When this occurs childClobbers_ = true and the parent expression's
+    // arguments are stored above the maximum depth clobbered by a child
+    // expression.
+
+    class Call
+    {
+        uint32_t lineno_;
+        uint32_t column_;
+        ABIArgGenerator abi_;
+        uint32_t prevMaxStackBytes_;
+        uint32_t maxChildStackBytes_;
+        uint32_t spIncrement_;
+        MAsmJSCall::Args regArgs_;
+        Vector<MAsmJSPassStackArg*, 0, SystemAllocPolicy> stackArgs_;
+        bool childClobbers_;
+
+        friend class FunctionCompiler;
+
+      public:
+        Call(FunctionCompiler& f, uint32_t lineno, uint32_t column)
+          : lineno_(lineno),
+            column_(column),
+            prevMaxStackBytes_(0),
+            maxChildStackBytes_(0),
+            spIncrement_(0),
+            childClobbers_(false)
+        { }
+    };
+
+    void startCallArgs(Call* call)
+    {
+        if (inDeadCode())
+            return;
+        call->prevMaxStackBytes_ = mirGen().resetAsmJSMaxStackArgBytes();
+    }
+
+    bool passArg(MDefinition* argDef, MIRType mirType, Call* call)
+    {
+        if (inDeadCode())
+            return true;
+
+        uint32_t childStackBytes = mirGen().resetAsmJSMaxStackArgBytes();
+        call->maxChildStackBytes_ = Max(call->maxChildStackBytes_, childStackBytes);
+        if (childStackBytes > 0 && !call->stackArgs_.empty())
+            call->childClobbers_ = true;
+
+        ABIArg arg = call->abi_.next(mirType);
+        if (arg.kind() == ABIArg::Stack) {
+            MAsmJSPassStackArg* mir = MAsmJSPassStackArg::New(alloc(), arg.offsetFromArgBase(),
+                                                              argDef);
+            curBlock_->add(mir);
+            if (!call->stackArgs_.append(mir))
+                return false;
+        } else {
+            if (!call->regArgs_.append(MAsmJSCall::Arg(arg.reg(), argDef)))
+                return false;
+        }
+        return true;
+    }
+
+    void finishCallArgs(Call* call)
+    {
+        if (inDeadCode())
+            return;
+        uint32_t parentStackBytes = call->abi_.stackBytesConsumedSoFar();
+        uint32_t newStackBytes;
+        if (call->childClobbers_) {
+            call->spIncrement_ = AlignBytes(call->maxChildStackBytes_, AsmJSStackAlignment);
+            for (unsigned i = 0; i < call->stackArgs_.length(); i++)
+                call->stackArgs_[i]->incrementOffset(call->spIncrement_);
+            newStackBytes = Max(call->prevMaxStackBytes_,
+                                call->spIncrement_ + parentStackBytes);
+        } else {
+            call->spIncrement_ = 0;
+            newStackBytes = Max(call->prevMaxStackBytes_,
+                                Max(call->maxChildStackBytes_, parentStackBytes));
+        }
+        mirGen_->setAsmJSMaxStackArgBytes(newStackBytes);
+    }
+
+  private:
+    bool callPrivate(MAsmJSCall::Callee callee, const Call& call, MIRType returnType, MDefinition** def)
+    {
+        if (inDeadCode()) {
+            *def = nullptr;
+            return true;
+        }
+
+        CallSiteDesc::Kind kind = CallSiteDesc::Kind(-1);  // initialize to silence GCC warning
+        switch (callee.which()) {
+          case MAsmJSCall::Callee::Internal: kind = CallSiteDesc::Relative; break;
+          case MAsmJSCall::Callee::Dynamic:  kind = CallSiteDesc::Register; break;
+          case MAsmJSCall::Callee::Builtin:  kind = CallSiteDesc::Register; break;
+        }
+
+        MAsmJSCall* ins = MAsmJSCall::New(alloc(), CallSiteDesc(call.lineno_, call.column_, kind),
+                                          callee, call.regArgs_, returnType, call.spIncrement_);
+        if (!ins)
+            return false;
+
+        curBlock_->add(ins);
+        *def = ins;
+        return true;
+    }
+
+  public:
+    bool internalCall(const Signature& sig, Label* entry, const Call& call, MDefinition** def)
+    {
+        MIRType returnType = sig.retType().toMIRType();
+        return callPrivate(MAsmJSCall::Callee(entry), call, returnType, def);
+    }
+
+    bool funcPtrCall(const Signature& sig, uint32_t maskLit, uint32_t globalDataOffset, MDefinition* index,
+                     const Call& call, MDefinition** def)
+    {
+        if (inDeadCode()) {
+            *def = nullptr;
+            return true;
+        }
+
+        MConstant* mask = MConstant::New(alloc(), Int32Value(maskLit));
+        curBlock_->add(mask);
+        MBitAnd* maskedIndex = MBitAnd::NewAsmJS(alloc(), index, mask);
+        curBlock_->add(maskedIndex);
+        MAsmJSLoadFuncPtr* ptrFun = MAsmJSLoadFuncPtr::New(alloc(), globalDataOffset, maskedIndex);
+        curBlock_->add(ptrFun);
+
+        MIRType returnType = sig.retType().toMIRType();
+        return callPrivate(MAsmJSCall::Callee(ptrFun), call, returnType, def);
+    }
+
+    bool ffiCall(unsigned globalDataOffset, const Call& call, MIRType returnType, MDefinition** def)
+    {
+        if (inDeadCode()) {
+            *def = nullptr;
+            return true;
+        }
+
+        MAsmJSLoadFFIFunc* ptrFun = MAsmJSLoadFFIFunc::New(alloc(), globalDataOffset);
+        curBlock_->add(ptrFun);
+
+        return callPrivate(MAsmJSCall::Callee(ptrFun), call, returnType, def);
+    }
+
+    bool builtinCall(AsmJSImmKind builtin, const Call& call, MIRType returnType, MDefinition** def)
+    {
+        return callPrivate(MAsmJSCall::Callee(builtin), call, returnType, def);
+    }
+
+    /*********************************************** Control flow generation */
+
+    inline bool inDeadCode() const {
+        return curBlock_ == nullptr;
+    }
+
+    void returnExpr(MDefinition* expr)
+    {
+        if (inDeadCode())
+            return;
+        MAsmJSReturn* ins = MAsmJSReturn::New(alloc(), expr);
+        curBlock_->end(ins);
+        curBlock_ = nullptr;
+    }
+
+    void returnVoid()
+    {
+        if (inDeadCode())
+            return;
+        MAsmJSVoidReturn* ins = MAsmJSVoidReturn::New(alloc());
+        curBlock_->end(ins);
+        curBlock_ = nullptr;
+    }
+
+    bool branchAndStartThen(MDefinition* cond, MBasicBlock** thenBlock, MBasicBlock** elseBlock)
+    {
+        if (inDeadCode())
+            return true;
+
+        bool hasThenBlock = *thenBlock != nullptr;
+        bool hasElseBlock = *elseBlock != nullptr;
+
+        if (!hasThenBlock && !newBlock(curBlock_, thenBlock))
+            return false;
+        if (!hasElseBlock && !newBlock(curBlock_, elseBlock))
+            return false;
+
+        curBlock_->end(MTest::New(alloc(), cond, *thenBlock, *elseBlock));
+
+        // Only add as a predecessor if newBlock hasn't been called (as it does it for us)
+        if (hasThenBlock && !(*thenBlock)->addPredecessor(alloc(), curBlock_))
+            return false;
+        if (hasElseBlock && !(*elseBlock)->addPredecessor(alloc(), curBlock_))
+            return false;
+
+        curBlock_ = *thenBlock;
+        mirGraph().moveBlockToEnd(curBlock_);
+        return true;
+    }
+
+    void assertCurrentBlockIs(MBasicBlock* block) {
+        if (inDeadCode())
+            return;
+        MOZ_ASSERT(curBlock_ == block);
+    }
+
+    bool appendThenBlock(BlockVector* thenBlocks)
+    {
+        if (inDeadCode())
+            return true;
+        return thenBlocks->append(curBlock_);
+    }
+
+    bool joinIf(const BlockVector& thenBlocks, MBasicBlock* joinBlock)
+    {
+        if (!joinBlock)
+            return true;
+        MOZ_ASSERT_IF(curBlock_, thenBlocks.back() == curBlock_);
+        for (size_t i = 0; i < thenBlocks.length(); i++) {
+            thenBlocks[i]->end(MGoto::New(alloc(), joinBlock));
+            if (!joinBlock->addPredecessor(alloc(), thenBlocks[i]))
+                return false;
+        }
+        curBlock_ = joinBlock;
+        mirGraph().moveBlockToEnd(curBlock_);
+        return true;
+    }
+
+    void switchToElse(MBasicBlock* elseBlock)
+    {
+        if (!elseBlock)
+            return;
+        curBlock_ = elseBlock;
+        mirGraph().moveBlockToEnd(curBlock_);
+    }
+
+    bool joinIfElse(const BlockVector& thenBlocks)
+    {
+        if (inDeadCode() && thenBlocks.empty())
+            return true;
+        MBasicBlock* pred = curBlock_ ? curBlock_ : thenBlocks[0];
+        MBasicBlock* join;
+        if (!newBlock(pred, &join))
+            return false;
+        if (curBlock_)
+            curBlock_->end(MGoto::New(alloc(), join));
+        for (size_t i = 0; i < thenBlocks.length(); i++) {
+            thenBlocks[i]->end(MGoto::New(alloc(), join));
+            if (pred == curBlock_ || i > 0) {
+                if (!join->addPredecessor(alloc(), thenBlocks[i]))
+                    return false;
+            }
+        }
+        curBlock_ = join;
+        return true;
+    }
+
+    void pushPhiInput(MDefinition* def)
+    {
+        if (inDeadCode())
+            return;
+        MOZ_ASSERT(curBlock_->stackDepth() == info().firstStackSlot());
+        curBlock_->push(def);
+    }
+
+    MDefinition* popPhiOutput()
+    {
+        if (inDeadCode())
+            return nullptr;
+        MOZ_ASSERT(curBlock_->stackDepth() == info().firstStackSlot() + 1);
+        return curBlock_->pop();
+    }
+
+    bool startPendingLoop(size_t pos, MBasicBlock** loopEntry)
+    {
+        if (!loopStack_.append(pos) || !breakableStack_.append(pos))
+            return false;
+        if (inDeadCode()) {
+            *loopEntry = nullptr;
+            return true;
+        }
+        MOZ_ASSERT(curBlock_->loopDepth() == loopStack_.length() - 1);
+        *loopEntry = MBasicBlock::NewAsmJS(mirGraph(), info(), curBlock_,
+                                           MBasicBlock::PENDING_LOOP_HEADER);
+        if (!*loopEntry)
+            return false;
+        mirGraph().addBlock(*loopEntry);
+        (*loopEntry)->setLoopDepth(loopStack_.length());
+        curBlock_->end(MGoto::New(alloc(), *loopEntry));
+        curBlock_ = *loopEntry;
+        return true;
+    }
+
+    bool branchAndStartLoopBody(MDefinition* cond, MBasicBlock** afterLoop)
+    {
+        if (inDeadCode()) {
+            *afterLoop = nullptr;
+            return true;
+        }
+        MOZ_ASSERT(curBlock_->loopDepth() > 0);
+        MBasicBlock* body;
+        if (!newBlock(curBlock_, &body))
+            return false;
+        if (cond->isConstant() && cond->toConstant()->valueToBoolean()) {
+            *afterLoop = nullptr;
+            curBlock_->end(MGoto::New(alloc(), body));
+        } else {
+            if (!newBlockWithDepth(curBlock_, curBlock_->loopDepth() - 1, afterLoop))
+                return false;
+            curBlock_->end(MTest::New(alloc(), cond, body, *afterLoop));
+        }
+        curBlock_ = body;
+        return true;
+    }
+
+  private:
+    size_t popLoop()
+    {
+        size_t pos = loopStack_.popCopy();
+        MOZ_ASSERT(!unlabeledContinues_.has(pos));
+        breakableStack_.popBack();
+        return pos;
+    }
+
+  public:
+    bool closeLoop(MBasicBlock* loopEntry, MBasicBlock* afterLoop)
+    {
+        size_t pos = popLoop();
+        if (!loopEntry) {
+            MOZ_ASSERT(!afterLoop);
+            MOZ_ASSERT(inDeadCode());
+            MOZ_ASSERT(!unlabeledBreaks_.has(pos));
+            return true;
+        }
+        MOZ_ASSERT(loopEntry->loopDepth() == loopStack_.length() + 1);
+        MOZ_ASSERT_IF(afterLoop, afterLoop->loopDepth() == loopStack_.length());
+        if (curBlock_) {
+            MOZ_ASSERT(curBlock_->loopDepth() == loopStack_.length() + 1);
+            curBlock_->end(MGoto::New(alloc(), loopEntry));
+            if (!loopEntry->setBackedgeAsmJS(curBlock_))
+                return false;
+        }
+        curBlock_ = afterLoop;
+        if (curBlock_)
+            mirGraph().moveBlockToEnd(curBlock_);
+        return bindUnlabeledBreaks(pos);
+    }
+
+    bool branchAndCloseDoWhileLoop(MDefinition* cond, MBasicBlock* loopEntry)
+    {
+        size_t pos = popLoop();
+        if (!loopEntry) {
+            MOZ_ASSERT(inDeadCode());
+            MOZ_ASSERT(!unlabeledBreaks_.has(pos));
+            return true;
+        }
+        MOZ_ASSERT(loopEntry->loopDepth() == loopStack_.length() + 1);
+        if (curBlock_) {
+            MOZ_ASSERT(curBlock_->loopDepth() == loopStack_.length() + 1);
+            if (cond->isConstant()) {
+                if (cond->toConstant()->valueToBoolean()) {
+                    curBlock_->end(MGoto::New(alloc(), loopEntry));
+                    if (!loopEntry->setBackedgeAsmJS(curBlock_))
+                        return false;
+                    curBlock_ = nullptr;
+                } else {
+                    MBasicBlock* afterLoop;
+                    if (!newBlock(curBlock_, &afterLoop))
+                        return false;
+                    curBlock_->end(MGoto::New(alloc(), afterLoop));
+                    curBlock_ = afterLoop;
+                }
+            } else {
+                MBasicBlock* afterLoop;
+                if (!newBlock(curBlock_, &afterLoop))
+                    return false;
+                curBlock_->end(MTest::New(alloc(), cond, loopEntry, afterLoop));
+                if (!loopEntry->setBackedgeAsmJS(curBlock_))
+                    return false;
+                curBlock_ = afterLoop;
+            }
+        }
+        return bindUnlabeledBreaks(pos);
+    }
+
+    bool bindContinues(size_t pos, const LabelVector* maybeLabels)
+    {
+        bool createdJoinBlock = false;
+        if (UnlabeledBlockMap::Ptr p = unlabeledContinues_.lookup(pos)) {
+            if (!bindBreaksOrContinues(&p->value(), &createdJoinBlock))
+                return false;
+            unlabeledContinues_.remove(p);
+        }
+        return bindLabeledBreaksOrContinues(maybeLabels, &labeledContinues_, &createdJoinBlock);
+    }
+
+    bool bindLabeledBreaks(const LabelVector* maybeLabels)
+    {
+        bool createdJoinBlock = false;
+        return bindLabeledBreaksOrContinues(maybeLabels, &labeledBreaks_, &createdJoinBlock);
+    }
+
+    bool addBreak(uint32_t* maybeLabelId) {
+        if (maybeLabelId)
+            return addBreakOrContinue(*maybeLabelId, &labeledBreaks_);
+        return addBreakOrContinue(breakableStack_.back(), &unlabeledBreaks_);
+    }
+
+    bool addContinue(uint32_t* maybeLabelId) {
+        if (maybeLabelId)
+            return addBreakOrContinue(*maybeLabelId, &labeledContinues_);
+        return addBreakOrContinue(loopStack_.back(), &unlabeledContinues_);
+    }
+
+    bool startSwitch(size_t pos, MDefinition* expr, int32_t low, int32_t high,
+                     MBasicBlock** switchBlock)
+    {
+        if (!breakableStack_.append(pos))
+            return false;
+        if (inDeadCode()) {
+            *switchBlock = nullptr;
+            return true;
+        }
+        curBlock_->end(MTableSwitch::New(alloc(), expr, low, high));
+        *switchBlock = curBlock_;
+        curBlock_ = nullptr;
+        return true;
+    }
+
+    bool startSwitchCase(MBasicBlock* switchBlock, MBasicBlock** next)
+    {
+        if (!switchBlock) {
+            *next = nullptr;
+            return true;
+        }
+        if (!newBlock(switchBlock, next))
+            return false;
+        if (curBlock_) {
+            curBlock_->end(MGoto::New(alloc(), *next));
+            if (!(*next)->addPredecessor(alloc(), curBlock_))
+                return false;
+        }
+        curBlock_ = *next;
+        return true;
+    }
+
+    bool startSwitchDefault(MBasicBlock* switchBlock, BlockVector* cases, MBasicBlock** defaultBlock)
+    {
+        if (!startSwitchCase(switchBlock, defaultBlock))
+            return false;
+        if (!*defaultBlock)
+            return true;
+        mirGraph().moveBlockToEnd(*defaultBlock);
+        return true;
+    }
+
+    bool joinSwitch(MBasicBlock* switchBlock, const BlockVector& cases, MBasicBlock* defaultBlock)
+    {
+        size_t pos = breakableStack_.popCopy();
+        if (!switchBlock)
+            return true;
+        MTableSwitch* mir = switchBlock->lastIns()->toTableSwitch();
+        size_t defaultIndex = mir->addDefault(defaultBlock);
+        for (unsigned i = 0; i < cases.length(); i++) {
+            if (!cases[i])
+                mir->addCase(defaultIndex);
+            else
+                mir->addCase(mir->addSuccessor(cases[i]));
+        }
+        if (curBlock_) {
+            MBasicBlock* next;
+            if (!newBlock(curBlock_, &next))
+                return false;
+            curBlock_->end(MGoto::New(alloc(), next));
+            curBlock_ = next;
+        }
+        return bindUnlabeledBreaks(pos);
+    }
+
+    /************************************************************ DECODING ***/
+
+    uint8_t  readU8()              { return func_.readU8(&pc_); }
+    uint32_t readU32()             { return func_.readU32(&pc_); }
+    int32_t  readI32()             { return func_.readI32(&pc_); }
+    float    readF32()             { return func_.readF32(&pc_); }
+    double   readF64()             { return func_.readF64(&pc_); }
+    LifoSignature* readSignature() { return func_.readSignature(&pc_); }
+    SimdConstant readI32X4()       { return func_.readI32X4(&pc_); }
+    SimdConstant readF32X4()       { return func_.readF32X4(&pc_); }
+
+    Stmt readStmtOp()              { return Stmt(readU8()); }
+
+    void assertDebugCheckPoint() {
+#ifdef DEBUG
+        MOZ_ASSERT(Stmt(readU8()) == Stmt::DebugCheckPoint);
+#endif
+    }
+
+    bool done() const { return pc_ == func_.size(); }
+    size_t pc() const { return pc_; }
+
+    bool prepareEmitMIR(const VarTypeVector& argTypes)
+    {
+        const AsmFunction::VarInitializerVector& varInitializers = func_.varInitializers();
+        size_t numLocals = func_.numLocals();
+
+        // Prepare data structures
+        alloc_  = lifo_.new_<TempAllocator>(&lifo_);
+        if (!alloc_)
+            return false;
+        jitContext_.emplace(m().runtime(), /* CompileCompartment = */ nullptr, alloc_);
+        graph_  = lifo_.new_<MIRGraph>(alloc_);
+        if (!graph_)
+            return false;
+        MOZ_ASSERT(numLocals == argTypes.length() + varInitializers.length());
+        info_   = lifo_.new_<CompileInfo>(numLocals);
+        if (!info_)
+            return false;
+        const OptimizationInfo* optimizationInfo = js_IonOptimizations.get(Optimization_AsmJS);
+        const JitCompileOptions options;
+        mirGen_ = lifo_.new_<MIRGenerator>(m().compartment(),
+                                           options, alloc_,
+                                           graph_, info_, optimizationInfo,
+                                           &m().onOutOfBoundsLabel(),
+                                           &m().onConversionErrorLabel(),
+                                           m().usesSignalHandlersForOOB());
+        if (!mirGen_)
+            return false;
+
+        if (!newBlock(/* pred = */ nullptr, &curBlock_))
+            return false;
+
+        // Emit parameters and local variables
+        for (ABIArgTypeIter i(argTypes); !i.done(); i++) {
+            MAsmJSParameter* ins = MAsmJSParameter::New(alloc(), *i, i.mirType());
+            curBlock_->add(ins);
+            curBlock_->initSlot(info().localSlot(i.index()), ins);
+            if (!mirGen_->ensureBallast())
+                return false;
+            localVarTypes_.append(argTypes[i.index()].toType());
+        }
+
+        unsigned firstLocalSlot = argTypes.length();
+        for (unsigned i = 0; i < varInitializers.length(); i++) {
+            const AsmJSNumLit& lit = varInitializers[i];
+            Type type = Type::Of(lit);
+            MIRType mirType = type.toMIRType();
+
+            MInstruction* ins;
+            if (lit.isSimd())
+               ins = MSimdConstant::New(alloc(), lit.simdValue(), mirType);
+            else
+               ins = MConstant::NewAsmJS(alloc(), lit.scalarValue(), mirType);
+
+            curBlock_->add(ins);
+            curBlock_->initSlot(info().localSlot(firstLocalSlot + i), ins);
+            if (!mirGen_->ensureBallast())
+                return false;
+            localVarTypes_.append(type);
+        }
+
+        return true;
+    }
+
+    /*************************************************************************/
+
+    MIRGenerator* extractMIR()
+    {
+        MOZ_ASSERT(mirGen_ != nullptr);
+        MIRGenerator* mirGen = mirGen_;
+        mirGen_ = nullptr;
+        return mirGen;
+    }
+
+    /*************************************************************************/
+  private:
+    bool newBlockWithDepth(MBasicBlock* pred, unsigned loopDepth, MBasicBlock** block)
+    {
+        *block = MBasicBlock::NewAsmJS(mirGraph(), info(), pred, MBasicBlock::NORMAL);
+        if (!*block)
+            return false;
+        mirGraph().addBlock(*block);
+        (*block)->setLoopDepth(loopDepth);
+        return true;
+    }
+
+    bool newBlock(MBasicBlock* pred, MBasicBlock** block)
+    {
+        return newBlockWithDepth(pred, loopStack_.length(), block);
+    }
+
+    bool bindBreaksOrContinues(BlockVector* preds, bool* createdJoinBlock)
+    {
+        for (unsigned i = 0; i < preds->length(); i++) {
+            MBasicBlock* pred = (*preds)[i];
+            if (*createdJoinBlock) {
+                pred->end(MGoto::New(alloc(), curBlock_));
+                if (!curBlock_->addPredecessor(alloc(), pred))
+                    return false;
+            } else {
+                MBasicBlock* next;
+                if (!newBlock(pred, &next))
+                    return false;
+                pred->end(MGoto::New(alloc(), next));
+                if (curBlock_) {
+                    curBlock_->end(MGoto::New(alloc(), next));
+                    if (!next->addPredecessor(alloc(), curBlock_))
+                        return false;
+                }
+                curBlock_ = next;
+                *createdJoinBlock = true;
+            }
+            MOZ_ASSERT(curBlock_->begin() == curBlock_->end());
+            if (!mirGen_->ensureBallast())
+                return false;
+        }
+        preds->clear();
+        return true;
+    }
+
+    bool bindLabeledBreaksOrContinues(const LabelVector* maybeLabels, LabeledBlockMap* map,
+                                      bool* createdJoinBlock)
+    {
+        if (!maybeLabels)
+            return true;
+        const LabelVector& labels = *maybeLabels;
+        for (unsigned i = 0; i < labels.length(); i++) {
+            if (LabeledBlockMap::Ptr p = map->lookup(labels[i])) {
+                if (!bindBreaksOrContinues(&p->value(), createdJoinBlock))
+                    return false;
+                map->remove(p);
+            }
+            if (!mirGen_->ensureBallast())
+                return false;
+        }
+        return true;
+    }
+
+    template <class Key, class Map>
+    bool addBreakOrContinue(Key key, Map* map)
+    {
+        if (inDeadCode())
+            return true;
+        typename Map::AddPtr p = map->lookupForAdd(key);
+        if (!p) {
+            BlockVector empty;
+            if (!map->add(p, key, Move(empty)))
+                return false;
+        }
+        if (!p->value().append(curBlock_))
+            return false;
+        curBlock_ = nullptr;
+        return true;
+    }
+
+    bool bindUnlabeledBreaks(size_t pos)
+    {
+        bool createdJoinBlock = false;
+        if (UnlabeledBlockMap::Ptr p = unlabeledBreaks_.lookup(pos)) {
+            if (!bindBreaksOrContinues(&p->value(), &createdJoinBlock))
+                return false;
+            unlabeledBreaks_.remove(p);
+        }
+        return true;
+    }
+};
+
+static bool
+EmitLiteral(FunctionCompiler& f, AsmType type, MDefinition**def)
+{
+    switch (type) {
+      case AsmType::Int32: {
+        int32_t val = f.readI32();
+        *def = f.constant(Int32Value(val), MIRType_Int32);
+        return true;
+      }
+      case AsmType::Float32: {
+        float val = f.readF32();
+        *def = f.constant(Float32Value(val), MIRType_Float32);
+        return true;
+      }
+      case AsmType::Float64: {
+        double val = f.readF64();
+        *def = f.constant(DoubleValue(val), MIRType_Double);
+        return true;
+      }
+      case AsmType::Int32x4: {
+        SimdConstant lit(f.readI32X4());
+        *def = f.constant(lit, MIRType_Int32x4);
+        return true;
+      }
+      case AsmType::Float32x4: {
+        SimdConstant lit(f.readF32X4());
+        *def = f.constant(lit, MIRType_Float32x4);
+        return true;
+      }
+    }
+    MOZ_CRASH("unexpected literal type");
+}
+
+static bool
+EmitGetLoc(FunctionCompiler& f, const DebugOnly<MIRType>& type, MDefinition** def)
+{
+    uint32_t slot = f.readU32();
+    *def = f.getLocalDef(slot);
+    MOZ_ASSERT_IF(*def, (*def)->type() == type);
+    return true;
+}
+
+static bool
+EmitGetGlo(FunctionCompiler& f, MIRType type, MDefinition** def)
+{
+    uint32_t globalDataOffset = f.readU32();
+    bool isConst = bool(f.readU8());
+    *def = f.loadGlobalVar(globalDataOffset, isConst, type);
+    return true;
+}
+
+static bool EmitI32Expr(FunctionCompiler& f, MDefinition** def);
+static bool EmitF32Expr(FunctionCompiler& f, MDefinition** def);
+static bool EmitF64Expr(FunctionCompiler& f, MDefinition** def);
+static bool EmitI32X4Expr(FunctionCompiler& f, MDefinition** def);
+static bool EmitF32X4Expr(FunctionCompiler& f, MDefinition** def);
+static bool EmitExpr(FunctionCompiler& f, AsmType type, MDefinition** def);
+
+static bool
+EmitLoadArray(FunctionCompiler& f, Scalar::Type scalarType, MDefinition** def)
+{
+    NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
+    MDefinition* ptr;
+    if (!EmitI32Expr(f, &ptr))
+        return false;
+    *def = f.loadHeap(scalarType, ptr, needsBoundsCheck);
+    return true;
+}
+
+static bool
+EmitSignMask(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MDefinition* in;
+    if (!EmitExpr(f, type, &in))
+        return false;
+    *def = f.extractSignMask(in);
+    return true;
+}
+
+static bool
+EmitStore(FunctionCompiler& f, Scalar::Type viewType, MDefinition** def)
+{
+    NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
+
+    MDefinition* ptr;
+    if (!EmitI32Expr(f, &ptr))
+        return false;
+
+    MDefinition* rhs = nullptr;
+    switch (viewType) {
+      case Scalar::Int8:
+      case Scalar::Int16:
+      case Scalar::Int32:
+        if (!EmitI32Expr(f, &rhs))
+            return false;
+        break;
+      case Scalar::Float32:
+        if (!EmitF32Expr(f, &rhs))
+            return false;
+        break;
+      case Scalar::Float64:
+        if (!EmitF64Expr(f, &rhs))
+            return false;
+        break;
+      default: MOZ_CRASH("unexpected scalar type");
+    }
+
+    f.storeHeap(viewType, ptr, rhs, needsBoundsCheck);
+    *def = rhs;
+    return true;
+}
+
+static bool
+EmitStoreWithCoercion(FunctionCompiler& f, Scalar::Type rhsType, Scalar::Type viewType,
+                      MDefinition **def)
+{
+    NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
+    MDefinition* ptr;
+    if (!EmitI32Expr(f, &ptr))
+        return false;
+
+    MDefinition* rhs = nullptr;
+    MDefinition* coerced = nullptr;
+    if (rhsType == Scalar::Float32 && viewType == Scalar::Float64) {
+        if (!EmitF32Expr(f, &rhs))
+            return false;
+        coerced = f.unary<MToDouble>(rhs);
+    } else if (rhsType == Scalar::Float64 && viewType == Scalar::Float32) {
+        if (!EmitF64Expr(f, &rhs))
+            return false;
+        coerced = f.unary<MToFloat32>(rhs);
+    } else {
+        MOZ_CRASH("unexpected coerced store");
+    }
+
+    f.storeHeap(viewType, ptr, coerced, needsBoundsCheck);
+    *def = rhs;
+    return true;
+}
+
+static bool
+EmitSetLoc(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    uint32_t slot = f.readU32();
+    MDefinition* expr;
+    if (!EmitExpr(f, type, &expr))
+        return false;
+    f.assign(slot, expr);
+    *def = expr;
+    return true;
+}
+
+static bool
+EmitSetGlo(FunctionCompiler& f, AsmType type, MDefinition**def)
+{
+    uint32_t globalDataOffset = f.readU32();
+    MDefinition* expr;
+    if (!EmitExpr(f, type, &expr))
+        return false;
+    f.storeGlobalVar(globalDataOffset, expr);
+    *def = expr;
+    return true;
+}
+
+static MIRType
+MIRTypeFromAsmType(AsmType type)
+{
+    switch(type) {
+      case AsmType::Int32:     return MIRType_Int32;
+      case AsmType::Float32:   return MIRType_Float32;
+      case AsmType::Float64:   return MIRType_Double;
+      case AsmType::Int32x4:   return MIRType_Int32x4;
+      case AsmType::Float32x4: return MIRType_Float32x4;
+    }
+    MOZ_CRASH("unexpected type in binary arith");
+}
+
+typedef bool IsMax;
+
+static bool
+EmitMathMinMax(FunctionCompiler& f, AsmType type, bool isMax, MDefinition** def)
+{
+    size_t numArgs = f.readU8();
+    MOZ_ASSERT(numArgs >= 2);
+    MDefinition* lastDef;
+    if (!EmitExpr(f, type, &lastDef))
+        return false;
+    MIRType mirType = MIRTypeFromAsmType(type);
+    for (size_t i = 1; i < numArgs; i++) {
+        MDefinition* next;
+        if (!EmitExpr(f, type, &next))
+            return false;
+        lastDef = f.minMax(lastDef, next, mirType, isMax);
+    }
+    *def = lastDef;
+    return true;
+}
+
+static bool
+EmitAtomicsLoad(FunctionCompiler& f, MDefinition** def)
+{
+    NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
+    Scalar::Type viewType = Scalar::Type(f.readU8());
+    MDefinition* index;
+    if (!EmitI32Expr(f, &index))
+        return false;
+    *def = f.atomicLoadHeap(viewType, index, needsBoundsCheck);
+    return true;
+}
+
+static bool
+EmitAtomicsStore(FunctionCompiler& f, MDefinition** def)
+{
+    NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
+    Scalar::Type viewType = Scalar::Type(f.readU8());
+    MDefinition* index;
+    if (!EmitI32Expr(f, &index))
+        return false;
+    MDefinition* value;
+    if (!EmitI32Expr(f, &value))
+        return false;
+    f.atomicStoreHeap(viewType, index, value, needsBoundsCheck);
+    *def = value;
+    return true;
+}
+
+static bool
+EmitAtomicsBinOp(FunctionCompiler& f, MDefinition** def)
+{
+    NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
+    Scalar::Type viewType = Scalar::Type(f.readU8());
+    js::jit::AtomicOp op = js::jit::AtomicOp(f.readU8());
+    MDefinition* index;
+    if (!EmitI32Expr(f, &index))
+        return false;
+    MDefinition* value;
+    if (!EmitI32Expr(f, &value))
+        return false;
+    *def = f.atomicBinopHeap(op, viewType, index, value, needsBoundsCheck);
+    return true;
+}
+
+static bool
+EmitAtomicsCompareExchange(FunctionCompiler& f, MDefinition** def)
+{
+    NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
+    Scalar::Type viewType = Scalar::Type(f.readU8());
+    MDefinition* index;
+    if (!EmitI32Expr(f, &index))
+        return false;
+    MDefinition* oldValue;
+    if (!EmitI32Expr(f, &oldValue))
+        return false;
+    MDefinition* newValue;
+    if (!EmitI32Expr(f, &newValue))
+        return false;
+    *def = f.atomicCompareExchangeHeap(viewType, index, oldValue, newValue, needsBoundsCheck);
+    return true;
+}
+
+static bool
+EmitAtomicsExchange(FunctionCompiler& f, MDefinition** def)
+{
+    NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
+    Scalar::Type viewType = Scalar::Type(f.readU8());
+    MDefinition* index;
+    if (!EmitI32Expr(f, &index))
+        return false;
+    MDefinition* value;
+    if (!EmitI32Expr(f, &value))
+        return false;
+    *def = f.atomicExchangeHeap(viewType, index, value, needsBoundsCheck);
+    return true;
+}
+
+static bool
+EmitCallArgs(FunctionCompiler& f, const Signature& sig, FunctionCompiler::Call* call)
+{
+    f.startCallArgs(call);
+    for (unsigned i = 0; i < sig.args().length(); i++) {
+        MDefinition *arg = nullptr;
+        switch (sig.arg(i).which()) {
+          case VarType::Int:       if (!EmitI32Expr(f, &arg))   return false; break;
+          case VarType::Float:     if (!EmitF32Expr(f, &arg))   return false; break;
+          case VarType::Double:    if (!EmitF64Expr(f, &arg))   return false; break;
+          case VarType::Int32x4:   if (!EmitI32X4Expr(f, &arg)) return false; break;
+          case VarType::Float32x4: if (!EmitF32X4Expr(f, &arg)) return false; break;
+          default: MOZ_CRASH("unexpected vartype");
+        }
+        if (!f.passArg(arg, sig.arg(i).toMIRType(), call))
+            return false;
+    }
+    f.finishCallArgs(call);
+    return true;
+}
+
+static void
+ReadCallLineCol(FunctionCompiler& f, uint32_t* line, uint32_t* column)
+{
+    *line = f.readU32();
+    *column = f.readU32();
+}
+
+static bool
+EmitInternalCall(FunctionCompiler& f, RetType retType, MDefinition** def)
+{
+    uint32_t funcIndex = f.readU32();
+
+    Label* entry;
+    if (!f.m().getOrCreateFunctionEntry(funcIndex, &entry))
+        return false;
+
+    const Signature& sig = *f.readSignature();
+
+    MOZ_ASSERT_IF(sig.retType() != RetType::Void, sig.retType() == retType);
+
+    uint32_t lineno, column;
+    ReadCallLineCol(f, &lineno, &column);
+
+    FunctionCompiler::Call call(f, lineno, column);
+    if (!EmitCallArgs(f, sig, &call))
+        return false;
+
+    return f.internalCall(sig, entry, call, def);
+}
+
+static bool
+EmitFuncPtrCall(FunctionCompiler& f, RetType retType, MDefinition** def)
+{
+    uint32_t mask = f.readU32();
+    uint32_t globalDataOffset = f.readU32();
+
+    const Signature& sig = *f.readSignature();
+    MOZ_ASSERT_IF(sig.retType() != RetType::Void, sig.retType() == retType);
+
+    uint32_t lineno, column;
+    ReadCallLineCol(f, &lineno, &column);
+
+    MDefinition *index;
+    if (!EmitI32Expr(f, &index))
+        return false;
+
+    FunctionCompiler::Call call(f, lineno, column);
+    if (!EmitCallArgs(f, sig, &call))
+        return false;
+
+    return f.funcPtrCall(sig, mask, globalDataOffset, index, call, def);
+}
+
+static bool
+EmitFFICall(FunctionCompiler& f, RetType retType, MDefinition** def)
+{
+    unsigned globalDataOffset = f.readI32();
+
+    const Signature& sig = *f.readSignature();
+    MOZ_ASSERT_IF(sig.retType() != RetType::Void, sig.retType() == retType);
+
+    uint32_t lineno, column;
+    ReadCallLineCol(f, &lineno, &column);
+
+    FunctionCompiler::Call call(f, lineno, column);
+    if (!EmitCallArgs(f, sig, &call))
+        return false;
+
+    return f.ffiCall(globalDataOffset, call, retType.toMIRType(), def);
+}
+
+static bool
+EmitMathBuiltinCall(FunctionCompiler& f, F32 f32, MDefinition** def)
+{
+    MOZ_ASSERT(f32 == F32::Ceil || f32 == F32::Floor);
+
+    uint32_t lineno, column;
+    ReadCallLineCol(f, &lineno, &column);
+
+    FunctionCompiler::Call call(f, lineno, column);
+    f.startCallArgs(&call);
+
+    MDefinition* firstArg;
+    if (!EmitF32Expr(f, &firstArg) || !f.passArg(firstArg, MIRType_Float32, &call))
+        return false;
+
+    f.finishCallArgs(&call);
+
+    AsmJSImmKind callee = f32 == F32::Ceil ? AsmJSImm_CeilF : AsmJSImm_FloorF;
+    return f.builtinCall(callee, call, MIRType_Float32, def);
+}
+
+static bool
+EmitMathBuiltinCall(FunctionCompiler& f, F64 f64, MDefinition** def)
+{
+    uint32_t lineno, column;
+    ReadCallLineCol(f, &lineno, &column);
+
+    FunctionCompiler::Call call(f, lineno, column);
+    f.startCallArgs(&call);
+
+    MDefinition* firstArg;
+    if (!EmitF64Expr(f, &firstArg) || !f.passArg(firstArg, MIRType_Double, &call))
+        return false;
+
+    if (f64 == F64::Pow || f64 == F64::Atan2) {
+        MDefinition* secondArg;
+        if (!EmitF64Expr(f, &secondArg) || !f.passArg(secondArg, MIRType_Double, &call))
+            return false;
+    }
+
+    AsmJSImmKind callee;
+    switch (f64) {
+      case F64::Ceil:  callee = AsmJSImm_CeilD; break;
+      case F64::Floor: callee = AsmJSImm_FloorD; break;
+      case F64::Sin:   callee = AsmJSImm_SinD; break;
+      case F64::Cos:   callee = AsmJSImm_CosD; break;
+      case F64::Tan:   callee = AsmJSImm_TanD; break;
+      case F64::Asin:  callee = AsmJSImm_ASinD; break;
+      case F64::Acos:  callee = AsmJSImm_ACosD; break;
+      case F64::Atan:  callee = AsmJSImm_ATanD; break;
+      case F64::Exp:   callee = AsmJSImm_ExpD; break;
+      case F64::Log:   callee = AsmJSImm_LogD; break;
+      case F64::Pow:   callee = AsmJSImm_PowD; break;
+      case F64::Atan2: callee = AsmJSImm_ATan2D; break;
+      default: MOZ_CRASH("unexpected double math builtin callee");
+    }
+
+    f.finishCallArgs(&call);
+
+    return f.builtinCall(callee, call, MIRType_Double, def);
+}
+
+static bool
+EmitSimdUnary(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MSimdUnaryArith::Operation op = MSimdUnaryArith::Operation(f.readU8());
+    MDefinition* in;
+    if (!EmitExpr(f, type, &in))
+        return false;
+    *def = f.unarySimd(in, op, MIRTypeFromAsmType(type));
+    return true;
+}
+
+template<class OpKind>
+inline bool
+EmitBinarySimdGuts(FunctionCompiler& f, AsmType type, OpKind op, MDefinition** def)
+{
+    MDefinition* lhs;
+    if (!EmitExpr(f, type, &lhs))
+        return false;
+    MDefinition* rhs;
+    if (!EmitExpr(f, type, &rhs))
+        return false;
+    *def = f.binarySimd(lhs, rhs, op, MIRTypeFromAsmType(type));
+    return true;
+}
+
+static bool
+EmitSimdBinaryArith(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MSimdBinaryArith::Operation op = MSimdBinaryArith::Operation(f.readU8());
+    return EmitBinarySimdGuts(f, type, op, def);
+}
+
+static bool
+EmitSimdBinaryBitwise(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MSimdBinaryBitwise::Operation op = MSimdBinaryBitwise::Operation(f.readU8());
+    return EmitBinarySimdGuts(f, type, op, def);
+}
+
+static bool
+EmitSimdBinaryComp(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MSimdBinaryComp::Operation op = MSimdBinaryComp::Operation(f.readU8());
+    MDefinition* lhs;
+    if (!EmitExpr(f, type, &lhs))
+        return false;
+    MDefinition* rhs;
+    if (!EmitExpr(f, type, &rhs))
+        return false;
+    *def = f.binarySimd<MSimdBinaryComp>(lhs, rhs, op);
+    return true;
+}
+
+static bool
+EmitSimdBinaryShift(FunctionCompiler& f, MDefinition** def)
+{
+    MSimdShift::Operation op = MSimdShift::Operation(f.readU8());
+    MDefinition* lhs;
+    if (!EmitI32X4Expr(f, &lhs))
+        return false;
+    MDefinition* rhs;
+    if (!EmitI32Expr(f, &rhs))
+        return false;
+    *def = f.binarySimd<MSimdShift>(lhs, rhs, op);
+    return true;
+}
+
+static MIRType
+ScalarMIRTypeFromSimdAsmType(AsmType type)
+{
+    switch (type) {
+      case AsmType::Int32:
+      case AsmType::Float32:
+      case AsmType::Float64:   break;
+      case AsmType::Int32x4:   return MIRType_Int32;
+      case AsmType::Float32x4: return MIRType_Float32;
+    }
+    MOZ_CRASH("unexpected simd type");
+}
+
+static bool
+EmitExtractLane(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MDefinition* vec;
+    if (!EmitExpr(f, type, &vec))
+        return false;
+
+    MDefinition* laneDef;
+    if (!EmitI32Expr(f, &laneDef))
+        return false;
+
+    if (!laneDef) {
+        *def = nullptr;
+        return true;
+    }
+
+    MOZ_ASSERT(laneDef->isConstant());
+    int32_t laneLit = laneDef->toConstant()->value().toInt32();
+    MOZ_ASSERT(laneLit < 4);
+    SimdLane lane = SimdLane(laneLit);
+
+    *def = f.extractSimdElement(lane, vec, ScalarMIRTypeFromSimdAsmType(type));
+    return true;
+}
+
+static AsmType
+AsmSimdTypeToScalarType(AsmType simd)
+{
+    switch (simd) {
+      case AsmType::Int32x4:   return AsmType::Int32;
+      case AsmType::Float32x4: return AsmType::Float32;
+      case AsmType::Int32:
+      case AsmType::Float32:
+      case AsmType::Float64:    break;
+    }
+    MOZ_CRASH("unexpected simd type");
+}
+
+static bool
+EmitSimdReplaceLane(FunctionCompiler& f, AsmType simdType, MDefinition** def)
+{
+    MDefinition* vector;
+    if (!EmitExpr(f, simdType, &vector))
+        return false;
+
+    MDefinition* laneDef;
+    if (!EmitI32Expr(f, &laneDef))
+        return false;
+
+    SimdLane lane;
+    if (laneDef) {
+        MOZ_ASSERT(laneDef->isConstant());
+        int32_t laneLit = laneDef->toConstant()->value().toInt32();
+        MOZ_ASSERT(laneLit < 4);
+        lane = SimdLane(laneLit);
+    } else {
+        lane = SimdLane(-1);
+    }
+
+    MDefinition* scalar;
+    if (!EmitExpr(f, AsmSimdTypeToScalarType(simdType), &scalar))
+        return false;
+    *def = f.insertElementSimd(vector, scalar, lane, MIRTypeFromAsmType(simdType));
+    return true;
+}
+
+template<class T>
+inline bool
+EmitSimdCast(FunctionCompiler& f, AsmType fromType, AsmType toType, MDefinition** def)
+{
+    MDefinition* in;
+    if (!EmitExpr(f, fromType, &in))
+        return false;
+    *def = f.convertSimd<T>(in, MIRTypeFromAsmType(fromType), MIRTypeFromAsmType(toType));
+    return true;
+}
+
+static bool
+EmitSimdSwizzle(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MDefinition* in;
+    if (!EmitExpr(f, type, &in))
+        return false;
+
+    uint8_t lanes[4];
+    for (unsigned i = 0; i < 4; i++)
+        lanes[i] = f.readU8();
+
+    *def = f.swizzleSimd(in, lanes[0], lanes[1], lanes[2], lanes[3], MIRTypeFromAsmType(type));
+    return true;
+}
+
+static bool
+EmitSimdShuffle(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MDefinition* lhs;
+    if (!EmitExpr(f, type, &lhs))
+        return false;
+
+    MDefinition* rhs;
+    if (!EmitExpr(f, type, &rhs))
+        return false;
+
+    uint8_t lanes[4];
+    for (unsigned i = 0; i < 4; i++)
+        lanes[i] = f.readU8();
+
+    *def = f.shuffleSimd(lhs, rhs, lanes[0], lanes[1], lanes[2], lanes[3],
+                         MIRTypeFromAsmType(type));
+    return true;
+}
+
+static bool
+EmitSimdLoad(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    Scalar::Type viewType = Scalar::Type(f.readU8());
+    NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
+    uint8_t numElems = f.readU8();
+
+    MDefinition* index;
+    if (!EmitI32Expr(f, &index))
+        return false;
+
+    *def = f.loadSimdHeap(viewType, index, needsBoundsCheck, numElems);
+    return true;
+}
+
+static bool
+EmitSimdStore(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    Scalar::Type viewType = Scalar::Type(f.readU8());
+    NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
+    uint8_t numElems = f.readU8();
+
+    MDefinition* index;
+    if (!EmitI32Expr(f, &index))
+        return false;
+
+    MDefinition* vec;
+    if (!EmitExpr(f, type, &vec))
+        return false;
+
+    f.storeSimdHeap(viewType, index, vec, needsBoundsCheck, numElems);
+    *def = vec;
+    return true;
+}
+
+typedef bool IsElementWise;
+
+static bool
+EmitSimdSelect(FunctionCompiler& f, AsmType type, bool isElementWise, MDefinition** def)
+{
+    MDefinition* defs[3];
+    if (!EmitI32X4Expr(f, &defs[0]) || !EmitExpr(f, type, &defs[1]) || !EmitExpr(f, type, &defs[2]))
+        return false;
+    *def = f.selectSimd(defs[0], defs[1], defs[2], MIRTypeFromAsmType(type), isElementWise);
+    return true;
+}
+
+static bool
+EmitSimdSplat(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MDefinition* in;
+    if (!EmitExpr(f, AsmSimdTypeToScalarType(type), &in))
+        return false;
+    *def = f.splatSimd(in, MIRTypeFromAsmType(type));
+    return true;
+}
+
+static bool
+EmitSimdCtor(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    switch (type) {
+      case AsmType::Int32x4: {
+        MDefinition* args[4];
+        for (unsigned i = 0; i < 4; i++) {
+            if (!EmitI32Expr(f, &args[i]))
+                return false;
+        }
+        *def = f.constructSimd<MSimdValueX4>(args[0], args[1], args[2], args[3], MIRType_Int32x4);
+        return true;
+      }
+      case AsmType::Float32x4: {
+        MDefinition* args[4];
+        for (unsigned i = 0; i < 4; i++) {
+            if (!EmitF32Expr(f, &args[i]))
+                return false;
+        }
+        *def = f.constructSimd<MSimdValueX4>(args[0], args[1], args[2], args[3], MIRType_Float32x4);
+        return true;
+      }
+      case AsmType::Int32:
+      case AsmType::Float32:
+      case AsmType::Float64:
+        break;
+    }
+    MOZ_CRASH("unexpected SIMD type");
+}
+
+template<class T>
+static bool
+EmitUnary(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MDefinition* in;
+    if (!EmitExpr(f, type, &in))
+        return false;
+    *def = f.unary<T>(in);
+    return true;
+}
+
+template<class T>
+static bool
+EmitUnaryMir(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MDefinition* in;
+    if (!EmitExpr(f, type, &in))
+        return false;
+    *def = f.unary<T>(in, MIRTypeFromAsmType(type));
+    return true;
+}
+
+static bool EmitStatement(FunctionCompiler& f, LabelVector* maybeLabels = nullptr);
+
+static bool
+EmitComma(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    uint32_t numExpr = f.readU32();
+    for (uint32_t i = 1; i < numExpr; i++) {
+        if (!EmitStatement(f))
+            return false;
+    }
+    return EmitExpr(f, type, def);
+}
+
+static bool
+EmitConditional(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MDefinition* cond;
+    if (!EmitI32Expr(f, &cond))
+        return false;
+
+    MBasicBlock* thenBlock = nullptr;
+    MBasicBlock* elseBlock = nullptr;
+    if (!f.branchAndStartThen(cond, &thenBlock, &elseBlock))
+        return false;
+
+    MDefinition* ifTrue;
+    if (!EmitExpr(f, type, &ifTrue))
+        return false;
+
+    BlockVector thenBlocks;
+    if (!f.appendThenBlock(&thenBlocks))
+        return false;
+
+    f.pushPhiInput(ifTrue);
+
+    f.switchToElse(elseBlock);
+
+    MDefinition* ifFalse;
+    if (!EmitExpr(f, type, &ifFalse))
+        return false;
+
+    f.pushPhiInput(ifFalse);
+
+    if (!f.joinIfElse(thenBlocks))
+        return false;
+
+    *def = f.popPhiOutput();
+    return true;
+}
+
+static bool
+EmitMultiply(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    MDefinition* lhs;
+    if (!EmitExpr(f, type, &lhs))
+        return false;
+    MDefinition* rhs;
+    if (!EmitExpr(f, type, &rhs))
+        return false;
+    MIRType mirType = MIRTypeFromAsmType(type);
+    *def = f.mul(lhs, rhs, mirType, type == AsmType::Int32 ? MMul::Integer : MMul::Normal);
+    return true;
+}
+
+typedef bool IsAdd;
+
+static bool
+EmitAddOrSub(FunctionCompiler& f, AsmType type, bool isAdd, MDefinition** def)
+{
+    MDefinition* lhs;
+    if (!EmitExpr(f, type, &lhs))
+        return false;
+    MDefinition* rhs;
+    if (!EmitExpr(f, type, &rhs))
+        return false;
+    MIRType mirType = MIRTypeFromAsmType(type);
+    *def = isAdd ? f.binary<MAdd>(lhs, rhs, mirType) : f.binary<MSub>(lhs, rhs, mirType);
+    return true;
+}
+
+typedef bool IsUnsigned;
+typedef bool IsDiv;
+
+static bool
+EmitDivOrMod(FunctionCompiler& f, AsmType type, bool isDiv, bool isUnsigned, MDefinition** def)
+{
+    MDefinition* lhs;
+    if (!EmitExpr(f, type, &lhs))
+        return false;
+    MDefinition* rhs;
+    if (!EmitExpr(f, type, &rhs))
+        return false;
+    *def = isDiv
+           ? f.div(lhs, rhs, MIRTypeFromAsmType(type), isUnsigned)
+           : f.mod(lhs, rhs, MIRTypeFromAsmType(type), isUnsigned);
+    return true;
+}
+
+static bool
+EmitDivOrMod(FunctionCompiler& f, AsmType type, bool isDiv, MDefinition** def)
+{
+    MOZ_ASSERT(type != AsmType::Int32, "int div or mod must precise signedness");
+    return EmitDivOrMod(f, type, isDiv, false, def);
+}
+
+static bool
+EmitComparison(FunctionCompiler& f, I32 stmt, MDefinition** def)
+{
+    MDefinition *lhs, *rhs;
+    MCompare::CompareType compareType;
+    switch (stmt) {
+      case I32::EqI32:
+      case I32::NeI32:
+      case I32::SLeI32:
+      case I32::SLtI32:
+      case I32::ULeI32:
+      case I32::ULtI32:
+      case I32::SGeI32:
+      case I32::SGtI32:
+      case I32::UGeI32:
+      case I32::UGtI32:
+        if (!EmitI32Expr(f, &lhs) || !EmitI32Expr(f, &rhs))
+            return false;
+        // The list of opcodes is sorted such that all signed comparisons
+        // stand before ULtI32.
+        compareType = stmt < I32::ULtI32
+                      ? MCompare::Compare_Int32
+                      : MCompare::Compare_UInt32;
+        break;
+      case I32::EqF32:
+      case I32::NeF32:
+      case I32::LeF32:
+      case I32::LtF32:
+      case I32::GeF32:
+      case I32::GtF32:
+        if (!EmitF32Expr(f, &lhs) || !EmitF32Expr(f, &rhs))
+            return false;
+        compareType = MCompare::Compare_Float32;
+        break;
+      case I32::EqF64:
+      case I32::NeF64:
+      case I32::LeF64:
+      case I32::LtF64:
+      case I32::GeF64:
+      case I32::GtF64:
+        if (!EmitF64Expr(f, &lhs) || !EmitF64Expr(f, &rhs))
+            return false;
+        compareType = MCompare::Compare_Double;
+        break;
+      default: MOZ_CRASH("unexpected comparison opcode");
+    }
+
+    JSOp compareOp;
+    switch (stmt) {
+      case I32::EqI32:
+      case I32::EqF32:
+      case I32::EqF64:
+        compareOp = JSOP_EQ;
+        break;
+      case I32::NeI32:
+      case I32::NeF32:
+      case I32::NeF64:
+        compareOp = JSOP_NE;
+        break;
+      case I32::SLeI32:
+      case I32::ULeI32:
+      case I32::LeF32:
+      case I32::LeF64:
+        compareOp = JSOP_LE;
+        break;
+      case I32::SLtI32:
+      case I32::ULtI32:
+      case I32::LtF32:
+      case I32::LtF64:
+        compareOp = JSOP_LT;
+        break;
+      case I32::SGeI32:
+      case I32::UGeI32:
+      case I32::GeF32:
+      case I32::GeF64:
+        compareOp = JSOP_GE;
+        break;
+      case I32::SGtI32:
+      case I32::UGtI32:
+      case I32::GtF32:
+      case I32::GtF64:
+        compareOp = JSOP_GT;
+        break;
+      default: MOZ_CRASH("unexpected comparison opcode");
+    }
+
+    *def = f.compare(lhs, rhs, compareOp, compareType);
+    return true;
+}
+
+template<class T>
+static bool
+EmitBitwise(FunctionCompiler& f, MDefinition** def)
+{
+    MDefinition* lhs;
+    if (!EmitI32Expr(f, &lhs))
+        return false;
+    MDefinition* rhs;
+    if (!EmitI32Expr(f, &rhs))
+        return false;
+    *def = f.bitwise<T>(lhs, rhs);
+    return true;
+}
+
+template<>
+bool
+EmitBitwise<MBitNot>(FunctionCompiler& f, MDefinition** def)
+{
+    MDefinition* in;
+    if (!EmitI32Expr(f, &in))
+        return false;
+    *def = f.bitwise<MBitNot>(in);
+    return true;
+}
+
+static bool
+EmitExpr(FunctionCompiler& f, AsmType type, MDefinition** def)
+{
+    switch (type) {
+      case AsmType::Int32:     return EmitI32Expr(f, def);
+      case AsmType::Float32:   return EmitF32Expr(f, def);
+      case AsmType::Float64:   return EmitF64Expr(f, def);
+      case AsmType::Int32x4:   return EmitI32X4Expr(f, def);
+      case AsmType::Float32x4: return EmitF32X4Expr(f, def);
+    }
+    MOZ_CRASH("unexpected asm type");
+}
+
+static bool
+EmitInterruptCheck(FunctionCompiler& f)
+{
+    unsigned lineno = f.readU32();
+    unsigned column = f.readU32();
+    f.addInterruptCheck(lineno, column);
+    return true;
+}
+
+static bool
+EmitInterruptCheckLoop(FunctionCompiler& f)
+{
+    if (!EmitInterruptCheck(f))
+        return false;
+    return EmitStatement(f);
+}
+
+static bool
+EmitWhile(FunctionCompiler& f, const LabelVector* maybeLabels)
+{
+    size_t headPc = f.pc();
+
+    MBasicBlock* loopEntry;
+    if (!f.startPendingLoop(headPc, &loopEntry))
+        return false;
+
+    MDefinition* condDef;
+    if (!EmitI32Expr(f, &condDef))
+        return false;
+
+    MBasicBlock* afterLoop;
+    if (!f.branchAndStartLoopBody(condDef, &afterLoop))
+        return false;
+
+    if (!EmitStatement(f))
+        return false;
+
+    if (!f.bindContinues(headPc, maybeLabels))
+        return false;
+
+    return f.closeLoop(loopEntry, afterLoop);
+}
+
+static bool
+EmitFor(FunctionCompiler& f, Stmt stmt, const LabelVector* maybeLabels)
+{
+    MOZ_ASSERT(stmt == Stmt::ForInitInc || stmt == Stmt::ForInitNoInc ||
+               stmt == Stmt::ForNoInitInc || stmt == Stmt::ForNoInitNoInc);
+    size_t headPc = f.pc();
+
+    if (stmt == Stmt::ForInitInc || stmt == Stmt::ForInitNoInc) {
+        if (!EmitStatement(f))
+            return false;
+    }
+
+    MBasicBlock* loopEntry;
+    if (!f.startPendingLoop(headPc, &loopEntry))
+        return false;
+
+    MDefinition* condDef;
+    if (!EmitI32Expr(f, &condDef))
+        return false;
+
+    MBasicBlock* afterLoop;
+    if (!f.branchAndStartLoopBody(condDef, &afterLoop))
+        return false;
+
+    if (!EmitStatement(f))
+        return false;
+
+    if (!f.bindContinues(headPc, maybeLabels))
+        return false;
+
+    if (stmt == Stmt::ForInitInc || stmt == Stmt::ForNoInitInc) {
+        if (!EmitStatement(f))
+            return false;
+    }
+
+    f.assertDebugCheckPoint();
+
+    return f.closeLoop(loopEntry, afterLoop);
+}
+
+static bool
+EmitDoWhile(FunctionCompiler& f, const LabelVector* maybeLabels)
+{
+    size_t headPc = f.pc();
+
+    MBasicBlock* loopEntry;
+    if (!f.startPendingLoop(headPc, &loopEntry))
+        return false;
+
+    if (!EmitStatement(f))
+        return false;
+
+    if (!f.bindContinues(headPc, maybeLabels))
+        return false;
+
+    MDefinition* condDef;
+    if (!EmitI32Expr(f, &condDef))
+        return false;
+
+    return f.branchAndCloseDoWhileLoop(condDef, loopEntry);
+}
+
+static bool
+EmitLabel(FunctionCompiler& f, LabelVector* maybeLabels)
+{
+    uint32_t labelId = f.readU32();
+
+    if (maybeLabels) {
+        if (!maybeLabels->append(labelId))
+            return false;
+        return EmitStatement(f, maybeLabels);
+    }
+
+    LabelVector labels;
+    if (!labels.append(labelId))
+        return false;
+
+    if (!EmitStatement(f, &labels))
+        return false;
+
+    return f.bindLabeledBreaks(&labels);
+}
+
+static bool EmitStatement(FunctionCompiler& f, Stmt stmt, LabelVector* maybeLabels = nullptr);
+
+typedef bool HasElseBlock;
+
+static bool
+EmitIfElse(FunctionCompiler& f, bool hasElse)
+{
+    // Handle if/else-if chains using iteration instead of recursion. This
+    // avoids blowing the C stack quota for long if/else-if chains and also
+    // creates fewer MBasicBlocks at join points (by creating one join block
+    // for the entire if/else-if chain).
+    BlockVector thenBlocks;
+
+  recurse:
+    MDefinition* condition;
+    if (!EmitI32Expr(f, &condition))
+        return false;
+
+    MBasicBlock* thenBlock = nullptr;
+    MBasicBlock* elseOrJoinBlock = nullptr;
+    if (!f.branchAndStartThen(condition, &thenBlock, &elseOrJoinBlock))
+        return false;
+
+    if (!EmitStatement(f))
+        return false;
+
+    if (!f.appendThenBlock(&thenBlocks))
+        return false;
+
+    if (hasElse) {
+        f.switchToElse(elseOrJoinBlock);
+
+        Stmt nextStmt(f.readStmtOp());
+        if (nextStmt == Stmt::IfThen) {
+            hasElse = false;
+            goto recurse;
+        }
+        if (nextStmt == Stmt::IfElse) {
+            hasElse = true;
+            goto recurse;
+        }
+
+        if (!EmitStatement(f, nextStmt))
+            return false;
+
+        return f.joinIfElse(thenBlocks);
+    } else {
+        return f.joinIf(thenBlocks, elseOrJoinBlock);
+    }
+}
+
+static bool
+EmitSwitch(FunctionCompiler& f)
+{
+    bool hasDefault = f.readU8();
+    int32_t low = f.readI32();
+    int32_t high = f.readI32();
+    uint32_t numCases = f.readU32();
+
+    MDefinition* exprDef;
+    if (!EmitI32Expr(f, &exprDef))
+        return false;
+
+    // Switch with no cases
+    if (!hasDefault && numCases == 0)
+        return true;
+
+    BlockVector cases;
+    if (!cases.resize(high - low + 1))
+        return false;
+
+    MBasicBlock* switchBlock;
+    if (!f.startSwitch(f.pc(), exprDef, low, high, &switchBlock))
+        return false;
+
+    while (numCases--) {
+        int32_t caseValue = f.readI32();
+        MOZ_ASSERT(caseValue >= low && caseValue <= high);
+        unsigned caseIndex = caseValue - low;
+        if (!f.startSwitchCase(switchBlock, &cases[caseIndex]))
+            return false;
+        if (!EmitStatement(f))
+            return false;
+    }
+
+    MBasicBlock* defaultBlock;
+    if (!f.startSwitchDefault(switchBlock, &cases, &defaultBlock))
+        return false;
+
+    if (hasDefault && !EmitStatement(f))
+        return false;
+
+    return f.joinSwitch(switchBlock, cases, defaultBlock);
+}
+
+static AsmType
+RetTypeToAsmType(RetType retType)
+{
+    switch (retType.which()) {
+      case RetType::Void:      break;
+      case RetType::Signed:    return AsmType::Int32;
+      case RetType::Float:     return AsmType::Float32;
+      case RetType::Double:    return AsmType::Float64;
+      case RetType::Int32x4:   return AsmType::Int32x4;
+      case RetType::Float32x4: return AsmType::Float32x4;
+    }
+    MOZ_CRASH("unexpected return type");
+}
+
+static bool
+EmitRet(FunctionCompiler& f)
+{
+    RetType retType = f.returnedType();
+
+    if (retType == RetType::Void) {
+        f.returnVoid();
+        return true;
+    }
+
+    AsmType type = RetTypeToAsmType(retType);
+    MDefinition *def = nullptr;
+    if (!EmitExpr(f, type, &def))
+        return false;
+    f.returnExpr(def);
+    return true;
+}
+
+static bool
+EmitBlock(FunctionCompiler& f)
+{
+    size_t numStmt = f.readU32();
+    for (size_t i = 0; i < numStmt; i++) {
+        if (!EmitStatement(f))
+            return false;
+    }
+    f.assertDebugCheckPoint();
+    return true;
+}
+
+typedef bool HasLabel;
+
+static bool
+EmitContinue(FunctionCompiler& f, bool hasLabel)
+{
+    if (!hasLabel)
+        return f.addContinue(nullptr);
+    uint32_t labelId = f.readU32();
+    return f.addContinue(&labelId);
+}
+
+static bool
+EmitBreak(FunctionCompiler& f, bool hasLabel)
+{
+    if (!hasLabel)
+        return f.addBreak(nullptr);
+    uint32_t labelId = f.readU32();
+    return f.addBreak(&labelId);
+}
+
+static bool
+EmitStatement(FunctionCompiler& f, Stmt stmt, LabelVector* maybeLabels /*= nullptr */)
+{
+    if (!f.mirGen().ensureBallast())
+        return false;
+
+    MDefinition* _;
+    switch (stmt) {
+      case Stmt::Block:              return EmitBlock(f);
+      case Stmt::IfThen:             return EmitIfElse(f, HasElseBlock(false));
+      case Stmt::IfElse:             return EmitIfElse(f, HasElseBlock(true));
+      case Stmt::Switch:             return EmitSwitch(f);
+      case Stmt::While:              return EmitWhile(f, maybeLabels);
+      case Stmt::DoWhile:            return EmitDoWhile(f, maybeLabels);
+      case Stmt::ForInitInc:
+      case Stmt::ForInitNoInc:
+      case Stmt::ForNoInitNoInc:
+      case Stmt::ForNoInitInc:       return EmitFor(f, stmt, maybeLabels);
+      case Stmt::Label:              return EmitLabel(f, maybeLabels);
+      case Stmt::Continue:           return EmitContinue(f, HasLabel(false));
+      case Stmt::ContinueLabel:      return EmitContinue(f, HasLabel(true));
+      case Stmt::Break:              return EmitBreak(f, HasLabel(false));
+      case Stmt::BreakLabel:         return EmitBreak(f, HasLabel(true));
+      case Stmt::Ret:                return EmitRet(f);
+      case Stmt::I32Expr:            return EmitI32Expr(f, &_);
+      case Stmt::F32Expr:            return EmitF32Expr(f, &_);
+      case Stmt::F64Expr:            return EmitF64Expr(f, &_);
+      case Stmt::I32X4Expr:          return EmitI32X4Expr(f, &_);
+      case Stmt::F32X4Expr:          return EmitF32X4Expr(f, &_);
+      case Stmt::CallInternal:       return EmitInternalCall(f, RetType::Void, &_);
+      case Stmt::CallIndirect:       return EmitFuncPtrCall(f, RetType::Void, &_);
+      case Stmt::CallImport:         return EmitFFICall(f, RetType::Void, &_);
+      case Stmt::AtomicsFence:       f.memoryBarrier(MembarFull); return true;
+      case Stmt::Noop:               return true;
+      case Stmt::Id:                 return EmitStatement(f);
+      case Stmt::InterruptCheckHead: return EmitInterruptCheck(f);
+      case Stmt::InterruptCheckLoop: return EmitInterruptCheckLoop(f);
+      case Stmt::DebugCheckPoint:
+      case Stmt::Bad:             break;
+    }
+    MOZ_CRASH("unexpected statement");
+}
+
+static bool
+EmitStatement(FunctionCompiler& f, LabelVector* maybeLabels /* = nullptr */)
+{
+    Stmt stmt(f.readStmtOp());
+    return EmitStatement(f, stmt, maybeLabels);
+}
+
+static bool
+EmitI32Expr(FunctionCompiler& f, MDefinition** def)
+{
+    I32 op = I32(f.readU8());
+    switch (op) {
+      case I32::Id:
+        return EmitI32Expr(f, def);
+      case I32::Literal:
+        return EmitLiteral(f, AsmType::Int32, def);
+      case I32::GetLocal:
+        return EmitGetLoc(f, DebugOnly<MIRType>(MIRType_Int32), def);
+      case I32::SetLocal:
+        return EmitSetLoc(f, AsmType::Int32, def);
+      case I32::GetGlobal:
+        return EmitGetGlo(f, MIRType_Int32, def);
+      case I32::SetGlobal:
+        return EmitSetGlo(f, AsmType::Int32, def);
+      case I32::CallInternal:
+        return EmitInternalCall(f, RetType::Signed, def);
+      case I32::CallIndirect:
+        return EmitFuncPtrCall(f, RetType::Signed, def);
+      case I32::CallImport:
+        return EmitFFICall(f, RetType::Signed, def);
+      case I32::Conditional:
+        return EmitConditional(f, AsmType::Int32, def);
+      case I32::Comma:
+        return EmitComma(f, AsmType::Int32, def);
+      case I32::Add:
+        return EmitAddOrSub(f, AsmType::Int32, IsAdd(true), def);
+      case I32::Sub:
+        return EmitAddOrSub(f, AsmType::Int32, IsAdd(false), def);
+      case I32::Mul:
+        return EmitMultiply(f, AsmType::Int32, def);
+      case I32::UDiv:
+      case I32::SDiv:
+        return EmitDivOrMod(f, AsmType::Int32, IsDiv(true), IsUnsigned(op == I32::UDiv), def);
+      case I32::UMod:
+      case I32::SMod:
+        return EmitDivOrMod(f, AsmType::Int32, IsDiv(false), IsUnsigned(op == I32::UMod), def);
+      case I32::Min:
+        return EmitMathMinMax(f, AsmType::Int32, IsMax(false), def);
+      case I32::Max:
+        return EmitMathMinMax(f, AsmType::Int32, IsMax(true), def);
+      case I32::Not:
+        return EmitUnary<MNot>(f, AsmType::Int32, def);
+      case I32::FromF32:
+        return EmitUnary<MTruncateToInt32>(f, AsmType::Float32, def);
+      case I32::FromF64:
+        return EmitUnary<MTruncateToInt32>(f, AsmType::Float64, def);
+      case I32::Clz:
+        return EmitUnary<MClz>(f, AsmType::Int32, def);
+      case I32::Abs:
+        return EmitUnaryMir<MAbs>(f, AsmType::Int32, def);
+      case I32::Neg:
+        return EmitUnaryMir<MAsmJSNeg>(f, AsmType::Int32, def);
+      case I32::BitOr:
+        return EmitBitwise<MBitOr>(f, def);
+      case I32::BitAnd:
+        return EmitBitwise<MBitAnd>(f, def);
+      case I32::BitXor:
+        return EmitBitwise<MBitXor>(f, def);
+      case I32::Lsh:
+        return EmitBitwise<MLsh>(f, def);
+      case I32::ArithRsh:
+        return EmitBitwise<MRsh>(f, def);
+      case I32::LogicRsh:
+        return EmitBitwise<MUrsh>(f, def);
+      case I32::BitNot:
+        return EmitBitwise<MBitNot>(f, def);
+      case I32::SLoad8:
+        return EmitLoadArray(f, Scalar::Int8, def);
+      case I32::SLoad16:
+        return EmitLoadArray(f, Scalar::Int16, def);
+      case I32::SLoad32:
+        return EmitLoadArray(f, Scalar::Int32, def);
+      case I32::ULoad8:
+        return EmitLoadArray(f, Scalar::Uint8, def);
+      case I32::ULoad16:
+        return EmitLoadArray(f, Scalar::Uint16, def);
+      case I32::ULoad32:
+        return EmitLoadArray(f, Scalar::Uint32, def);
+      case I32::Store8:
+        return EmitStore(f, Scalar::Int8, def);
+      case I32::Store16:
+        return EmitStore(f, Scalar::Int16, def);
+      case I32::Store32:
+        return EmitStore(f, Scalar::Int32, def);
+      case I32::EqI32:
+      case I32::NeI32:
+      case I32::SLtI32:
+      case I32::SLeI32:
+      case I32::SGtI32:
+      case I32::SGeI32:
+      case I32::ULtI32:
+      case I32::ULeI32:
+      case I32::UGtI32:
+      case I32::UGeI32:
+      case I32::EqF32:
+      case I32::NeF32:
+      case I32::LtF32:
+      case I32::LeF32:
+      case I32::GtF32:
+      case I32::GeF32:
+      case I32::EqF64:
+      case I32::NeF64:
+      case I32::LtF64:
+      case I32::LeF64:
+      case I32::GtF64:
+      case I32::GeF64:
+        return EmitComparison(f, op, def);
+      case I32::AtomicsCompareExchange:
+        return EmitAtomicsCompareExchange(f, def);
+      case I32::AtomicsExchange:
+        return EmitAtomicsExchange(f, def);
+      case I32::AtomicsLoad:
+        return EmitAtomicsLoad(f, def);
+      case I32::AtomicsStore:
+        return EmitAtomicsStore(f, def);
+      case I32::AtomicsBinOp:
+        return EmitAtomicsBinOp(f, def);
+      case I32::I32X4SignMask:
+        return EmitSignMask(f, AsmType::Int32x4, def);
+      case I32::F32X4SignMask:
+        return EmitSignMask(f, AsmType::Float32x4, def);
+      case I32::I32X4ExtractLane:
+        return EmitExtractLane(f, AsmType::Int32x4, def);
+      case I32::Bad:
+        break;
+    }
+    MOZ_CRASH("unexpected i32 expression");
+}
+
+static bool
+EmitF32Expr(FunctionCompiler& f, MDefinition** def)
+{
+    F32 op = F32(f.readU8());
+    switch (op) {
+      case F32::Id:
+        return EmitF32Expr(f, def);
+      case F32::Literal:
+        return EmitLiteral(f, AsmType::Float32, def);
+      case F32::GetLocal:
+        return EmitGetLoc(f, DebugOnly<MIRType>(MIRType_Float32), def);
+      case F32::SetLocal:
+        return EmitSetLoc(f, AsmType::Float32, def);
+      case F32::GetGlobal:
+        return EmitGetGlo(f, MIRType_Float32, def);
+      case F32::SetGlobal:
+        return EmitSetGlo(f, AsmType::Float32, def);
+      case F32::CallInternal:
+        return EmitInternalCall(f, RetType::Float, def);
+      case F32::CallIndirect:
+        return EmitFuncPtrCall(f, RetType::Float, def);
+      case F32::CallImport:
+        return EmitFFICall(f, RetType::Float, def);
+      case F32::Conditional:
+        return EmitConditional(f, AsmType::Float32, def);
+      case F32::Comma:
+        return EmitComma(f, AsmType::Float32, def);
+      case F32::Add:
+        return EmitAddOrSub(f, AsmType::Float32, IsAdd(true), def);
+      case F32::Sub:
+        return EmitAddOrSub(f, AsmType::Float32, IsAdd(false), def);
+      case F32::Mul:
+        return EmitMultiply(f, AsmType::Float32, def);
+      case F32::Div:
+        return EmitDivOrMod(f, AsmType::Float32, IsDiv(true), def);
+      case F32::Min:
+        return EmitMathMinMax(f, AsmType::Float32, IsMax(false), def);
+      case F32::Max:
+        return EmitMathMinMax(f, AsmType::Float32, IsMax(true), def);
+      case F32::Neg:
+        return EmitUnaryMir<MAsmJSNeg>(f, AsmType::Float32, def);
+      case F32::Abs:
+        return EmitUnaryMir<MAbs>(f, AsmType::Float32, def);
+      case F32::Sqrt:
+        return EmitUnaryMir<MSqrt>(f, AsmType::Float32, def);
+      case F32::Ceil:
+      case F32::Floor:
+        return EmitMathBuiltinCall(f, op, def);
+      case F32::FromF64:
+        return EmitUnary<MToFloat32>(f, AsmType::Float64, def);
+      case F32::FromS32:
+        return EmitUnary<MToFloat32>(f, AsmType::Int32, def);
+      case F32::FromU32:
+        return EmitUnary<MAsmJSUnsignedToFloat32>(f, AsmType::Int32, def);
+      case F32::Load:
+        return EmitLoadArray(f, Scalar::Float32, def);
+      case F32::StoreF32:
+        return EmitStore(f, Scalar::Float32, def);
+      case F32::StoreF64:
+        return EmitStoreWithCoercion(f, Scalar::Float32, Scalar::Float64, def);
+      case F32::F32X4ExtractLane:
+        return EmitExtractLane(f, AsmType::Float32x4, def);
+      case F32::Bad:
+        break;
+    }
+    MOZ_CRASH("unexpected f32 expression");
+}
+
+static bool
+EmitF64Expr(FunctionCompiler& f, MDefinition** def)
+{
+    F64 op = F64(f.readU8());
+    switch (op) {
+      case F64::Id:
+        return EmitF64Expr(f, def);
+      case F64::GetLocal:
+        return EmitGetLoc(f, DebugOnly<MIRType>(MIRType_Double), def);
+      case F64::SetLocal:
+        return EmitSetLoc(f, AsmType::Float64, def);
+      case F64::GetGlobal:
+        return EmitGetGlo(f, MIRType_Double, def);
+      case F64::SetGlobal:
+        return EmitSetGlo(f, AsmType::Float64, def);
+      case F64::Literal:
+        return EmitLiteral(f, AsmType::Float64, def);
+      case F64::Add:
+        return EmitAddOrSub(f, AsmType::Float64, IsAdd(true), def);
+      case F64::Sub:
+        return EmitAddOrSub(f, AsmType::Float64, IsAdd(false), def);
+      case F64::Mul:
+        return EmitMultiply(f, AsmType::Float64, def);
+      case F64::Div:
+        return EmitDivOrMod(f, AsmType::Float64, IsDiv(true), def);
+      case F64::Mod:
+        return EmitDivOrMod(f, AsmType::Float64, IsDiv(false), def);
+      case F64::Min:
+        return EmitMathMinMax(f, AsmType::Float64, IsMax(false), def);
+      case F64::Max:
+        return EmitMathMinMax(f, AsmType::Float64, IsMax(true), def);
+      case F64::Neg:
+        return EmitUnaryMir<MAsmJSNeg>(f, AsmType::Float64, def);
+      case F64::Abs:
+        return EmitUnaryMir<MAbs>(f, AsmType::Float64, def);
+      case F64::Sqrt:
+        return EmitUnaryMir<MSqrt>(f, AsmType::Float64, def);
+      case F64::Ceil:
+      case F64::Floor:
+      case F64::Sin:
+      case F64::Cos:
+      case F64::Tan:
+      case F64::Asin:
+      case F64::Acos:
+      case F64::Atan:
+      case F64::Exp:
+      case F64::Log:
+      case F64::Pow:
+      case F64::Atan2:
+        return EmitMathBuiltinCall(f, op, def);
+      case F64::FromF32:
+        return EmitUnary<MToDouble>(f, AsmType::Float32, def);
+      case F64::FromS32:
+        return EmitUnary<MToDouble>(f, AsmType::Int32, def);
+      case F64::FromU32:
+        return EmitUnary<MAsmJSUnsignedToDouble>(f, AsmType::Int32, def);
+      case F64::Load:
+        return EmitLoadArray(f, Scalar::Float64, def);
+      case F64::StoreF64:
+        return EmitStore(f, Scalar::Float64, def);
+      case F64::StoreF32:
+        return EmitStoreWithCoercion(f, Scalar::Float64, Scalar::Float32, def);
+      case F64::CallInternal:
+        return EmitInternalCall(f, RetType::Double, def);
+      case F64::CallIndirect:
+        return EmitFuncPtrCall(f, RetType::Double, def);
+      case F64::CallImport:
+        return EmitFFICall(f, RetType::Double, def);
+      case F64::Conditional:
+        return EmitConditional(f, AsmType::Float64, def);
+      case F64::Comma:
+        return EmitComma(f, AsmType::Float64, def);
+      case F64::Bad:
+        break;
+    }
+    MOZ_CRASH("unexpected f64 expression");
+}
+
+static bool
+EmitI32X4Expr(FunctionCompiler& f, MDefinition** def)
+{
+    I32X4 op = I32X4(f.readU8());
+    switch (op) {
+      case I32X4::Id:
+        return EmitI32X4Expr(f, def);
+      case I32X4::GetLocal:
+        return EmitGetLoc(f, DebugOnly<MIRType>(MIRType_Int32x4), def);
+      case I32X4::SetLocal:
+        return EmitSetLoc(f, AsmType::Int32x4, def);
+      case I32X4::GetGlobal:
+        return EmitGetGlo(f, MIRType_Int32x4, def);
+      case I32X4::SetGlobal:
+        return EmitSetGlo(f, AsmType::Int32x4, def);
+      case I32X4::Comma:
+        return EmitComma(f, AsmType::Int32x4, def);
+      case I32X4::Conditional:
+        return EmitConditional(f, AsmType::Int32x4, def);
+      case I32X4::CallInternal:
+        return EmitInternalCall(f, RetType::Int32x4, def);
+      case I32X4::CallIndirect:
+        return EmitFuncPtrCall(f, RetType::Int32x4, def);
+      case I32X4::CallImport:
+        return EmitFFICall(f, RetType::Int32x4, def);
+      case I32X4::Literal:
+        return EmitLiteral(f, AsmType::Int32x4, def);
+      case I32X4::Ctor:
+        return EmitSimdCtor(f, AsmType::Int32x4, def);
+      case I32X4::Unary:
+        return EmitSimdUnary(f, AsmType::Int32x4, def);
+      case I32X4::Binary:
+        return EmitSimdBinaryArith(f, AsmType::Int32x4, def);
+      case I32X4::BinaryBitwise:
+        return EmitSimdBinaryBitwise(f, AsmType::Int32x4, def);
+      case I32X4::BinaryCompI32X4:
+        return EmitSimdBinaryComp(f, AsmType::Int32x4, def);
+      case I32X4::BinaryCompF32X4:
+        return EmitSimdBinaryComp(f, AsmType::Float32x4, def);
+      case I32X4::BinaryShift:
+        return EmitSimdBinaryShift(f, def);
+      case I32X4::ReplaceLane:
+        return EmitSimdReplaceLane(f, AsmType::Int32x4, def);
+      case I32X4::FromF32X4:
+        return EmitSimdCast<MSimdConvert>(f, AsmType::Float32x4, AsmType::Int32x4, def);
+      case I32X4::FromF32X4Bits:
+        return EmitSimdCast<MSimdReinterpretCast>(f, AsmType::Float32x4, AsmType::Int32x4, def);
+      case I32X4::Swizzle:
+        return EmitSimdSwizzle(f, AsmType::Int32x4, def);
+      case I32X4::Shuffle:
+        return EmitSimdShuffle(f, AsmType::Int32x4, def);
+      case I32X4::Select:
+        return EmitSimdSelect(f, AsmType::Int32x4, IsElementWise(true), def);
+      case I32X4::BitSelect:
+        return EmitSimdSelect(f, AsmType::Int32x4, IsElementWise(false), def);
+      case I32X4::Splat:
+        return EmitSimdSplat(f, AsmType::Int32x4, def);
+      case I32X4::Load:
+        return EmitSimdLoad(f, AsmType::Int32x4, def);
+      case I32X4::Store:
+        return EmitSimdStore(f, AsmType::Int32x4, def);
+      case I32X4::Bad:
+        break;
+    }
+    MOZ_CRASH("unexpected int32x4 expression");
+}
+
+static bool
+EmitF32X4Expr(FunctionCompiler& f, MDefinition** def)
+{
+    F32X4 op = F32X4(f.readU8());
+    switch (op) {
+      case F32X4::Id:
+        return EmitF32X4Expr(f, def);
+      case F32X4::GetLocal:
+        return EmitGetLoc(f, DebugOnly<MIRType>(MIRType_Float32x4), def);
+      case F32X4::SetLocal:
+        return EmitSetLoc(f, AsmType::Float32x4, def);
+      case F32X4::GetGlobal:
+        return EmitGetGlo(f, MIRType_Float32x4, def);
+      case F32X4::SetGlobal:
+        return EmitSetGlo(f, AsmType::Float32x4, def);
+      case F32X4::Comma:
+        return EmitComma(f, AsmType::Float32x4, def);
+      case F32X4::Conditional:
+        return EmitConditional(f, AsmType::Float32x4, def);
+      case F32X4::CallInternal:
+        return EmitInternalCall(f, RetType::Float32x4, def);
+      case F32X4::CallIndirect:
+        return EmitFuncPtrCall(f, RetType::Float32x4, def);
+      case F32X4::CallImport:
+        return EmitFFICall(f, RetType::Float32x4, def);
+      case F32X4::Literal:
+        return EmitLiteral(f, AsmType::Float32x4, def);
+      case F32X4::Ctor:
+        return EmitSimdCtor(f, AsmType::Float32x4, def);
+      case F32X4::Unary:
+        return EmitSimdUnary(f, AsmType::Float32x4, def);
+      case F32X4::Binary:
+        return EmitSimdBinaryArith(f, AsmType::Float32x4, def);
+      case F32X4::BinaryBitwise:
+        return EmitSimdBinaryBitwise(f, AsmType::Float32x4, def);
+      case F32X4::ReplaceLane:
+        return EmitSimdReplaceLane(f, AsmType::Float32x4, def);
+      case F32X4::FromI32X4:
+        return EmitSimdCast<MSimdConvert>(f, AsmType::Int32x4, AsmType::Float32x4, def);
+      case F32X4::FromI32X4Bits:
+        return EmitSimdCast<MSimdReinterpretCast>(f, AsmType::Int32x4, AsmType::Float32x4, def);
+      case F32X4::Swizzle:
+        return EmitSimdSwizzle(f, AsmType::Float32x4, def);
+      case F32X4::Shuffle:
+        return EmitSimdShuffle(f, AsmType::Float32x4, def);
+      case F32X4::Select:
+        return EmitSimdSelect(f, AsmType::Float32x4, IsElementWise(true), def);
+      case F32X4::BitSelect:
+        return EmitSimdSelect(f, AsmType::Float32x4, IsElementWise(false), def);
+      case F32X4::Splat:
+        return EmitSimdSplat(f, AsmType::Float32x4, def);
+      case F32X4::Load:
+        return EmitSimdLoad(f, AsmType::Float32x4, def);
+      case F32X4::Store:
+        return EmitSimdStore(f, AsmType::Float32x4, def);
+      case F32X4::Bad:
+        break;
+    }
+    MOZ_CRASH("unexpected float32x4 expression");
+}
+
+bool
+js::GenerateAsmFunctionMIR(ModuleCompiler& m, LifoAlloc& lifo, AsmFunction& func, MIRGenerator** mir)
+{
+    int64_t before = PRMJ_Now();
+
+    FunctionCompiler f(m, func, lifo);
+    if (!f.init())
+        return false;
+
+    if (!f.prepareEmitMIR(func.argTypes()))
+        return false;
+
+    while (!f.done()) {
+        if (!EmitStatement(f))
+            return false;
+    }
+
+    *mir = f.extractMIR();
+    if (!*mir)
+        return false;
+
+    jit::SpewBeginFunction(*mir, nullptr);
+
+    f.checkPostconditions();
+
+    func.accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC);
+    return true;
+}
+
+bool
+js::GenerateAsmFunctionCode(ModuleCompiler& m, AsmFunction& func, MIRGenerator& mir, LIRGraph& lir)
+{
+    JitContext jitContext(m.runtime(), /* CompileCompartment = */ nullptr, &mir.alloc());
+
+    int64_t before = PRMJ_Now();
+
+    // A single MacroAssembler is reused for all function compilations so
+    // that there is a single linear code segment for each module. To avoid
+    // spiking memory, a LifoAllocScope in the caller frees all MIR/LIR
+    // after each function is compiled. This method is responsible for cleaning
+    // out any dangling pointers that the MacroAssembler may have kept.
+    m.masm().resetForNewCodeGenerator(mir.alloc());
+
+    ScopedJSDeletePtr<CodeGenerator> codegen(js_new<CodeGenerator>(&mir, &lir, &m.masm()));
+    if (!codegen)
+        return false;
+
+    Label* funcEntry;
+    if (!m.getOrCreateFunctionEntry(func.funcIndex(), &funcEntry))
+        return false;
+
+    AsmJSFunctionLabels labels(*funcEntry, m.stackOverflowLabel());
+    if (!codegen->generateAsmJS(&labels))
+        return false;
+
+    func.accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC);
+
+    if (!m.finishGeneratingFunction(func, *codegen, labels))
+        return false;
+
+    // Unlike regular IonMonkey, which links and generates a new JitCode for
+    // every function, we accumulate all the functions in the module in a
+    // single MacroAssembler and link at end. Linking asm.js doesn't require a
+    // CodeGenerator so we can destroy it now (via ScopedJSDeletePtr).
+    return true;
+}
+
+bool
+js::CreateAsmModuleCompiler(ModuleCompileInputs mci, AsmModuleCompilerScope* scope)
+{
+    auto* mc = js_new<ModuleCompiler>(mci);
+    if (!mc || !mc->init())
+        return false;
+    scope->setModule(mc);
+    return true;
+}
+
+AsmModuleCompilerScope::~AsmModuleCompilerScope()
+{
+    if (m_) {
+        js_delete(m_);
+        m_ = nullptr;
+    }
+}
+
+void
+js::FinishAsmModuleCompilation(ModuleCompiler& m, ScopedJSDeletePtr<ModuleCompileResults>* results)
+{
+    m.finish(results);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/asmjs/AsmJSCompile.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Copyright 2015 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef jit_AsmJSCompile_h
+#define jit_AsmJSCompile_h
+
+#include "jit/CompileWrappers.h"
+
+namespace js {
+
+class AsmFunction;
+class LifoAlloc;
+class ModuleCompiler;
+class ModuleCompileResults;
+
+namespace jit {
+    class LIRGraph;
+    class MIRGenerator;
+}
+
+struct ModuleCompileInputs
+{
+    jit::CompileCompartment* compartment;
+    jit::CompileRuntime* runtime;
+    bool usesSignalHandlersForOOB;
+
+    ModuleCompileInputs(jit::CompileCompartment* compartment,
+                        jit::CompileRuntime* runtime,
+                        bool usesSignalHandlersForOOB)
+      : compartment(compartment),
+        runtime(runtime),
+        usesSignalHandlersForOOB(usesSignalHandlersForOOB)
+    {}
+};
+
+class MOZ_RAII AsmModuleCompilerScope
+{
+    ModuleCompiler* m_;
+
+    AsmModuleCompilerScope(const AsmModuleCompilerScope&) = delete;
+    AsmModuleCompilerScope(const AsmModuleCompilerScope&&) = delete;
+    AsmModuleCompilerScope& operator=(const AsmModuleCompilerScope&&) = delete;
+
+  public:
+    AsmModuleCompilerScope()
+      : m_(nullptr)
+    {}
+
+    void setModule(ModuleCompiler* m) {
+        MOZ_ASSERT(m);
+        m_ = m;
+    }
+
+    ModuleCompiler& module() const {
+        MOZ_ASSERT(m_);
+        return *m_;
+    }
+
+    ~AsmModuleCompilerScope();
+};
+
+bool CreateAsmModuleCompiler(ModuleCompileInputs mci, AsmModuleCompilerScope* scope);
+bool GenerateAsmFunctionMIR(ModuleCompiler& m, LifoAlloc& lifo, AsmFunction& func, jit::MIRGenerator** mir);
+bool GenerateAsmFunctionCode(ModuleCompiler& m, AsmFunction& func, jit::MIRGenerator& mir, jit::LIRGraph& lir);
+void FinishAsmModuleCompilation(ModuleCompiler& m, ScopedJSDeletePtr<ModuleCompileResults>* results);
+
+} // namespace js
+
+#endif // jit_AsmJSCompile_h
new file mode 100644
--- /dev/null
+++ b/js/src/asmjs/AsmJSGlobals.h
@@ -0,0 +1,1148 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Copyright 2015 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef jit_AsmJSGlobals_h
+#define jit_AsmJSGlobals_h
+
+#include "asmjs/AsmJSModule.h"
+
+namespace js {
+namespace wasm {
+
+enum NeedsBoundsCheck {
+    NO_BOUNDS_CHECK,
+    NEEDS_BOUNDS_CHECK
+};
+
+// Respresents the type of a general asm.js expression.
+class Type
+{
+  public:
+    enum Which {
+        Fixnum = AsmJSNumLit::Fixnum,
+        Signed = AsmJSNumLit::NegativeInt,
+        Unsigned = AsmJSNumLit::BigUnsigned,
+        DoubleLit = AsmJSNumLit::Double,
+        Float = AsmJSNumLit::Float,
+        Int32x4 = AsmJSNumLit::Int32x4,
+        Float32x4 = AsmJSNumLit::Float32x4,
+        Double,
+        MaybeDouble,
+        MaybeFloat,
+        Floatish,
+        Int,
+        Intish,
+        Void
+    };
+
+  private:
+    Which which_;
+
+  public:
+    Type() : which_(Which(-1)) {}
+    static Type Of(const AsmJSNumLit& lit) {
+        MOZ_ASSERT(lit.hasType());
+        Which which = Type::Which(lit.which());
+        MOZ_ASSERT(which >= Fixnum && which <= Float32x4);
+        Type t;
+        t.which_ = which;
+        return t;
+    }
+    MOZ_IMPLICIT Type(Which w) : which_(w) {}
+    Which which() const { return which_; }
+    MOZ_IMPLICIT Type(AsmJSSimdType type) {
+        switch (type) {
+          case AsmJSSimdType_int32x4:
+            which_ = Int32x4;
+            return;
+          case AsmJSSimdType_float32x4:
+            which_ = Float32x4;
+            return;
+        }
+        MOZ_CRASH("unexpected AsmJSSimdType");
+    }
+
+    bool operator==(Type rhs) const { return which_ == rhs.which_; }
+    bool operator!=(Type rhs) const { return which_ != rhs.which_; }
+
+    inline bool operator<=(Type rhs) const {
+        switch (rhs.which_) {
+          case Signed:      return isSigned();
+          case Unsigned:    return isUnsigned();
+          case DoubleLit:   return isDoubleLit();
+          case Double:      return isDouble();
+          case Float:       return isFloat();
+          case Int32x4:     return isInt32x4();
+          case Float32x4:   return isFloat32x4();
+          case MaybeDouble: return isMaybeDouble();
+          case MaybeFloat:  return isMaybeFloat();
+          case Floatish:    return isFloatish();
+          case Int:         return isInt();
+          case Intish:      return isIntish();
+          case Fixnum:      return isFixnum();
+          case Void:        return isVoid();
+        }
+        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected this type");
+    }
+
+    bool isFixnum() const {
+        return which_ == Fixnum;
+    }
+
+    bool isSigned() const {
+        return which_ == Signed || which_ == Fixnum;
+    }
+
+    bool isUnsigned() const {
+        return which_ == Unsigned || which_ == Fixnum;
+    }
+
+    bool isInt() const {
+        return isSigned() || isUnsigned() || which_ == Int;
+    }
+
+    bool isIntish() const {
+        return isInt() || which_ == Intish;
+    }
+
+    bool isDoubleLit() const {
+        return which_ == DoubleLit;
+    }
+
+    bool isDouble() const {
+        return isDoubleLit() || which_ == Double;
+    }
+
+    bool isMaybeDouble() const {
+        return isDouble() || which_ == MaybeDouble;
+    }
+
+    bool isFloat() const {
+        return which_ == Float;
+    }
+
+    bool isMaybeFloat() const {
+        return isFloat() || which_ == MaybeFloat;
+    }
+
+    bool isFloatish() const {
+        return isMaybeFloat() || which_ == Floatish;
+    }
+
+    bool isVoid() const {
+        return which_ == Void;
+    }
+
+    bool isExtern() const {
+        return isDouble() || isSigned();
+    }
+
+    bool isInt32x4() const {
+        return which_ == Int32x4;
+    }
+
+    bool isFloat32x4() const {
+        return which_ == Float32x4;
+    }
+
+    bool isSimd() const {
+        return isInt32x4() || isFloat32x4();
+    }
+
+    bool isVarType() const {
+        return isInt() || isDouble() || isFloat() || isSimd();
+    }
+
+    jit::MIRType toMIRType() const {
+        switch (which_) {
+          case Double:
+          case DoubleLit:
+          case MaybeDouble:
+            return jit::MIRType_Double;
+          case Float:
+          case Floatish:
+          case MaybeFloat:
+            return jit::MIRType_Float32;
+          case Fixnum:
+          case Int:
+          case Signed:
+          case Unsigned:
+          case Intish:
+            return jit::MIRType_Int32;
+          case Int32x4:
+            return jit::MIRType_Int32x4;
+          case Float32x4:
+            return jit::MIRType_Float32x4;
+          case Void:
+            return jit::MIRType_None;
+        }
+        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Invalid Type");
+    }
+
+    AsmJSSimdType simdType() const {
+        MOZ_ASSERT(isSimd());
+        switch (which_) {
+          case Int32x4:
+            return AsmJSSimdType_int32x4;
+          case Float32x4:
+            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_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";
+          case Floatish:    return "floatish";
+          case MaybeFloat:  return "float?";
+          case Fixnum:      return "fixnum";
+          case Int:         return "int";
+          case Signed:      return "signed";
+          case Unsigned:    return "unsigned";
+          case Intish:      return "intish";
+          case Int32x4:     return "int32x4";
+          case Float32x4:   return "float32x4";
+          case Void:        return "void";
+        }
+        MOZ_CRASH("Invalid Type");
+    }
+};
+
+// Represents the subset of Type that can be used as a variable or
+// argument's type. Note: AsmJSCoercion and VarType are kept separate to
+// make very clear the signed/int distinction: a coercion may explicitly sign
+// an *expression* but, when stored as a variable, this signedness information
+// is explicitly thrown away by the asm.js type system. E.g., in
+//
+//   function f(i) {
+//     i = i | 0;             (1)
+//     if (...)
+//         i = foo() >>> 0;
+//     else
+//         i = bar() | 0;
+//     return i | 0;          (2)
+//   }
+//
+// the AsmJSCoercion of (1) is Signed (since | performs ToInt32) but, when
+// translated to a VarType, the result is a plain Int since, as shown, it
+// is legal to assign both Signed and Unsigned (or some other Int) values to
+// it. For (2), the AsmJSCoercion is also Signed but, when translated to an
+// RetType, the result is Signed since callers (asm.js and non-asm.js) can
+// rely on the return value being Signed.
+class VarType
+{
+  public:
+    enum Which {
+        Int = Type::Int,
+        Double = Type::Double,
+        Float = Type::Float,
+        Int32x4 = Type::Int32x4,
+        Float32x4 = Type::Float32x4
+    };
+
+  private:
+    Which which_;
+
+  public:
+    VarType()
+      : which_(Which(-1)) {}
+    MOZ_IMPLICIT VarType(Which w)
+      : which_(w) {}
+    MOZ_IMPLICIT VarType(AsmJSCoercion coercion) {
+        switch (coercion) {
+          case AsmJS_ToInt32: which_ = Int; break;
+          case AsmJS_ToNumber: which_ = Double; break;
+          case AsmJS_FRound: which_ = Float; break;
+          case AsmJS_ToInt32x4: which_ = Int32x4; break;
+          case AsmJS_ToFloat32x4: which_ = Float32x4; break;
+        }
+    }
+    static VarType Of(const AsmJSNumLit& lit) {
+        MOZ_ASSERT(lit.hasType());
+        switch (lit.which()) {
+          case AsmJSNumLit::Fixnum:
+          case AsmJSNumLit::NegativeInt:
+          case AsmJSNumLit::BigUnsigned:
+            return Int;
+          case AsmJSNumLit::Double:
+            return Double;
+          case AsmJSNumLit::Float:
+            return Float;
+          case AsmJSNumLit::Int32x4:
+            return Int32x4;
+          case AsmJSNumLit::Float32x4:
+            return Float32x4;
+          case AsmJSNumLit::OutOfRangeInt:
+            MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("can't be out of range int");
+        }
+        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected literal type");
+    }
+
+    Which which() const {
+        return which_;
+    }
+    Type toType() const {
+        return Type::Which(which_);
+    }
+    jit::MIRType toMIRType() const {
+        switch(which_) {
+          case Int:       return jit::MIRType_Int32;
+          case Double:    return jit::MIRType_Double;
+          case Float:     return jit::MIRType_Float32;
+          case Int32x4:   return jit::MIRType_Int32x4;
+          case Float32x4: return jit::MIRType_Float32x4;
+        }
+        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("VarType can only be Int, SIMD, Double or Float");
+    }
+    AsmJSCoercion toCoercion() const {
+        switch(which_) {
+          case Int:       return AsmJS_ToInt32;
+          case Double:    return AsmJS_ToNumber;
+          case Float:     return AsmJS_FRound;
+          case Int32x4:   return AsmJS_ToInt32x4;
+          case Float32x4: return AsmJS_ToFloat32x4;
+        }
+        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("VarType can only be Int, SIMD, Double or Float");
+    }
+    static VarType FromCheckedType(Type type) {
+        MOZ_ASSERT(type.isInt() || type.isMaybeDouble() || type.isFloatish() || type.isSimd());
+        if (type.isMaybeDouble())
+            return Double;
+        else if (type.isFloatish())
+            return Float;
+        else if (type.isInt())
+            return Int;
+        else if (type.isInt32x4())
+            return Int32x4;
+        else if (type.isFloat32x4())
+            return Float32x4;
+        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unknown type in FromCheckedType");
+    }
+    bool operator==(VarType rhs) const { return which_ == rhs.which_; }
+    bool operator!=(VarType rhs) const { return which_ != rhs.which_; }
+};
+
+// Represents the subset of Type that can be used as the return type of a
+// function.
+class RetType
+{
+  public:
+    enum Which {
+        Void      = Type::Void,
+        Signed    = Type::Signed,
+        Double    = Type::Double,
+        Float     = Type::Float,
+        Int32x4   = Type::Int32x4,
+        Float32x4 = Type::Float32x4
+    };
+
+  private:
+    Which which_;
+
+  public:
+    RetType() : which_(Which(-1)) {}
+    MOZ_IMPLICIT RetType(Which w) : which_(w) {}
+    MOZ_IMPLICIT RetType(AsmJSCoercion coercion) {
+        which_ = Which(-1);  // initialize to silence GCC warning
+        switch (coercion) {
+          case AsmJS_ToInt32:     which_ = Signed;    break;
+          case AsmJS_ToNumber:    which_ = Double;    break;
+          case AsmJS_FRound:      which_ = Float;     break;
+          case AsmJS_ToInt32x4:   which_ = Int32x4;   break;
+          case AsmJS_ToFloat32x4: which_ = Float32x4; break;
+        }
+    }
+    Which which() const {
+        return which_;
+    }
+    Type toType() const {
+        return Type::Which(which_);
+    }
+    AsmJSModule::ReturnType toModuleReturnType() const {
+        switch (which_) {
+          case Void:      return AsmJSModule:: Return_Void;
+          case Signed:    return AsmJSModule:: Return_Int32;
+          case Float: // will be converted to a Double
+          case Double:    return AsmJSModule:: Return_Double;
+          case Int32x4:   return AsmJSModule:: Return_Int32x4;
+          case Float32x4: return AsmJSModule:: Return_Float32x4;
+        }
+        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected return type");
+    }
+    jit::MIRType toMIRType() const {
+        switch (which_) {
+          case Void:      return jit::MIRType_None;
+          case Signed:    return jit::MIRType_Int32;
+          case Double:    return jit::MIRType_Double;
+          case Float:     return jit::MIRType_Float32;
+          case Int32x4:   return jit::MIRType_Int32x4;
+          case Float32x4: return jit::MIRType_Float32x4;
+        }
+        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected return type");
+    }
+    bool operator==(RetType rhs) const { return which_ == rhs.which_; }
+    bool operator!=(RetType rhs) const { return which_ != rhs.which_; }
+};
+
+inline jit::MIRType ToMIRType(jit::MIRType t) { return t; }
+inline jit::MIRType ToMIRType(VarType t) { return t.toMIRType(); }
+
+template <class VecT>
+class ABIArgIter
+{
+    jit::ABIArgGenerator gen_;
+    const VecT& types_;
+    unsigned i_;
+
+    void settle() { if (!done()) gen_.next(ToMIRType(types_[i_])); }
+
+  public:
+    explicit ABIArgIter(const VecT& types) : types_(types), i_(0) { settle(); }
+    void operator++(int) { MOZ_ASSERT(!done()); i_++; settle(); }
+    bool done() const { return i_ == types_.length(); }
+
+    jit::ABIArg* operator->() { MOZ_ASSERT(!done()); return &gen_.current(); }
+    jit::ABIArg& operator*() { MOZ_ASSERT(!done()); return gen_.current(); }
+
+    unsigned index() const { MOZ_ASSERT(!done()); return i_; }
+    jit::MIRType mirType() const { MOZ_ASSERT(!done()); return ToMIRType(types_[i_]); }
+    uint32_t stackBytesConsumedSoFar() const { return gen_.stackBytesConsumedSoFar(); }
+};
+
+typedef Vector<jit::MIRType, 8> MIRTypeVector;
+typedef ABIArgIter<MIRTypeVector> ABIArgMIRTypeIter;
+
+typedef Vector<VarType, 8, LifoAllocPolicy<Fallible>> VarTypeVector;
+typedef ABIArgIter<VarTypeVector> ABIArgTypeIter;
+
+class Signature
+{
+    VarTypeVector argTypes_;
+    RetType retType_;
+
+  public:
+    explicit Signature(LifoAlloc& alloc)
+      : argTypes_(alloc) {}
+    Signature(LifoAlloc& alloc, RetType retType)
+      : argTypes_(alloc), retType_(retType) {}
+    Signature(VarTypeVector&& argTypes, RetType retType)
+      : argTypes_(Move(argTypes)), retType_(Move(retType)) {}
+    Signature(Signature&& rhs)
+      : argTypes_(Move(rhs.argTypes_)), retType_(Move(rhs.retType_)) {}
+
+    bool copy(const Signature& rhs) {
+        if (!argTypes_.resize(rhs.argTypes_.length()))
+            return false;
+        for (unsigned i = 0; i < argTypes_.length(); i++)
+            argTypes_[i] = rhs.argTypes_[i];
+        retType_ = rhs.retType_;
+        return true;
+    }
+
+    bool appendArg(VarType type) { return argTypes_.append(type); }
+    VarType arg(unsigned i) const { return argTypes_[i]; }
+    const VarTypeVector& args() const { return argTypes_; }
+    VarTypeVector&& extractArgs() { return Move(argTypes_); }
+
+    RetType retType() const { return retType_; }
+};
+
+// Signature that can be only allocated with a LifoAlloc.
+class LifoSignature : public Signature
+{
+    explicit LifoSignature(Signature&& rhs)
+      : Signature(Move(rhs))
+    {}
+
+    LifoSignature(const LifoSignature&) = delete;
+    LifoSignature(const LifoSignature&&) = delete;
+    LifoSignature& operator=(const LifoSignature&) = delete;
+    LifoSignature& operator=(const LifoSignature&&) = delete;
+
+  public:
+    static LifoSignature* new_(LifoAlloc& lifo, Signature&& sig) {
+        void* mem = lifo.alloc(sizeof(LifoSignature));
+        if (!mem)
+            return nullptr;
+        return new (mem) LifoSignature(Move(sig));
+    }
+};
+
+enum class Stmt : uint8_t {
+    Ret,
+
+    Block,
+
+    IfThen,
+    IfElse,
+    Switch,
+
+    While,
+    DoWhile,
+
+    ForInitInc,
+    ForInitNoInc,
+    ForNoInitNoInc,
+    ForNoInitInc,
+
+    Label,
+    Continue,
+    ContinueLabel,
+    Break,
+    BreakLabel,
+
+    CallInternal,
+    CallIndirect,
+    CallImport,
+
+    AtomicsFence,
+
+    // asm.js specific
+    // Expression statements (to be removed in the future)
+    I32Expr,
+    F32Expr,
+    F64Expr,
+    I32X4Expr,
+    F32X4Expr,
+
+    Id,
+    Noop,
+    InterruptCheckHead,
+    InterruptCheckLoop,
+
+    DebugCheckPoint,
+
+    Bad
+};
+
+enum class I32 : uint8_t {
+    // Common opcodes
+    GetLocal,
+    SetLocal,
+    GetGlobal,
+    SetGlobal,
+
+    CallInternal,
+    CallIndirect,
+    CallImport,
+
+    Conditional,
+    Comma,
+
+    Literal,
+
+    // Binary arith opcodes
+    Add,
+    Sub,
+    Mul,
+    SDiv,
+    SMod,
+    UDiv,
+    UMod,
+    Min,
+    Max,
+
+    // Unary arith opcodes
+    Not,
+    Neg,
+
+    // Bitwise opcodes
+    BitOr,
+    BitAnd,
+    BitXor,
+    BitNot,
+
+    Lsh,
+    ArithRsh,
+    LogicRsh,
+
+    // Conversion opcodes
+    FromF32,
+    FromF64,
+
+    // Math builtin opcodes
+    Clz,
+    Abs,
+
+    // Comparison opcodes
+    // Ordering matters (EmitComparison expects signed opcodes to be placed
+    // before unsigned opcodes)
+    EqI32,
+    NeI32,
+    SLtI32,
+    SLeI32,
+    SGtI32,
+    SGeI32,
+    ULtI32,
+    ULeI32,
+    UGtI32,
+    UGeI32,
+
+    EqF32,
+    NeF32,
+    LtF32,
+    LeF32,
+    GtF32,
+    GeF32,
+
+    EqF64,
+    NeF64,
+    LtF64,
+    LeF64,
+    GtF64,
+    GeF64,
+
+    // Heap accesses opcodes
+    SLoad8,
+    SLoad16,
+    SLoad32,
+    ULoad8,
+    ULoad16,
+    ULoad32,
+    Store8,
+    Store16,
+    Store32,
+
+    // Atomics opcodes
+    AtomicsCompareExchange,
+    AtomicsExchange,
+    AtomicsLoad,
+    AtomicsStore,
+    AtomicsBinOp,
+
+    // SIMD opcodes
+    I32X4SignMask,
+    F32X4SignMask,
+
+    I32X4ExtractLane,
+
+    // Specific to AsmJS
+    Id,
+
+    Bad
+};
+
+enum class F32 : uint8_t {
+    // Common opcodes
+    GetLocal,
+    SetLocal,
+    GetGlobal,
+    SetGlobal,
+
+    CallInternal,
+    CallIndirect,
+    CallImport,
+
+    Conditional,
+    Comma,
+
+    Literal,
+
+    // Binary arith opcodes
+    Add,
+    Sub,
+    Mul,
+    Div,
+    Min,
+    Max,
+    Neg,
+
+    // Math builtin opcodes
+    Abs,
+    Sqrt,
+    Ceil,
+    Floor,
+
+    // Conversion opcodes
+    FromF64,
+    FromS32,
+    FromU32,
+
+    // Heap accesses opcodes
+    Load,
+    StoreF32,
+    StoreF64,
+
+    // SIMD opcodes
+    F32X4ExtractLane,
+
+    // asm.js specific
+    Id,
+    Bad
+};
+
+enum class F64 : uint8_t {
+    // Common opcodes
+    GetLocal,
+    SetLocal,
+    GetGlobal,
+    SetGlobal,
+
+    CallInternal,
+    CallIndirect,
+    CallImport,
+
+    Conditional,
+    Comma,
+
+    Literal,
+
+    // Binary arith opcodes
+    Add,
+    Sub,
+    Mul,
+    Div,
+    Min,
+    Max,
+    Mod,
+    Neg,
+
+    // Math builtin opcodes
+    Abs,
+    Sqrt,
+    Ceil,
+    Floor,
+    Sin,
+    Cos,
+    Tan,
+    Asin,
+    Acos,
+    Atan,
+    Exp,
+    Log,
+    Pow,
+    Atan2,
+
+    // Conversions opcodes
+    FromF32,
+    FromS32,
+    FromU32,
+
+    // Heap accesses opcodes
+    Load,
+    StoreF32,
+    StoreF64,
+
+    // asm.js specific
+    Id,
+    Bad
+};
+
+enum class I32X4 : uint8_t {
+    // Common opcodes
+    GetLocal,
+    SetLocal,
+
+    GetGlobal,
+    SetGlobal,
+
+    CallInternal,
+    CallIndirect,
+    CallImport,
+
+    Conditional,
+    Comma,
+
+    Literal,
+
+    // Specific opcodes
+    Ctor,
+
+    Unary,
+
+    Binary,
+    BinaryCompI32X4,
+    BinaryCompF32X4,
+    BinaryBitwise,
+    BinaryShift,
+
+    ReplaceLane,
+
+    FromF32X4,
+    FromF32X4Bits,
+
+    Swizzle,
+    Shuffle,
+    Select,
+    BitSelect,
+    Splat,
+
+    Load,
+    Store,
+
+    // asm.js specific
+    Id,
+    Bad
+};
+
+enum class F32X4 : uint8_t {
+    // Common opcodes
+    GetLocal,
+    SetLocal,
+
+    GetGlobal,
+    SetGlobal,
+
+    CallInternal,
+    CallIndirect,
+    CallImport,
+
+    Conditional,
+    Comma,
+
+    Literal,
+
+    // Specific opcodes
+    Ctor,
+
+    Unary,
+
+    Binary,
+    BinaryBitwise,
+
+    ReplaceLane,
+
+    FromI32X4,
+    FromI32X4Bits,
+    Swizzle,
+    Shuffle,
+    Select,
+    BitSelect,
+    Splat,
+
+    Load,
+    Store,
+
+    // asm.js specific
+    Id,
+    Bad
+};
+
+} // namespace wasm
+
+class AsmFunction
+{
+  public:
+    typedef Vector<AsmJSNumLit, 8, LifoAllocPolicy<Fallible>> VarInitializerVector;
+
+  private:
+    typedef Vector<uint8_t, 4096, LifoAllocPolicy<Fallible>> Bytecode;
+
+    VarInitializerVector varInitializers_;
+    Bytecode bytecode_;
+
+    wasm::VarTypeVector argTypes_;
+    wasm::RetType returnedType_;
+
+    PropertyName* name_;
+
+    unsigned funcIndex_;
+    unsigned srcBegin_;
+    unsigned lineno_;
+    unsigned column_;
+    unsigned compileTime_;
+
+  public:
+    explicit AsmFunction(LifoAlloc& alloc)
+      : varInitializers_(alloc),
+        bytecode_(alloc),
+        argTypes_(alloc),
+        returnedType_(wasm::RetType::Which(-1)),
+        name_(nullptr),
+        funcIndex_(-1),
+        srcBegin_(-1),
+        lineno_(-1),
+        column_(-1),
+        compileTime_(-1)
+    {}
+
+    bool init(const wasm::VarTypeVector& args) {
+        if (!argTypes_.initCapacity(args.length()))
+            return false;
+        for (size_t i = 0; i < args.length(); i++)
+            argTypes_.append(args[i]);
+        return true;
+    }
+
+    bool finish(const wasm::VarTypeVector& args, PropertyName* name, unsigned funcIndex,
+                unsigned srcBegin, unsigned lineno, unsigned column, unsigned compileTime)
+    {
+        if (!argTypes_.initCapacity(args.length()))
+            return false;
+        for (size_t i = 0; i < args.length(); i++)
+            argTypes_.infallibleAppend(args[i]);
+
+        MOZ_ASSERT(name_ == nullptr);
+        name_ = name;
+
+        MOZ_ASSERT(funcIndex_ == unsigned(-1));
+        funcIndex_ = funcIndex;
+
+        MOZ_ASSERT(srcBegin_ == unsigned(-1));
+        srcBegin_ = srcBegin;
+
+        MOZ_ASSERT(lineno_ == unsigned(-1));
+        lineno_ = lineno;
+
+        MOZ_ASSERT(column_ == unsigned(-1));
+        column_ = column;
+
+        MOZ_ASSERT(compileTime_ == unsigned(-1));
+        compileTime_ = compileTime;
+        return true;
+    }
+
+  private:
+    AsmFunction(const AsmFunction&) = delete;
+    AsmFunction(AsmFunction&& other) = delete;
+    AsmFunction& operator=(const AsmFunction&) = delete;
+
+    // Helper functions
+    template<class T> size_t writePrimitive(T v) {
+        size_t writeAt = bytecode_.length();
+        if (!bytecode_.append(reinterpret_cast<uint8_t*>(&v), sizeof(T)))
+            return -1;
+        return writeAt;
+    }
+
+    template<class T> T readPrimitive(size_t* pc) const {
+        MOZ_ASSERT(*pc + sizeof(T) <= bytecode_.length());
+        T ret;
+        memcpy(&ret, &bytecode_[*pc], sizeof(T));
+        *pc += sizeof(T);
+        return ret;
+    }
+
+  public:
+    size_t writeU8(uint8_t i)   { return writePrimitive<uint8_t>(i); }
+    size_t writeI32(int32_t i)  { return writePrimitive<int32_t>(i); }
+    size_t writeU32(uint32_t i) { return writePrimitive<uint32_t>(i); }
+    size_t writeF32(float f)    { return writePrimitive<float>(f); }
+    size_t writeF64(double d)   { return writePrimitive<double>(d); }
+
+    size_t writeI32X4(const int32_t* i4) {
+        size_t pos = bytecode_.length();
+        for (size_t i = 0; i < 4; i++)
+            writePrimitive<int32_t>(i4[i]);
+        return pos;
+    }
+    size_t writeF32X4(const float* f4) {
+        size_t pos = bytecode_.length();
+        for (size_t i = 0; i < 4; i++)
+            writePrimitive<float>(f4[i]);
+        return pos;
+    }
+
+    uint8_t  readU8 (size_t* pc) const { return readPrimitive<uint8_t>(pc); }
+    int32_t  readI32(size_t* pc) const { return readPrimitive<int32_t>(pc); }
+    float    readF32(size_t* pc) const { return readPrimitive<float>(pc); }
+    uint32_t readU32(size_t* pc) const { return readPrimitive<uint32_t>(pc); }
+    double   readF64(size_t* pc) const { return readPrimitive<double>(pc); }
+    wasm::LifoSignature* readSignature(size_t* pc) const { return readPrimitive<wasm::LifoSignature*>(pc); }
+
+    jit::SimdConstant readI32X4(size_t* pc) const {
+        int32_t x = readI32(pc);
+        int32_t y = readI32(pc);
+        int32_t z = readI32(pc);
+        int32_t w = readI32(pc);
+        return jit::SimdConstant::CreateX4(x, y, z, w);
+    }
+    jit::SimdConstant readF32X4(size_t* pc) const {
+        float x = readF32(pc);
+        float y = readF32(pc);
+        float z = readF32(pc);
+        float w = readF32(pc);
+        return jit::SimdConstant::CreateX4(x, y, z, w);
+    }
+
+#ifdef DEBUG
+    bool pcIsPatchable(size_t pc, unsigned size) const {
+        bool patchable = true;
+        for (unsigned i = 0; patchable && i < size; i++)
+            patchable &= wasm::Stmt(bytecode_[pc]) == wasm::Stmt::Bad;
+        return patchable;
+    }
+#endif // DEBUG
+
+    void patchU8(size_t pc, uint8_t i) {
+        MOZ_ASSERT(pcIsPatchable(pc, sizeof(uint8_t)));
+        bytecode_[pc] = i;
+    }
+
+    template<class T>
+    void patch32(size_t pc, T i) {
+        static_assert(sizeof(T) == sizeof(uint32_t),
+                      "patch32 must be used with 32-bits wide types");
+        MOZ_ASSERT(pcIsPatchable(pc, sizeof(uint32_t)));
+        memcpy(&bytecode_[pc], &i, sizeof(uint32_t));
+    }
+
+    void patchSignature(size_t pc, const wasm::LifoSignature* ptr) {
+        MOZ_ASSERT(pcIsPatchable(pc, sizeof(wasm::LifoSignature*)));
+        memcpy(&bytecode_[pc], &ptr, sizeof(wasm::LifoSignature*));
+    }
+
+    // Setters
+    void accumulateCompileTime(unsigned ms) {
+        compileTime_ += ms;
+    }
+    bool addVariable(const AsmJSNumLit& init) {
+        return varInitializers_.append(init);
+    }
+    void setReturnedType(wasm::RetType retType) {
+        MOZ_ASSERT(returnedType_ == wasm::RetType::Which(-1));
+        returnedType_ = retType;
+    }
+
+    // Read-only interface
+    PropertyName* name() const { return name_; }
+    unsigned funcIndex() const { return funcIndex_; }
+    unsigned srcBegin() const { return srcBegin_; }
+    unsigned lineno() const { return lineno_; }
+    unsigned column() const { return column_; }
+    unsigned compileTime() const { return compileTime_; }
+
+    size_t size() const { return bytecode_.length(); }
+
+    const wasm::VarTypeVector& argTypes() const { return argTypes_; }
+
+    const VarInitializerVector& varInitializers() const { return varInitializers_; }
+    size_t numLocals() const { return argTypes_.length() + varInitializers_.length(); }
+    wasm::RetType returnedType() const {
+        MOZ_ASSERT(returnedType_ != wasm::RetType::Which(-1));
+        return returnedType_;
+    }
+};
+
+const size_t LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12;
+
+class ModuleCompileResults
+{
+  public:
+    struct SlowFunction
+    {
+        SlowFunction(PropertyName* name, unsigned ms, unsigned line, unsigned column)
+         : name(name), ms(ms), line(line), column(column)
+        {}
+
+        PropertyName* name;
+        unsigned ms;
+        unsigned line;
+        unsigned column;
+    };
+
+    typedef Vector<SlowFunction                  , 0, SystemAllocPolicy> SlowFunctionVector;
+    typedef Vector<jit::Label*                   , 8, SystemAllocPolicy> LabelVector;
+    typedef Vector<AsmJSModule::FunctionCodeRange, 8, SystemAllocPolicy> FunctionCodeRangeVector;
+    typedef Vector<jit::IonScriptCounts*         , 0, SystemAllocPolicy> ScriptCountVector;
+#if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
+    typedef Vector<AsmJSModule::ProfiledFunction , 0, SystemAllocPolicy> ProfiledFunctionVector;
+#endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF)
+
+  private:
+    LifoAlloc               lifo_;
+    jit::MacroAssembler     masm_;
+
+    SlowFunctionVector      slowFunctions_;
+    LabelVector             functionEntries_;
+    FunctionCodeRangeVector codeRanges_;
+    ScriptCountVector       functionCounts_;
+#if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
+    ProfiledFunctionVector  profiledFunctions_;
+#endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF)
+
+    jit::NonAssertingLabel stackOverflowLabel_;
+    jit::NonAssertingLabel asyncInterruptLabel_;
+    jit::NonAssertingLabel syncInterruptLabel_;
+    jit::NonAssertingLabel onDetachedLabel_;
+    jit::NonAssertingLabel onConversionErrorLabel_;
+    jit::NonAssertingLabel onOutOfBoundsLabel_;
+    int64_t                usecBefore_;
+
+  public:
+    ModuleCompileResults()
+      : lifo_(LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
+        masm_(jit::MacroAssembler::AsmJSToken()),
+        usecBefore_(PRMJ_Now())
+    {}
+
+    jit::MacroAssembler& masm()           { return masm_; }
+    jit::Label& stackOverflowLabel()      { return stackOverflowLabel_; }
+    jit::Label& asyncInterruptLabel()     { return asyncInterruptLabel_; }
+    jit::Label& syncInterruptLabel()      { return syncInterruptLabel_; }
+    jit::Label& onOutOfBoundsLabel()      { return onOutOfBoundsLabel_; }
+    jit::Label& onDetachedLabel()         { return onDetachedLabel_; }
+    jit::Label& onConversionErrorLabel()  { return onConversionErrorLabel_; }
+    int64_t usecBefore()                  { return usecBefore_; }
+
+    SlowFunctionVector& slowFunctions()   { return slowFunctions_; }
+
+    size_t numFunctionEntries() const     { return functionEntries_.length(); }
+    jit::Label* functionEntry(unsigned i) { return functionEntries_[i]; }
+
+    bool getOrCreateFunctionEntry(unsigned i, jit::Label** label) {
+        if (i == UINT32_MAX)
+            return false;
+        while (functionEntries_.length() <= i) {
+            jit::Label* newEntry = lifo_.new_<jit::Label>();
+            if (!newEntry || !functionEntries_.append(newEntry))
+                return false;
+        }
+        *label = functionEntries_[i];
+        return true;
+    }
+
+    size_t numCodeRanges() const { return codeRanges_.length(); }
+    bool addCodeRange(AsmJSModule::FunctionCodeRange range) { return codeRanges_.append(range); }
+    AsmJSModule::FunctionCodeRange& codeRange(unsigned i) { return codeRanges_[i]; }
+
+    size_t numFunctionCounts() const { return functionCounts_.length(); }
+    bool addFunctionCounts(jit::IonScriptCounts* counts) { return functionCounts_.append(counts); }
+    jit::IonScriptCounts* functionCount(unsigned i) { return functionCounts_[i]; }
+
+#if defined(MOZ_VTUNE) || defined(JS_ION_PERF)
+    size_t numProfiledFunctions() const { return profiledFunctions_.length(); }
+    bool addProfiledFunction(AsmJSModule::ProfiledFunction func) {
+        return profiledFunctions_.append(func);
+    }
+    AsmJSModule::ProfiledFunction& profiledFunction(unsigned i) {
+        return profiledFunctions_[i];
+    }
+#endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF)
+};
+
+} // namespace js
+
+#endif //jit_AsmJSGlobals_h
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -24,18 +24,19 @@
 #ifdef MOZ_VTUNE
 # include "vtune/VTuneWrapper.h"
 #endif
 
 #include "jsmath.h"
 #include "jsprf.h"
 #include "jsutil.h"
 
+#include "asmjs/AsmJSCompile.h"
+#include "asmjs/AsmJSGlobals.h"
 #include "asmjs/AsmJSLink.h"
-#include "asmjs/AsmJSModule.h"
 #include "asmjs/AsmJSSignalHandlers.h"
 #include "builtin/SIMD.h"
 #include "frontend/Parser.h"
 #include "jit/AtomicOperations.h"
 #include "jit/CodeGenerator.h"
 #include "jit/CompileWrappers.h"
 #include "jit/MIR.h"
 #include "jit/MIRGraph.h"
@@ -51,32 +52,31 @@
 #include "frontend/ParseNode-inl.h"
 #include "frontend/Parser-inl.h"
 #include "jit/AtomicOperations-inl.h"
 #include "jit/MacroAssembler-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 using namespace js::jit;
+using namespace js::wasm;
 
 using mozilla::AddToHash;
 using mozilla::ArrayLength;
 using mozilla::CountLeadingZeroes32;
 using mozilla::DebugOnly;
 using mozilla::HashGeneric;
 using mozilla::IsNaN;
 using mozilla::IsNegativeZero;
 using mozilla::Maybe;
 using mozilla::Move;
 using mozilla::PositiveInfinity;
 using mozilla::UniquePtr;
 using JS::GenericNaN;
 
-static const size_t LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12;
-
 /*****************************************************************************/
 // ParseNode utilities
 
 static inline ParseNode*
 NextNode(ParseNode* pn)
 {
     return pn->pn_next;
 }
@@ -521,516 +521,32 @@ ParseVarOrConstStatement(AsmJSParser& pa
         return false;
 
     MOZ_ASSERT((*var)->isKind(PNK_VAR) || (*var)->isKind(PNK_CONST));
     return true;
 }
 
 /*****************************************************************************/
 
-namespace {
-
-// Respresents the type of a general asm.js expression.
-class Type
-{
-  public:
-    enum Which {
-        Fixnum = AsmJSNumLit::Fixnum,
-        Signed = AsmJSNumLit::NegativeInt,
-        Unsigned = AsmJSNumLit::BigUnsigned,
-        DoubleLit = AsmJSNumLit::Double,
-        Float = AsmJSNumLit::Float,
-        Int32x4 = AsmJSNumLit::Int32x4,
-        Float32x4 = AsmJSNumLit::Float32x4,
-        Double,
-        MaybeDouble,
-        MaybeFloat,
-        Floatish,
-        Int,
-        Intish,
-        Void
-    };
-
-  private:
-    Which which_;
-
-  public:
-    Type() : which_(Which(-1)) {}
-    static Type Of(const AsmJSNumLit& lit) {
-        MOZ_ASSERT(lit.hasType());
-        Which which = Type::Which(lit.which());
-        MOZ_ASSERT(which >= Fixnum && which <= Float32x4);
-        Type t;
-        t.which_ = which;
-        return t;
-    }
-    MOZ_IMPLICIT Type(Which w) : which_(w) {}
-    Which which() const { return which_; }
-    MOZ_IMPLICIT Type(AsmJSSimdType type) {
-        switch (type) {
-          case AsmJSSimdType_int32x4:
-            which_ = Int32x4;
-            return;
-          case AsmJSSimdType_float32x4:
-            which_ = Float32x4;
-            return;
-        }
-        MOZ_CRASH("unexpected AsmJSSimdType");
-    }
-
-    bool operator==(Type rhs) const { return which_ == rhs.which_; }
-    bool operator!=(Type rhs) const { return which_ != rhs.which_; }
-
-    inline bool operator<=(Type rhs) const {
-        switch (rhs.which_) {
-          case Signed:      return isSigned();
-          case Unsigned:    return isUnsigned();
-          case DoubleLit:   return isDoubleLit();
-          case Double:      return isDouble();
-          case Float:       return isFloat();
-          case Int32x4:     return isInt32x4();
-          case Float32x4:   return isFloat32x4();
-          case MaybeDouble: return isMaybeDouble();
-          case MaybeFloat:  return isMaybeFloat();
-          case Floatish:    return isFloatish();
-          case Int:         return isInt();
-          case Intish:      return isIntish();
-          case Fixnum:      return isFixnum();
-          case Void:        return isVoid();
-        }
-        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected this type");
-    }
-
-    bool isFixnum() const {
-        return which_ == Fixnum;
-    }
-
-    bool isSigned() const {
-        return which_ == Signed || which_ == Fixnum;
-    }
-
-    bool isUnsigned() const {
-        return which_ == Unsigned || which_ == Fixnum;
-    }
-
-    bool isInt() const {
-        return isSigned() || isUnsigned() || which_ == Int;
-    }
-
-    bool isIntish() const {
-        return isInt() || which_ == Intish;
-    }
-
-    bool isDoubleLit() const {
-        return which_ == DoubleLit;
-    }
-
-    bool isDouble() const {
-        return isDoubleLit() || which_ == Double;
-    }
-
-    bool isMaybeDouble() const {
-        return isDouble() || which_ == MaybeDouble;
-    }
-
-    bool isFloat() const {
-        return which_ == Float;
-    }
-
-    bool isMaybeFloat() const {
-        return isFloat() || which_ == MaybeFloat;
-    }
-
-    bool isFloatish() const {
-        return isMaybeFloat() || which_ == Floatish;
-    }
-
-    bool isVoid() const {
-        return which_ == Void;
-    }
-
-    bool isExtern() const {
-        return isDouble() || isSigned();
-    }
-
-    bool isInt32x4() const {
-        return which_ == Int32x4;
-    }
-
-    bool isFloat32x4() const {
-        return which_ == Float32x4;
-    }
-
-    bool isSimd() const {
-        return isInt32x4() || isFloat32x4();
-    }
-
-    bool isVarType() const {
-        return isInt() || isDouble() || isFloat() || isSimd();
-    }
-
-    MIRType toMIRType() const {
-        switch (which_) {
-          case Double:
-          case DoubleLit:
-          case MaybeDouble:
-            return MIRType_Double;
-          case Float:
-          case Floatish:
-          case MaybeFloat:
-            return MIRType_Float32;
-          case Fixnum:
-          case Int:
-          case Signed:
-          case Unsigned:
-          case Intish:
-            return MIRType_Int32;
-          case Int32x4:
-            return MIRType_Int32x4;
-          case Float32x4:
-            return MIRType_Float32x4;
-          case Void:
-            return MIRType_None;
-        }
-        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Invalid Type");
-    }
-
-    AsmJSSimdType simdType() const {
-        MOZ_ASSERT(isSimd());
-        switch (which_) {
-          case Int32x4:
-            return AsmJSSimdType_int32x4;
-          case Float32x4:
-            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_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";
-          case Floatish:    return "floatish";
-          case MaybeFloat:  return "float?";
-          case Fixnum:      return "fixnum";
-          case Int:         return "int";
-          case Signed:      return "signed";
-          case Unsigned:    return "unsigned";
-          case Intish:      return "intish";
-          case Int32x4:     return "int32x4";
-          case Float32x4:   return "float32x4";
-          case Void:        return "void";
-        }
-        MOZ_CRASH("Invalid Type");
-    }
-};
-
-} /* anonymous namespace */
-
-// Represents the subset of Type that can be used as the return type of a
-// function.
-class RetType
-{
-  public:
-    enum Which {
-        Void = Type::Void,
-        Signed = Type::Signed,
-        Double = Type::Double,
-        Float = Type::Float,
-        Int32x4 = Type::Int32x4,
-        Float32x4 = Type::Float32x4
-    };
-
-  private:
-    Which which_;
-
-  public:
-    RetType() : which_(Which(-1)) {}
-    MOZ_IMPLICIT RetType(Which w) : which_(w) {}
-    MOZ_IMPLICIT RetType(AsmJSCoercion coercion) {
-        which_ = Which(-1);  // initialize to silence GCC warning
-        switch (coercion) {
-          case AsmJS_ToInt32: which_ = Signed; break;
-          case AsmJS_ToNumber: which_ = Double; break;
-          case AsmJS_FRound: which_ = Float; break;
-          case AsmJS_ToInt32x4: which_ = Int32x4; break;
-          case AsmJS_ToFloat32x4: which_ = Float32x4; break;
-        }
-    }
-    Which which() const {
-        return which_;
-    }
-    Type toType() const {
-        return Type::Which(which_);
-    }
-    AsmJSModule::ReturnType toModuleReturnType() const {
-        switch (which_) {
-          case Void: return AsmJSModule::Return_Void;
-          case Signed: return AsmJSModule::Return_Int32;
-          case Float: // will be converted to a Double
-          case Double: return AsmJSModule::Return_Double;
-          case Int32x4: return AsmJSModule::Return_Int32x4;
-          case Float32x4: return AsmJSModule::Return_Float32x4;
-        }
-        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected return type");
-    }
-    MIRType toMIRType() const {
-        switch (which_) {
-          case Void: return MIRType_None;
-          case Signed: return MIRType_Int32;
-          case Double: return MIRType_Double;
-          case Float: return MIRType_Float32;
-          case Int32x4: return MIRType_Int32x4;
-          case Float32x4: return MIRType_Float32x4;
-        }
-        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected return type");
-    }
-    bool operator==(RetType rhs) const { return which_ == rhs.which_; }
-    bool operator!=(RetType rhs) const { return which_ != rhs.which_; }
-};
-
-namespace {
-
-// Represents the subset of Type that can be used as a variable or
-// argument's type. Note: AsmJSCoercion and VarType are kept separate to
-// make very clear the signed/int distinction: a coercion may explicitly sign
-// an *expression* but, when stored as a variable, this signedness information
-// is explicitly thrown away by the asm.js type system. E.g., in
-//
-//   function f(i) {
-//     i = i | 0;             (1)
-//     if (...)
-//         i = foo() >>> 0;
-//     else
-//         i = bar() | 0;
-//     return i | 0;          (2)
-//   }
-//
-// the AsmJSCoercion of (1) is Signed (since | performs ToInt32) but, when
-// translated to a VarType, the result is a plain Int since, as shown, it
-// is legal to assign both Signed and Unsigned (or some other Int) values to
-// it. For (2), the AsmJSCoercion is also Signed but, when translated to an
-// RetType, the result is Signed since callers (asm.js and non-asm.js) can
-// rely on the return value being Signed.
-class VarType
-{
-  public:
-    enum Which {
-        Int = Type::Int,
-        Double = Type::Double,
-        Float = Type::Float,
-        Int32x4 = Type::Int32x4,
-        Float32x4 = Type::Float32x4
-    };
-
-  private:
-    Which which_;
-
-  public:
-    VarType()
-      : which_(Which(-1)) {}
-    MOZ_IMPLICIT VarType(Which w)
-      : which_(w) {}
-    MOZ_IMPLICIT VarType(AsmJSCoercion coercion) {
-        switch (coercion) {
-          case AsmJS_ToInt32: which_ = Int; break;
-          case AsmJS_ToNumber: which_ = Double; break;
-          case AsmJS_FRound: which_ = Float; break;
-          case AsmJS_ToInt32x4: which_ = Int32x4; break;
-          case AsmJS_ToFloat32x4: which_ = Float32x4; break;
-        }
-    }
-    static VarType Of(const AsmJSNumLit& lit) {
-        MOZ_ASSERT(lit.hasType());
-        switch (lit.which()) {
-          case AsmJSNumLit::Fixnum:
-          case AsmJSNumLit::NegativeInt:
-          case AsmJSNumLit::BigUnsigned:
-            return Int;
-          case AsmJSNumLit::Double:
-            return Double;
-          case AsmJSNumLit::Float:
-            return Float;
-          case AsmJSNumLit::Int32x4:
-            return Int32x4;
-          case AsmJSNumLit::Float32x4:
-            return Float32x4;
-          case AsmJSNumLit::OutOfRangeInt:
-            MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("can't be out of range int");
-        }
-        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected literal type");
-    }
-
-    Which which() const {
-        return which_;
-    }
-    Type toType() const {
-        return Type::Which(which_);
-    }
-    MIRType toMIRType() const {
-        switch(which_) {
-          case Int:       return MIRType_Int32;
-          case Double:    return MIRType_Double;
-          case Float:     return MIRType_Float32;
-          case Int32x4:   return MIRType_Int32x4;
-          case Float32x4: return MIRType_Float32x4;
-        }
-        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("VarType can only be Int, SIMD, Double or Float");
-    }
-    AsmJSCoercion toCoercion() const {
-        switch(which_) {
-          case Int:       return AsmJS_ToInt32;
-          case Double:    return AsmJS_ToNumber;
-          case Float:     return AsmJS_FRound;
-          case Int32x4:   return AsmJS_ToInt32x4;
-          case Float32x4: return AsmJS_ToFloat32x4;
-        }
-        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("VarType can only be Int, SIMD, Double or Float");
-    }
-    static VarType FromCheckedType(Type type) {
-        MOZ_ASSERT(type.isInt() || type.isMaybeDouble() || type.isFloatish() || type.isSimd());
-        if (type.isMaybeDouble())
-            return Double;
-        else if (type.isFloatish())
-            return Float;
-        else if (type.isInt())
-            return Int;
-        else if (type.isInt32x4())
-            return Int32x4;
-        else if (type.isFloat32x4())
-            return Float32x4;
-        MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unknown type in FromCheckedType");
-    }
-    bool operator==(VarType rhs) const { return which_ == rhs.which_; }
-    bool operator!=(VarType rhs) const { return which_ != rhs.which_; }
-};
-
-} /* anonymous namespace */
-
 // Implements <: (subtype) operator when the rhs is a VarType
 static inline bool
 operator<=(Type lhs, VarType rhs)
 {
     switch (rhs.which()) {
       case VarType::Int:       return lhs.isInt();
       case VarType::Double:    return lhs.isDouble();
       case VarType::Float:     return lhs.isFloat();
       case VarType::Int32x4:   return lhs.isInt32x4();
       case VarType::Float32x4: return lhs.isFloat32x4();
     }
     MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected rhs type");
 }
 
 /*****************************************************************************/
 
-static inline MIRType ToMIRType(MIRType t) { return t; }
-static inline MIRType ToMIRType(VarType t) { return t.toMIRType(); }
-
-namespace {
-
-template <class VecT>
-class ABIArgIter
-{
-    ABIArgGenerator gen_;
-    const VecT& types_;
-    unsigned i_;
-
-    void settle() { if (!done()) gen_.next(ToMIRType(types_[i_])); }
-
-  public:
-    explicit ABIArgIter(const VecT& types) : types_(types), i_(0) { settle(); }
-    void operator++(int) { MOZ_ASSERT(!done()); i_++; settle(); }
-    bool done() const { return i_ == types_.length(); }
-