merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 18 Mar 2016 15:51:43 +0100
changeset 289323 9c5d494d05485aebf3fedf649abc0e7ae9d2dcf2
parent 289178 3d37c7e5b8dd28a4a017e2296e824619f9379b88 (current diff)
parent 289322 51803770c37d818e9e0a14ba946d0f76822938c6 (diff)
child 289324 90b96acae18ae3424142964cd4a8df5ef26a8560
child 289360 5096e12520cd2ac31f554d7b2d7df40671f992ec
child 289476 16654149f9a1c32cf75b15549d2505d3b08c4a26
push id30099
push usercbook@mozilla.com
push dateFri, 18 Mar 2016 14:52:23 +0000
treeherdermozilla-central@9c5d494d0548 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone48.0a1
first release with
nightly linux32
9c5d494d0548 / 48.0a1 / 20160319030558 / files
nightly linux64
9c5d494d0548 / 48.0a1 / 20160319030558 / files
nightly mac
9c5d494d0548 / 48.0a1 / 20160319030558 / files
nightly win32
9c5d494d0548 / 48.0a1 / 20160319030558 / files
nightly win64
9c5d494d0548 / 48.0a1 / 20160319030558 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
dom/media/tests/mochitest/ipc/ipc.json
dom/media/tests/mochitest/ipc/mochitest.ini
dom/media/tests/mochitest/ipc/test_ipc.html
dom/push/test/push-server.sjs
dom/workers/Navigator.cpp
dom/workers/Navigator.h
mobile/android/config/tooltool-manifests/b2gdroid/releng.manifest
testing/mozharness/configs/builds/releng_sub_android_configs/64_api_15_b2gdroid.py
testing/taskcluster/tasks/builds/android_api_15_b2gdroid.yml
testing/web-platform/meta/XMLHttpRequest/open-method-bogus.htm.ini
testing/web-platform/meta/html/semantics/embedded-content/the-img-element/srcset/parse-a-srcset-attribute.html.ini
new file mode 100644
--- /dev/null
+++ b/b2g/common.configure
@@ -0,0 +1,21 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Truetype fonts for B2G
+# ==============================================================
+option(env='MOZTTDIR', nargs=1, help='Path to truetype fonts for B2G')
+
+@depends('MOZTTDIR')
+def mozttdir(value):
+    if value:
+        path = value[0]
+        if not os.path.isdir(path):
+            error('MOZTTDIR "%s" is not a valid directory' % path)
+        set_config('MOZTTDIR', path)
+        set_define('PACKAGE_MOZTT', '1')
+
+
+include('../toolkit/moz.configure')
--- a/b2g/dev/moz.configure
+++ b/b2g/dev/moz.configure
@@ -1,7 +1,7 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-include('../../toolkit/moz.configure')
+include('../common/moz.configure')
--- a/b2g/graphene/moz.configure
+++ b/b2g/graphene/moz.configure
@@ -1,7 +1,7 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-include('../../toolkit/moz.configure')
+include('../common.configure')
--- a/b2g/moz.configure
+++ b/b2g/moz.configure
@@ -6,9 +6,9 @@
 
 option('--with-gonk', nargs=1, help='Path to the gonk base directory')
 
 @depends('--with-gonk')
 def gonkdir(value):
     return value[0] if value else ''
 
 
-include('../toolkit/moz.configure')
+include('common.configure')
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -188,16 +188,21 @@ extensions.registerSchemaAPI("windows", 
         if (updateInfo.focused) {
           Services.focus.activeWindow = window;
         }
 
         if (updateInfo.state !== null) {
           WindowManager.setState(window, updateInfo.state);
         }
 
+        if (updateInfo.drawAttention) {
+          // Bug 1257497 - Firefox can't cancel attention actions.
+          window.getAttention();
+        }
+
         // TODO: All the other properties, focused=false...
 
         return Promise.resolve(WindowManager.convert(extension, window));
       },
 
       remove: function(windowId) {
         let window = WindowManager.getWindow(windowId, context);
         window.close();
--- a/browser/components/extensions/schemas/windows.json
+++ b/browser/components/extensions/schemas/windows.json
@@ -404,17 +404,16 @@
                 "description": "The height to resize the window to in pixels. This value is ignored for panels."
               },
               "focused": {
                 "type": "boolean",
                 "optional": true,
                 "description": "If true, brings the window to the front. If false, brings the next window in the z-order to the front."
               },
               "drawAttention": {
-                "unsupported": true,
                 "type": "boolean",
                 "optional": true,
                 "description": "If true, causes the window to be displayed in a manner that draws the user's attention to the window, without changing the focused window. The effect lasts until the user changes focus to the window. This option has no effect if the window already has focus. Set to false to cancel a previous draw attention request."
               },
               "state": {
                 "$ref": "WindowState",
                 "optional": true,
                 "description": "The new state of the window. The 'minimized', 'maximized' and 'fullscreen' states cannot be combined with 'left', 'top', 'width' or 'height'."
--- a/browser/components/extensions/test/browser/browser_ext_windows_update.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_update.js
@@ -120,8 +120,31 @@ add_task(function* testWindowUpdate() {
 
     extension.sendMessage("checked-window");
   });
 
   yield extension.startup();
   yield extension.awaitFinish("window-update");
   yield extension.unload();
 });
+
+add_task(function* () {
+  let window2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background: function() {
+      browser.windows.getAll(undefined, function(wins) {
+        browser.test.assertEq(wins.length, 2, "should have two windows");
+
+        let unfocused = wins.find(win => !win.focused);
+        browser.windows.update(unfocused.id, {drawAttention: true}, function() {
+          browser.test.sendMessage("check");
+        });
+      });
+    },
+  });
+
+  yield Promise.all([extension.startup(), extension.awaitMessage("check")]);
+
+  yield extension.unload();
+
+  yield BrowserTestUtils.closeWindow(window2);
+});
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -250,21 +250,30 @@ def wanted_mozconfig_variables(help):
          'AUTOCONF',
          'AWK',
          'DISABLE_EXPORT_JS',
          'DISABLE_SHARED_JS',
          'DOXYGEN',
          'DSYMUTIL',
          'EXTERNAL_SOURCE_DIR',
          'GENISOIMAGE',
+         'L10NBASEDIR',
          'MOZILLABUILD',
          'MOZ_ARTIFACT_BUILDS',
          'MOZ_BUILD_APP',
+         'MOZ_CALLGRIND',
+         'MOZ_DMD',
          'MOZ_FMP4',
          'MOZ_INSTRUMENT_EVENT_LOOP',
+         'MOZ_INSTRUMENTS',
+         'MOZ_JPROF',
+         'MOZ_PROFILING',
+         'MOZ_USE_SYSTRACE',
+         'MOZ_VTUNE',
+         'MOZTTDIR',
          'PERL',
          'RPMBUILD',
          'TAR',
          'UNZIP',
          'USE_FC_FREETYPE',
          'WITHOUT_X',
          'XARGS',
          'ZIP',
@@ -412,35 +421,44 @@ def split_triplet(triplet):
         cpu=canonical_cpu,
         kernel=canonical_kernel,
         os=canonical_os,
         raw_cpu=cpu,
         raw_os=os,
     )
 
 
+@template
+@advanced
+def config_sub(shell, triplet):
+    import subprocess
+    config_sub = os.path.join(os.path.dirname(__file__), '..',
+                              'autoconf', 'config.sub')
+    return subprocess.check_output([shell, config_sub, triplet]).strip()
+
+
 @depends('--host', shell)
 @advanced
 def host(value, shell):
     if not value:
         import subprocess
         config_guess = os.path.join(os.path.dirname(__file__), '..',
                                     'autoconf', 'config.guess')
         host = subprocess.check_output([shell, config_guess]).strip()
     else:
         host = value[0]
 
-    return split_triplet(host)
+    return split_triplet(config_sub(shell, host))
 
 
-@depends('--target', host)
-def target(value, host):
+@depends('--target', host, shell)
+def target(value, host, shell):
     if not value:
         return host
-    return split_triplet(value[0])
+    return split_triplet(config_sub(shell, value[0]))
 
 
 @depends(host, target)
 def host_and_target_for_old_configure(host, target):
     # Autoconf needs these set
     add_old_configure_arg('--host=%s' % host.alias)
 
     target_alias = target.alias
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -173,31 +173,29 @@ def old_configure_options(*options):
     '--enable-android-apz',
     '--enable-android-omx',
     '--enable-android-resource-constrained',
     '--enable-approximate-location',
     '--enable-b2g-bt',
     '--enable-b2g-camera',
     '--enable-b2g-ril',
     '--enable-bundled-fonts',
-    '--enable-callgrind',
     '--enable-chrome-format',
     '--enable-clang-plugin',
     '--enable-content-sandbox',
     '--enable-cookies',
     '--enable-cpp-rtti',
     '--enable-crashreporter',
     '--enable-ctypes',
     '--enable-dbm',
     '--enable-dbus',
     '--enable-debug',
     '--enable-debug-js-modules',
     '--enable-debug-symbols',
     '--enable-directshow',
-    '--enable-dmd',
     '--enable-dtrace',
     '--enable-dump-painting',
     '--enable-elf-hack',
     '--enable-extensions',
     '--enable-faststripe',
     '--enable-feeds',
     '--enable-gamepad',
     '--enable-gc-trace',
@@ -205,23 +203,21 @@ def old_configure_options(*options):
     '--enable-gczeal',
     '--enable-gio',
     '--enable-gnomeui',
     '--enable-gold',
     '--enable-gps-debug',
     '--enable-hardware-aec-ns',
     '--enable-icf',
     '--enable-install-strip',
-    '--enable-instruments',
     '--enable-ion',
     '--enable-ios-target',
     '--enable-ipdl-tests',
     '--enable-jemalloc',
     '--enable-jitspew',
-    '--enable-jprof',
     '--enable-libjpeg-turbo',
     '--enable-libproxy',
     '--enable-llvm-hacks',
     '--enable-logrefcnt',
     '--enable-macos-target',
     '--enable-maintenance-service',
     '--enable-media-navigator',
     '--enable-memory-sanitizer',
@@ -241,17 +237,16 @@ def old_configure_options(*options):
     '--enable-perf',
     '--enable-permissions',
     '--enable-pie',
     '--enable-png-arm-neon-support',
     '--enable-posix-nspr-emulation',
     '--enable-pref-extensions',
     '--enable-printing',
     '--enable-profilelocking',
-    '--enable-profiling',
     '--enable-pulseaudio',
     '--enable-raw',
     '--enable-readline',
     '--enable-reflow-perf',
     '--enable-release',
     '--enable-replace-malloc',
     '--enable-require-all-d3dc-versions',
     '--enable-rust',
@@ -269,31 +264,29 @@ def old_configure_options(*options):
     '--enable-synth-pico',
     '--enable-synth-speechd',
     '--enable-system-cairo',
     '--enable-system-extension-dirs',
     '--enable-system-ffi',
     '--enable-system-hunspell',
     '--enable-system-pixman',
     '--enable-system-sqlite',
-    '--enable-systrace',
     '--enable-tasktracer',
     '--enable-tests',
     '--enable-thread-sanitizer',
     '--enable-trace-logging',
     '--enable-tree-freetype',
     '--enable-ui-locale',
     '--enable-universalchardet',
     '--enable-update-channel',
     '--enable-update-packaging',
     '--enable-updater',
     '--enable-url-classifier',
     '--enable-valgrind',
     '--enable-verify-mar',
-    '--enable-vtune',
     '--enable-warnings-as-errors',
     '--enable-webapp-runtime',
     '--enable-webrtc',
     '--enable-websms-backend',
     '--enable-webspeech',
     '--enable-webspeechtestbackend',
     '--enable-xul',
     '--enable-zipwriter',
@@ -330,17 +323,16 @@ def old_configure_options(*options):
     '--with-gonk-toolchain-prefix',
     '--with-google-api-keyfile',
     '--with-google-oauth-api-keyfile',
     '--with-gradle',
     '--with-intl-api',
     '--with-ios-sdk',
     '--with-java-bin-path',
     '--with-jitreport-granularity',
-    '--with-l10n-base',
     '--with-linux-headers',
     '--with-macbundlename-prefix',
     '--with-macos-private-frameworks',
     '--with-macos-sdk',
     '--with-mozilla-api-keyfile',
     '--with-nspr-prefix',
     '--with-nspr-cflags',
     '--with-nspr-libs',
--- a/docshell/test/navigation/mochitest.ini
+++ b/docshell/test/navigation/mochitest.ini
@@ -36,20 +36,20 @@ skip-if = buildapp == "mulet" || buildap
 [test_bug386782.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
 [test_bug430624.html]
 [test_bug430723.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android' #TIMED_OUT # b2g-debug(bug 874423) b2g-desktop(Bug 931116, b2g desktop specific, initial triage)
 [test_child.html]
 [test_grandchild.html]
 [test_not-opener.html]
-skip-if = buildapp == 'b2g' || (toolkit == 'android' && processor == 'x86') #x86 only
+skip-if = buildapp == 'b2g'
 [test_opener.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_popup-navigates-children.html]
 skip-if = buildapp == 'b2g' # b2g(Needs multiple window.open support, also uses docshelltreenode) b2g-debug(Needs multiple window.open support, also uses docshelltreenode) b2g-desktop(Needs multiple window.open support, also uses docshelltreenode)
 [test_reserved.html]
-skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || android_version == '10' || android_version == '18' || (e10s && debug && os == 'win') #too slow on Android 2.3 and 4.3 aws only; bug 1030403
+skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || (toolkit == 'android') || (e10s && debug && os == 'win') #too slow on Android 4.3 aws only; bug 1030403
 [test_sessionhistory.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android' #RANDOM # b2g-debug(Perma-orange on debug emulator builds) b2g-desktop(Bug 931116, b2g desktop specific, initial triage)
 [test_sibling-matching-parent.html]
 [test_sibling-off-domain.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -568,44 +568,54 @@ KeyframeEffectReadOnly::ComposeStyle(Ref
     aSetProperties.AddProperty(prop.mProperty);
 
     MOZ_ASSERT(prop.mSegments.Length() > 0,
                "property should not be in animations if it has no segments");
 
     // FIXME: Maybe cache the current segment?
     const AnimationPropertySegment *segment = prop.mSegments.Elements(),
                                 *segmentEnd = segment + prop.mSegments.Length();
-    while (segment->mToKey < computedTiming.mProgress.Value()) {
-      MOZ_ASSERT(segment->mFromKey < segment->mToKey, "incorrect keys");
+    while (segment->mToKey <= computedTiming.mProgress.Value()) {
+      MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys");
       if ((segment+1) == segmentEnd) {
         break;
       }
       ++segment;
       MOZ_ASSERT(segment->mFromKey == (segment-1)->mToKey, "incorrect keys");
     }
-    MOZ_ASSERT(segment->mFromKey < segment->mToKey, "incorrect keys");
+    MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys");
     MOZ_ASSERT(segment >= prop.mSegments.Elements() &&
                size_t(segment - prop.mSegments.Elements()) <
                  prop.mSegments.Length(),
                "out of array bounds");
 
     if (!aStyleRule) {
       // Allocate the style rule now that we know we have animation data.
       aStyleRule = new AnimValuesStyleRule();
     }
 
+    StyleAnimationValue* val = aStyleRule->AddEmptyValue(prop.mProperty);
+
+    // Special handling for zero-length segments
+    if (segment->mToKey == segment->mFromKey) {
+      if (computedTiming.mProgress.Value() < 0) {
+        *val = segment->mFromValue;
+      } else {
+        *val = segment->mToValue;
+      }
+      continue;
+    }
+
     double positionInSegment =
       (computedTiming.mProgress.Value() - segment->mFromKey) /
       (segment->mToKey - segment->mFromKey);
     double valuePosition =
       ComputedTimingFunction::GetPortion(segment->mTimingFunction,
                                          positionInSegment);
 
-    StyleAnimationValue *val = aStyleRule->AddEmptyValue(prop.mProperty);
-
 #ifdef DEBUG
     bool result =
 #endif
       StyleAnimationValue::Interpolate(prop.mProperty,
                                        segment->mFromValue,
                                        segment->mToValue,
                                        valuePosition, *val);
     MOZ_ASSERT(result, "interpolate must succeed now");
@@ -1784,16 +1794,103 @@ KeyframeEffectReadOnly::GetTarget(
       break;
 
     default:
       NS_NOTREACHED("Animation of unsupported pseudo-type");
       aRv.SetNull();
   }
 }
 
+static void
+CreatePropertyValue(nsCSSProperty aProperty,
+                    float aOffset,
+                    const Maybe<ComputedTimingFunction>& aTimingFunction,
+                    const StyleAnimationValue& aValue,
+                    AnimationPropertyValueDetails& aResult)
+{
+  aResult.mOffset = aOffset;
+
+  nsString stringValue;
+  StyleAnimationValue::UncomputeValue(aProperty, aValue, stringValue);
+  aResult.mValue = stringValue;
+
+  if (aTimingFunction) {
+    aResult.mEasing.Construct();
+    aTimingFunction->AppendToString(aResult.mEasing.Value());
+  } else {
+    aResult.mEasing.Construct(NS_LITERAL_STRING("linear"));
+  }
+
+  aResult.mComposite = CompositeOperation::Replace;
+}
+
+void
+KeyframeEffectReadOnly::GetProperties(
+    nsTArray<AnimationPropertyDetails>& aProperties,
+    ErrorResult& aRv) const
+{
+  for (const AnimationProperty& property : mProperties) {
+    AnimationPropertyDetails propertyDetails;
+    propertyDetails.mProperty =
+      NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(property.mProperty));
+    propertyDetails.mRunningOnCompositor = property.mIsRunningOnCompositor;
+
+    nsXPIDLString localizedString;
+    if (property.mPerformanceWarning &&
+        property.mPerformanceWarning->ToLocalizedString(localizedString)) {
+      propertyDetails.mWarning.Construct(localizedString);
+    }
+
+    if (!propertyDetails.mValues.SetCapacity(property.mSegments.Length(),
+                                             mozilla::fallible)) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+
+    for (size_t segmentIdx = 0, segmentLen = property.mSegments.Length();
+         segmentIdx < segmentLen;
+         segmentIdx++)
+    {
+      const AnimationPropertySegment& segment = property.mSegments[segmentIdx];
+
+      binding_detail::FastAnimationPropertyValueDetails fromValue;
+      CreatePropertyValue(property.mProperty, segment.mFromKey,
+                          segment.mTimingFunction, segment.mFromValue,
+                          fromValue);
+      // We don't apply timing functions for zero-length segments, so
+      // don't return one here.
+      if (segment.mFromKey == segment.mToKey) {
+        fromValue.mEasing.Reset();
+      }
+      // The following won't fail since we have already allocated the capacity
+      // above.
+      propertyDetails.mValues.AppendElement(fromValue, mozilla::fallible);
+
+      // Normally we can ignore the to-value for this segment since it is
+      // identical to the from-value from the next segment. However, we need
+      // to add it if either:
+      // a) this is the last segment, or
+      // b) the next segment's from-value differs.
+      if (segmentIdx == segmentLen - 1 ||
+          property.mSegments[segmentIdx + 1].mFromValue != segment.mToValue) {
+        binding_detail::FastAnimationPropertyValueDetails toValue;
+        CreatePropertyValue(property.mProperty, segment.mToKey,
+                            Nothing(), segment.mToValue, toValue);
+        // It doesn't really make sense to have a timing function on the
+        // last property value or before a sudden jump so we just drop the
+        // easing property altogether.
+        toValue.mEasing.Reset();
+        propertyDetails.mValues.AppendElement(toValue, mozilla::fallible);
+      }
+    }
+
+    aProperties.AppendElement(propertyDetails);
+  }
+}
+
 void
 KeyframeEffectReadOnly::GetFrames(JSContext*& aCx,
                                   nsTArray<JSObject*>& aResult,
                                   ErrorResult& aRv)
 {
   nsTArray<OrderedKeyframeValueEntry> entries;
 
   for (const AnimationProperty& property : mProperties) {
@@ -1882,42 +1979,16 @@ KeyframeEffectReadOnly::GetFrames(JSCont
       previousEntry = entry;
       entry = &entries[i];
     } while (entry->SameKeyframe(*previousEntry));
 
     aResult.AppendElement(keyframe);
   }
 }
 
-
-void
-KeyframeEffectReadOnly::GetPropertyState(
-    nsTArray<AnimationPropertyState>& aStates) const
-{
-  for (const AnimationProperty& property : mProperties) {
-    // Bug 1252730: We should also expose this winsInCascade as well.
-    if (!property.mWinsInCascade) {
-      continue;
-    }
-
-    AnimationPropertyState state;
-    state.mProperty.Construct(
-      NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(property.mProperty)));
-    state.mRunningOnCompositor.Construct(property.mIsRunningOnCompositor);
-
-    nsXPIDLString localizedString;
-    if (property.mPerformanceWarning &&
-        property.mPerformanceWarning->ToLocalizedString(localizedString)) {
-      state.mWarning.Construct(localizedString);
-    }
-
-    aStates.AppendElement(state);
-  }
-}
-
 /* static */ const TimeDuration
 KeyframeEffectReadOnly::OverflowRegionRefreshInterval()
 {
   // The amount of time we can wait between updating throttled animations
   // on the main thread that influence the overflow region.
   static const TimeDuration kOverflowRegionRefreshInterval =
     TimeDuration::FromMilliseconds(200);
 
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -41,17 +41,17 @@ enum class CSSPseudoElementType : uint8_
 
 namespace dom {
 class ElementOrCSSPseudoElement;
 class OwningElementOrCSSPseudoElement;
 class UnrestrictedDoubleOrKeyframeAnimationOptions;
 class UnrestrictedDoubleOrKeyframeEffectOptions;
 enum class IterationCompositeOperation : uint32_t;
 enum class CompositeOperation : uint32_t;
-struct AnimationPropertyState;
+struct AnimationPropertyDetails;
 }
 
 /**
  * Stores the results of calculating the timing properties of an animation
  * at a given sample time.
  */
 struct ComputedTiming
 {
@@ -204,16 +204,18 @@ public:
               JS::Handle<JSObject*> aFrames,
               const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
               ErrorResult& aRv);
 
   void GetTarget(Nullable<OwningElementOrCSSPseudoElement>& aRv) const;
   void GetFrames(JSContext*& aCx,
                  nsTArray<JSObject*>& aResult,
                  ErrorResult& aRv);
+  void GetProperties(nsTArray<AnimationPropertyDetails>& aProperties,
+                     ErrorResult& aRv) const;
 
   // Temporary workaround to return both the target element and pseudo-type
   // until we implement PseudoElement (bug 1174575).
   void GetTarget(Element*& aTarget,
                  CSSPseudoElementType& aPseudoType) const {
     aTarget = mTarget;
     aPseudoType = mPseudoType;
   }
@@ -299,18 +301,16 @@ public:
   // contained in |aSetProperties|.
   // Any updated properties are added to |aSetProperties|.
   void ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
                     nsCSSPropertySet& aSetProperties);
   // Returns true if at least one property is being animated on compositor.
   bool IsRunningOnCompositor() const;
   void SetIsRunningOnCompositor(nsCSSProperty aProperty, bool aIsRunning);
 
-  void GetPropertyState(nsTArray<AnimationPropertyState>& aStates) const;
-
   // Returns true if this effect, applied to |aFrame|, contains properties
   // that mean we shouldn't run transform compositor animations on this element.
   //
   // For example, if we have an animation of geometric properties like 'left'
   // and 'top' on an element, we force all 'transform' animations running at
   // the same time on the same element to run on the main thread.
   //
   // When returning true, |aPerformanceWarning| stores the reason why
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -2,13 +2,14 @@
 support-files =
   testcommon.js
   ../../imptests/testharness.js
   ../../imptests/testharnessreport.js
 [chrome/test_animate_xrays.html]
 # file_animate_xrays.html needs to go in mochitest.ini since it is served
 # over HTTP
 [chrome/test_animation_observers.html]
+[chrome/test_animation_properties.html]
 [chrome/test_animation_property_state.html]
 [chrome/test_generated_content_getAnimations.html]
 [chrome/test_restyles.html]
 [chrome/test_running_on_compositor.html]
 skip-if = buildapp == 'b2g'
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/chrome/test_animation_properties.html
@@ -0,0 +1,503 @@
+<!doctype html>
+<head>
+<meta charset=utf-8>
+<title>Bug 1254419 - Test the values returned by
+       KeyframeEffectReadOnly.getProperties()</title>
+<script type="application/javascript" src="../testharness.js"></script>
+<script type="application/javascript" src="../testharnessreport.js"></script>
+<script type="application/javascript" src="../testcommon.js"></script>
+</head>
+<body>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1254419"
+  target="_blank">Mozilla Bug 1254419</a>
+<div id="log"></div>
+<style>
+div {
+  font-size: 10px; /* For calculating em-based units */
+}
+</style>
+<script>
+'use strict';
+
+function assert_properties_equal(actual, expected) {
+  assert_equals(actual.length, expected.length);
+
+  var compareProperties = (a, b) =>
+    a.property == b.property ? 0 : (a.property < b.property ? -1 : 1);
+
+  var sortedActual   = actual.sort(compareProperties);
+  var sortedExpected = expected.sort(compareProperties);
+
+  // We want to serialize the values in the following form:
+  //
+  //  { offset: 0, easing: linear, composite: replace, value: 5px }, ...
+  //
+  // So that we can just compare strings and, in the failure case,
+  // easily see where the differences lie.
+  var serializeMember = value => {
+    return typeof value === 'undefined' ? '<not set>' : value;
+  }
+  var serializeValues = values =>
+    values.map(value =>
+      '{ ' +
+        [ 'offset', 'value', 'easing', 'composite' ].map(
+          member => `${member}: ${serializeMember(value[member])}`
+        ).join(', ') +
+      ' }')
+    .join(', ');
+
+  for (var i = 0; i < sortedActual.length; i++) {
+    assert_equals(sortedActual[i].property,
+                  sortedExpected[i].property,
+                  'CSS property name should match');
+    assert_equals(serializeValues(sortedActual[i].values),
+                  serializeValues(sortedExpected[i].values),
+                  `Values arrays do not match for `
+                  + `${sortedActual[i].property} property`);
+  }
+}
+
+// Shorthand for constructing a value object
+function value(offset, value, composite, easing) {
+  return { offset: offset, value: value, easing: easing, composite: composite };
+}
+
+var gTests = [
+
+  // ---------------------------------------------------------------------
+  //
+  // Tests for property-indexed specifications
+  //
+  // ---------------------------------------------------------------------
+
+  { desc:     'a one-property two-value property-indexed specification',
+    frames:   { left: ['10px', '20px'] },
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '20px', 'replace') ] } ]
+  },
+  { desc:     'a one-shorthand-property two-value property-indexed'
+              + ' specification',
+    frames:   { margin: ['10px', '10px 20px 30px 40px'] },
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '10px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '20px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '40px', 'replace') ] } ]
+  },
+  { desc:     'a two-property (one shorthand and one of its longhand'
+              + ' components) two-value property-indexed specification',
+    frames:   { marginTop: ['50px', '60px'],
+                margin: ['10px', '10px 20px 30px 40px'] },
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '50px', 'replace', 'linear'),
+                            value(1, '60px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '20px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '40px', 'replace') ] } ]
+  },
+  { desc:     'a two-property property-indexed specification with different'
+              + ' numbers of values',
+    frames:   { left: ['10px', '20px', '30px'],
+                top: ['40px', '50px'] },
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(0.5, '20px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] },
+                { property: 'top',
+                  values: [ value(0, '40px', 'replace', 'linear'),
+                            value(1, '50px', 'replace') ] } ]
+  },
+  { desc:     'a property-indexed specification with an invalid value',
+    frames:   { left: ['10px', '20px', '30px', '40px', '50px'],
+                top:  ['15px', '25px', 'invalid', '45px', '55px'] },
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(0.25, '20px', 'replace', 'linear'),
+                            value(0.5, '30px', 'replace', 'linear'),
+                            value(0.75, '40px', 'replace', 'linear'),
+                            value(1, '50px', 'replace') ] },
+                { property: 'top',
+                  values: [ value(0, '15px', 'replace', 'linear'),
+                            value(0.25, '25px', 'replace', 'linear'),
+                            value(0.75, '45px', 'replace', 'linear'),
+                            value(1, '55px', 'replace') ] } ]
+  },
+  { desc:     'a one-property two-value property-indexed specification that'
+              + ' needs to stringify its values',
+    frames:   { opacity: [0, 1] },
+    expected: [ { property: 'opacity',
+                  values: [ value(0, '0', 'replace', 'linear'),
+                            value(1, '1', 'replace') ] } ]
+  },
+  { desc:     'a property-indexed keyframe where a lesser shorthand precedes'
+              + ' a greater shorthand',
+    frames:   { borderLeft: [ '1px solid rgb(1, 2, 3)',
+                              '2px solid rgb(4, 5, 6)' ],
+                border:     [ '3px dotted rgb(7, 8, 9)',
+                              '4px dashed rgb(10, 11, 12)' ] },
+    expected: [ { property: 'border-bottom-color',
+                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                { property: 'border-left-color',
+                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
+                            value(1, 'rgb(4, 5, 6)', 'replace') ] },
+                { property: 'border-right-color',
+                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                { property: 'border-top-color',
+                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                { property: 'border-bottom-width',
+                  values: [ value(0, '3px', 'replace', 'linear'),
+                            value(1, '4px', 'replace') ] },
+                { property: 'border-left-width',
+                  values: [ value(0, '1px', 'replace', 'linear'),
+                            value(1, '2px', 'replace') ] },
+                { property: 'border-right-width',
+                  values: [ value(0, '3px', 'replace', 'linear'),
+                            value(1, '4px', 'replace') ] },
+                { property: 'border-top-width',
+                  values: [ value(0, '3px', 'replace', 'linear'),
+                            value(1, '4px', 'replace') ] } ]
+  },
+  { desc:     'a property-indexed keyframe where a greater shorthand precedes'
+              + ' a lesser shorthand',
+    frames:   { border:     [ '3px dotted rgb(7, 8, 9)',
+                              '4px dashed rgb(10, 11, 12)' ],
+                borderLeft: [ '1px solid rgb(1, 2, 3)',
+                              '2px solid rgb(4, 5, 6)' ] },
+    expected: [ { property: 'border-bottom-color',
+                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                { property: 'border-left-color',
+                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
+                            value(1, 'rgb(4, 5, 6)', 'replace') ] },
+                { property: 'border-right-color',
+                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                { property: 'border-top-color',
+                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                { property: 'border-bottom-width',
+                  values: [ value(0, '3px', 'replace', 'linear'),
+                            value(1, '4px', 'replace') ] },
+                { property: 'border-left-width',
+                  values: [ value(0, '1px', 'replace', 'linear'),
+                            value(1, '2px', 'replace') ] },
+                { property: 'border-right-width',
+                  values: [ value(0, '3px', 'replace', 'linear'),
+                            value(1, '4px', 'replace') ] },
+                { property: 'border-top-width',
+                  values: [ value(0, '3px', 'replace', 'linear'),
+                            value(1, '4px', 'replace') ] } ]
+  },
+
+  // ---------------------------------------------------------------------
+  //
+  // Tests for keyframe sequences
+  //
+  // ---------------------------------------------------------------------
+
+  { desc:     'a keyframe sequence specification with repeated values at'
+              + ' offset 0/1 with different easings',
+    frames:   [ { offset: 0.0, left: '100px', easing: 'ease' },
+                { offset: 0.0, left: '200px', easing: 'ease' },
+                { offset: 0.5, left: '300px', easing: 'linear' },
+                { offset: 1.0, left: '400px', easing: 'ease-out' },
+                { offset: 1.0, left: '500px', easing: 'step-end' } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '100px', 'replace'),
+                            value(0, '200px', 'replace', 'ease'),
+                            value(0.5, '300px', 'replace', 'linear'),
+                            value(1, '400px', 'replace'),
+                            value(1, '500px', 'replace') ] } ]
+  },
+  { desc:     'a one-property two-keyframe sequence',
+    frames:   [ { offset: 0, left: '10px' },
+                { offset: 1, left: '20px' } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '20px', 'replace') ] } ]
+  },
+  { desc:     'a two-property two-keyframe sequence',
+    frames:   [ { offset: 0, left: '10px', top: '30px' },
+                { offset: 1, left: '20px', top: '40px' } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '20px', 'replace') ] },
+                { property: 'top',
+                  values: [ value(0, '30px', 'replace', 'linear'),
+                            value(1, '40px', 'replace') ] } ]
+  },
+  { desc:     'a one shorthand property two-keyframe sequence',
+    frames:   [ { offset: 0, margin: '10px' },
+                { offset: 1, margin: '20px 30px 40px 50px' } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '20px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '40px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '50px', 'replace') ] } ]
+  },
+  { desc:     'a two-property (a shorthand and one of its component longhands)'
+              + ' two-keyframe sequence',
+    frames:   [ { offset: 0, margin: '10px', marginTop: '20px' },
+                { offset: 1, marginTop: '70px',
+                             margin: '30px 40px 50px 60px' } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '20px', 'replace', 'linear'),
+                            value(1, '70px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '40px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '50px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '60px', 'replace') ] } ]
+  },
+  { desc:     'a keyframe sequence with duplicate values for a given interior'
+              + ' offset',
+    frames:   [ { offset: 0.0, left: '10px' },
+                { offset: 0.5, left: '20px' },
+                { offset: 0.5, left: '30px' },
+                { offset: 0.5, left: '40px' },
+                { offset: 1.0, left: '50px' } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(0.5, '20px', 'replace'),
+                            value(0.5, '40px', 'replace', 'linear'),
+                            value(1, '50px', 'replace') ] } ]
+  },
+  { desc:     'a keyframe sequence with duplicate values for offsets 0 and 1',
+    frames:   [ { offset: 0, left: '10px' },
+                { offset: 0, left: '20px' },
+                { offset: 0, left: '30px' },
+                { offset: 1, left: '40px' },
+                { offset: 1, left: '50px' },
+                { offset: 1, left: '60px' } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace'),
+                            value(0, '30px', 'replace', 'linear'),
+                            value(1, '40px', 'replace'),
+                            value(1, '60px', 'replace') ] } ]
+  },
+  { desc:     'a two-property four-keyframe sequence',
+    frames:   [ { offset: 0, left: '10px' },
+                { offset: 0, top: '20px' },
+                { offset: 1, top: '30px' },
+                { offset: 1, left: '40px' } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '40px', 'replace') ] },
+                { property: 'top',
+                  values: [ value(0, '20px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] } ]
+  },
+  { desc:     'a one-property keyframe sequence with some omitted offsets',
+    frames:   [ { offset: 0.00, left: '10px' },
+                { offset: 0.25, left: '20px' },
+                { left: '30px' },
+                { left: '40px' },
+                { offset: 1.00, left: '50px' } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(0.25, '20px', 'replace', 'linear'),
+                            value(0.5, '30px', 'replace', 'linear'),
+                            value(0.75, '40px', 'replace', 'linear'),
+                            value(1, '50px', 'replace') ] } ]
+  },
+  { desc:     'a two-property keyframe sequence with some omitted offsets',
+    frames:   [ { offset: 0.00, left: '10px', top: '20px' },
+                { offset: 0.25, left: '30px' },
+                { left: '40px' },
+                { left: '50px', top: '60px' },
+                { offset: 1.00, left: '70px', top: '80px' } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(0.25, '30px', 'replace', 'linear'),
+                            value(0.5, '40px', 'replace', 'linear'),
+                            value(0.75, '50px', 'replace', 'linear'),
+                            value(1, '70px', 'replace') ] },
+                { property: 'top',
+                  values: [ value(0, '20px', 'replace', 'linear'),
+                            value(0.75, '60px', 'replace', 'linear'),
+                            value(1, '80px', 'replace') ] } ]
+  },
+  { desc:     'a one-property keyframe sequence with all omitted offsets',
+    frames:   [ { left: '10px' },
+                { left: '20px' },
+                { left: '30px' },
+                { left: '40px' },
+                { left: '50px' } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(0.25, '20px', 'replace', 'linear'),
+                            value(0.5, '30px', 'replace', 'linear'),
+                            value(0.75, '40px', 'replace', 'linear'),
+                            value(1, '50px', 'replace') ] } ]
+  },
+  { desc:     'a keyframe sequence with different easing values, but the'
+              + ' same easing value for a given offset',
+    frames:   [ { offset: 0.0, easing: 'ease',     left: '10px'},
+                { offset: 0.0, easing: 'ease',     top: '20px'},
+                { offset: 0.5, easing: 'linear',   left: '30px' },
+                { offset: 0.5, easing: 'linear',   top: '40px' },
+                { offset: 1.0, easing: 'step-end', left: '50px' },
+                { offset: 1.0, easing: 'step-end', top: '60px' } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'ease'),
+                            value(0.5, '30px', 'replace', 'linear'),
+                            value(1, '50px', 'replace') ] },
+                { property: 'top',
+                  values: [ value(0, '20px', 'replace', 'ease'),
+                            value(0.5, '40px', 'replace', 'linear'),
+                            value(1, '60px', 'replace') ] } ]
+  },
+  { desc:     'a one-property two-keyframe sequence that needs to'
+              + ' stringify its values',
+    frames:   [ { offset: 0, opacity: 0 },
+                { offset: 1, opacity: 1 } ],
+    expected: [ { property: 'opacity',
+                  values: [ value(0, '0', 'replace', 'linear'),
+                            value(1, '1', 'replace') ] } ]
+  },
+  { desc:     'a keyframe sequence where shorthand precedes longhand',
+    frames:   [ { offset: 0, margin: '10px', marginRight: '20px' },
+                { offset: 1, margin: '30px' } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '20px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] } ]
+  },
+  { desc:     'a keyframe sequence where longhand precedes shorthand',
+    frames:   [ { offset: 0, marginRight: '20px', margin: '10px' },
+                { offset: 1, margin: '30px' } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '20px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '30px', 'replace') ] } ]
+  },
+  { desc:     'a keyframe sequence where lesser shorthand precedes greater'
+              + ' shorthand',
+    frames:   [ { offset: 0, borderLeft: '1px solid rgb(1, 2, 3)',
+                             border: '2px dotted rgb(4, 5, 6)' },
+                { offset: 1, border: '3px dashed rgb(7, 8, 9)' } ],
+    expected: [ { property: 'border-bottom-color',
+                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                { property: 'border-left-color',
+                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
+                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                { property: 'border-right-color',
+                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                { property: 'border-top-color',
+                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                { property: 'border-bottom-width',
+                  values: [ value(0, '2px', 'replace', 'linear'),
+                            value(1, '3px', 'replace') ] },
+                { property: 'border-left-width',
+                  values: [ value(0, '1px', 'replace', 'linear'),
+                            value(1, '3px', 'replace') ] },
+                { property: 'border-right-width',
+                  values: [ value(0, '2px', 'replace', 'linear'),
+                            value(1, '3px', 'replace') ] },
+                { property: 'border-top-width',
+                  values: [ value(0, '2px', 'replace', 'linear'),
+                            value(1, '3px', 'replace') ] } ]
+  },
+  { desc:     'a keyframe sequence where greater shorthand precedes' +
+              + ' lesser shorthand',
+    frames:   [ { offset: 0, border: '2px dotted rgb(4, 5, 6)',
+                             borderLeft: '1px solid rgb(1, 2, 3)' },
+                { offset: 1, border: '3px dashed rgb(7, 8, 9)' } ],
+    expected: [ { property: 'border-bottom-color',
+                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                { property: 'border-left-color',
+                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
+                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                { property: 'border-right-color',
+                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                { property: 'border-top-color',
+                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                { property: 'border-bottom-width',
+                  values: [ value(0, '2px', 'replace', 'linear'),
+                            value(1, '3px', 'replace') ] },
+                { property: 'border-left-width',
+                  values: [ value(0, '1px', 'replace', 'linear'),
+                            value(1, '3px', 'replace') ] },
+                { property: 'border-right-width',
+                  values: [ value(0, '2px', 'replace', 'linear'),
+                            value(1, '3px', 'replace') ] },
+                { property: 'border-top-width',
+                  values: [ value(0, '2px', 'replace', 'linear'),
+                            value(1, '3px', 'replace') ] } ]
+  },
+
+  // ---------------------------------------------------------------------
+  //
+  // Tests for unit conversion
+  //
+  // ---------------------------------------------------------------------
+
+  { desc:     'em units are resolved to px values',
+    frames:   { left: ['10em', '20em'] },
+    expected: [ { property: 'left',
+                  values: [ value(0, '100px', 'replace', 'linear'),
+                            value(1, '200px', 'replace') ] } ]
+  }
+];
+
+gTests.forEach(function(subtest) {
+  test(function(t) {
+    var div = addDiv(t);
+    var animation = div.animate(subtest.frames, 1000);
+    assert_properties_equal(animation.effect.getProperties(),
+                            subtest.expected);
+  }, subtest.desc);
+});
+
+</script>
+</body>
--- a/dom/animation/test/chrome/test_animation_property_state.html
+++ b/dom/animation/test/chrome/test_animation_property_state.html
@@ -1,23 +1,27 @@
 <!doctype html>
 <head>
 <meta charset=utf-8>
-<title>Bug 1196114 - Animation property which indicates
-       running on the compositor or not</title>
+<title>Bug 1196114 - Test metadata related to which animation properties
+       are running on the compositor</title>
 <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>
 .compositable {
   /* Element needs geometry to be eligible for layerization */
   width: 100px;
   height: 100px;
   background-color: white;
 }
+@keyframes fade {
+  from { opacity: 1 }
+  to   { opacity: 0 }
+}
 </style>
 </head>
 <body>
 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1196114"
   target="_blank">Mozilla Bug 1196114</a>
 <div id="log"></div>
 <script>
 'use strict';
@@ -36,17 +40,17 @@ function compare_property_state(a, b) {
   }
   if (a.runningOnCompositor != b.runningOnCompositor) {
     return a.runningOnCompositor ? 1 : -1;
   }
   return a.warning > b.warning ? -1 : 1;
 }
 
 function assert_animation_property_state_equals(actual, expected) {
-  assert_equals(actual.length, expected.length);
+  assert_equals(actual.length, expected.length, 'Number of properties');
 
   var sortedActual = actual.sort(compare_property_state);
   var sortedExpected = expected.sort(compare_property_state);
 
   for (var i = 0; i < sortedActual.length; i++) {
     assert_equals(sortedActual[i].property,
                   sortedExpected[i].property,
                   'CSS property name should match');
@@ -210,17 +214,17 @@ var gAnimationsTests = [
 ];
 
 gAnimationsTests.forEach(function(subtest) {
   promise_test(function(t) {
     var div = addDiv(t, { class: 'compositable' });
     var animation = div.animate(subtest.frames, 100000);
     return animation.ready.then(t.step_func(function() {
       assert_animation_property_state_equals(
-        animation.effect.getPropertyState(),
+        animation.effect.getProperties(),
         subtest.expected);
     }));
   }, subtest.desc);
 });
 
 var gPerformanceWarningTests = [
   {
     desc: 'preserve-3d transform',
@@ -496,46 +500,46 @@ function start() {
     .createBundle("chrome://global/locale/layout_errors.properties");
 
   gAnimationsTests.forEach(function(subtest) {
     promise_test(function(t) {
       var div = addDiv(t, { class: 'compositable' });
       var animation = div.animate(subtest.frames, 100000);
       return animation.ready.then(t.step_func(function() {
         assert_animation_property_state_equals(
-          animation.effect.getPropertyState(),
+          animation.effect.getProperties(),
           subtest.expected);
       }));
     }, subtest.desc);
   });
 
   gPerformanceWarningTests.forEach(function(subtest) {
     // FIXME: Bug 1255710: 'preserve-3d' frame breaks other tests,
     // we should skip all 'preserve-3d' tests here.
     if (subtest.desc.includes('preserve-3d')) {
       return;
     }
     promise_test(function(t) {
       var div = addDiv(t, { class: 'compositable' });
       var animation = div.animate(subtest.frames, 100000);
       return animation.ready.then(t.step_func(function() {
         assert_property_state_on_compositor(
-          animation.effect.getPropertyState(),
+          animation.effect.getProperties(),
           subtest.expected);
         div.style = subtest.style;
         return waitForFrame();
       })).then(t.step_func(function() {
         assert_animation_property_state_equals(
-          animation.effect.getPropertyState(),
+          animation.effect.getProperties(),
           subtest.expected);
         div.style = '';
         return waitForFrame();
       })).then(t.step_func(function() {
         assert_property_state_on_compositor(
-          animation.effect.getPropertyState(),
+          animation.effect.getProperties(),
           subtest.expected);
       }));
     }, subtest.desc);
   });
 
   gMultipleAsyncAnimationsTests.forEach(function(subtest) {
     // FIXME: Bug 1255710: 'preserve-3d' frame breaks other tests,
     // we should skip all 'preserve-3d' tests here.
@@ -549,33 +553,33 @@ function start() {
 
         // Bind expected values to animation object.
         animation.expected = anim.expected;
         return animation;
       });
       return waitForAllAnimations(animations).then(t.step_func(function() {
         animations.forEach(t.step_func(function(anim) {
           assert_property_state_on_compositor(
-            anim.effect.getPropertyState(),
+            anim.effect.getProperties(),
             anim.expected);
         }));
         div.style = subtest.style;
         return waitForFrame();
       })).then(t.step_func(function() {
         animations.forEach(t.step_func(function(anim) {
           assert_animation_property_state_equals(
-            anim.effect.getPropertyState(),
+            anim.effect.getProperties(),
             anim.expected);
         }));
         div.style = '';
         return waitForFrame();
       })).then(t.step_func(function() {
         animations.forEach(t.step_func(function(anim) {
           assert_property_state_on_compositor(
-            anim.effect.getPropertyState(),
+            anim.effect.getProperties(),
             anim.expected);
         }));
       }));
     }, 'Multiple animations: ' + subtest.desc);
   });
 
   gMultipleAsyncAnimationsWithGeometricKeyframeTests.forEach(function(subtest) {
     promise_test(function(t) {
@@ -585,17 +589,17 @@ function start() {
 
         // Bind expected values to animation object.
         animation.expected = anim.expected;
         return animation;
       });
       return waitForAllAnimations(animations).then(t.step_func(function() {
         animations.forEach(t.step_func(function(anim) {
           assert_animation_property_state_equals(
-            anim.effect.getPropertyState(),
+            anim.effect.getProperties(),
             anim.expected);
         }));
       }));
     }, 'Multiple animations with geometric property: ' + subtest.desc);
   });
 
   gMultipleAsyncAnimationsWithGeometricAnimationTests.forEach(function(subtest) {
     promise_test(function(t) {
@@ -608,74 +612,74 @@ function start() {
         return animation;
       });
 
       var widthAnimation;
 
       return waitForAllAnimations(animations).then(t.step_func(function() {
         animations.forEach(t.step_func(function(anim) {
           assert_property_state_on_compositor(
-            anim.effect.getPropertyState(),
+            anim.effect.getProperties(),
             anim.expected);
         }));
       })).then(t.step_func(function() {
         // Append 'width' animation on the same element.
         widthAnimation = div.animate({ width: ['100px', '200px'] }, 100000);
         return waitForFrame();
       })).then(t.step_func(function() {
         // Now transform animations are not running on compositor because of
         // the 'width' animation.
         animations.forEach(t.step_func(function(anim) {
           assert_animation_property_state_equals(
-            anim.effect.getPropertyState(),
+            anim.effect.getProperties(),
             anim.expected);
         }));
         // Remove the 'width' animation.
         widthAnimation.cancel();
         return waitForFrame();
       })).then(t.step_func(function() {
         // Now all animations are running on compositor.
         animations.forEach(t.step_func(function(anim) {
           assert_property_state_on_compositor(
-            anim.effect.getPropertyState(),
+            anim.effect.getProperties(),
             anim.expected);
         }));
       }));
     }, 'Multiple async animations and geometric animation: ' + subtest.desc);
   });
 
   promise_test(function(t) {
     var div = addDiv(t, { class: 'compositable' });
     var animation = div.animate(
       { transform: ['translate(0px)', 'translate(100px)'] }, 100000);
     return animation.ready.then(t.step_func(function() {
       assert_animation_property_state_equals(
-        animation.effect.getPropertyState(),
+        animation.effect.getProperties(),
         [ { property: 'transform', runningOnCompositor: true } ]);
       div.style = 'width: 10000px; height: 10000px';
       return waitForFrame();
     })).then(t.step_func(function() {
       // viewport depends on test environment.
       var expectedWarning = new RegExp(
         "Async animation disabled because frame size \\(10000, 10000\\) is " +
         "bigger than the viewport \\(\\d+, \\d+\\) or the visual rectangle " +
         "\\(10000, 10000\\) is larger than the max allowed value \\(\\d+\\)");
       assert_animation_property_state_equals(
-        animation.effect.getPropertyState(),
+        animation.effect.getProperties(),
         [ {
           property: 'transform',
           runningOnCompositor: false,
           warning: expectedWarning
         } ]);
       div.style = 'width: 100px; height: 100px';
       return waitForFrame();
     })).then(t.step_func(function() {
       // FIXME: Bug 1253164: the animation should get back on compositor.
       assert_animation_property_state_equals(
-        animation.effect.getPropertyState(),
+        animation.effect.getProperties(),
         [ { property: 'transform', runningOnCompositor: false } ]);
     }));
   }, 'transform on too big element');
 
   promise_test(function(t) {
     var svg  = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
     svg.setAttribute('width', '100');
     svg.setAttribute('height', '100');
@@ -688,33 +692,48 @@ function start() {
     t.add_cleanup(function() {
       svg.remove();
     });
 
     var animation = svg.animate(
       { transform: ['translate(0px)', 'translate(100px)'] }, 100000);
     return animation.ready.then(t.step_func(function() {
       assert_animation_property_state_equals(
-        animation.effect.getPropertyState(),
+        animation.effect.getProperties(),
         [ { property: 'transform', runningOnCompositor: true } ]);
       svg.setAttribute('transform', 'translate(10, 20)');
       return waitForFrame();
     })).then(t.step_func(function() {
       assert_animation_property_state_equals(
-        animation.effect.getPropertyState(),
+        animation.effect.getProperties(),
         [ {
           property: 'transform',
           runningOnCompositor: false,
           warning: 'AnimationWarningTransformSVG'
         } ]);
       svg.removeAttribute('transform');
       return waitForFrame();
     })).then(t.step_func(function() {
       assert_animation_property_state_equals(
-        animation.effect.getPropertyState(),
+        animation.effect.getProperties(),
         [ { property: 'transform', runningOnCompositor: true } ]);
     }));
   }, 'transform of nsIFrame with SVG transform');
+
+  promise_test(function(t) {
+    var div = addDiv(t, { class: 'compositable',
+                          style: 'animation: fade 100s' });
+    var cssAnimation = div.getAnimations()[0];
+    var scriptAnimation = div.animate({ opacity: [ 1, 0 ] }, 1000);
+    return scriptAnimation.ready.then(function() {
+      assert_animation_property_state_equals(
+        cssAnimation.effect.getProperties(),
+        [ { property: 'opacity', runningOnCompositor: false } ]);
+      assert_animation_property_state_equals(
+        scriptAnimation.effect.getProperties(),
+        [ { property: 'opacity', runningOnCompositor: true } ]);
+    });
+  }, 'overridden animation');
 }
 
 </script>
 
 </body>
--- a/dom/apps/tests/mochitest.ini
+++ b/dom/apps/tests/mochitest.ini
@@ -56,19 +56,17 @@ skip-if = toolkit == "gonk" || e10s # se
 [test_install_dev_mode.html]
 [test_install_multiple_apps_origin.html]
 [test_install_receipts.html]
 [test_langpacks.html]
 skip-if = os == "android" || toolkit == "gonk" || e10s # embed-apps doesn't work in mochitest app
 [test_marketplace_pkg_install.html]
 skip-if = buildapp == "b2g" || toolkit == "android" # see bug 989806
 [test_packaged_app_install.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_packaged_app_update.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_receipt_operations.html]
 [test_signed_pkg_install.html]
 [test_uninstall_errors.html]
 [test_theme_role.html]
 [test_third_party_homescreen.html]
 skip-if = os == "android" || toolkit == "gonk" || e10s # embed-apps doesn't work in mochitest app
 [test_web_app_install.html]
 [test_widget.html]
--- a/dom/base/ResponsiveImageSelector.cpp
+++ b/dom/base/ResponsiveImageSelector.cpp
@@ -38,16 +38,76 @@ ParseInteger(const nsAString& aString, i
   aInt = nsContentUtils::ParseHTMLInteger(aString, &parseResult);
   return !(parseResult &
            ( nsContentUtils::eParseHTMLInteger_Error |
              nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput |
              nsContentUtils::eParseHTMLInteger_IsPercent |
              nsContentUtils::eParseHTMLInteger_NonStandard ));
 }
 
+static bool
+ParseFloat(const nsAString& aString, double& aDouble)
+{
+  // Check if it is a valid floating-point number first since the result of
+  // nsString.ToDouble() is more lenient than the spec,
+  // https://html.spec.whatwg.org/#valid-floating-point-number
+  nsAString::const_iterator iter, end;
+  aString.BeginReading(iter);
+  aString.EndReading(end);
+
+  if (iter == end) {
+    return false;
+  }
+
+  if (*iter == char16_t('-') && ++iter == end) {
+    return false;
+  }
+
+  if (nsCRT::IsAsciiDigit(*iter)) {
+    for (; iter != end && nsCRT::IsAsciiDigit(*iter) ; ++iter);
+  } else if (*iter == char16_t('.')) {
+    // Do nothing, jumps to fraction part
+  } else {
+    return false;
+  }
+
+  // Fraction
+  if (*iter == char16_t('.')) {
+    ++iter;
+    if (iter == end || !nsCRT::IsAsciiDigit(*iter)) {
+      // U+002E FULL STOP character (.) must be followed by one or more ASCII digits
+      return false;
+    }
+
+    for (; iter != end && nsCRT::IsAsciiDigit(*iter) ; ++iter);
+  }
+
+  if (iter != end && (*iter == char16_t('e') || *iter == char16_t('E'))) {
+    ++iter;
+    if (*iter == char16_t('-') || *iter == char16_t('+')) {
+      ++iter;
+    }
+
+    if (iter == end || !nsCRT::IsAsciiDigit(*iter)) {
+      // Should have one or more ASCII digits
+      return false;
+    }
+
+    for (; iter != end && nsCRT::IsAsciiDigit(*iter) ; ++iter);
+  }
+
+  if (iter != end) {
+    return false;
+  }
+
+  nsresult rv;
+  aDouble = PromiseFlatString(aString).ToDouble(&rv);
+  return NS_SUCCEEDED(rv);
+}
+
 ResponsiveImageSelector::ResponsiveImageSelector(nsIContent *aContent)
   : mOwnerNode(aContent),
     mSelectedCandidateIndex(-1)
 {
 }
 
 ResponsiveImageSelector::ResponsiveImageSelector(nsIDocument *aDocument)
   : mOwnerNode(aDocument),
@@ -528,19 +588,18 @@ ResponsiveImageDescriptors::AddDescripto
         mInvalid = true;
       }
 
       return;
     }
   } else if (*descType == char16_t('x')) {
     // If the value is not a valid floating point number, it doesn't match this
     // descriptor, fall through.
-    nsresult rv;
-    double possibleDensity = PromiseFlatString(valueStr).ToDouble(&rv);
-    if (NS_SUCCEEDED(rv)) {
+    double possibleDensity = 0.0;
+    if (ParseFloat(valueStr, possibleDensity)) {
       if (possibleDensity >= 0.0 &&
           mWidth.isNothing() &&
           mDensity.isNothing() &&
           mFutureCompatHeight.isNothing()) {
         mDensity.emplace(possibleDensity);
       } else {
         // Valid density descriptor, but height or width or density were already
         // seen, or it parsed to less than zero, which is an error per spec
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -827,37 +827,23 @@ danger::AutoCxPusher::IsStackTop() const
   return currentDepth == mStackDepthAfterPush;
 }
 
 } // namespace dom
 
 AutoJSContext::AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
   : mCx(nullptr)
 {
-  Init(false MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT);
-}
-
-AutoJSContext::AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
-  : mCx(nullptr)
-{
-  Init(aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT);
-}
-
-void
-AutoJSContext::Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
-{
   JS::AutoSuppressGCAnalysis nogc;
   MOZ_ASSERT(!mCx, "mCx should not be initialized!");
 
   MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 
   nsXPConnect *xpc = nsXPConnect::XPConnect();
-  if (!aSafe) {
-    mCx = xpc->GetCurrentJSContext();
-  }
+  mCx = xpc->GetCurrentJSContext();
 
   if (!mCx) {
     mJSAPI.Init();
     mCx = mJSAPI.cx();
   }
 }
 
 AutoJSContext::operator JSContext*() const
@@ -883,14 +869,22 @@ ThreadsafeAutoJSContext::operator JSCont
   if (mCx) {
     return mCx;
   } else {
     return *mAutoJSContext;
   }
 }
 
 AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
-  : AutoJSContext(true MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
-  , mAc(mCx, xpc::UnprivilegedJunkScope())
+  : AutoJSAPI()
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
+  DebugOnly<bool> ok = Init(xpc::UnprivilegedJunkScope());
+  MOZ_ASSERT(ok,
+             "This is quite odd.  We should have crashed in the "
+             "xpc::NativeGlobal() call if xpc::UnprivilegedJunkScope() "
+             "returned null, and inited correctly otherwise!");
 }
 
 } // namespace mozilla
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -422,23 +422,16 @@ private:
  * appropriate JS context and release it when leaving the stack.
  */
 class MOZ_RAII AutoJSContext {
 public:
   explicit AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
   operator JSContext*() const;
 
 protected:
-  explicit AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
-
-  // We need this Init() method because we can't use delegating constructor for
-  // the moment. It is a C++11 feature and we do not require C++11 to be
-  // supported to be able to compile Gecko.
-  void Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
-
   JSContext* mCx;
   dom::AutoJSAPI mJSAPI;
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 /**
  * Use ThreadsafeAutoJSContext when you want an AutoJSContext but might be
  * running on a worker thread.
@@ -456,18 +449,23 @@ private:
 };
 
 /**
  * AutoSafeJSContext is similar to AutoJSContext but will only return the safe
  * JS context. That means it will never call nsContentUtils::GetCurrentJSContext().
  *
  * Note - This is deprecated. Please use AutoJSAPI instead.
  */
-class MOZ_RAII AutoSafeJSContext : public AutoJSContext {
+class MOZ_RAII AutoSafeJSContext : public dom::AutoJSAPI {
 public:
   explicit AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
+  operator JSContext*() const
+  {
+    return cx();
+  }
+
 private:
-  JSAutoCompartment mAc;
+  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 } // namespace mozilla
 
 #endif // mozilla_dom_ScriptSettings_h
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2719,16 +2719,20 @@ public:
   }
 
   bool UserHasInteracted()
   {
     return mUserHasInteracted;
   }
 
   void ReportHasScrollLinkedEffect();
+  bool HasScrollLinkedEffect() const
+  {
+    return mHasScrollLinkedEffect;
+  }
 
 protected:
   bool GetUseCounter(mozilla::UseCounter aUseCounter)
   {
     return mUseCounters[aUseCounter];
   }
 
   void SetChildDocumentUseCounter(mozilla::UseCounter aUseCounter)
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -1408,30 +1408,39 @@ nsScriptLoader::OnStreamComplete(nsIIncr
                                  nsresult aSRIStatus,
                                  mozilla::Vector<char16_t> &aString,
                                  mozilla::dom::SRICheckDataVerifier* aSRIDataVerifier)
 {
   nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
   NS_ASSERTION(request, "null request in stream complete handler");
   NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
 
+  nsCOMPtr<nsIRequest> channelRequest;
+  aLoader->GetRequest(getter_AddRefs(channelRequest));
+  nsCOMPtr<nsIChannel> channel;
+  channel = do_QueryInterface(channelRequest);
+
   nsresult rv = NS_OK;
   if (!request->mIntegrity.IsEmpty() &&
       NS_SUCCEEDED((rv = aSRIStatus))) {
     MOZ_ASSERT(aSRIDataVerifier);
-
-    nsCOMPtr<nsIRequest> channelRequest;
-    aLoader->GetRequest(getter_AddRefs(channelRequest));
-    nsCOMPtr<nsIChannel> channel;
-    channel = do_QueryInterface(channelRequest);
-
     if (NS_FAILED(aSRIDataVerifier->Verify(request->mIntegrity, channel,
                                            request->mCORSMode, mDocument))) {
       rv = NS_ERROR_SRI_CORRUPT;
     }
+  } else {
+    nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
+
+    bool enforceSRI = false;
+    loadInfo->GetEnforceSRI(&enforceSRI);
+    if (enforceSRI) {
+      MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+             ("nsScriptLoader::OnStreamComplete, required SRI not found"));
+      rv = NS_ERROR_SRI_CORRUPT;
+    }
   }
 
   if (NS_SUCCEEDED(rv)) {
     rv = PrepareLoadedRequest(request, aLoader, aChannelStatus, aString);
   }
 
   if (NS_FAILED(rv)) {
     /*
--- a/dom/base/nsXMLHttpRequest.cpp
+++ b/dom/base/nsXMLHttpRequest.cpp
@@ -1484,17 +1484,19 @@ nsXMLHttpRequest::Open(const nsACString&
   return Open(method, url, async, realUser, realPassword);
 }
 
 nsresult
 nsXMLHttpRequest::Open(const nsACString& inMethod, const nsACString& url,
                        bool async, const Optional<nsAString>& user,
                        const Optional<nsAString>& password)
 {
-  NS_ENSURE_ARG(!inMethod.IsEmpty());
+  if (inMethod.IsEmpty()) {
+    return NS_ERROR_DOM_SYNTAX_ERR;
+  }
 
   if (!async && !DontWarnAboutSyncXHR() && GetOwner() &&
       GetOwner()->GetExtantDoc()) {
     GetOwner()->GetExtantDoc()->WarnOnceAbout(nsIDocument::eSyncXMLHttpRequest);
   }
 
   Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC,
                         async ? 0 : 1);
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -762,17 +762,16 @@ skip-if = buildapp == 'b2g' || toolkit =
 skip-if = buildapp == 'b2g' || toolkit == 'android' #bug 775227
 [test_getElementById.html]
 [test_html_colors_quirks.html]
 [test_html_colors_standards.html]
 [test_html_in_xhr.html]
 [test_htmlcopyencoder.html]
 [test_htmlcopyencoder.xhtml]
 [test_ipc_messagemanager_blob.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 936226
 [test_meta_viewport0.html]
 skip-if = (os != 'b2g' && os != 'android')    # meta-viewport tag support is mobile-only
 [test_meta_viewport1.html]
 skip-if = (os != 'b2g' && os != 'android')    # meta-viewport tag support is mobile-only
 [test_meta_viewport2.html]
 skip-if = (os != 'b2g' && os != 'android')    # meta-viewport tag support is mobile-only
 [test_meta_viewport3.html]
 skip-if = (os != 'b2g' && os != 'android')    # meta-viewport tag support is mobile-only
--- a/dom/bindings/CallbackFunction.h
+++ b/dom/bindings/CallbackFunction.h
@@ -27,16 +27,24 @@ class CallbackFunction : public Callback
 public:
   // See CallbackObject for an explanation of the arguments.
   explicit CallbackFunction(JSContext* aCx, JS::Handle<JSObject*> aCallable,
                             nsIGlobalObject* aIncumbentGlobal)
     : CallbackObject(aCx, aCallable, aIncumbentGlobal)
   {
   }
 
+  // See CallbackObject for an explanation of the arguments.
+  explicit CallbackFunction(JS::Handle<JSObject*> aCallable,
+                            JS::Handle<JSObject*> aAsyncStack,
+                            nsIGlobalObject* aIncumbentGlobal)
+    : CallbackObject(aCallable, aAsyncStack, aIncumbentGlobal)
+  {
+  }
+
   JS::Handle<JSObject*> Callable() const
   {
     return Callback();
   }
 
   bool HasGrayCallable() const
   {
     // Play it safe in case this gets called after unlink.
--- a/dom/bindings/CallbackInterface.h
+++ b/dom/bindings/CallbackInterface.h
@@ -26,16 +26,24 @@ class CallbackInterface : public Callbac
 public:
   // See CallbackObject for an explanation of the arguments.
   explicit CallbackInterface(JSContext* aCx, JS::Handle<JSObject*> aCallback,
                              nsIGlobalObject *aIncumbentGlobal)
     : CallbackObject(aCx, aCallback, aIncumbentGlobal)
   {
   }
 
+  // See CallbackObject for an explanation of the arguments.
+  explicit CallbackInterface(JS::Handle<JSObject*> aCallback,
+                             JS::Handle<JSObject*> aAsyncStack,
+                             nsIGlobalObject* aIncumbentGlobal)
+    : CallbackObject(aCallback, aAsyncStack, aIncumbentGlobal)
+  {
+  }
+
 protected:
   bool GetCallableProperty(JSContext* cx, JS::Handle<jsid> aPropId,
                            JS::MutableHandle<JS::Value> aCallable);
 
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -62,16 +62,26 @@ public:
         JS_ClearPendingException(aCx);
       }
       Init(aCallback, stack, aIncumbentGlobal);
     } else {
       Init(aCallback, nullptr, aIncumbentGlobal);
     }
   }
 
+  // Instead of capturing the current stack to use as an async parent when the
+  // callback is invoked, the caller can use this overload to pass in a stack
+  // for that purpose.
+  explicit CallbackObject(JS::Handle<JSObject*> aCallback,
+                          JS::Handle<JSObject*> aAsyncStack,
+                          nsIGlobalObject *aIncumbentGlobal)
+  {
+    Init(aCallback, aAsyncStack, aIncumbentGlobal);
+  }
+
   JS::Handle<JSObject*> Callback() const
   {
     JS::ExposeObjectToActiveJS(mCallback);
     return CallbackPreserveColor();
   }
 
   JSObject* GetCreationStack() const
   {
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -14682,27 +14682,39 @@ class CGCallback(CGClass):
     def getConstructors(self):
         if (not self.idlObject.isInterface() and
             not self.idlObject._treatNonObjectAsNull):
             body = "MOZ_ASSERT(JS::IsCallable(mCallback));\n"
         else:
             # Not much we can assert about it, other than not being null, and
             # CallbackObject does that already.
             body = ""
-        return [ClassConstructor(
-            [Argument("JSContext*", "aCx"),
-             Argument("JS::Handle<JSObject*>", "aCallback"),
-             Argument("nsIGlobalObject*", "aIncumbentGlobal")],
-            bodyInHeader=True,
-            visibility="public",
-            explicit=True,
-            baseConstructors=[
-                "%s(aCx, aCallback, aIncumbentGlobal)" % self.baseName,
-            ],
-            body=body)]
+        return [
+            ClassConstructor(
+                [Argument("JSContext*", "aCx"),
+                 Argument("JS::Handle<JSObject*>", "aCallback"),
+                 Argument("nsIGlobalObject*", "aIncumbentGlobal")],
+                bodyInHeader=True,
+                visibility="public",
+                explicit=True,
+                baseConstructors=[
+                    "%s(aCx, aCallback, aIncumbentGlobal)" % self.baseName,
+                ],
+                body=body),
+            ClassConstructor(
+                [Argument("JS::Handle<JSObject*>", "aCallback"),
+                 Argument("JS::Handle<JSObject*>", "aAsyncStack"),
+                 Argument("nsIGlobalObject*", "aIncumbentGlobal")],
+                bodyInHeader=True,
+                visibility="public",
+                explicit=True,
+                baseConstructors=[
+                    "%s(aCallback, aAsyncStack, aIncumbentGlobal)" % self.baseName,
+                ],
+                body=body)]
 
     def getMethodImpls(self, method):
         assert method.needThisHandling
         args = list(method.args)
         # Strip out the JSContext*/JSObject* args
         # that got added.
         assert args[0].name == "cx" and args[0].argType == "JSContext*"
         assert args[1].name == "aThisVal" and args[1].argType == "JS::Handle<JS::Value>"
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -199,17 +199,16 @@ disabled = bug 1022281
 [test_browserElement_inproc_ExecuteScript.html]
 [test_browserElement_inproc_ExposableURI.html]
 [test_browserElement_inproc_Find.html]
 [test_browserElement_inproc_FirstPaint.html]
 [test_browserElement_inproc_ForwardName.html]
 [test_browserElement_inproc_FrameWrongURI.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_GetScreenshot.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_browserElement_inproc_GetScreenshotDppx.html]
 [test_browserElement_inproc_Iconchange.html]
 [test_browserElement_inproc_LoadEvents.html]
 [test_browserElement_inproc_Manifestchange.html]
 [test_browserElement_inproc_Metachange.html]
 [test_browserElement_inproc_NextPaint.html]
 [test_browserElement_inproc_NoAudioTrack.html]
 tags = audiochannel
@@ -253,23 +252,22 @@ skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_Titlechange.html]
 [test_browserElement_inproc_TopBarrier.html]
 [test_browserElement_inproc_VisibilityChange.html]
 [test_browserElement_inproc_XFrameOptions.html]
 [test_browserElement_inproc_XFrameOptionsAllowFrom.html]
 [test_browserElement_inproc_XFrameOptionsDeny.html]
 [test_browserElement_inproc_XFrameOptionsSameOrigin.html]
 [test_browserElement_oop_NextPaint.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 936226
 # Disabled due to https://bugzilla.mozilla.org/show_bug.cgi?id=774100
 [test_browserElement_inproc_Reload.html]
 disabled = bug 774100
 [test_browserElement_inproc_GetContentDimensions.html]
 [test_browserElement_inproc_AudioChannel.html]
 tags = audiochannel
 [test_browserElement_inproc_AudioChannel_nested.html]
 tags = audiochannel
 [test_browserElement_inproc_SetNFCFocus.html]
 [test_browserElement_inproc_getStructuredData.html]
 [test_browserElement_inproc_OpenWindowEmpty.html]
 skip-if = (toolkit == 'gonk') # Test doesn't work on B2G emulator
 [test_browserElement_inproc_ActiveStateChangeOnChangingMutedOrVolume.html]
-tags = audiochannel
\ No newline at end of file
+tags = audiochannel
--- a/dom/cache/moz.build
+++ b/dom/cache/moz.build
@@ -88,19 +88,15 @@ LOCAL_INCLUDES += [
 ]
 
 FINAL_LIBRARY = 'xul'
 
 MOCHITEST_MANIFESTS += [
     'test/mochitest/mochitest.ini',
 ]
 
-MOCHITEST_CHROME_MANIFESTS += [
-    'test/mochitest/chrome.ini',
-]
-
 BROWSER_CHROME_MANIFESTS += [
     'test/mochitest/browser.ini',
 ]
 
 XPCSHELL_TESTS_MANIFESTS += [
     'test/xpcshell/xpcshell.ini',
 ]
--- a/dom/cache/test/mochitest/chrome.ini
+++ b/dom/cache/test/mochitest/chrome.ini
@@ -1,1 +0,0 @@
-[test_chrome_constructor.html]
--- a/dom/cache/test/mochitest/mochitest.ini
+++ b/dom/cache/test/mochitest/mochitest.ini
@@ -42,8 +42,9 @@ skip-if = e10s && debug && os == 'win'
   skip-if = buildapp == 'b2g' # bug 1162353
 [test_cache_restart.html]
 [test_cache_shrink.html]
 [test_cache_clear_on_app_uninstall.html]
   skip-if = buildapp != 'mulet' || e10s # bug 1178685
 [test_cache_orphaned_cache.html]
 [test_cache_orphaned_body.html]
 [test_cache_untrusted.html]
+[test_chrome_constructor.html]
--- a/dom/cache/test/mochitest/test_chrome_constructor.html
+++ b/dom/cache/test/mochitest/test_chrome_constructor.html
@@ -1,32 +1,28 @@
 <!-- Any copyright is dedicated to the Public Domain.
    - http://creativecommons.org/publicdomain/zero/1.0/ -->
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Validate Interfaces Exposed to Workers</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <script class="testbody" type="text/javascript">
-  var Cu = Components.utils;
-  Cu.import('resource://gre/modules/Services.jsm');
   SimpleTest.waitForExplicitFinish();
 
   SpecialPowers.pushPrefEnv({
     "set": [["dom.caches.enabled", true],
             ["dom.caches.testing.enabled", true]],
   }, function() {
     // attach to a different origin's CacheStorage
     var url = 'http://example.com/';
-    var uri = Services.io.newURI(url, null, null);
-    var principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
-    var storage = new CacheStorage('content', principal);
+    var storage = SpecialPowers.createChromeCache('content', url);
 
     // verify we can use the other origin's CacheStorage as normal
     var req = new Request('http://example.com/index.html');
     var res = new Response('hello world');
     var cache;
     storage.open('foo').then(function(c) {
       cache = c;
       ok(cache, 'storage should create cache');
--- a/dom/canvas/CanvasPath.h
+++ b/dom/canvas/CanvasPath.h
@@ -46,16 +46,19 @@ public:
                      double cp2x, double cp2y,
                      double x, double y);
   void ArcTo(double x1, double y1, double x2, double y2, double radius,
              ErrorResult& error);
   void Rect(double x, double y, double w, double h);
   void Arc(double x, double y, double radius,
            double startAngle, double endAngle, bool anticlockwise,
            ErrorResult& error);
+  void Ellipse(double x, double y, double radiusX, double radiusY,
+               double rotation, double startAngle, double endAngle,
+               bool anticlockwise, ErrorResult& error);
 
   void LineTo(const gfx::Point& aPoint);
   void BezierTo(const gfx::Point& aCP1,
                 const gfx::Point& aCP2,
                 const gfx::Point& aCP3);
 
   already_AddRefed<gfx::Path> GetPath(const CanvasWindingRule& aWinding,
                                   const gfx::DrawTarget* aTarget) const;
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3041,16 +3041,32 @@ CanvasRenderingContext2D::Rect(double aX
     mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(aX + aW, aY));
     mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(aX + aW, aY + aH));
     mDSPathBuilder->LineTo(mTarget->GetTransform() * Point(aX, aY + aH));
     mDSPathBuilder->Close();
   }
 }
 
 void
+CanvasRenderingContext2D::Ellipse(double aX, double aY, double aRadiusX, double aRadiusY,
+                                  double aRotation, double aStartAngle, double aEndAngle,
+                                  bool aAnticlockwise, ErrorResult& aError)
+{
+  if (aRadiusX < 0.0 || aRadiusY < 0.0) {
+    aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+    return;
+  }
+
+  EnsureWritablePath();
+
+  ArcToBezier(this, Point(aX, aY), Size(aRadiusX, aRadiusY), aStartAngle, aEndAngle,
+              aAnticlockwise, aRotation);
+}
+
+void
 CanvasRenderingContext2D::EnsureWritablePath()
 {
   EnsureTarget();
 
   if (mDSPathBuilder) {
     return;
   }
 
@@ -5937,16 +5953,32 @@ CanvasPath::Arc(double aX, double aY, do
   }
 
   EnsurePathBuilder();
 
   ArcToBezier(this, Point(aX, aY), Size(aRadius, aRadius), aStartAngle, aEndAngle, aAnticlockwise);
 }
 
 void
+CanvasPath::Ellipse(double x, double y, double radiusX, double radiusY,
+                    double rotation, double startAngle, double endAngle,
+                    bool anticlockwise, ErrorResult& error)
+{
+  if (radiusX < 0.0 || radiusY < 0.0) {
+    error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+    return;
+  }
+
+  EnsurePathBuilder();
+
+  ArcToBezier(this, Point(x, y), Size(radiusX, radiusY), startAngle, endAngle,
+              anticlockwise, rotation);
+}
+
+void
 CanvasPath::LineTo(const gfx::Point& aPoint)
 {
   EnsurePathBuilder();
 
   mPathBuilder->LineTo(aPoint);
 }
 
 void
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -341,16 +341,19 @@ public:
              mozilla::gfx::Point(ToFloat(aX), ToFloat(aY)));
   }
 
   void ArcTo(double aX1, double aY1, double aX2, double aY2, double aRadius,
              mozilla::ErrorResult& aError);
   void Rect(double aX, double aY, double aW, double aH);
   void Arc(double aX, double aY, double aRadius, double aStartAngle,
            double aEndAngle, bool aAnticlockwise, mozilla::ErrorResult& aError);
+  void Ellipse(double aX, double aY, double aRadiusX, double aRadiusY,
+               double aRotation, double aStartAngle, double aEndAngle,
+               bool aAnticlockwise, ErrorResult& aError);
 
   void GetMozCurrentTransform(JSContext* aCx,
                               JS::MutableHandle<JSObject*> aResult,
                               mozilla::ErrorResult& aError) const;
   void SetMozCurrentTransform(JSContext* aCx,
                               JS::Handle<JSObject*> aCurrentTransform,
                               mozilla::ErrorResult& aError);
   void GetMozCurrentTransformInverse(JSContext* aCx,
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -42,16 +42,18 @@ public:
     ~ScopedResolveTexturesForDraw();
 };
 
 ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(WebGLContext* webgl,
                                                            const char* funcName,
                                                            bool* const out_error)
     : mWebGL(webgl)
 {
+    MOZ_ASSERT(webgl->gl->IsCurrent());
+
     typedef decltype(WebGLContext::mBound2DTextures) TexturesT;
 
     const auto fnResolveAll = [this, funcName](const TexturesT& textures)
     {
         const auto len = textures.Length();
         for (uint32_t texUnit = 0; texUnit < len; ++texUnit) {
             WebGLTexture* tex = textures[texUnit];
             if (!tex)
@@ -215,17 +217,17 @@ WebGLContext::DrawArrays_check(GLint fir
         return false;
     }
 
     if (uint32_t(primcount) > mMaxFetchedInstances) {
         ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info);
         return false;
     }
 
-    MakeContextCurrent();
+    MOZ_ASSERT(gl->IsCurrent());
 
     if (mBoundDrawFramebuffer) {
         if (!mBoundDrawFramebuffer->ValidateAndInitAttachments(info))
             return false;
     } else {
         ClearBackbufferIfNeeded();
     }
 
@@ -241,16 +243,18 @@ WebGLContext::DrawArrays(GLenum mode, GL
 {
     const char funcName[] = "drawArrays";
     if (IsContextLost())
         return;
 
     if (!ValidateDrawModeEnum(mode, funcName))
         return;
 
+    MakeContextCurrent();
+
     bool error;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
     if (!DrawArrays_check(first, count, 1, funcName))
         return;
 
@@ -269,16 +273,18 @@ WebGLContext::DrawArraysInstanced(GLenum
 {
     const char funcName[] = "drawArraysInstanced";
     if (IsContextLost())
         return;
 
     if (!ValidateDrawModeEnum(mode, funcName))
         return;
 
+    MakeContextCurrent();
+
     bool error;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
     if (!DrawArrays_check(first, count, primcount, funcName))
         return;
 
@@ -404,17 +410,17 @@ WebGLContext::DrawElements_check(GLsizei
     // Bug 1008310 - Check if buffer has been used with a different previous type
     if (elemArrayBuffer.IsElementArrayUsedWithMultipleTypes()) {
         GenerateWarning("%s: bound element array buffer previously used with a type other than "
                         "%s, this will affect performance.",
                         info,
                         WebGLContext::EnumName(type));
     }
 
-    MakeContextCurrent();
+    MOZ_ASSERT(gl->IsCurrent());
 
     if (mBoundDrawFramebuffer) {
         if (!mBoundDrawFramebuffer->ValidateAndInitAttachments(info))
             return false;
     } else {
         ClearBackbufferIfNeeded();
     }
 
@@ -431,16 +437,18 @@ WebGLContext::DrawElements(GLenum mode, 
 {
     const char funcName[] = "drawElements";
     if (IsContextLost())
         return;
 
     if (!ValidateDrawModeEnum(mode, funcName))
         return;
 
+    MakeContextCurrent();
+
     bool error;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
     GLuint upperBound = 0;
     if (!DrawElements_check(count, type, byteOffset, 1, funcName, &upperBound))
         return;
@@ -468,16 +476,18 @@ WebGLContext::DrawElementsInstanced(GLen
 {
     const char funcName[] = "drawElementsInstanced";
     if (IsContextLost())
         return;
 
     if (!ValidateDrawModeEnum(mode, funcName))
         return;
 
+    MakeContextCurrent();
+
     bool error;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
     GLuint upperBound = 0;
     if (!DrawElements_check(count, type, byteOffset, primcount, funcName, &upperBound))
         return;
--- a/dom/canvas/test/mochitest.ini
+++ b/dom/canvas/test/mochitest.ini
@@ -34,61 +34,38 @@ support-files =
   offscreencanvas.js
   offscreencanvas_mask.svg
   offscreencanvas_neuter.js
   offscreencanvas_serviceworker_inner.html
 
 [test_2d.clearRect.image.offscreen.html]
 [test_2d.clip.winding.html]
 [test_2d.composite.canvas.color-burn.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.color-dodge.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.color.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.darken.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.destination-atop.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.destination-in.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.difference.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.exclusion.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.hard-light.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.hue.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.lighten.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.luminosity.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.multiply.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.overlay.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.saturation.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.screen.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.soft-light.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.source-in.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.canvas.source-out.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.image.destination-atop.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.image.destination-in.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.image.source-in.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.image.source-out.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 # xor and lighter aren't well handled by cairo; they mostly work, but we don't want
 # to test that
 [test_2d.composite.solid.xor.html]
 disabled =
 [test_2d.composite.solid.lighter.html]
 disabled =
 [test_2d.composite.transparent.xor.html]
 disabled =
@@ -118,17 +95,17 @@ disabled =
 [test_2d.composite.solid.luminosity.html]
 [test_2d.composite.solid.multiply.html]
 [test_2d.composite.solid.overlay.html]
 [test_2d.composite.solid.saturation.html]
 [test_2d.composite.solid.screen.html]
 [test_2d.composite.solid.soft-light.html]
 [test_2d.composite.uncovered.image.destination-atop.html]
 # This test fails in Suite on Linux for some reason, disable it there
-skip-if = (os == 'linux' && buildapp == 'suite') || (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
+skip-if = (os == 'linux' && buildapp == 'suite')
 [test_2d.composite.uncovered.fill.color-burn.html]
 [test_2d.composite.uncovered.fill.color-dodge.html]
 [test_2d.composite.uncovered.fill.color.html]
 [test_2d.composite.uncovered.fill.darken.html]
 [test_2d.composite.uncovered.fill.difference.html]
 [test_2d.composite.uncovered.fill.exclusion.html]
 [test_2d.composite.uncovered.fill.hard-light.html]
 [test_2d.composite.uncovered.fill.hue.html]
@@ -144,21 +121,18 @@ skip-if = (os == 'linux' && buildapp == 
 skip-if = toolkit != 'cocoa'
 [test_2d.composite.uncovered.fill.destination-in.html]
 skip-if = toolkit != 'cocoa'
 [test_2d.composite.uncovered.fill.source-out.html]
 skip-if = toolkit != 'cocoa'
 [test_2d.composite.uncovered.fill.destination-atop.html]
 skip-if = toolkit != 'cocoa'
 [test_2d.composite.uncovered.image.destination-in.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.uncovered.image.source-in.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 [test_2d.composite.uncovered.image.source-out.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 913662
 # Tests that fail on non-Mac (bug 407107)
 [test_2d.composite.uncovered.pattern.source-in.html]
 skip-if = toolkit != 'cocoa'
 [test_2d.composite.uncovered.pattern.destination-in.html]
 skip-if = toolkit != 'cocoa'
 [test_2d.composite.uncovered.pattern.source-out.html]
 skip-if = toolkit != 'cocoa'
 [test_2d.composite.uncovered.pattern.destination-atop.html]
@@ -228,17 +202,17 @@ disabled = bug 407107
 [test_bug753758.html]
 [test_bug764125.html]
 [test_bug856472.html]
 [test_bug866575.html]
 skip-if = (toolkit == 'gonk' && debug) #bug 1045153
 [test_bug902651.html]
 [test_bug1215072.html]
 [test_canvas.html]
-skip-if = (toolkit == 'gonk' && debug) || (toolkit == 'android' && processor == 'x86') || (android_version == '18' && debug) #debug-only crash; bug 933541 #x86 only bug 913662 #android 4.3 debug bug 1143317
+skip-if = (toolkit == 'gonk' && debug) || (android_version == '18' && debug) #debug-only crash; bug 933541 #android 4.3 debug bug 1143317
 [test_canvas_focusring.html]
 skip-if = (toolkit == 'gonk' && !debug) || os == 'win' #specialpowers.wrap
 [test_canvas_font_setter.html]
 [test_canvas_path.html]
 [test_hitregion_canvas.html]
 [test_hitregion_event.html]
 skip-if = os == "android" || appname == "b2g"
 [test_canvas_strokeStyle_getter.html]
--- a/dom/canvas/test/reftest/reftest.list
+++ b/dom/canvas/test/reftest/reftest.list
@@ -148,15 +148,15 @@ skip-if(!winWidget) pref(webgl.disable-a
 
 # Do we correctly handle multiple clip paths?
 != clip-multiple-paths.html clip-multiple-paths-badref.html
 
 # Bug 815648
 == stroketext-shadow.html stroketext-shadow-ref.html
 
 # focus rings
-pref(canvas.focusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html
-pref(canvas.customfocusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html
+pref(canvas.focusring.enabled,true) skip-if(B2G) skip-if(cocoaWidget) skip-if(winWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html
+pref(canvas.customfocusring.enabled,true) skip-if(B2G) skip-if(cocoaWidget) skip-if(Android) skip-if(winWidget) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html
 
 # Check that captureStream() displays in a local video element
 skip-if(winWidget&&layersGPUAccelerated&&d2d) == capturestream.html wrapper.html?green.png
 
 fuzzy-if(azureSkiaGL,1,2) fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),1,1) == 1177726-text-stroke-bounds.html 1177726-text-stroke-bounds-ref.html
--- a/dom/canvas/test/test_canvas.html
+++ b/dom/canvas/test/test_canvas.html
@@ -21521,16 +21521,936 @@ isPixel(ctx, 50,25, 0,255,0,255, 0);
 function test_2d_clearRect_testdoubleprecision() {
   var canvas = document.getElementById('c690');
   ctx = canvas.getContext('2d');
   ctx.setTransform(1, 1, 1, 1, 0, 0);
   ctx.clearRect(-1.79e+308, 0, 1.79e+308, 8);
 }
 </script>
 
+<!-- [[[ test_2d.path.ellipse.angle.1.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.angle.1</p>
+<!-- Testing: ellipse() draws pi/2 .. -pi anticlockwise correctly -->
+<canvas id="c690" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_angle_1() {
+
+var canvas = document.getElementById('c690');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+ctx.fillStyle = '#f00';
+ctx.beginPath();
+ctx.moveTo(100, 0);
+ctx.ellipse(100, 0, 150, 100, 0, Math.PI/2, -Math.PI, true);
+ctx.fill();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.angle.2.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.angle.2</p>
+<!-- Testing: ellipse() draws -3pi/2 .. -pi anticlockwise correctly -->
+<canvas id="c691" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_angle_2() {
+
+var canvas = document.getElementById('c691');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+ctx.fillStyle = '#f00';
+ctx.beginPath();
+ctx.moveTo(100, 0);
+ctx.ellipse(100, 0, 150, 100, 0, -3*Math.PI/2, -Math.PI, true);
+ctx.fill();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.angle.3.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.angle.3</p>
+<!-- Testing: ellipse() wraps angles mod 2pi when anticlockwise and end > start+2pi -->
+<canvas id="c692" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_angle_3() {
+
+var canvas = document.getElementById('c692');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+ctx.fillStyle = '#f00';
+ctx.beginPath();
+ctx.moveTo(100, 0);
+ctx.ellipse(100, 0, 150, 100, 0, (512+1/2)*Math.PI, (1024-1)*Math.PI, true);
+ctx.fill();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.angle.4.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.angle.4</p>
+<!-- Testing: ellipse() draws a full circle when clockwise and end > start+2pi -->
+<canvas id="c693" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_angle_4() {
+
+var canvas = document.getElementById('c693');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.fillStyle = '#0f0';
+ctx.beginPath();
+ctx.moveTo(50, 25);
+ctx.ellipse(50, 25, 60, 50, 0, (512+1/2)*Math.PI, (1024-1)*Math.PI, false);
+ctx.fill();
+isPixel(ctx, 1,1, 0,255,0,255, 0);
+isPixel(ctx, 98,1, 0,255,0,255, 0);
+isPixel(ctx, 1,48, 0,255,0,255, 0);
+isPixel(ctx, 98,48, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.angle.5.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.angle.5</p>
+<!-- Testing: ellipse() wraps angles mod 2pi when clockwise and start > end+2pi -->
+<canvas id="c694" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_angle_5() {
+
+var canvas = document.getElementById('c694');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+ctx.fillStyle = '#f00';
+ctx.beginPath();
+ctx.moveTo(100, 0);
+ctx.ellipse(100, 0, 150, 100, 0, (1024-1)*Math.PI, (512+1/2)*Math.PI, false);
+ctx.fill();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.angle.6.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.angle.6</p>
+<!-- Testing: ellipse() draws a full circle when anticlockwise and start > end+2pi -->
+<canvas id="c695" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+function test_2d_path_ellipse_angle_6() {
+
+var canvas = document.getElementById('c695');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.fillStyle = '#0f0';
+ctx.beginPath();
+ctx.moveTo(50, 25);
+ctx.ellipse(50, 25, 60, 50, 0, (1024-1)*Math.PI, (512+1/2)*Math.PI, true);
+ctx.fill();
+isPixel(ctx, 1,1, 0,255,0,255, 0);
+isPixel(ctx, 98,1, 0,255,0,255, 0);
+isPixel(ctx, 1,48, 0,255,0,255, 0);
+isPixel(ctx, 98,48, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.empty.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.empty</p>
+<!-- Testing: ellipse() with an empty path does not draw a straight line to the start point -->
+<canvas id="c696" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_empty() {
+
+var canvas = document.getElementById('c696');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+ctx.lineWidth = 50;
+ctx.strokeStyle = '#f00';
+ctx.beginPath();
+ctx.ellipse(200, 25, 5, 5, 0, 0, 2*Math.PI, true);
+ctx.stroke();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.end.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.end</p>
+<!-- Testing: ellipse() adds the end point of the ellipse to the subpath -->
+<canvas id="c697" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+function test_2d_path_ellipse_end() {
+
+var canvas = document.getElementById('c697');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.lineWidth = 50;
+ctx.strokeStyle = '#0f0';
+ctx.beginPath();
+ctx.moveTo(-100, 0);
+ctx.ellipse(-100, 0, 25, 25, 0, -Math.PI/2, Math.PI/2, true);
+ctx.lineTo(100, 25);
+ctx.stroke();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.negative.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.negative</p>
+<!-- Testing: ellipse() with negative radius throws INDEX_SIZE_ERR -->
+<canvas id="c698" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+function test_2d_path_ellipse_negative() {
+
+var canvas = document.getElementById('c698');
+var ctx = canvas.getContext('2d');
+
+var _thrown = undefined;
+try {
+  ctx.ellipse(0, 0, -1, 0, 0, -Math.PI/2, Math.PI/2, true);
+} catch (e) { _thrown = e }; ok(_thrown && _thrown.name == "IndexSizeError" && _thrown.code == DOMException.INDEX_SIZE_ERR, "should throw IndexSizeError");
+
+try {
+  ctx.ellipse(0, 0, 0, -1, 0, -Math.PI/2, Math.PI/2, true);
+} catch (e) { _thrown = e }; ok(_thrown && _thrown.name == "IndexSizeError" && _thrown.code == DOMException.INDEX_SIZE_ERR, "should throw IndexSizeError");
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.nonempty.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.nonempty</p>
+<!-- Testing: ellipse() with a non-empty path does draw a straight line to the start point -->
+<canvas id="c699" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+function test_2d_path_ellipse_nonempty() {
+
+var canvas = document.getElementById('c699');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.lineWidth = 50;
+ctx.strokeStyle = '#0f0';
+ctx.beginPath();
+ctx.moveTo(0, 25);
+ctx.ellipse(200, 25, 5, 2, 0, 0, 2*Math.PI, true);
+ctx.stroke();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.bezierCurveTo.nonfinite.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.nonfinite</p>
+<!-- Testing: bezierCurveTo() with Infinity/NaN is ignored -->
+<canvas id="c700" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+function test_2d_path_ellipse_nonfinite() {
+
+var canvas = document.getElementById('c700');
+var ctx = canvas.getContext('2d');
+
+var _thrown_outer = false;
+try {
+
+ctx.moveTo(0, 0);
+ctx.lineTo(100, 0);
+ctx.ellipse(Infinity, 25, 0, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(-Infinity, 25, 0, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(NaN, 25, 0, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, 0, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, -Infinity, 0, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, NaN, 50, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, Infinity, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, -Infinity, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, NaN, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, Infinity, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, -Infinity, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, NaN, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, 0, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, 0, -Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, 0, NaN, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, 0, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, 0, 0, -Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, 0, 0, NaN, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, 0, 0, 0, Infinity, false);
+ctx.ellipse(50, 25, 0, 0, 0, 0, -Infinity, false);
+ctx.ellipse(50, 25, 0, 0, 0, 0, NaN, false);
+ctx.ellipse(Infinity, Infinity, 0, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, Infinity, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, Infinity, Infinity, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, Infinity, Infinity, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, Infinity, Infinity, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, Infinity, Infinity, Infinity, Infinity, Infinity, false);
+ctx.ellipse(Infinity, Infinity, Infinity, Infinity, Infinity, 0, Infinity, false);
+ctx.ellipse(Infinity, Infinity, Infinity, Infinity, 0, Infinity, Infinity, false);
+ctx.ellipse(Infinity, Infinity, Infinity, 0, Infinity, Infinity, Infinity, false);
+ctx.ellipse(Infinity, Infinity, 0, Infinity, Infinity, Infinity, Infinity, false);
+ctx.ellipse(Infinity, 25, Infinity, Infinity, Infinity, Infinity, Infinity, false);
+ctx.ellipse(50, Infinity, Infinity, Infinity, Infinity, Infinity, Infinity, false);
+ctx.ellipse(Infinity, Infinity, Infinity, Infinity, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, Infinity, 0, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, 0, Infinity, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, Infinity, Infinity, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, Infinity, Infinity, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, Infinity, Infinity, 0, 0, Infinity, false);
+ctx.ellipse(Infinity, Infinity, Infinity, 0, Infinity, 0, Infinity, false);
+ctx.ellipse(Infinity, Infinity, 0, Infinity, Infinity, 0, Infinity, false);
+ctx.ellipse(Infinity, 25, Infinity, Infinity, Infinity, 0, Infinity, false);
+ctx.ellipse(50, Infinity, Infinity, Infinity, Infinity, 0, Infinity, false);
+ctx.ellipse(Infinity, Infinity, Infinity, 0, 0, Infinity, Infinity, false);
+ctx.ellipse(Infinity, Infinity, 0, Infinity, 0, Infinity, Infinity, false);
+ctx.ellipse(Infinity, 25, Infinity, Infinity, 0, Infinity, Infinity, false);
+ctx.ellipse(50, Infinity, Infinity, Infinity, 0, Infinity, Infinity, false);
+ctx.ellipse(Infinity, Infinity, 50, 0, Infinity, Infinity, Infinity, false);
+ctx.ellipse(Infinity, 25, Infinity, 0, Infinity, Infinity, Infinity, false);
+ctx.ellipse(50, Infinity, Infinity, 0, Infinity, Infinity, Infinity, false);
+ctx.ellipse(Infinity, 25, 50, Infinity, Infinity, Infinity, Infinity, false);
+ctx.ellipse(50, Infinity, 50, Infinity, Infinity, Infinity, Infinity, false);
+ctx.ellipse(50, 25, Infinity, Infinity, Infinity, Infinity, Infinity, false);
+ctx.ellipse(Infinity, Infinity, Infinity, 0, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, 0, Infinity, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, Infinity, Infinity, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, Infinity, Infinity, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, Infinity, 0, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, 0, Infinity, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, Infinity, Infinity, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, Infinity, Infinity, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, 0, 0, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, Infinity, 0, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, Infinity, 0, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, 0, Infinity, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, 0, Infinity, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, 25, Infinity, Infinity, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, Infinity, 0, 0, 0, Infinity, false);
+ctx.ellipse(Infinity, Infinity, 0, Infinity, 0, 0, Infinity, false);
+ctx.ellipse(Infinity, 25, Infinity, Infinity, 0, 0, Infinity, false);
+ctx.ellipse(50, Infinity, Infinity, Infinity, 0, 0, Infinity, false);
+ctx.ellipse(Infinity, Infinity, 0, 0, Infinity, 0, Infinity, false);
+ctx.ellipse(Infinity, 25, Infinity, 0, Infinity, 0, Infinity, false);
+ctx.ellipse(50, Infinity, Infinity, 0, Infinity, 0, Infinity, false);
+ctx.ellipse(Infinity, 25, 0, Infinity, Infinity, 0, Infinity, false);
+ctx.ellipse(50, Infinity, 0, Infinity, Infinity, 0, Infinity, false);
+ctx.ellipse(50, 25, Infinity, Infinity, Infinity, 0, Infinity, false);
+ctx.ellipse(Infinity, Infinity, 0, 0, 0, Infinity, Infinity, false);
+ctx.ellipse(Infinity, 25, Infinity, 0, 0, Infinity, Infinity, false);
+ctx.ellipse(50, Infinity, Infinity, 0, 0, Infinity, Infinity, false);
+ctx.ellipse(Infinity, 25, 0, Infinity, 0, Infinity, Infinity, false);
+ctx.ellipse(50, Infinity, 0, Infinity, 0, Infinity, Infinity, false);
+ctx.ellipse(50, 25, Infinity, Infinity, 0, Infinity, Infinity, false);
+ctx.ellipse(Infinity, 25, 0, 0, Infinity, Infinity, Infinity, false);
+ctx.ellipse(50, Infinity, 0, 0, Infinity, Infinity, Infinity, false);
+ctx.ellipse(50, 25, Infinity, 0, Infinity, Infinity, Infinity, false);
+ctx.ellipse(50, 25, 50, Infinity, Infinity, Infinity, Infinity, false);
+ctx.ellipse(Infinity, Infinity, 0, Infinity, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, Infinity, Infinity, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, Infinity, Infinity, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, 0, 0, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, Infinity, 0, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, Infinity, 0, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, 0, Infinity, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, 0, Infinity, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, Infinity, Infinity, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, 0, 0, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, Infinity, 0, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, Infinity, 0, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, 0, Infinity, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, 0, Infinity, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, 25, Infinity, Infinity, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, 0, 0, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, 0, 0, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, 25, Infinity, 0, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, Infinity, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, Infinity, 0, 0, 0, 0, Infinity, false);
+ctx.ellipse(Infinity, 25, Infinity, 0, 0, 0, Infinity, false);
+ctx.ellipse(50, Infinity, Infinity, 0, 0, 0, Infinity, false);
+ctx.ellipse(Infinity, 25, 0, Infinity, 0, 0, Infinity, false);
+ctx.ellipse(50, Infinity, 0, Infinity, 0, 0, Infinity, false);
+ctx.ellipse(50, 25, Infinity, Infinity, 0, 0, Infinity, false);
+ctx.ellipse(Infinity, 25, 0, 0, Infinity, 0, Infinity, false);
+ctx.ellipse(50, Infinity, 0, 0, Infinity, 0, Infinity, false);
+ctx.ellipse(50, 25, Infinity, 0, Infinity, 0, Infinity, false);
+ctx.ellipse(50, 25, 0, Infinity, Infinity, 0, Infinity, false);
+ctx.ellipse(Infinity, 25, 0, 0, 0, Infinity, Infinity, false);
+ctx.ellipse(50, Infinity, 0, 0, 0, Infinity, Infinity, false);
+ctx.ellipse(50, 25, Infinity, 0, 0, Infinity, Infinity, false);
+ctx.ellipse(50, 25, 0, Infinity, 0, Infinity, Infinity, false);
+ctx.ellipse(50, 25, 0, 0, Infinity, Infinity, Infinity, false);
+ctx.ellipse(Infinity, 25, Infinity, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, Infinity, 0, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, 0, Infinity, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, 0, Infinity, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, Infinity, Infinity, 0, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, 0, 0, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, 0, 0, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, Infinity, 0, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, Infinity, Infinity, 0, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, 0, 0, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, Infinity, 0, 0, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, 25, Infinity, 0, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, Infinity, 0, Infinity, 2 * Math.PI, false);
+ctx.ellipse(50, 25, 0, 0, Infinity, Infinity, 2 * Math.PI, false);
+ctx.ellipse(Infinity, 25, 0, 0, 0, 0, Infinity, false);
+ctx.ellipse(50, Infinity, 0, 0, 0, 0, Infinity, false);
+ctx.ellipse(50, 25, Infinity, 0, 0, 0, Infinity, false);
+ctx.ellipse(50, 25, 0, Infinity, 0, 0, Infinity, false);
+ctx.ellipse(50, 25, 0, 0, Infinity, 0, Infinity, false);
+ctx.ellipse(50, 25, 0, 0, 0, Infinity, Infinity, false);
+ctx.lineTo(100, 50);
+ctx.lineTo(0, 50);
+ctx.fillStyle = '#0f0';
+ctx.fill();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+isPixel(ctx, 90,45, 0,255,0,255, 0);
+
+} catch (e) {
+    _thrown_outer = true;
+}
+ok(!_thrown_outer, ctx.canvas.id + ' should not throw exception');
+
+
+}
+</script>
+
+
+<!-- [[[ test_2d.path.ellipse.scale.1.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.scale.1</p>
+<!-- Testing: Non-uniformly scaled ellipse are the right shape -->
+<canvas id="c701" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+function test_2d_path_ellipse_scale_1() {
+
+var canvas = document.getElementById('c701');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.scale(2, 0.5);
+ctx.fillStyle = '#0f0';
+ctx.beginPath();
+var hypothenuse = Math.sqrt(50 * 50 + 25 * 25);
+var tolerance = 0.5;
+var radius = hypothenuse + tolerance;
+ctx.ellipse(25, 50, radius, radius, 0, 0, 2*Math.PI, false);
+ctx.fill();
+ctx.fillStyle = '#f00';
+ctx.beginPath();
+ctx.moveTo(-25, 50);
+ctx.ellipse(-25, 50, 24, 34, 0, 0, 2 * Math.PI, false);
+ctx.moveTo(75, 50);
+ctx.ellipse(75, 50, 24, 34, 0, 0, 2 * Math.PI, false);
+ctx.moveTo(25, -25);
+ctx.ellipse(25, -25, 34, 24, 0, 0, 2 * Math.PI, false);
+ctx.moveTo(25, 125);
+ctx.ellipse(25, -25, 34, 24, 0, 0, 2 * Math.PI, false);
+ctx.fill();
+
+isPixel(ctx, 0,0, 0,255,0,255, 0);
+isPixel(ctx, 50,0, 0,255,0,255, 0);
+isPixel(ctx, 99,0, 0,255,0,255, 0);
+isPixel(ctx, 0,25, 0,255,0,255, 0);
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+isPixel(ctx, 99,25, 0,255,0,255, 0);
+isPixel(ctx, 0,49, 0,255,0,255, 0);
+isPixel(ctx, 50,49, 0,255,0,255, 0);
+isPixel(ctx, 99,49, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.scale.2.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.scale.2</p>
+<!-- Testing: Highly scaled ellipse are the right shape -->
+<canvas id="c702" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_scale_2() {
+
+var canvas = document.getElementById('c702');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.scale(100, 100);
+ctx.strokeStyle = '#0f0';
+ctx.lineWidth = 1.2;
+ctx.beginPath();
+ctx.ellipse(0, 0, 0.6, 1, 0, 0, Math.PI/2, false);
+ctx.ellipse(0, 0, 1, 0.6, 0, 0, Math.PI/2, false);
+ctx.stroke();
+
+isPixel(ctx, 1,1, 0,255,0,255, 0);
+isPixel(ctx, 50,1, 0,255,0,255, 0);
+isPixel(ctx, 98,1, 0,255,0,255, 0);
+isPixel(ctx, 1,25, 0,255,0,255, 0);
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+isPixel(ctx, 98,25, 0,255,0,255, 0);
+isPixel(ctx, 1,48, 0,255,0,255, 0);
+isPixel(ctx, 50,48, 0,255,0,255, 0);
+isPixel(ctx, 98,48, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.selfintersect.1.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.selfintersect.1</p>
+<!-- Testing: ellipse() with lineWidth > 2*radius is drawn sensibly -->
+<canvas id="c703" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_selfintersect_1() {
+
+var canvas = document.getElementById('c703');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+ctx.lineWidth = 200;
+ctx.strokeStyle = '#f00';
+ctx.beginPath();
+ctx.ellipse(100, 50, 35, 25, 0, 0, -Math.PI/2, true);
+ctx.stroke();
+ctx.beginPath();
+ctx.ellipse(0, 0, 35, 25, 0, 0, -Math.PI/2, true);
+ctx.stroke();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.selfintersect.2.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.selfintersect.2</p>
+<!-- Testing: ellipse() with lineWidth > 2*radius is drawn sensibly -->
+<canvas id="c704" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_selfintersect_2() {
+
+var canvas = document.getElementById('c704');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.lineWidth = 180;
+ctx.strokeStyle = '#0f0';
+ctx.beginPath();
+ctx.ellipse(-50, 50, 25, 25, 0, 0, -Math.PI/2, true);
+ctx.stroke();
+ctx.beginPath();
+ctx.ellipse(100, 0, 25, 25, 0, 0, -Math.PI/2, true);
+ctx.stroke();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+isPixel(ctx, 90,10, 0,255,0,255, 0);
+isPixel(ctx, 97,1, 0,255,0,255, 0);
+isPixel(ctx, 97,2, 0,255,0,255, 0);
+isPixel(ctx, 97,3, 0,255,0,255, 0);
+isPixel(ctx, 2,48, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.shape.1.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.shape.1</p>
+<!-- Testing: ellipse() from 0 to pi does not draw anything in the wrong half -->
+<canvas id="c705" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+function test_2d_path_ellipse_shape_1() {
+
+var canvas = document.getElementById('c705');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+ctx.lineWidth = 50;
+ctx.strokeStyle = '#f00';
+ctx.beginPath();
+ctx.ellipse(50, 50, 40, 60, 0, 0, Math.PI, false);
+ctx.stroke();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+isPixel(ctx, 1,1, 0,255,0,255, 0);
+isPixel(ctx, 98,1, 0,255,0,255, 0);
+isPixel(ctx, 1,48, 0,255,0,255, 0);
+isPixel(ctx, 20,48, 0,255,0,255, 0);
+isPixel(ctx, 98,48, 0,255,0,255, 0);
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.shape.2.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.shape.2</p>
+<!-- Testing: ellipse() from 0 to pi draws stuff in the right half -->
+<canvas id="c706" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+function test_2d_path_ellipse_shape_2() {
+
+var canvas = document.getElementById('c706');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.lineWidth = 100;
+ctx.strokeStyle = '#0f0';
+ctx.beginPath();
+ctx.ellipse(50, 50, 30, 15, 0, 0, Math.PI, true);
+ctx.stroke();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+isPixel(ctx, 1,1, 0,255,0,255, 0);
+isPixel(ctx, 98,1, 0,255,0,255, 0);
+isPixel(ctx, 1,48, 0,255,0,255, 0);
+isPixel(ctx, 20,48, 0,255,0,255, 0);
+isPixel(ctx, 98,48, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.shape.3.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.shape.3</p>
+<!-- Testing: ellipse() from 0 to -pi/2 draws stuff in the right quadrant -->
+<canvas id="c707" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_shape_3() {
+
+var canvas = document.getElementById('c707');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.lineWidth = 150;
+ctx.strokeStyle = '#0f0';
+ctx.beginPath();
+ctx.ellipse(-50, 50, 100, 200, 0, 0, -Math.PI/2, true);
+ctx.stroke();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+isPixel(ctx, 1,1, 0,255,0,255, 0);
+isPixel(ctx, 98,1, 0,255,0,255, 0);
+isPixel(ctx, 1,48, 0,255,0,255, 0);
+isPixel(ctx, 98,48, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.shape.4.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.shape.4</p>
+<!-- Testing: ellipse() from 0 to 5pi does not draw crazy things -->
+<canvas id="c708" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_shape_4() {
+
+var canvas = document.getElementById('c708');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+ctx.lineWidth = 200;
+ctx.strokeStyle = '#f00';
+ctx.beginPath();
+ctx.ellipse(300, 0, 100, 200, 0, 0, 5*Math.PI, false);
+ctx.stroke();
+isPixel(ctx, 50,25, 0,255,0,255, 0);
+isPixel(ctx, 1,1, 0,255,0,255, 0);
+isPixel(ctx, 98,1, 0,255,0,255, 0);
+isPixel(ctx, 1,48, 0,255,0,255, 0);
+isPixel(ctx, 98,48, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.twopie.1.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.twopie.1</p>
+<!-- Testing: ellipse() draws nothing when end = start + 2pi-e and anticlockwise -->
+<canvas id="c709" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+function test_2d_path_ellipse_twopie_1() {
+
+var canvas = document.getElementById('c709');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+ctx.strokeStyle = '#f00';
+ctx.lineWidth = 100;
+ctx.beginPath();
+ctx.ellipse(50, 25, 50, 60, 0, 0, 2*Math.PI - 1e-4, true);
+ctx.stroke();
+isPixel(ctx, 50,20, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.twopie.2.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.twopie.2</p>
+<!-- Testing: ellipse() draws a full ellipse when end = start + 2pi-e and clockwise -->
+<canvas id="c710" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_twopie_2() {
+
+var canvas = document.getElementById('c710');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.strokeStyle = '#0f0';
+ctx.lineWidth = 100;
+ctx.beginPath();
+ctx.ellipse(50, 25, 50, 30, 0, 0, 2*Math.PI - 1e-4, false);
+ctx.stroke();
+isPixel(ctx, 50,20, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.twopie.3.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.twopie.3</p>
+<!-- Testing: ellipse() draws a full circle when end = start + 2pi+e and anticlockwise -->
+<canvas id="c711" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_twopie_3() {
+
+var canvas = document.getElementById('c711');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.strokeStyle = '#0f0';
+ctx.lineWidth = 100;
+ctx.beginPath();
+ctx.ellipse(50, 25, 50, 25, 0, 0, 2*Math.PI + 1e-4, true);
+ctx.stroke();
+isPixel(ctx, 50,20, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.twopie.4.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.twopie.4</p>
+<!-- Testing: ellipse() draws nothing when end = start + 2pi+e and clockwise -->
+<canvas id="c712" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_twopie_4() {
+
+var canvas = document.getElementById('c712');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+ctx.strokeStyle = '#0f0';
+ctx.lineWidth = 100;
+ctx.beginPath();
+ctx.ellipse(50, 25, 50, 25, 0, 0, 2*Math.PI + 1e-4, false);
+ctx.stroke();
+isPixel(ctx, 50,20, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.zero.1.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.zero.1</p>
+<!-- Testing: ellipse() draws nothing when startAngle = endAngle and anticlockwise -->
+<canvas id="c713" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_zero_1() {
+
+var canvas = document.getElementById('c713');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+ctx.strokeStyle = '#f00';
+ctx.lineWidth = 100;
+ctx.beginPath();
+ctx.ellipse(50, 25, 50, 25, 0, 0, 0, true);
+ctx.stroke();
+isPixel(ctx, 50,20, 0,255,0,255, 0);
+
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.zero.2.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.zero.2</p>
+<!-- Testing: ellipse() draws nothing when startAngle = endAngle and clockwise -->
+<canvas id="c714" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+
+function test_2d_path_ellipse_zero_2() {
+
+var canvas = document.getElementById('c714');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+ctx.strokeStyle = '#f00';
+ctx.lineWidth = 100;
+ctx.beginPath();
+ctx.ellipse(50, 25, 50, 25, 0, 0, 0, true);
+ctx.stroke();
+isPixel(ctx, 50,20, 0,255,0,255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.zeroradius.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.zeroradius</p>
+<!-- Testing: ellipse() with zero radius draws a line to the start point -->
+<canvas id="c715" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+function test_2d_path_ellipse_zeroradius() {
+
+var canvas = document.getElementById('c715');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#f00'
+ctx.fillRect(0, 0, 100, 50);
+ctx.lineWidth = 50;
+ctx.strokeStyle = '#0f0';
+ctx.beginPath();
+ctx.moveTo(0, 25);
+ctx.ellipse(200, 25, 0, 0, 0, 0, Math.PI, true);
+ctx.stroke();
+
+isPixel(ctx, 50, 25, 0, 255, 0, 255, 0);
+
+
+}
+</script>
+
+<!-- [[[ test_2d.path.ellipse.rotate.html ]]] -->
+
+<p>Canvas test: 2d.path.ellipse.rotate</p>
+<!-- Testing: ellipse() with a rotation angle works -->
+<canvas id="c716" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+function test_2d_path_ellipse_rotate() {
+
+var canvas = document.getElementById('c716');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = '#0f0';
+ctx.fillRect(0, 0, 100, 50);
+ctx.lineWidth = 5;
+ctx.strokeStyle = '#f00';
+ctx.beginPath();
+ctx.ellipse(50, 25, 50, 25, Math.PI/4, 0, 2 * Math.PI, false);
+ctx.stroke();
+ctx.beginPath();
+ctx.ellipse(50, 25, 50, 25, -Math.PI/4, 0, 2 * Math.PI, false);
+ctx.stroke();
+isPixel(ctx, 50, 25, 0,255,0,255, 0);
+isPixel(ctx, 48,1, 0,255,0,255, 0);
+isPixel(ctx, 98,24, 0,255,0,255, 0);
+isPixel(ctx, 48,48, 0,255,0,255, 0);
+isPixel(ctx, 24,48, 0,255,0,255, 0);
+}
+</script>
+
 <script>
 
 function asyncTestsDone() {
 	if (isDone_test_2d_drawImage_animated_apng &&
 		isDone_test_2d_pattern_animated_gif &&
 		isDone_test_2d_drawImage_animated_gif) {
 		SimpleTest.finish();
 	} else {
@@ -24815,16 +25735,151 @@ try {
   ok(false, "unexpected exception thrown in: test_opaque");
  }
  try {
   test_2d_transformation_reset_transform();
  } catch (e) {
   ok(false, "unexpected exception thrown in: test_2d_transformation_reset_transform");
  }
  try {
+  test_2d_path_ellipse_angle_1();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_angle_1");
+ }
+ try {
+  test_2d_path_ellipse_angle_2();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_angle_2");
+ }
+ try {
+  test_2d_path_ellipse_angle_3();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_angle_3");
+ }
+ try {
+  test_2d_path_ellipse_angle_4();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_angle_4");
+ }
+ try {
+  test_2d_path_ellipse_angle_5();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_angle_5");
+ }
+ try {
+  test_2d_path_ellipse_angle_6();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_angle_6");
+ }
+ try {
+  test_2d_path_ellipse_empty();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_empty");
+ }
+ try {
+  test_2d_path_ellipse_end();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_end");
+ }
+ try {
+  test_2d_path_ellipse_negative();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_negative");
+ }
+ try {
+  test_2d_path_ellipse_nonempty();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_nonempty");
+ }
+ try {
+  test_2d_path_ellipse_nonfinite();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_nonfinite");
+ }
+ try {
+  test_2d_path_ellipse_scale_1();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_scale_1");
+ }
+ try {
+  test_2d_path_ellipse_scale_2();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_scale_2");
+ }
+ try {
+  test_2d_path_ellipse_selfintersect_1();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_selfintersect_1");
+ }
+ try {
+  test_2d_path_ellipse_selfintersect_2();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_selfintersect_2");
+ }
+ try {
+  test_2d_path_ellipse_shape_1();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_shape_1");
+ }
+ try {
+  test_2d_path_ellipse_shape_2();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_shape_2");
+ }
+ try {
+  test_2d_path_ellipse_shape_3();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_shape_3");
+ }
+ try {
+  test_2d_path_ellipse_shape_4();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_shape_4");
+ }
+ try {
+  test_2d_path_ellipse_twopie_1();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_twopie_1");
+ }
+ try {
+  test_2d_path_ellipse_twopie_2();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_twopie_2");
+ }
+ try {
+  test_2d_path_ellipse_twopie_3();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_twopie_3");
+ }
+ try {
+  test_2d_path_ellipse_twopie_4();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_twopie_4");
+ }
+ try {
+  test_2d_path_ellipse_zero_1();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_zero_1");
+ }
+ try {
+  test_2d_path_ellipse_zero_2();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_zero_2");
+ }
+ try {
+  test_2d_path_ellipse_zeroradius();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_zeroradius");
+ }
+ try {
+  test_2d_path_ellipse_rotate();
+ } catch (e) {
+  ok(false, "unexpected exception thrown in: test_2d_path_ellipse_rotate");
+ }
+ try {
   // run this test last since it replaces the getContext method
   test_type_replace();
  } catch (e) {
   ok(false, "unexpected exception thrown in: test_type_replace");
  }
  try {
    test_2d_clearRect_testdoubleprecision();
  } catch(e) {
--- a/dom/contacts/tests/mochitest.ini
+++ b/dom/contacts/tests/mochitest.ini
@@ -18,17 +18,17 @@ support-files =
 [test_contacts_basics.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_contacts_basics2.html]
 skip-if = (toolkit == 'gonk' && debug) || (os == 'win' && os_version == '5.1') #debug-only failure, bug 967258 on XP
 [test_contacts_blobs.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_contacts_events.html]
 [test_contacts_getall.html]
-skip-if = (toolkit == 'gonk' && debug) || (toolkit == 'android' && processor == 'x86') #debug-only failure #x86 only
+skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_contacts_getall2.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_contacts_international.html]
 [test_contacts_substringmatching.html]
 [test_contacts_substringmatchingVE.html]
 [test_contacts_substringmatchingCL.html]
 [test_migration.html]
   support-files +=
--- a/dom/datastore/DataStoreService.cpp
+++ b/dom/datastore/DataStoreService.cpp
@@ -405,19 +405,17 @@ public:
       mTxn = aDb->Transaction();
 
       RefPtr<IDBObjectStore> store =
       mTxn->ObjectStore(NS_LITERAL_STRING(DATASTOREDB_REVISION), error);
       if (NS_WARN_IF(error.Failed())) {
         return;
       }
 
-      AutoSafeJSContext cx;
-      mRequest = store->OpenCursor(cx, JS::UndefinedHandleValue,
-                                   IDBCursorDirection::Prev, error);
+      mRequest = store->OpenCursor(IDBCursorDirection::Prev, error);
       if (NS_WARN_IF(error.Failed())) {
         return;
       }
 
       nsresult rv;
       rv = mRequest->EventTarget::AddEventListener(NS_LITERAL_STRING("success"),
                                                    this, false);
       if (NS_FAILED(rv)) {
@@ -487,21 +485,19 @@ public:
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
 #ifdef DEBUG
     MOZ_ASSERT(type.EqualsASCII("success"));
 #endif
 
-    AutoSafeJSContext cx;
-
     ErrorResult error;
-    JS::Rooted<JS::Value> result(cx);
-    request->GetResult(cx, &result, error);
+    JS::Rooted<JS::Value> result(nsContentUtils::RootingCx());
+    request->GetResult(&result, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
 
     // This means that the content is a IDBCursor, so the first revision already
     // exists.
     if (result.isObject()) {
 #ifdef DEBUG
--- a/dom/datastore/tests/mochitest.ini
+++ b/dom/datastore/tests/mochitest.ini
@@ -33,25 +33,22 @@ support-files =
   file_bug957086.html
   file_notify_system_message.html
   file_worker_close.html
   file_worker_close.js
 
 [test_app_install.html]
 skip-if = toolkit == 'gonk' # embed-apps doesn't work in the mochitest app
 [test_readonly.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 936226
 [test_basic.html]
 [test_basic_worker.html]
 [test_changes.html]
 [test_arrays.html]
 [test_oop.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 936226
 [test_sync.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 936226
 [test_sync_worker.html]
 [test_bug924104.html]
 [test_certifiedApp.html]
 [test_keys.html]
 [test_duplicate.html]
 [test_bug976311.html]
 [test_bug986056.html]
 [test_oop_events.html]
--- a/dom/devicestorage/test/mochitest.ini
+++ b/dom/devicestorage/test/mochitest.ini
@@ -1,10 +1,9 @@
 [DEFAULT]
-skip-if = (toolkit == 'android' && processor == 'x86') # Android: bug 781789 & bug 782275
 support-files = devicestorage_common.js
                 remove_testing_directory.js
 
 [test_823965.html]
 # [test_add.html]
 # man, our mime database sucks hard.  followup bug # 788273
 [test_addCorrectType.html]
 [test_available.html]
--- a/dom/fetch/FetchUtil.cpp
+++ b/dom/fetch/FetchUtil.cpp
@@ -10,20 +10,24 @@ namespace mozilla {
 namespace dom {
 
 // static
 nsresult
 FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod)
 {
   nsAutoCString upperCaseMethod(aMethod);
   ToUpperCase(upperCaseMethod);
+  if (!NS_IsValidHTTPToken(aMethod)) {
+    outMethod.SetIsVoid(true);
+    return NS_ERROR_DOM_SYNTAX_ERR;
+  }
+
   if (upperCaseMethod.EqualsLiteral("CONNECT") ||
       upperCaseMethod.EqualsLiteral("TRACE") ||
-      upperCaseMethod.EqualsLiteral("TRACK") ||
-      !NS_IsValidHTTPToken(aMethod)) {
+      upperCaseMethod.EqualsLiteral("TRACK")) {
     outMethod.SetIsVoid(true);
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   if (upperCaseMethod.EqualsLiteral("DELETE") ||
       upperCaseMethod.EqualsLiteral("GET") ||
       upperCaseMethod.EqualsLiteral("HEAD") ||
       upperCaseMethod.EqualsLiteral("OPTIONS") ||
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -770,17 +770,17 @@ nsGeolocationRequest::Shutdown()
 // nsGeolocationRequest::TimerCallbackHolder
 ////////////////////////////////////////////////////
 
 NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder, nsISupports, nsITimerCallback)
 
 NS_IMETHODIMP
 nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer*)
 {
-  if (mRequest) {
+  if (mRequest.get()) {
     mRequest->Notify();
   }
   return NS_OK;
 }
 
 
 ////////////////////////////////////////////////////
 // nsGeolocationService
--- a/dom/html/test/forms/mochitest.ini
+++ b/dom/html/test/forms/mochitest.ini
@@ -54,17 +54,16 @@ skip-if = os == "android" || appname == 
 [test_input_range_key_events.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_input_range_mouse_and_touch_events.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure; bug 926546
 [test_input_range_rounding.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_input_sanitization.html]
 [test_input_textarea_set_value_no_scroll.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_input_typing_sanitization.html]
 skip-if = buildapp == 'mulet'
 [test_input_untrusted_key_events.html]
 [test_input_url.html]
 [test_interactive_content_in_label.html]
 [test_label_control_attribute.html]
 [test_label_input_controls.html]
 [test_max_attribute.html]
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -399,17 +399,17 @@ support-files =
   bug649134/file_bug649134-1.sjs
   bug649134/file_bug649134-2.sjs
   bug649134/index.html
 [test_bug651956.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug658746.html]
 [test_bug659596.html]
 [test_bug659743.xml]
-skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || android_version == '10' #Bug 931116, b2g desktop specific, initial triage #Android 2.3 aws only; bug 1031103
+skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug660663.html]
 [test_bug660959-1.html]
 [test_bug660959-2.html]
 [test_bug660959-3.html]
 [test_bug666200.html]
 [test_bug666666.html]
 [test_bug669012.html]
 [test_bug674558.html]
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -18000,28 +18000,22 @@ DatabaseMaintenance::CheckIntegrity(mozI
       MOZ_ASSERT(hasResult);
 
       rv = stmt->GetInt32(0, &foreignKeysWereEnabled);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
-    bool changedForeignKeys;
-    if (foreignKeysWereEnabled) {
-      changedForeignKeys = false;
-    } else {
+    if (!foreignKeysWereEnabled) {
       rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-        "PRAGMA foreign_keys = ON;"
-      ));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      changedForeignKeys = true;
+        "PRAGMA foreign_keys = ON;"));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
     }
 
     bool foreignKeyError;
     {
       nsCOMPtr<mozIStorageStatement> stmt;
       rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
         "PRAGMA foreign_key_check;"
       ), getter_AddRefs(stmt));
@@ -18030,24 +18024,20 @@ DatabaseMaintenance::CheckIntegrity(mozI
       }
 
       rv = stmt->ExecuteStep(&foreignKeyError);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
-    if (changedForeignKeys) {
+    if (!foreignKeysWereEnabled) {
       nsAutoCString stmtSQL;
       stmtSQL.AssignLiteral("PRAGMA foreign_keys = ");
-      if (foreignKeysWereEnabled) {
-        stmtSQL.AppendLiteral("ON");
-      } else {
-        stmtSQL.AppendLiteral("OFF");
-      }
+      stmtSQL.AppendLiteral("OFF");
       stmtSQL.Append(';');
 
       rv = aConnection->ExecuteSimpleSQL(stmtSQL);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
--- a/dom/indexedDB/IDBKeyRange.cpp
+++ b/dom/indexedDB/IDBKeyRange.cpp
@@ -94,16 +94,18 @@ IDBKeyRange::AssertIsOnOwningThread() co
 #endif // DEBUG
 
 // static
 nsresult
 IDBKeyRange::FromJSVal(JSContext* aCx,
                        JS::Handle<JS::Value> aVal,
                        IDBKeyRange** aKeyRange)
 {
+  MOZ_ASSERT_IF(!aCx, aVal.isUndefined());
+
   RefPtr<IDBKeyRange> keyRange;
 
   if (aVal.isNullOrUndefined()) {
     // undefined and null returns no IDBKeyRange.
     keyRange.forget(aKeyRange);
     return NS_OK;
   }
 
--- a/dom/indexedDB/IDBKeyRange.h
+++ b/dom/indexedDB/IDBKeyRange.h
@@ -52,16 +52,17 @@ protected:
 #ifdef DEBUG
   PRThread* mOwningThread;
 #endif
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(IDBKeyRange)
 
+  // aCx is allowed to be null, but only if aVal.isUndefined().
   static nsresult
   FromJSVal(JSContext* aCx,
             JS::Handle<JS::Value> aVal,
             IDBKeyRange** aKeyRange);
 
   static already_AddRefed<IDBKeyRange>
   FromSerialized(const indexedDB::SerializedKeyRange& aKeyRange);
 
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -2084,16 +2084,17 @@ IDBObjectStore::Count(JSContext* aCx,
 already_AddRefed<IDBRequest>
 IDBObjectStore::OpenCursorInternal(bool aKeysOnly,
                                    JSContext* aCx,
                                    JS::Handle<JS::Value> aRange,
                                    IDBCursorDirection aDirection,
                                    ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT_IF(!aCx, aRange.isUndefined());
 
   if (mDeletedSpec) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
     return nullptr;
   }
 
   if (!mTransaction->IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -257,16 +257,26 @@ public:
   {
     AssertIsOnOwningThread();
 
     return OpenCursorInternal(/* aKeysOnly */ false, aCx, aRange, aDirection,
                               aRv);
   }
 
   already_AddRefed<IDBRequest>
+  OpenCursor(IDBCursorDirection aDirection,
+             ErrorResult& aRv)
+  {
+    AssertIsOnOwningThread();
+
+    return OpenCursorInternal(/* aKeysOnly */ false, nullptr,
+                              JS::UndefinedHandleValue, aDirection, aRv);
+  }
+
+  already_AddRefed<IDBRequest>
   OpenKeyCursor(JSContext* aCx,
                 JS::Handle<JS::Value> aRange,
                 IDBCursorDirection aDirection,
                 ErrorResult& aRv)
   {
     AssertIsOnOwningThread();
 
     return OpenCursorInternal(/* aKeysOnly */ true, aCx, aRange, aDirection,
@@ -332,16 +342,18 @@ private:
                  ErrorResult& aRv);
 
   already_AddRefed<IDBIndex>
   CreateIndexInternal(const nsAString& aName,
                       const KeyPath& aKeyPath,
                       const IDBIndexParameters& aOptionalParameters,
                       ErrorResult& aRv);
 
+  // aCx is allowed to be null but only if aRange.isUndefined().  In that case,
+  // we don't actually use aCx for anything, so it's OK.
   already_AddRefed<IDBRequest>
   OpenCursorInternal(bool aKeysOnly,
                      JSContext* aCx,
                      JS::Handle<JS::Value> aRange,
                      IDBCursorDirection aDirection,
                      ErrorResult& aRv);
 };
 
--- a/dom/media/FlushableTaskQueue.cpp
+++ b/dom/media/FlushableTaskQueue.cpp
@@ -15,23 +15,29 @@ FlushableTaskQueue::Flush()
   AutoSetFlushing autoFlush(this);
   FlushLocked();
   AwaitIdleLocked();
 }
 
 nsresult
 FlushableTaskQueue::FlushAndDispatch(already_AddRefed<nsIRunnable> aRunnable)
 {
-  MonitorAutoLock mon(mQueueMonitor);
-  AutoSetFlushing autoFlush(this);
-  FlushLocked();
-  nsCOMPtr<nsIRunnable> r = dont_AddRef(aRunnable.take());
-  nsresult rv = DispatchLocked(r.forget(), IgnoreFlushing, AssertDispatchSuccess);
-  NS_ENSURE_SUCCESS(rv, rv);
-  AwaitIdleLocked();
+  nsCOMPtr<nsIRunnable> r = aRunnable;
+  {
+    MonitorAutoLock mon(mQueueMonitor);
+    AutoSetFlushing autoFlush(this);
+    FlushLocked();
+    nsresult rv = DispatchLocked(/* passed by ref */r, IgnoreFlushing, AssertDispatchSuccess);
+    NS_ENSURE_SUCCESS(rv, rv);
+    AwaitIdleLocked();
+  }
+  // If the ownership of |r| is not transferred in DispatchLocked() due to
+  // dispatch failure, it will be deleted here outside the lock. We do so
+  // since the destructor of the runnable might access TaskQueue and result
+  // in deadlocks.
   return NS_OK;
 }
 
 void
 FlushableTaskQueue::FlushLocked()
 {
   // Make sure there are no tasks for this queue waiting in the caller's tail
   // dispatcher.
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -657,16 +657,17 @@ AudioCallbackDriver::Init()
   STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver started."));
 }
 
 
 void
 AudioCallbackDriver::Destroy()
 {
   STREAM_LOG(LogLevel::Debug, ("AudioCallbackDriver destroyed."));
+  mAudioInput = nullptr;
   mAudioStream.reset();
 }
 
 void
 AudioCallbackDriver::Resume()
 {
   STREAM_LOG(LogLevel::Debug, ("Resuming audio threads for MediaStreamGraph %p", mGraphImpl));
   if (cubeb_stream_start(mAudioStream) != CUBEB_OK) {
--- a/dom/media/fmp4/MP4Decoder.cpp
+++ b/dom/media/fmp4/MP4Decoder.cpp
@@ -265,20 +265,20 @@ MP4Decoder::IsVideoAccelerated(layers::L
            [promise, decoder, taskQueue] (TrackInfo::TrackType aTrack) {
              nsCString failureReason;
              bool ok = decoder->IsHardwareAccelerated(failureReason);
              nsAutoString result;
              if (ok) {
                result.AssignLiteral("Yes");
              } else {
                result.AssignLiteral("No");
-               if (failureReason.Length()) {
-                 result.AppendLiteral("; ");
-                 AppendUTF8toUTF16(failureReason, result);
-               }
+             }
+             if (failureReason.Length()) {
+               result.AppendLiteral("; ");
+               AppendUTF8toUTF16(failureReason, result);
              }
              decoder->Shutdown();
              taskQueue->BeginShutdown();
              taskQueue->AwaitShutdownAndIdle();
              promise->MaybeResolve(result);
            },
            [promise, decoder, taskQueue] (MediaDataDecoder::DecoderFailureReason aResult) {
              decoder->Shutdown();
--- a/dom/media/gtest/TestMozPromise.cpp
+++ b/dom/media/gtest/TestMozPromise.cpp
@@ -215,9 +215,43 @@ TEST(MozPromise, PromiseAllReject)
       [queue] (float aRejectValue) -> void {
         EXPECT_EQ(aRejectValue, 32.0);
         queue->BeginShutdown();
       }
     );
   });
 }
 
+// Test we don't hit the assertions in MozPromise when exercising promise
+// chaining upon task queue shutdown.
+TEST(MozPromise, Chaining)
+{
+  AutoTaskQueue atq;
+  RefPtr<TaskQueue> queue = atq.Queue();
+  MozPromiseRequestHolder<TestPromise> holder;
+
+  RunOnTaskQueue(queue, [queue, &holder] () {
+    auto p = TestPromise::CreateAndResolve(42, __func__);
+    const size_t kIterations = 100;
+    for (size_t i = 0; i < kIterations; ++i) {
+      p = p->Then(queue, __func__,
+        [] (int aVal) {
+          EXPECT_EQ(aVal, 42);
+        },
+        [] () {}
+      )->CompletionPromise();
+
+      if (i == kIterations / 2) {
+        p->Then(queue, __func__,
+          [queue, &holder] () {
+            holder.Disconnect();
+            queue->BeginShutdown();
+          },
+          DO_FAIL);
+      }
+    }
+    // We will hit the assertion if we don't disconnect the leaf Request
+    // in the promise chain.
+    holder.Begin(p->Then(queue, __func__, [] () {}, [] () {}));
+  });
+}
+
 #undef DO_FAIL
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -63,17 +63,16 @@ if CONFIG['MOZ_EME']:
 TEST_DIRS += [
     'compiledtest',
     'gtest',
 ]
 
 MOCHITEST_MANIFESTS += [
     'test/mochitest.ini',
     'tests/mochitest/identity/mochitest.ini',
-    'tests/mochitest/ipc/mochitest.ini',
 ]
 
 if CONFIG['MOZ_WEBRTC']:
     MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
     WEBRTC_SIGNALLING_TEST_MANIFESTS += ['tests/mochitest/steeplechase.ini']
     WEBRTC_SIGNALLING_TEST_MANIFESTS += ['tests/mochitest/steeplechase_long/steeplechase_long.ini']
 
 XPIDL_SOURCES += [
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -151,23 +151,33 @@ class CreateDXVAManagerEvent : public ns
 public:
   CreateDXVAManagerEvent(LayersBackend aBackend, nsCString& aFailureReason)
     : mBackend(aBackend)
     , mFailureReason(aFailureReason)
   {}
 
   NS_IMETHOD Run() {
     NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
+    nsACString* failureReason = &mFailureReason;
+    nsCString secondFailureReason;
     if (mBackend == LayersBackend::LAYERS_D3D11 &&
         Preferences::GetBool("media.windows-media-foundation.allow-d3d11-dxva", true) &&
         IsWin8OrLater()) {
-      mDXVA2Manager = DXVA2Manager::CreateD3D11DXVA(mFailureReason);
-    } else {
-      mDXVA2Manager = DXVA2Manager::CreateD3D9DXVA(mFailureReason);
+      mDXVA2Manager = DXVA2Manager::CreateD3D11DXVA(*failureReason);
+      if (mDXVA2Manager) {
+        return NS_OK;
+      }
+      // Try again with d3d9, but record the failure reason
+      // into a new var to avoid overwriting the d3d11 failure.
+      failureReason = &secondFailureReason;
+      mFailureReason.Append(NS_LITERAL_CSTRING("; "));
     }
+    mDXVA2Manager = DXVA2Manager::CreateD3D9DXVA(*failureReason);
+    // Make sure we include the messages from both attempts (if applicable).
+    mFailureReason.Append(secondFailureReason);
     return NS_OK;
   }
   nsAutoPtr<DXVA2Manager> mDXVA2Manager;
   LayersBackend mBackend;
   nsACString& mFailureReason;
 };
 
 bool
@@ -201,24 +211,24 @@ WMFVideoMFTManager::InitializeDXVA(bool 
   return mDXVA2Manager != nullptr;
 }
 
 bool
 WMFVideoMFTManager::Init()
 {
   bool success = InitInternal(/* aForceD3D9 = */ false);
 
-  // If initialization failed with d3d11 DXVA then try falling back
-  // to d3d9.
-  if (!success && mDXVA2Manager && mDXVA2Manager->IsD3D11()) {
-    mDXVA2Manager = nullptr;
-    nsCString d3d11Failure = mDXVAFailureReason;
-    success = InitInternal(true);
-    mDXVAFailureReason.Append(NS_LITERAL_CSTRING("; "));
-    mDXVAFailureReason.Append(d3d11Failure);
+  if (success && mDXVA2Manager) {
+    // If we had some failures but eventually made it work,
+    // make sure we preserve the messages.
+    if (mDXVA2Manager->IsD3D11()) {
+      mDXVAFailureReason.Append(NS_LITERAL_CSTRING("Using D3D11 API"));
+    } else {
+      mDXVAFailureReason.Append(NS_LITERAL_CSTRING("Using D3D9 API"));
+    }
   }
 
   return success;
 }
 
 bool
 WMFVideoMFTManager::InitInternal(bool aForceD3D9)
 {
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -572,35 +572,30 @@ support-files =
   wavedata_ulaw.wav
   wavedata_ulaw.wav^headers^
 
 [test_access_control.html]
 skip-if = buildapp == 'b2g' && toolkit != 'gonk' # bug 1082984
 [test_aspectratio_mp4.html]
 [test_audio1.html]
 [test_audio2.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_audioDocumentTitle.html]
 skip-if = true # bug 475110 - disabled since we don't play Wave files standalone
 [test_autoplay.html]
 [test_autoplay_contentEditable.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_buffered.html]
 [test_bug448534.html]
-skip-if = buildapp == 'mulet' || os == 'win' || (toolkit == 'android' && processor == 'x86') # bug 894922 #x86 only bug 914439
+skip-if = buildapp == 'mulet' || os == 'win' # bug 894922
 [test_bug463162.xhtml]
 [test_bug465498.html]
-skip-if = (toolkit == 'android' && processor == 'x86')
 [test_bug495145.html]
-skip-if = (toolkit == 'android' && processor == 'x86') || (os == 'mac' && os_version == '10.6') #x86 only bug 914439, bug 1021174
+skip-if = (os == 'mac' && os_version == '10.6') # bug 1021174
 [test_bug495300.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_bug654550.html]
 [test_bug686942.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_bug726904.html]
 [test_bug874897.html]
 [test_bug879717.html]
 tags=capturestream
 skip-if = os == 'win' && !debug # bug 1140675
 [test_bug883173.html]
 [test_bug895091.html]
 [test_bug895305.html]
@@ -609,22 +604,21 @@ skip-if = os == 'win' && !debug # bug 11
 [test_bug1018933.html]
 [test_bug1113600.html]
 tags=capturestream
 [test_bug1242338.html]
 [test_bug1248229.html]
 tags=capturestream
 [test_can_play_type.html]
 [test_can_play_type_mpeg.html]
-skip-if = buildapp == 'b2g' || (toolkit == 'android' && processor == 'x86') # bug 1021675 #x86 only bug 914439
+skip-if = buildapp == 'b2g' # bug 1021675
 [test_can_play_type_no_ogg.html]
 [test_can_play_type_ogg.html]
 [test_chaining.html]
 [test_clone_media_element.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_closing_connections.html]
 [test_constants.html]
 [test_controls.html]
 [test_cueless_webm_seek-1.html]
 [test_cueless_webm_seek-2.html]
 [test_cueless_webm_seek-3.html]
 [test_currentTime.html]
 [test_decode_error.html]
@@ -663,38 +657,31 @@ skip-if = toolkit == 'android' || toolki
 tags=msg capturestream
 skip-if = toolkit == 'android' || toolkit == 'gonk' # android: bug 1149374; gonk: bug 1193351
 [test_empty_resource.html]
 skip-if = os == 'win' && debug #win debug : Bug 1202683
 [test_error_in_video_document.html]
 skip-if = toolkit == 'android' || (os == 'win' && !debug) || (os == 'mac' && !debug) # bug 608634
 [test_error_on_404.html]
 [test_fastSeek.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_fastSeek-forwards.html]
 [test_gmp_playback.html]
 skip-if = (os != 'win' || os_version == '5.1') # Only gmp-clearkey on Windows Vista and later decodes
 [test_imagecapture.html]
 [test_info_leak.html]
 [test_invalid_reject.html]
 [test_invalid_reject_play.html]
 [test_invalid_seek.html]
 [test_load.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_load_candidates.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_load_same_resource.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_load_source.html]
 [test_loop.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_media_selection.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_media_sniffer.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_mediarecorder_avoid_recursion.html]
 skip-if = os == 'win' && !debug # bug 1228605
 tags=msg
 [test_mediarecorder_bitrate.html]
 skip-if = (toolkit == 'gonk' || toolkit == 'android') # B2G emulator is too slow to run this without timing out and Fennec does not support video recording
 tags=msg
 [test_mediarecorder_creation.html]
 tags=msg capturestream
@@ -753,94 +740,78 @@ skip-if = toolkit == 'gonk' && debug # b
 [test_mediatrack_replay_from_end.html]
 [test_metadata.html]
 [test_mixed_principals.html]
 skip-if = true # bug 567954 and intermittent leaks
 [test_mozHasAudio.html]
 [test_multiple_mediastreamtracks.html]
 [test_networkState.html]
 [test_new_audio.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_no_load_event.html]
 [test_paused.html]
 [test_paused_after_ended.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_play_events.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_play_events_2.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_play_twice.html]
 # Seamonkey: Bug 598252
 skip-if = appname == "seamonkey"
 [test_playback.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_playback_errors.html]
 skip-if = toolkit == 'gonk' # bug 1128845
 [test_playback_rate.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #bug 845162
 [test_playback_rate_playpause.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_playback_reactivate.html]
-#skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_played.html]
 [test_preload_actions.html]
 [test_preload_attribute.html]
 [test_preload_suspend.html]
 [test_progress.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_reactivate.html]
-skip-if = toolkit == 'gonk' || (toolkit == 'android' && processor == 'x86') #x86 only bug 914439 and bug 1128845 on gonk
+skip-if = toolkit == 'gonk' # bug 1128845 on gonk
 [test_readyState.html]
 [test_referer.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_replay_metadata.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_reset_events_async.html]
 [test_reset_src.html]
 [test_video_dimensions.html]
 tags=capturestream
 [test_resume.html]
 skip-if = true # bug 1021673
 [test_seek_negative.html]
 [test_seek_nosrc.html]
 [test_seek_out_of_range.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_seek-1.html]
-skip-if = android_version == '10' # bug 1059116
 [test_seek-2.html]
 [test_seek-3.html]
 [test_seek-4.html]
 [test_seek-5.html]
 [test_seek-6.html]
 [test_seek-7.html]
 [test_seek-8.html]
 [test_seek-9.html]
 [test_seek-10.html]
 [test_seek-11.html]
 [test_seek-12.html]
 [test_seek-13.html]
 [test_seekable1.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #timeout x86 only bug 914439
 [test_seekLies.html]
 [test_source.html]
 [test_source_media.html]
 [test_source_null.html]
 [test_source_write.html]
 [test_standalone.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_streams_autoplay.html]
 tags=msg capturestream
 [test_streams_capture_origin.html]
 tags=msg capturestream
 [test_streams_element_capture.html]
 #x86 only bug 914439, b2g desktop bug 752796
-skip-if = (toolkit == 'android' && processor == 'x86') || (buildapp == 'b2g' && toolkit != 'gonk')
+skip-if = (buildapp == 'b2g' && toolkit != 'gonk')
 tags=msg capturestream
 [test_streams_element_capture_createObjectURL.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 tags=msg capturestream
 [test_streams_element_capture_playback.html]
 tags=msg capturestream
 [test_streams_element_capture_reset.html]
 tags=msg capturestream
 [test_streams_gc.html]
 skip-if = buildapp == 'b2g' && toolkit != 'gonk' # bug 1096270
 tags=msg capturestream
@@ -854,23 +825,21 @@ tags=msg capturestream
 [test_texttrackcue.html]
 [test_texttrackcue_moz.html]
 [test_texttrackevents_video.html]
 [test_texttracklist.html]
 [test_texttracklist_moz.html]
 [test_texttrackregion.html]
 [test_texttrack_moz.html]
 [test_timeupdate_small_files.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_trackelementevent.html]
 [test_trackevent.html]
 [test_unseekable.html]
-skip-if = toolkit == 'gonk' || (toolkit == 'android' && processor == 'x86') #x86 only and bug 1128845 on gonk
+skip-if = toolkit == 'gonk' # bug 1128845 on gonk
 [test_video_to_canvas.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_video_in_audio_element.html]
 [test_videoDocumentTitle.html]
 [test_VideoPlaybackQuality.html]
 [test_VideoPlaybackQuality_disabled.html]
 [test_volume.html]
 skip-if = e10s && debug && os == 'win' # bug 1245574
 [test_vttparser.html]
 skip-if = e10s && debug && os == 'win'
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -263,34 +263,46 @@ var setTestOptions;
 var testConfigured = new Promise(r => setTestOptions = r);
 
 function setupEnvironment() {
   if (!window.SimpleTest) {
     // Running under Steeplechase
     return;
   }
 
-  // Running as a Mochitest.
-  SimpleTest.requestFlakyTimeout("WebRTC inherently depends on timeouts");
-  window.finish = () => SimpleTest.finish();
-  SpecialPowers.pushPrefEnv({
+  var defaultMochitestPrefs = {
     'set': [
       ['media.peerconnection.enabled', true],
       ['media.peerconnection.identity.enabled', true],
       ['media.peerconnection.identity.timeout', 120000],
       ['media.peerconnection.ice.stun_client_maximum_transmits', 14],
       ['media.peerconnection.ice.trickle_grace_period', 30000],
       ['media.navigator.permission.disabled', true],
       ['media.navigator.streams.fake', FAKE_ENABLED],
       ['media.getusermedia.screensharing.enabled', true],
       ['media.getusermedia.screensharing.allowed_domains', "mochi.test"],
       ['media.getusermedia.audiocapture.enabled', true],
       ['media.recorder.audio_node.enabled', true]
     ]
-  }, setTestOptions);
+  };
+
+  const isAndroid = !!navigator.userAgent.includes("Android");
+
+  if (isAndroid) {
+    defaultMochitestPrefs.set.push(
+      ["media.navigator.video.default_width", 320],
+      ["media.navigator.video.default_height", 240],
+      ["media.navigator.video.max_fr", 10]
+    );
+  }
+
+  // Running as a Mochitest.
+  SimpleTest.requestFlakyTimeout("WebRTC inherently depends on timeouts");
+  window.finish = () => SimpleTest.finish();
+  SpecialPowers.pushPrefEnv(defaultMochitestPrefs, setTestOptions);
 
   // We don't care about waiting for this to complete, we just want to ensure
   // that we don't build up a huge backlog of GC work.
   SpecialPowers.exactGC(window);
 }
 
 // This is called by steeplechase; which provides the test configuration options
 // directly to the test through this function.  If we're not on steeplechase,
--- a/dom/media/tests/mochitest/identity/mochitest.ini
+++ b/dom/media/tests/mochitest/identity/mochitest.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
-# Android 2.3 - bug 981881
 # won't run on b2g desktop tests - bug 1119993
 # broken HTTPS on b2g emulator - bug 1135339
+# Android 4.3 - bug 981881
+# Exit code -11 for linux/opt/non-e10s - bug 1256284
 subsuite = media
-skip-if = android_version == '10' || android_version == '18' || (buildapp == 'b2g' && toolkit != 'gonk') || (buildapp == 'b2g' && toolkit == 'gonk') || buildapp == 'mulet'
+skip-if = android_version == '18' || (buildapp == 'b2g' && toolkit != 'gonk') || (buildapp == 'b2g' && toolkit == 'gonk') || buildapp == 'mulet' || (os == 'linux' && !debug && !e10s)
 support-files =
   /.well-known/idp-proxy/idp.js
   identityPcTest.js
 tags = msg
 
 [test_idpproxy.html]
 support-files =
   /.well-known/idp-proxy/idp-redirect-http.js
deleted file mode 100644
--- a/dom/media/tests/mochitest/ipc/ipc.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-"runtests":{
-    "dom/media/tests/mochitest/test_getUserMedia_basicVideoAudio.html":"included",
-    "dom/media/tests/mochitest/test_peerConnection_basicAudioVideo.html":"included",
-    "dom/media/tests/mochitest/test_dataChannel_basicAudioVideo.html":"included"
-},
-"excludetests":{
-   }
-}
deleted file mode 100644
--- a/dom/media/tests/mochitest/ipc/mochitest.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[DEFAULT]
-tags=msg
-subsuite=media
-support-files =
-  ipc.json
-
-skip-if = e10s
-
-[test_ipc.html]
-skip-if =  buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'android' #bug 910661 # b2g(nested ipc not working) b2g-debug(debug-only failure) b2g-desktop(nested ipc not working)
deleted file mode 100644
--- a/dom/media/tests/mochitest/ipc/test_ipc.html
+++ /dev/null
@@ -1,176 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test for OOP PeerConncection</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-  <body>
-
-  <script type="application/javascript;version=1.7">
-    "use strict";
-
-    SimpleTest.waitForExplicitFinish();
-
-    // This isn't a single test. It runs the entirety of the PeerConnection
-    // tests. Each of those has a normal timeout handler, so there's no point in
-    // having a timeout here. I'm setting this really high just to avoid getting
-    // killed.
-    SimpleTest.requestLongerTimeout(100);
-
-    // The crash observer registration functions are stubbed out here to
-    // prevent the iframe test runner from breaking later crash-related tests.
-    function iframeScriptFirst() {
-      SpecialPowers.prototype.registerProcessCrashObservers = () => {};
-      SpecialPowers.prototype.unregisterProcessCrashObservers = () => {};
-
-      content.wrappedJSObject.RunSet.reloadAndRunAll({
-        preventDefault: function() { },
-        __exposedProps__: { preventDefault: 'r' }
-      });
-    }
-
-    function iframeScriptSecond() {
-      let TestRunner = content.wrappedJSObject.TestRunner;
-      let oldComplete = TestRunner.onComplete;
-
-      TestRunner.onComplete = function() {
-        TestRunner.onComplete = oldComplete;
-
-        sendAsyncMessage("test:PeerConnection:ipcTestComplete", {
-          result: JSON.stringify(TestRunner._failedTests)
-        });
-
-        if (oldComplete) {
-          oldComplete();
-        }
-      };
-
-      TestRunner.structuredLogger._dumpMessage = function(msg) {
-        sendAsyncMessage("test:PeerConnection:ipcTestMessage", { msg: msg });
-      }
-    }
-
-    let VALID_ACTIONS = ['suite_start', 'suite_end', 'test_start', 'test_end', 'test_status', 'process_output', 'log'];
-    function validStructuredMessage(message) {
-      return message.action !== undefined && VALID_ACTIONS.indexOf(message.action) >= 0;
-    }
-    function onTestMessage(data) {
-      let message = SpecialPowers.wrap(data).data.msg;
-
-      if (validStructuredMessage(message)) {
-        switch (message.action) {
-          case "test_status":
-          case "test_end":
-            let test_tokens = message.test.split("/");
-            let test_name = test_tokens[test_tokens.length - 1];
-            if (message.subtest) {
-                test_name += " | " + message.subtest;
-            }
-            ok(message.expected === undefined, test_name, message.message);
-            break;
-          case "log":
-            info(message.message);
-            break;
-          default:
-            // nothing
-        }
-      }
-    }
-
-    function onTestComplete() {
-      let comp = SpecialPowers.wrap(SpecialPowers.Components);
-      let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
-      let spObserver = comp.classes["@mozilla.org/special-powers-observer;1"]
-                            .getService(comp.interfaces.nsIMessageListener);
-
-      mm.removeMessageListener("SPPrefService", spObserver);
-      mm.removeMessageListener("SPProcessCrashService", spObserver);
-      mm.removeMessageListener("SPPingService", spObserver);
-      mm.removeMessageListener("SpecialPowers.Quit", spObserver);
-      mm.removeMessageListener("SPPermissionManager", spObserver);
-
-      mm.removeMessageListener("test:PeerConnection:ipcTestMessage", onTestMessage);
-      mm.removeMessageListener("test:PeerConnection:ipcTestComplete", onTestComplete);
-
-      SimpleTest.executeSoon(function () { SimpleTest.finish(); });
-    }
-
-    function runTests() {
-      let iframe = document.createElement("iframe");
-      SpecialPowers.wrap(iframe).mozbrowser = true;
-      iframe.id = "iframe";
-      iframe.style.width = "100%";
-      iframe.style.height = "1000px";
-
-      function iframeLoadSecond() {
-        ok(true, "Got second iframe load event.");
-        iframe.removeEventListener("mozbrowserloadend", iframeLoadSecond);
-        let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
-        mm.loadFrameScript("data:,(" + iframeScriptSecond.toString() + ")();",
-                           false);
-      }
-
-      function iframeLoadFirst() {
-        ok(true, "Got first iframe load event.");
-        iframe.removeEventListener("mozbrowserloadend", iframeLoadFirst);
-        iframe.addEventListener("mozbrowserloadend", iframeLoadSecond);
-
-        let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
-
-        let comp = SpecialPowers.wrap(SpecialPowers.Components);
-
-        let spObserver =
-          comp.classes["@mozilla.org/special-powers-observer;1"]
-              .getService(comp.interfaces.nsIMessageListener);
-
-        mm.addMessageListener("SPPrefService", spObserver);
-        mm.addMessageListener("SPProcessCrashService", spObserver);
-        mm.addMessageListener("SPPingService", spObserver);
-        mm.addMessageListener("SpecialPowers.Quit", spObserver);
-        mm.addMessageListener("SPPermissionManager", spObserver);
-
-        mm.addMessageListener("test:PeerConnection:ipcTestMessage", onTestMessage);
-        mm.addMessageListener("test:PeerConnection:ipcTestComplete", onTestComplete);
-
-        let specialPowersBase = "chrome://specialpowers/content/";
-        mm.loadFrameScript(specialPowersBase + "MozillaLogger.js", false);
-        mm.loadFrameScript(specialPowersBase + "specialpowersAPI.js", false);
-        mm.loadFrameScript(specialPowersBase + "specialpowers.js", false);
-
-        mm.loadFrameScript("data:,(" + iframeScriptFirst.toString() + ")();", false);
-      }
-
-      iframe.addEventListener("mozbrowserloadend", iframeLoadFirst);
-
-      // Strip the filename and a directory level.
-      let href =  window.location.href;
-      href = href.substring(0, href.lastIndexOf('/'));
-      href = href.substring(0, href.lastIndexOf('/'));
-
-      href += "?consoleLevel=INFO" +
-              "&testManifest=tests/dom/media/tests/mochitest/ipc/ipc.json";
-      iframe.src = href;
-      document.body.appendChild(iframe);
-    }
-
-    addEventListener("load", function() {
-
-      SpecialPowers.addPermission("browser", true, document);
-      SpecialPowers.pushPrefEnv({
-        "set": [
-          ["media.peerconnection.ipc.enabled", true],
-
-          // TODO: remove this as part of bug 820712
-          ["network.disable.ipc.security", true],
-
-          ["dom.ipc.browser_frames.oop_by_default", true],
-          ["dom.mozBrowserFramesEnabled", true],
-          ["browser.pagethumbnails.capturing_disabled", true]
-        ]
-      }, runTests);
-    });
-
-  </script>
-</body>
-</html>
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -1,13 +1,12 @@
 [DEFAULT]
-# Android 2.3 - bug 981881
 tags = msg webrtc
 subsuite = media
-skip-if = android_version == '10' || (buildapp == 'mulet') || (toolkit == 'gonk' && debug) # b2g(Either bug 1171118 or bug 1169838, take your pick)
+skip-if = (buildapp == 'mulet') || (toolkit == 'gonk' && debug) # b2g(Either bug 1171118 or bug 1169838, take your pick)
 support-files =
   head.js
   dataChannel.js
   mediaStreamPlayback.js
   network.js
   nonTrickleIce.js
   pc.js
   templates.js
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -36,17 +36,17 @@ support-files =
 [test_AudioBuffer.html]
 [test_audioBufferSourceNode.html]
 [test_audioBufferSourceNodeEnded.html]
 [test_audioBufferSourceNodeLazyLoopParam.html]
 [test_audioBufferSourceNodeLoop.html]
 [test_audioBufferSourceNodeLoopStartEnd.html]
 [test_audioBufferSourceNodeLoopStartEndSame.html]
 [test_audioBufferSourceNodeDetached.html]
-skip-if = (toolkit == 'android' && (processor == 'x86' || debug)) || os == 'win' # bug 1127845, bug 1138468
+skip-if = (toolkit == 'android' && debug) || os == 'win' # bug 1127845, bug 1138468
 [test_audioBufferSourceNodeNoStart.html]
 [test_audioBufferSourceNodeNullBuffer.html]
 [test_audioBufferSourceNodeOffset.html]
 skip-if = (toolkit == 'gonk') || (toolkit == 'android') || debug #bug 906752
 [test_audioBufferSourceNodePassThrough.html]
 [test_audioBufferSourceNodeRate.html]
 [test_AudioContext.html]
 skip-if = android_version == '10' # bug 1138462
@@ -141,20 +141,20 @@ skip-if = toolkit == 'android' # bug 114
 [test_mediaStreamAudioDestinationNode.html]
 [test_mediaStreamAudioSourceNode.html]
 [test_mediaStreamAudioSourceNodeCrossOrigin.html]
 tags=capturestream
 [test_mediaStreamAudioSourceNodePassThrough.html]
 [test_mediaStreamAudioSourceNodeResampling.html]
 tags=capturestream
 [test_mixingRules.html]
-skip-if = android_version == '10' || android_version == '18' # bug 1091965
+skip-if = toolkit == 'android' # bug 1091965
 [test_mozaudiochannel.html]
 # Android: bug 1061675; OSX 10.6: bug 1097721
-skip-if = (toolkit == 'gonk' && !debug) || android_version == '10' || android_version == '18' || (os == 'mac' && os_version == '10.6')
+skip-if = (toolkit == 'gonk' && !debug) || (toolkit == 'android') || (os == 'mac' && os_version == '10.6')
 [test_nodeToParamConnection.html]
 [test_OfflineAudioContext.html]
 [test_offlineDestinationChannelCountLess.html]
 [test_offlineDestinationChannelCountMore.html]
 [test_oscillatorNode.html]
 [test_oscillatorNode2.html]
 [test_oscillatorNodeNegativeFrequency.html]
 [test_oscillatorNodePassThrough.html]
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -37,16 +37,17 @@ MediaEngineTabVideoSource::MediaEngineTa
   , mWindowId(0)
   , mScrollWithPage(false)
   , mViewportOffsetX(0)
   , mViewportOffsetY(0)
   , mViewportWidth(0)
   , mViewportHeight(0)
   , mTimePerFrame(0)
   , mDataSize(0)
+  , mBlackedoutWindow(false)
   , mMonitor("MediaEngineTabVideoSource") {}
 
 nsresult
 MediaEngineTabVideoSource::StartRunnable::Run()
 {
   mVideoSource->Draw();
   mVideoSource->mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   mVideoSource->mTimer->InitWithCallback(mVideoSource, mVideoSource->mTimePerFrame, nsITimer:: TYPE_REPEATING_SLACK);
@@ -75,40 +76,48 @@ MediaEngineTabVideoSource::HandleEvent(n
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MediaEngineTabVideoSource::Notify(nsITimer*) {
   Draw();
   return NS_OK;
 }
-#define LOGTAG "TabVideo"
 
 nsresult
 MediaEngineTabVideoSource::InitRunnable::Run()
 {
   if (mVideoSource->mWindowId != -1) {
-    nsCOMPtr<nsPIDOMWindowOuter> window =
-      nsGlobalWindow::GetOuterWindowWithId(mVideoSource->mWindowId)->AsOuter();
-    if (window) {
-      mVideoSource->mWindow = window;
+    nsGlobalWindow* globalWindow =
+      nsGlobalWindow::GetOuterWindowWithId(mVideoSource->mWindowId);
+    if (!globalWindow) {
+      // We can't access the window, just send a blacked out screen.
+      mVideoSource->mWindow = nullptr;
+      mVideoSource->mBlackedoutWindow = true;
+    } else {
+      nsCOMPtr<nsPIDOMWindowOuter> window = globalWindow->AsOuter();
+      if (window) {
+        mVideoSource->mWindow = window;
+        mVideoSource->mBlackedoutWindow = false;
+      }
     }
   }
-  if (!mVideoSource->mWindow) {
+  if (!mVideoSource->mWindow && !mVideoSource->mBlackedoutWindow) {
     nsresult rv;
     mVideoSource->mTabSource = do_GetService(NS_TABSOURCESERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<mozIDOMWindowProxy> win;
     rv = mVideoSource->mTabSource->GetTabToStream(getter_AddRefs(win));
     NS_ENSURE_SUCCESS(rv, rv);
     if (!win)
       return NS_OK;
 
     mVideoSource->mWindow = nsPIDOMWindowOuter::From(win);
+    MOZ_ASSERT(mVideoSource->mWindow);
   }
   nsCOMPtr<nsIRunnable> start(new StartRunnable(mVideoSource));
   start->Run();
   return NS_OK;
 }
 
 void
 MediaEngineTabVideoSource::GetName(nsAString_internal& aName)
@@ -206,34 +215,43 @@ MediaEngineTabVideoSource::NotifyPull(Me
     // This can fail if either a) we haven't added the track yet, or b)
     // we've removed or finished the track.
     aSource->AppendToTrack(aID, &(segment));
   }
 }
 
 void
 MediaEngineTabVideoSource::Draw() {
-  if (!mWindow) {
+  if (!mWindow && !mBlackedoutWindow) {
     return;
   }
 
-  if (mScrollWithPage || mViewportWidth == INT32_MAX) {
-    mWindow->GetInnerWidth(&mViewportWidth);
-  }
-  if (mScrollWithPage || mViewportHeight == INT32_MAX) {
-    mWindow->GetInnerHeight(&mViewportHeight);
-  }
-  if (!mViewportWidth || !mViewportHeight) {
-    return;
+  if (mWindow) {
+    if (mScrollWithPage || mViewportWidth == INT32_MAX) {
+      mWindow->GetInnerWidth(&mViewportWidth);
+    }
+    if (mScrollWithPage || mViewportHeight == INT32_MAX) {
+      mWindow->GetInnerHeight(&mViewportHeight);
+    }
+    if (!mViewportWidth || !mViewportHeight) {
+      return;
+    }
+  } else {
+    mViewportWidth = 640;
+    mViewportHeight = 480;
   }
 
   IntSize size;
   {
     float pixelRatio;
-    mWindow->GetDevicePixelRatio(&pixelRatio);
+    if (mWindow) {
+      mWindow->GetDevicePixelRatio(&pixelRatio);
+    } else {
+      pixelRatio = 1.0f;
+    }
     const int32_t deviceWidth = (int32_t)(pixelRatio * mViewportWidth);
     const int32_t deviceHeight = (int32_t)(pixelRatio * mViewportHeight);
 
     if ((deviceWidth <= mBufWidthMax) && (deviceHeight <= mBufHeightMax)) {
       size = IntSize(deviceWidth, deviceHeight);
     } else {
       const float scaleWidth = (float)mBufWidthMax / (float)deviceWidth;
       const float scaleHeight = (float)mBufHeightMax / (float)deviceHeight;
@@ -250,69 +268,73 @@ MediaEngineTabVideoSource::Draw() {
     mDataSize = stride * size.height;
     mData = MakeUniqueFallible<unsigned char[]>(mDataSize);
   }
   if (!mData) {
     return;
   }
 
   nsCOMPtr<nsIPresShell> presShell;
-  {
+  if (mWindow) {
     RefPtr<nsPresContext> presContext;
     nsIDocShell* docshell = mWindow->GetDocShell();
     if (docshell) {
       docshell->GetPresContext(getter_AddRefs(presContext));
     }
     if (!presContext) {
       return;
     }
     presShell = presContext->PresShell();
   }
 
-  nscolor bgColor = NS_RGB(255, 255, 255);
-  uint32_t renderDocFlags = mScrollWithPage? 0 :
-      (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
-       nsIPresShell::RENDER_DOCUMENT_RELATIVE);
-  nsRect r(nsPresContext::CSSPixelsToAppUnits((float)mViewportOffsetX),
-           nsPresContext::CSSPixelsToAppUnits((float)mViewportOffsetY),
-           nsPresContext::CSSPixelsToAppUnits((float)mViewportWidth),
-           nsPresContext::CSSPixelsToAppUnits((float)mViewportHeight));
-
   RefPtr<layers::ImageContainer> container = layers::LayerManager::CreateImageContainer();
   RefPtr<DrawTarget> dt =
     Factory::CreateDrawTargetForData(BackendType::CAIRO,
                                      mData.get(),
                                      size,
                                      stride,
                                      SurfaceFormat::B8G8R8X8);
   if (!dt) {
     return;
   }
   RefPtr<gfxContext> context = new gfxContext(dt);
   context->SetMatrix(context->CurrentMatrix().Scale((((float) size.width)/mViewportWidth),
                                                     (((float) size.height)/mViewportHeight)));
 
-  NS_ENSURE_SUCCESS_VOID(presShell->RenderDocument(r, renderDocFlags, bgColor, context));
+  if (mWindow) {
+    nscolor bgColor = NS_RGB(255, 255, 255);
+    uint32_t renderDocFlags = mScrollWithPage? 0 :
+      (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING |
+       nsIPresShell::RENDER_DOCUMENT_RELATIVE);
+    nsRect r(nsPresContext::CSSPixelsToAppUnits((float)mViewportOffsetX),
+             nsPresContext::CSSPixelsToAppUnits((float)mViewportOffsetY),
+             nsPresContext::CSSPixelsToAppUnits((float)mViewportWidth),
+             nsPresContext::CSSPixelsToAppUnits((float)mViewportHeight));
+    NS_ENSURE_SUCCESS_VOID(presShell->RenderDocument(r, renderDocFlags, bgColor, context));
+  }
 
   RefPtr<SourceSurface> surface = dt->Snapshot();
   if (!surface) {
     return;
   }
 
   RefPtr<layers::SourceSurfaceImage> image = new layers::SourceSurfaceImage(size, surface);
 
   MonitorAutoLock mon(mMonitor);
   mImage = image;
 }
 
 nsresult
 MediaEngineTabVideoSource::Stop(mozilla::SourceMediaStream*, mozilla::TrackID)
 {
-  if (!mWindow)
+  // If mBlackedoutWindow is true, we may be running
+  // despite mWindow == nullptr.
+  if (!mWindow && !mBlackedoutWindow) {
     return NS_OK;
+  }
 
   NS_DispatchToMainThread(new StopRunnable(this));
   return NS_OK;
 }
 
 bool
 MediaEngineTabVideoSource::IsFake()
 {
--- a/dom/media/webrtc/MediaEngineTabVideoSource.h
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.h
@@ -84,14 +84,16 @@ private:
     int32_t mViewportOffsetX;
     int32_t mViewportOffsetY;
     int32_t mViewportWidth;
     int32_t mViewportHeight;
     int32_t mTimePerFrame;
     UniquePtr<unsigned char[]> mData;
     size_t mDataSize;
     nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+    // If this is set, we will run despite mWindow == nullptr.
+    bool mBlackedoutWindow;
     RefPtr<layers::SourceSurfaceImage> mImage;
     nsCOMPtr<nsITimer> mTimer;
     Monitor mMonitor;
     nsCOMPtr<nsITabSource> mTabSource;
   };
 }
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -215,19 +215,16 @@ MediaEngineWebRTC::EnumerateVideoDevices
    * Enumeration is not neccessary if GIPS reports the same set of devices
    * for a given instance of the engine. Likewise, if a device was plugged out,
    * mVideoSources must be updated.
    */
   int num;
   num = mozilla::camera::GetChildAndCall(
     &mozilla::camera::CamerasChild::NumberOfCaptureDevices,
     capEngine);
-  if (num <= 0) {
-    return;
-  }
 
   for (int i = 0; i < num; i++) {
     char deviceName[MediaEngineSource::kMaxDeviceNameLength];
     char uniqueId[MediaEngineSource::kMaxUniqueIdLength];
 
     // paranoia
     deviceName[0] = '\0';
     uniqueId[0] = '\0';
--- a/dom/media/webspeech/synth/SpeechSynthesisUtterance.cpp
+++ b/dom/media/webspeech/synth/SpeechSynthesisUtterance.cpp
@@ -8,16 +8,18 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsGkAtoms.h"
 
 #include "mozilla/dom/SpeechSynthesisEvent.h"
 #include "mozilla/dom/SpeechSynthesisUtteranceBinding.h"
 #include "SpeechSynthesisUtterance.h"
 #include "SpeechSynthesisVoice.h"
 
+#include <stdlib.h>
+
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(SpeechSynthesisUtterance,
                                    DOMEventTargetHelper, mVoice);
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SpeechSynthesisUtterance)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
@@ -115,41 +117,41 @@ float
 SpeechSynthesisUtterance::Volume() const
 {
   return mVolume;
 }
 
 void
 SpeechSynthesisUtterance::SetVolume(float aVolume)
 {
-  mVolume = aVolume;
+  mVolume = std::max<float>(std::min<float>(aVolume, 1), 0);
 }
 
 float
 SpeechSynthesisUtterance::Rate() const
 {
   return mRate;
 }
 
 void
 SpeechSynthesisUtterance::SetRate(float aRate)
 {
-  mRate = aRate;
+  mRate = std::max<float>(std::min<float>(aRate, 10), 0.1f);
 }
 
 float
 SpeechSynthesisUtterance::Pitch() const
 {
   return mPitch;
 }
 
 void
 SpeechSynthesisUtterance::SetPitch(float aPitch)
 {
-  mPitch = aPitch;
+  mPitch = std::max<float>(std::min<float>(aPitch, 2), 0);
 }
 
 void
 SpeechSynthesisUtterance::GetChosenVoiceURI(nsString& aResult) const
 {
   aResult = mChosenVoiceURI;
 }
 
--- a/dom/media/webspeech/synth/test/file_setup.html
+++ b/dom/media/webspeech/synth/test/file_setup.html
@@ -43,16 +43,34 @@ ssu.lang = "he-IL";
 ssu.volume = 0.5;
 ssu.rate = 2.0;
 ssu.pitch = 1.5;
 is(ssu.lang, "he-IL", "SpeechSynthesisUtterance.lang is correct");
 is(ssu.volume, 0.5,  "SpeechSynthesisUtterance.volume is correct");
 is(ssu.rate, 2.0,  "SpeechSynthesisUtterance.rate is correct");
 is(ssu.pitch, 1.5,  "SpeechSynthesisUtterance.pitch is correct");
 
+// Assign a rate that is out of bounds
+ssu.rate = 20;
+is(ssu.rate, 10, "SpeechSynthesisUtterance.rate enforces max of 10");
+ssu.rate = 0;
+is(ssu.rate.toPrecision(1), "0.1", "SpeechSynthesisUtterance.rate enforces min of 0.1");
+
+// Assign a volume which is out of bounds
+ssu.volume = 2;
+is(ssu.volume, 1, "SpeechSynthesisUtterance.volume enforces max of 1");
+ssu.volume = -1;
+is(ssu.volume, 0, "SpeechSynthesisUtterance.volume enforces min of 0");
+
+// Assign a pitch which is out of bounds
+ssu.pitch = 2.1;
+is(ssu.pitch, 2, "SpeechSynthesisUtterance.pitch enforces max of 2");
+ssu.pitch = -1;
+is(ssu.pitch, 0, "SpeechSynthesisUtterance.pitch enforces min of 0");
+
 // Test for singleton instance hanging off of window.
 ok(speechSynthesis, "speechSynthesis exists in global scope");
 is(typeof speechSynthesis, "object", "speechSynthesis instance is an object");
 is(typeof speechSynthesis.speak, "function", "speechSynthesis.speak is a function");
 is(typeof speechSynthesis.cancel, "function", "speechSynthesis.cancel is a function");
 is(typeof speechSynthesis.pause, "function", "speechSynthesis.pause is a function");
 is(typeof speechSynthesis.resume, "function", "speechSynthesis.resume is a function");
 is(typeof speechSynthesis.getVoices, "function", "speechSynthesis.getVoices is a function");
--- a/dom/push/PushComponents.js
+++ b/dom/push/PushComponents.js
@@ -250,16 +250,27 @@ Object.assign(PushServiceParent.prototyp
 
     return Promise.reject(new Error("Invalid request: unknown name"));
   },
 
   _getResponseName(requestName, suffix) {
     let name = requestName.slice("Push:".length);
     return "PushService:" + name + ":" + suffix;
   },
+
+  // Methods used for mocking in tests.
+
+  replaceServiceBackend(options) {
+    this._service.changeTestServer(options.serverURI, options);
+  },
+
+  restoreServiceBackend() {
+    var defaultServerURL = Services.prefs.getCharPref("dom.push.serverURL");
+    this._service.changeTestServer(defaultServerURL);
+  },
 });
 
 /**
  * The content process implementation of `nsIPushService`. This version
  * uses the child message manager to forward calls to the parent process.
  * The parent Push service instance handles the request, and responds with a
  * message containing the result.
  */
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -230,16 +230,30 @@ this.PushService = {
     } else {
       if (this._state == PUSH_SERVICE_RUNNING) {
         this._service.disconnect();
       }
       this._setState(PUSH_SERVICE_CONNECTION_DISABLE);
     }
   },
 
+  // Used for testing.
+  changeTestServer(url, options = {}) {
+    console.debug("changeTestServer()");
+
+    this._stateChangeProcessEnqueue(_ => {
+      if (this._state < PUSH_SERVICE_ACTIVATING) {
+        console.debug("changeTestServer: PushService not activated?");
+        return Promise.resolve();
+      }
+
+      return this._changeServerURL(url, CHANGING_SERVICE_EVENT, options);
+    });
+  },
+
   observe: function observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       /*
        * We need to call uninit() on shutdown to clean up things that modules
        * aren't very good at automatically cleaning up, so we don't get shutdown
        * leaks on browser shutdown.
        */
       case "quit-application":
@@ -362,17 +376,17 @@ this.PushService = {
       if (connProtocol.validServerURI(uri)) {
         service = connProtocol;
         break;
       }
     }
     return [service, uri];
   },
 
-  _changeServerURL: function(serverURI, event) {
+  _changeServerURL: function(serverURI, event, options = {}) {
     console.debug("changeServerURL()");
 
     switch(event) {
       case UNINIT_EVENT:
         return this._stopService(event);
 
       case STARTING_SERVICE_EVENT:
       {
@@ -387,29 +401,29 @@ this.PushService = {
           );
       }
       case CHANGING_SERVICE_EVENT:
         let [service, uri] = this._findService(serverURI);
         if (service) {
           if (this._state == PUSH_SERVICE_INIT) {
             this._setState(PUSH_SERVICE_ACTIVATING);
             // The service has not been running - start it.
-            return this._startService(service, uri)
+            return this._startService(service, uri, options)
               .then(_ => this._stateChangeProcessEnqueue(_ =>
                 this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled")))
               );
 
           } else {
             this._setState(PUSH_SERVICE_ACTIVATING);
             // If we already had running service - stop service, start the new
             // one and check connection.enabled and offline state(offline state
             // check is called in changeStateConnectionEnabledEvent function)
             return this._stopService(CHANGING_SERVICE_EVENT)
               .then(_ =>
-                 this._startService(service, uri)
+                 this._startService(service, uri, options)
               )
               .then(_ => this._stateChangeProcessEnqueue(_ =>
                 this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled")))
               );
 
           }
         } else {
           if (this._state == PUSH_SERVICE_INIT) {
@@ -605,19 +619,20 @@ this.PushService = {
       return;
     }
 
     prefs.ignore("serverURL", this);
     Services.obs.removeObserver(this, "quit-application");
 
     this._stateChangeProcessEnqueue(_ =>
       {
-        this._changeServerURL("", UNINIT_EVENT);
+        var p = this._changeServerURL("", UNINIT_EVENT);
         this._setState(PUSH_SERVICE_UNINIT);
         console.debug("uninit: shutdown complete!");
+        return p;
       });
   },
 
   /**
    * Drops all active registrations and notifies the associated service
    * workers. This function is called when the user switches Push servers,
    * or when the server invalidates all existing registrations.
    *
--- a/dom/push/test/mochitest.ini
+++ b/dom/push/test/mochitest.ini
@@ -1,17 +1,17 @@
 [DEFAULT]
 subsuite = push
 support-files =
   worker.js
-  push-server.sjs
   frame.html
   webpush.js
   lifetime_worker.js
   test_utils.js
+  mockpushserviceparent.js
 skip-if = os == "android" || toolkit == "gonk"
 
 [test_has_permissions.html]
 [test_permissions.html]
 [test_register.html]
 [test_multiple_register.html]
 [test_multiple_register_during_service_activation.html]
 [test_unregister.html]
new file mode 100644
--- /dev/null
+++ b/dom/push/test/mockpushserviceparent.js
@@ -0,0 +1,111 @@
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+/**
+ * Defers one or more callbacks until the next turn of the event loop. Multiple
+ * callbacks are executed in order.
+ *
+ * @param {Function[]} callbacks The callbacks to execute. One callback will be
+ *  executed per tick.
+ */
+function waterfall(...callbacks) {
+  callbacks.reduce((promise, callback) => promise.then(() => {
+    callback();
+  }), Promise.resolve()).catch(Cu.reportError);
+}
+
+/**
+ * Minimal implementation of a mock WebSocket connect to be used with
+ * PushService. Forwards and receive messages from the implementation
+ * that lives in the content process.
+ */
+function MockWebSocketParent(originalURI) {
+  this._originalURI = originalURI;
+}
+
+MockWebSocketParent.prototype = {
+  _originalURI: null,
+
+  _listener: null,
+  _context: null,
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsISupports,
+    Ci.nsIWebSocketChannel
+  ]),
+
+  get originalURI() {
+    return this._originalURI;
+  },
+
+  asyncOpen(uri, origin, windowId, listener, context) {
+    this._listener = listener;
+    this._context = context;
+    waterfall(() => this._listener.onStart(this._context));
+  },
+
+  sendMsg(msg) {
+    sendAsyncMessage("client-msg", msg);
+  },
+
+  close() {
+    waterfall(() => this._listener.onStop(this._context, Cr.NS_OK));
+  },
+
+  serverSendMsg(msg) {
+    waterfall(() => this._listener.onMessageAvailable(this._context, msg),
+              () => this._listener.onAcknowledge(this._context, 0));
+  },
+};
+
+function MockNetworkInfo() {}
+
+MockNetworkInfo.prototype = {
+  getNetworkInformation() {
+    return {mcc: '', mnc: '', ip: ''};
+  },
+
+  getNetworkState(callback) {
+    callback({mcc: '', mnc: '', ip: '', netid: ''});
+  },
+
+  getNetworkStateChangeEventName() {
+    return 'network:offline-status-changed';
+  }
+};
+
+var pushService = Cc["@mozilla.org/push/Service;1"].
+                  getService(Ci.nsIPushService).
+                  wrappedJSObject;
+
+var mockWebSocket;
+
+addMessageListener("setup", function () {
+  mockWebSocket = new Promise((resolve, reject) => {
+    pushService.replaceServiceBackend({
+      serverURI: "wss://push.example.org/",
+      networkInfo: new MockNetworkInfo(),
+      makeWebSocket(uri) {
+        var socket = new MockWebSocketParent(uri);
+        resolve(socket);
+        return socket;
+      }
+    });
+  });
+});
+
+addMessageListener("teardown", function () {
+  mockWebSocket.then(socket => {
+    socket.close();
+    pushService.restoreServiceBackend();
+  });
+});
+
+addMessageListener("server-msg", function (msg) {
+  mockWebSocket.then(socket => {
+    socket.serverSendMsg(msg);
+  });
+});
deleted file mode 100644
--- a/dom/push/test/push-server.sjs
+++ /dev/null
@@ -1,59 +0,0 @@
-function debug(str) {
-//  dump("@@@ push-server " + str + "\n");
-}
-
-function concatUint8Arrays(arrays, size) {
-  let index = 0;
-  return arrays.reduce((result, a) => {
-    result.set(new Uint8Array(a), index);
-    index += a.byteLength;
-    return result;
-  }, new Uint8Array(size));
-}
-
-function handleRequest(request, response)
-{
-  debug("handling request!");
-
-  const Cc = Components.classes;
-  const Ci = Components.interfaces;
-
-  let params = request.getHeader("X-Push-Server");
-  debug("params = " + params);
-
-  let xhr =  Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
-  xhr.open(request.getHeader("X-Push-Method"), params);
-
-  for (let headers = request.headers; headers.hasMoreElements();) {
-    let header = headers.getNext().QueryInterface(Ci.nsISupportsString).data;
-    if (header.toLowerCase() != "x-push-server") {
-      xhr.setRequestHeader(header, request.getHeader(header));
-    }
-  }
-
-  let bodyStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
-  bodyStream.setInputStream(request.bodyInputStream);
-  let size = 0;
-  let data = [];
-  for (let available; available = bodyStream.available();) {
-    data.push(bodyStream.readByteArray(available));
-    size += available;
-  }
-
-  function reply(statusCode, statusText) {
-    response.setStatusLine(request.httpVersion, statusCode, statusText);
-    response.finish();
-  }
-
-  xhr.onload = function(e) {
-    debug("xhr : " + this.status);
-    reply(this.status, this.statusText);
-  };
-  xhr.onerror = function(e) {
-    debug("xhr error: " + e);
-    reply(500, "Internal Server Error");
-  };
-
-  response.processAsync();
-  xhr.send(concatUint8Arrays(data, size));
-}
--- a/dom/push/test/test_data.html
+++ b/dom/push/test/test_data.html
@@ -20,20 +20,34 @@ http://creativecommons.org/licenses/publ
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 </pre>
 
 <script class="testbody" type="text/javascript">
+  var userAgentID = "ac44402c-85fc-41e4-a0d0-483316d15351";
+  var channelID = null;
+
+  var mockSocket = new MockWebSocket();
+  mockSocket.onRegister = function(request) {
+    channelID = request.channelID;
+    this.serverSendMsg(JSON.stringify({
+      messageType: "register",
+      uaid: userAgentID,
+      channelID,
+      status: 200,
+      pushEndpoint: "https://example.com/endpoint/1"
+    }));
+  };
 
   var registration;
   add_task(function* start() {
-    yield setupPrefs();
+    yield setupPrefsAndMock(mockSocket);
     yield setPushPermission(true);
 
     var url = "worker.js" + "?" + (Math.random());
     registration = yield navigator.serviceWorker.register(url, {scope: "."});
   });
 
   var controlledFrame;
   add_task(function* createControlledIFrame() {
@@ -99,20 +113,38 @@ http://creativecommons.org/licenses/publ
     ok(authSecret.length > 0, "Missing auth secret");
     isDeeply(
       authSecret,
       new Uint8Array(data.auth),
       "Mismatched auth secret"
     );
   });
 
+  var version = 0;
+  function sendEncryptedMsg(pushSubscription, message) {
+    return webPushEncrypt(pushSubscription, message)
+      .then((encryptedData) => {
+        mockSocket.serverSendMsg(JSON.stringify({
+          messageType: 'notification',
+          version: version++,
+          channelID: channelID,
+          data: encryptedData.data,
+          headers: {
+            encryption: encryptedData.encryption,
+            encryption_key: encryptedData.encryption_key,
+            encoding: encryptedData.encoding
+          }
+        }));
+      });
+  }
+
   function waitForMessage(pushSubscription, message) {
     return Promise.all([
       controlledFrame.waitOnWorkerMessage("finished"),
-      webpush(pushSubscription, message, 120),
+      sendEncryptedMsg(pushSubscription, message),
     ]).then(([message]) => message);
   }
 
   add_task(function* sendPushMessageFromPage() {
     var typedArray = new Uint8Array([226, 130, 40, 240, 40, 140, 188]);
     var json = { hello: "world" };
 
     var message = yield waitForMessage(pushSubscription, "Text message from page");
@@ -154,28 +186,25 @@ http://creativecommons.org/licenses/publ
         } else {
           resolve(reader.result);
         }
       };
       reader.readAsText(message.data.blob);
     });
     is(text, "Hi! \ud83d\udc40", "Wrong blob data for message with emoji");
 
+    var finishedPromise = controlledFrame.waitOnWorkerMessage("finished");
     // Send a blank message.
-    var [message] = yield Promise.all([
-      controlledFrame.waitOnWorkerMessage("finished"),
-      fetch("http://mochi.test:8888/tests/dom/push/test/push-server.sjs", {
-        method: "PUT",
-        headers: {
-          "X-Push-Method": "POST",
-          "X-Push-Server": pushSubscription.endpoint,
-          "TTL": "120",
-        },
-      }),
-    ]);
+    mockSocket.serverSendMsg(JSON.stringify({
+      messageType: "notification",
+      version: "vDummy",
+      channelID: channelID
+    }));
+
+    var message = yield finishedPromise;
     ok(!message.data, "Should exclude data for blank messages");
   });
 
   add_task(function* unsubscribe() {
     controlledFrame.remove();
     yield pushSubscription.unsubscribe();
   });
 
--- a/dom/push/test/test_multiple_register.html
+++ b/dom/push/test/test_multiple_register.html
@@ -5,16 +5,17 @@ Bug 1038811: Push tests.
 
 Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/licenses/publicdomain/
 
 -->
 <head>
   <title>Test for Bug 1038811</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
 </head>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1038811">Mozilla Bug 1038811</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
@@ -113,20 +114,14 @@ http://creativecommons.org/licenses/publ
     .then(getEndpoint)
     .then(unregisterPushNotification)
     .then(unregister)
     .catch(function(e) {
       ok(false, "Some test failed with error " + e);
     }).then(SimpleTest.finish);
   }
 
-  SpecialPowers.pushPrefEnv({"set": [
-    ["dom.push.enabled", true],
-    ["dom.push.connection.enabled", true],
-    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
-    ["dom.serviceWorkers.enabled", true],
-    ["dom.serviceWorkers.testing.enabled", true]
-    ]}, runTest);
+  setupPrefsAndMock(new MockWebSocket()).then(_ => runTest());
   SpecialPowers.addPermission("desktop-notification", true, document);
   SimpleTest.waitForExplicitFinish();
 </script>
 </body>
 </html>
--- a/dom/push/test/test_multiple_register_different_scope.html
+++ b/dom/push/test/test_multiple_register_different_scope.html
@@ -5,16 +5,17 @@ Bug 1150812: Test registering for two di
 
 Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/licenses/publicdomain/
 
 -->
 <head>
   <title>Test for Bug 1150812</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
 </head>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1150812">Mozilla Bug 1150812</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
@@ -108,20 +109,14 @@ http://creativecommons.org/licenses/publ
             .then(_ => unregister(swrB))
         )
     )
     .catch(err => {
       ok(false, "Some test failed with error " + err);
     }).then(SimpleTest.finish);
   }
 
-  SpecialPowers.pushPrefEnv({"set": [
-    ["dom.push.enabled", true],
-    ["dom.push.connection.enabled", true],
-    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
-    ["dom.serviceWorkers.enabled", true],
-    ["dom.serviceWorkers.testing.enabled", true]
-    ]}, runTest);
+  setupPrefsAndMock(new MockWebSocket()).then(_ => runTest());
   SpecialPowers.addPermission("desktop-notification", true, document);
   SimpleTest.waitForExplicitFinish();
 </script>
 </body>
 </html>
--- a/dom/push/test/test_multiple_register_during_service_activation.html
+++ b/dom/push/test/test_multiple_register_during_service_activation.html
@@ -7,16 +7,17 @@ multiple subscription for the same scope
 
 Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/licenses/publicdomain/
 
 -->
 <head>
   <title>Test for Bug 1150812</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
 </head>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1150812">Mozilla Bug 1150812</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
@@ -49,23 +50,21 @@ http://creativecommons.org/licenses/publ
         ok(true, "successful registered for push notification");
         return sub;
       }, err => {
         ok(false, "could not register for push notification");
         throw err;
       });
   }
 
-var defaultServerURL = SpecialPowers.getCharPref("dom.push.serverURL");
-
   function setupMultipleSubscriptions(swr) {
+    // We need to do this to restart service so that a queue will be formed.
+    teardownMockPushService();
+    setupMockPushService(new MockWebSocket());
 
-    // We need to do this to restart service so that a queue will be formed.
-    SpecialPowers.pushPrefEnv({"set": [["dom.push.serverURL", defaultServerURL]]},
-                              null);
     return Promise.all([
       subscribe(swr),
       subscribe(swr)
     ]).then(a => {
       ok(a[0].endpoint == a[1].endpoint, "setupMultipleSubscriptions - Got the same endpoint back.");
       return a[0];
     }, err => {
       ok(false, "could not register for push notification");
@@ -95,21 +94,14 @@ var defaultServerURL = SpecialPowers.get
         .then(sub => unsubscribe(sub))
         .then(_ => unregister(swr))
     )
     .catch(err => {
       ok(false, "Some test failed with error " + err);
     }).then(SimpleTest.finish);
   }
 
-  SpecialPowers.pushPrefEnv({"set": [
-    ["dom.push.enabled", true],
-    ["dom.push.connection.enabled", true],
-    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
-    ["dom.serviceWorkers.enabled", true],
-    ["dom.serviceWorkers.testing.enabled", true],
-    ["dom.push.serverURL", "wss://something.org"]
-    ]}, runTest);
+  setupPrefsAndMock(new MockWebSocket()).then(_ => runTest());
   SpecialPowers.addPermission("desktop-notification", true, document);
   SimpleTest.waitForExplicitFinish();
 </script>
 </body>
 </html>
--- a/dom/push/test/test_permissions.html
+++ b/dom/push/test/test_permissions.html
@@ -26,17 +26,17 @@ http://creativecommons.org/licenses/publ
 <script class="testbody" type="text/javascript">
 
   function debug(str) {
   //  console.log(str + "\n");
   }
 
   var registration;
   add_task(function* start() {
-    yield setupPrefs();
+    yield setupPrefsAndMock(new MockWebSocket());
     yield setPushPermission(false);
 
     var url = "worker.js" + "?" + Math.random();
     registration = yield navigator.serviceWorker.register(url, {scope: "."});
   });
 
   add_task(function* denySubscribe() {
     try {
--- a/dom/push/test/test_register.html
+++ b/dom/push/test/test_register.html
@@ -24,19 +24,34 @@ http://creativecommons.org/licenses/publ
 </pre>
 
 <script class="testbody" type="text/javascript">
 
   function debug(str) {
   //  console.log(str + "\n");
   }
 
+  var mockSocket = new MockWebSocket();
+
+  var channelID = null;
+
+  mockSocket.onRegister = function(request) {
+    channelID = request.channelID;
+    this.serverSendMsg(JSON.stringify({
+      messageType: "register",
+      uaid: "c69e2014-9e15-438d-b253-d79cc2df60a8",
+      channelID,
+      status: 200,
+      pushEndpoint: "https://example.com/endpoint/1"
+    }));
+  };
+
   var registration;
   add_task(function* start() {
-    yield setupPrefs();
+    yield setupPrefsAndMock(mockSocket);
     yield setPushPermission(true);
 
     var url = "worker.js" + "?" + (Math.random());
     registration = yield navigator.serviceWorker.register(url, {scope: "."});
   });
 
   var controlledFrame;
   add_task(function* createControlledIFrame() {
@@ -59,27 +74,26 @@ http://creativecommons.org/licenses/publ
       endpoint: pushSubscription.endpoint,
     });
     pushSubscription = yield registration.pushManager.getSubscription();
     is(data.endpoint, pushSubscription.endpoint,
        "Subscription endpoints should match after resubscribing in worker");
   });
 
   add_task(function* waitForPushNotification() {
-    yield Promise.all([
-      controlledFrame.waitOnWorkerMessage("finished"),
-      fetch("http://mochi.test:8888/tests/dom/push/test/push-server.sjs", {
-        method: "PUT",
-        headers: {
-          "X-Push-Method": "POST",
-          "X-Push-Server": pushSubscription.endpoint,
-          "TTL": "120",
-        },
-      }),
-    ]);
+    var finishedPromise = controlledFrame.waitOnWorkerMessage("finished");
+
+    // Send a blank message.
+    mockSocket.serverSendMsg(JSON.stringify({
+      messageType: "notification",
+      version: "vDummy",
+      channelID: channelID
+    }));
+
+    yield finishedPromise;
   });
 
   add_task(function* unsubscribe() {
     controlledFrame.remove();
     yield pushSubscription.unsubscribe();
   });
 
   add_task(function* unregister() {
--- a/dom/push/test/test_serviceworker_lifetime.html
+++ b/dom/push/test/test_serviceworker_lifetime.html
@@ -18,16 +18,17 @@
     if the service worker is in the correct state as we send it different
     events.
   - We also wait and assert for service worker termination using an event dispatched
     through nsIObserverService.
   -->
 <head>
   <title>Test for Bug 1188545</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
 </head>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1188545">Mozilla Bug 118845</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
@@ -66,24 +67,37 @@
         }, function(error) {
           ok(false, "could not register for push notification");
           res(ctx);
         });
     });
     return p;
   }
 
+  var mockSocket = new MockWebSocket();
+  var endpoint = "https://example.com/endpoint/1";
+  var channelID = null;
+  mockSocket.onRegister = function(request) {
+    channelID = request.channelID;
+    this.serverSendMsg(JSON.stringify({
+      messageType: "register",
+      uaid: "fa8f2e4b-5ddc-4408-b1e3-5f25a02abff0",
+      channelID,
+      status: 200,
+      pushEndpoint: endpoint
+    }));
+  };
+
   function sendPushToPushServer(pushEndpoint) {
-    // Work around CORS for now.
-    var xhr = new XMLHttpRequest();
-    xhr.open('GET', "http://mochi.test:8888/tests/dom/push/test/push-server.sjs", true);
-    xhr.setRequestHeader("X-Push-Method", "POST");
-    xhr.setRequestHeader("X-Push-Server", pushEndpoint);
-    xhr.setRequestHeader("TTL", "120");
-    xhr.send(null);
+    is(pushEndpoint, endpoint, "Got unexpected endpoint");
+    mockSocket.serverSendMsg(JSON.stringify({
+      messageType: "notification",
+      version: "vDummy",
+      channelID
+    }));
   }
 
   function unregisterPushNotification(ctx) {
     return ctx.subscription.unsubscribe().then(function(result) {
       ok(result, "unsubscribe should succeed.");
       ctx.subscription = null;
       return ctx;
     });
@@ -316,20 +330,14 @@
       .then(subTest(test3))
       .then(unregisterPushNotification)
       .then(unregister)
       .catch(function(e) {
         ok(false, "Some test failed with error " + e)
       }).then(SimpleTest.finish);
   }
 
-  SpecialPowers.pushPrefEnv({"set": [
-    ["dom.push.enabled", true],
-    ["dom.push.connection.enabled", true],
-    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
-    ["dom.serviceWorkers.enabled", true],
-    ["dom.serviceWorkers.testing.enabled", true],
-    ]}, runTest);
+  setupPrefsAndMock(mockSocket).then(_ => runTest());
   SpecialPowers.addPermission('desktop-notification', true, document);
   SimpleTest.waitForExplicitFinish();
 </script>
 </body>
 </html>
--- a/dom/push/test/test_subscription_change.html
+++ b/dom/push/test/test_subscription_change.html
@@ -22,17 +22,17 @@ http://creativecommons.org/licenses/publ
 </div>
 <pre id="test">
 </pre>
 
 <script class="testbody" type="text/javascript">
 
   var registration;
   add_task(function* start() {
-    yield setupPrefs();
+    yield setupPrefsAndMock(new MockWebSocket());
     yield setPushPermission(true);
 
     var url = "worker.js" + "?" + (Math.random());
     registration = yield navigator.serviceWorker.register(url, {scope: "."});
   });
 
   var controlledFrame;
   add_task(function* createControlledIFrame() {
--- a/dom/push/test/test_unregister.html
+++ b/dom/push/test/test_unregister.html
@@ -5,16 +5,17 @@ Bug 1170817: Push tests.
 
 Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/licenses/publicdomain/
 
 -->
 <head>
   <title>Test for Bug 1170817</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
 </head>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1170817">Mozilla Bug 1170817</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
@@ -74,21 +75,15 @@ http://creativecommons.org/licenses/publ
     .then(unregisterPushNotification)
     .then(unregisterAgain)
     .then(unregisterSW)
     .catch(function(e) {
       ok(false, "Some test failed with error " + e);
     }).then(SimpleTest.finish);
   }
 
-  SpecialPowers.pushPrefEnv({"set": [
-    ["dom.push.enabled", true],
-    ["dom.push.connection.enabled", true],
-    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
-    ["dom.serviceWorkers.enabled", true],
-    ["dom.serviceWorkers.testing.enabled", true]
-    ]}, runTest);
+  setupPrefsAndMock(new MockWebSocket()).then(_ => runTest());
   SpecialPowers.addPermission("desktop-notification", true, document);
   SimpleTest.waitForExplicitFinish();
 </script>
 </body>
 </html>
 
--- a/dom/push/test/test_utils.js
+++ b/dom/push/test/test_utils.js
@@ -1,27 +1,132 @@
+(function (g) {
+  "use strict";
+
+  let url = SimpleTest.getTestFileURL("mockpushserviceparent.js");
+  let chromeScript = SpecialPowers.loadChromeScript(url);
+
+  let userAgentID = "8e1c93a9-139b-419c-b200-e715bb1e8ce8";
+
+  let currentMockSocket = null;
+
+  function setupMockPushService(mockWebSocket) {
+    currentMockSocket = mockWebSocket;
+    currentMockSocket._isActive = true;
+    chromeScript.sendSyncMessage("setup");
+    chromeScript.addMessageListener("client-msg", function(msg) {
+      mockWebSocket.handleMessage(msg);
+    });
+  }
+
+  function teardownMockPushService() {
+    if (currentMockSocket) {
+      currentMockSocket._isActive = false;
+      chromeScript.sendSyncMessage("teardown");
+    }
+  }
+
+  /**
+   * Minimal implementation of web sockets for use in testing. Forwards
+   * messages to a mock web socket in the parent process that is used
+   * by the push service.
+   */
+  function MockWebSocket() {}
+
+  let registerCount = 0;
+
+  // Default implementation to make the push server work minimally.
+  // Override methods to implement custom functionality.
+  MockWebSocket.prototype = {
+    // We only allow one active mock web socket to talk to the parent.
+    // This flag is used to keep track of which mock web socket is active.
+    _isActive: false,
+
+    onHello(request) {
+      this.serverSendMsg(JSON.stringify({
+        messageType: "hello",
+        uaid: userAgentID,
+        status: 200,
+        use_webpush: true,
+      }));
+    },
+
+    onRegister(request) {
+      this.serverSendMsg(JSON.stringify({
+        messageType: "register",
+        uaid: userAgentID,
+        channelID: request.channelID,
+        status: 200,
+        pushEndpoint: "https://example.com/endpoint/" + registerCount++
+      }));
+    },
+
+    onUnregister(request) {
+      // Do nothing.
+    },
+
+    onAck(request) {
+      // Do nothing.
+    },
+
+    handleMessage(msg) {
+      let request = JSON.parse(msg);
+      let messageType = request.messageType;
+      switch (messageType) {
+      case "hello":
+        this.onHello(request);
+        break;
+      case "register":
+        this.onRegister(request);
+        break;
+      case "unregister":
+        this.onUnregister(request);
+        break;
+      case "ack":
+        this.onAck(request);
+        break;
+      default:
+        throw new Error("Unexpected message: " + messageType);
+      }
+    },
+
+    serverSendMsg(msg) {
+      if (this._isActive) {
+        chromeScript.sendAsyncMessage("server-msg", msg);
+      }
+    },
+  };
+
+  g.MockWebSocket = MockWebSocket;
+  g.setupMockPushService = setupMockPushService;
+  g.teardownMockPushService = teardownMockPushService;
+}(this));
+
 // Remove permissions and prefs when the test finishes.
-SimpleTest.registerCleanupFunction(() =>
+SimpleTest.registerCleanupFunction(() => {
   new Promise(resolve => {
     SpecialPowers.flushPermissions(_ => {
       SpecialPowers.flushPrefEnv(resolve);
     });
-  })
-);
+  }).then(_ => {
+    teardownMockPushService();
+  });
+});
 
 function setPushPermission(allow) {
   return new Promise(resolve => {
     SpecialPowers.pushPermissions([
       { type: "desktop-notification", allow, context: document },
       ], resolve);
   });
 }
 
-function setupPrefs() {
+function setupPrefsAndMock(mockSocket) {
   return new Promise(resolve => {
+    setupMockPushService(mockSocket);
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.push.enabled", true],
       ["dom.push.connection.enabled", true],
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true]
       ]}, resolve);
   });
--- a/dom/push/test/webpush.js
+++ b/dom/push/test/webpush.js
@@ -156,52 +156,31 @@
           return webCrypto.encrypt({
             name: 'AES-GCM',
             iv: generateNonce(nonce, index)
           }, key, padded);
         }));
       }).then(bsConcat);
   }
 
-  /*
-   * Request push for a message.  This returns a promise that resolves when the
-   * push has been delivered to the push service.
-   *
-   * @param subscription A PushSubscription that contains endpoint and p256dh
-   *                     parameters.
-   * @param data         The message to send.
-   */
-  function webpush(subscription, data, ttl) {
+  function webPushEncrypt(subscription, data) {
     data = ensureView(data);
 
     var salt = g.crypto.getRandomValues(new Uint8Array(16));
     return webCrypto.generateKey(P256DH, false, ['deriveBits'])
       .then(localKey => {
         return Promise.all([
           encrypt(localKey.privateKey, subscription.getKey("p256dh"), salt, data),
           // 1337 p-256 specific haxx to get the raw value out of the spki value
           webCrypto.exportKey('raw', localKey.publicKey),
         ]);
       }).then(([payload, pubkey]) => {
-        var options = {
-          method: 'PUT',
-          headers: {
-            'X-Push-Server': subscription.endpoint,
-            // Web Push requires POST requests.
-            'X-Push-Method': 'POST',
-            'Encryption-Key': 'keyid=p256dh;dh=' + base64url.encode(pubkey),
-            Encryption: 'keyid=p256dh;salt=' + base64url.encode(salt),
-            'Content-Encoding': 'aesgcm128',
-            'TTL': ttl,
-          },
-          body: payload,
+        return {
+          data: base64url.encode(payload),
+          encryption: 'keyid=p256dh;salt=' + base64url.encode(salt),
+          encryption_key: 'keyid=p256dh;dh=' + base64url.encode(pubkey),
+          encoding: 'aesgcm128'
         };
-        return fetch('http://mochi.test:8888/tests/dom/push/test/push-server.sjs', options);
-      }).then(response => {
-        if (Math.floor(response.status / 100) !== 2) {
-          throw new Error('Unable to deliver message');
-        }
-        return response;
       });
   }
 
-  g.webpush = webpush;
+  g.webPushEncrypt = webPushEncrypt;
 }(this));
--- a/dom/security/test/contentverifier/browser.ini
+++ b/dom/security/test/contentverifier/browser.ini
@@ -1,10 +1,14 @@
 [DEFAULT]
 support-files =
   file_contentserver.sjs
   file_about_newtab.html
   file_about_newtab_bad.html
   file_about_newtab_good_signature
   file_about_newtab_bad_signature
   file_about_newtab_broken_signature
+  file_about_newtab_sri.html
+  file_about_newtab_sri_signature
+  script.js
+  style.css
 
 [browser_verify_content_about_newtab.js]
--- a/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
+++ b/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
@@ -38,48 +38,66 @@ const URI_BAD_FILE = BASE + "sig=good&ke
 const URI_BAD_ALL = BASE + "sig=bad&key=bad&file=bad&header=bad";
 
 const URI_BAD_FILE_CACHED = BASE + "sig=good&key=good&file=bad&header=good&cached=true";
 
 const GOOD_ABOUT_STRING = "Just a fully good testpage for Bug 1226928";
 const BAD_ABOUT_STRING = "Just a bad testpage for Bug 1226928";
 const ABOUT_BLANK = "<head></head><body></body>";
 
+const URI_CLEANUP = BASE + "cleanup=true";
+const CLEANUP_DONE = "Done";
+
+const URI_SRI = BASE + "sig=sri&key=good&file=sri&header=good";
+const STYLESHEET_WITHOUT_SRI_BLOCKED = "Stylesheet without SRI blocked";
+const STYLESHEET_WITH_SRI_BLOCKED = "Stylesheet with SRI blocked";
+const STYLESHEET_WITH_SRI_LOADED = "Stylesheet with SRI loaded";
+const SCRIPT_WITHOUT_SRI_BLOCKED = "Script without SRI blocked";
+const SCRIPT_WITH_SRI_BLOCKED = "Script with SRI blocked";
+const SCRIPT_WITH_SRI_LOADED = "Script with SRI loaded";
+
 const TESTS = [
   // { newtab (aboutURI) or regular load (url) : url,
-  //   testString : expected string in the loaded page }
-  { "aboutURI" : URI_GOOD, "testString" : GOOD_ABOUT_STRING },
-  { "aboutURI" : URI_ERROR_HEADER, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_KEYERROR_HEADER, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_SIGERROR_HEADER, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_NO_HEADER, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_BAD_SIG, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_BROKEN_SIG, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_BAD_KEY, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_BAD_FILE, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_BAD_ALL, "testString" : ABOUT_BLANK },
-  { "url" : URI_BAD_FILE_CACHED, "testString" : BAD_ABOUT_STRING },
-  { "aboutURI" : URI_BAD_FILE_CACHED, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_GOOD, "testString" : GOOD_ABOUT_STRING }
+  //   testStrings : expected strings in the loaded page }
+  { "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
+  { "aboutURI" : URI_ERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_KEYERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_SIGERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_NO_HEADER, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BAD_SIG, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BROKEN_SIG, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BAD_KEY, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BAD_FILE, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BAD_ALL, "testStrings" : [ABOUT_BLANK] },
+  { "url" : URI_BAD_FILE_CACHED, "testStrings" : [BAD_ABOUT_STRING] },
+  { "aboutURI" : URI_BAD_FILE_CACHED, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
+  { "aboutURI" : URI_SRI, "testStrings" : [
+    STYLESHEET_WITHOUT_SRI_BLOCKED,
+    STYLESHEET_WITH_SRI_LOADED,
+    SCRIPT_WITHOUT_SRI_BLOCKED,
+    SCRIPT_WITH_SRI_LOADED,
+    ]},
+  { "url" : URI_CLEANUP, "testStrings" : [CLEANUP_DONE] },
 ];
 
 var browser = null;
 var aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
                            .getService(Ci.nsIAboutNewTabService);
 
 function pushPrefs(...aPrefs) {
   return new Promise((resolve) => {
     SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
   });
 }
 
 /*
  * run tests with input from TESTS
  */
-function doTest(aExpectedString, reload, aUrl, aNewTabPref) {
+function doTest(aExpectedStrings, reload, aUrl, aNewTabPref) {
   // set about:newtab location for this test if it's a newtab test
   if (aNewTabPref) {
     aboutNewTabService.newTabURL = aNewTabPref;
   }
 
   // set prefs
   yield pushPrefs(
       ["browser.newtabpage.remote.content-signing-test", true],
@@ -110,20 +128,22 @@ function doTest(aExpectedString, reload,
       // we only check this if we really do a newtab test
       if (aNewTabPref) {
         ok(aboutNewTabService.overridden,
             "sanity check: default URL for about:newtab should be overriden");
         is(aboutNewTabService.newTabURL, aNewTabPref,
             "sanity check: default URL for about:newtab should return the new URL");
       }
       yield ContentTask.spawn(
-          browser, aExpectedString, function * (aExpectedString) {
-            ok(content.document.documentElement.innerHTML.includes(aExpectedString),
-               "Expect the following value in the result\n" + aExpectedString +
-               "\nand got " + content.document.documentElement.innerHTML);
+          browser, aExpectedStrings, function * (aExpectedStrings) {
+            for (let expectedString of aExpectedStrings) {
+              ok(content.document.documentElement.innerHTML.includes(expectedString),
+                 "Expect the following value in the result\n" + expectedString +
+                 "\nand got " + content.document.documentElement.innerHTML);
+            }
           });
 
       // for good test cases we check if a reload fails if the remote page
       // changed from valid to invalid in the meantime
       if (reload) {
         yield BrowserTestUtils.withNewTab({
             gBrowser,
             url: INVALIDATE_FILE,
@@ -135,22 +155,32 @@ function doTest(aExpectedString, reload,
                  "\nand got " + content.document.documentElement.innerHTML);
             });
           }
         );
 
         browser.reload();
         yield BrowserTestUtils.browserLoaded(browser);
 
-        aExpectedString = ABOUT_BLANK;
-        yield ContentTask.spawn(browser, aExpectedString,
-          function * (aExpectedString) {
-            ok(content.document.documentElement.innerHTML.includes(aExpectedString),
-               "Expect the following value in the result\n" + aExpectedString +
-               "\nand got " + content.document.documentElement.innerHTML);
+        let expectedStrings = [ABOUT_BLANK];
+        if (aNewTabPref == URI_SRI) {
+          expectedStrings = [
+            STYLESHEET_WITHOUT_SRI_BLOCKED,
+            STYLESHEET_WITH_SRI_BLOCKED,
+            SCRIPT_WITHOUT_SRI_BLOCKED,
+            SCRIPT_WITH_SRI_BLOCKED
+          ];
+        }
+        yield ContentTask.spawn(browser, expectedStrings,
+          function * (expectedStrings) {
+            for (let expectedString of expectedStrings) {
+              ok(content.document.documentElement.innerHTML.includes(expectedString),
+                 "Expect the following value in the result\n" + expectedString +
+                 "\nand got " + content.document.documentElement.innerHTML);
+            }
           }
         );
 
         yield BrowserTestUtils.withNewTab({
             gBrowser,
             url: VALIDATE_FILE,
           },
           function * (browser2) {
@@ -167,22 +197,22 @@ function doTest(aExpectedString, reload,
 }
 
 add_task(function * test() {
   // run tests from TESTS
   for (let i = 0; i < TESTS.length; i++) {
     let testCase = TESTS[i];
     let url = "", aNewTabPref = "";
     let reload = false;
-    let aExpectedString = testCase.testString;
+    var aExpectedStrings = testCase.testStrings;
     if (testCase.aboutURI) {
       url = ABOUT_NEWTAB_URI;
       aNewTabPref = testCase.aboutURI;
-      if (aExpectedString == GOOD_ABOUT_STRING) {
+      if (aNewTabPref == URI_GOOD || aNewTabPref == URI_SRI) {
         reload = true;
       }
     } else {
       url = testCase.url;
     }
 
-    yield doTest(aExpectedString, reload, url, aNewTabPref);
+    yield doTest(aExpectedStrings, reload, url, aNewTabPref);
   }
 });
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_sri.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1235572 -->
+<head>
+  <meta charset="utf-8">
+  <title>Testpage for bug 1235572</title>
+  <script>
+    function loaded(resource) {
+      document.getElementById("result").innerHTML += resource + " loaded\n";
+    }
+    function blocked(resource) {
+      document.getElementById("result").innerHTML += resource + " blocked\n";
+    }
+  </script>
+</head>
+<body>
+  Testing script loading without SRI for Bug 1235572<br/>
+  <div id="result"></div>
+
+  <!-- use css1 and css2 to make urls different to avoid the resource being cached-->
+  <link rel="stylesheet" href="https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?resource=css1"
+        onload="loaded('Stylesheet without SRI')"
+        onerror="blocked('Stylesheet without SRI')">
+  <link rel="stylesheet" href="https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?resource=css2"
+        integrity="sha384-/6Tvxh7SX39y62qePcvYoi5Vrf0lK8Ix3wJFLCYKI5KNJ5wIlCR8UsFC1OXwmwgd"
+        onload="loaded('Stylesheet with SRI')"
+        onerror="blocked('Stylesheet with SRI')">
+  <script src="https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?resource=script"
+          onload="loaded('Script without SRI')"
+          onerror="blocked('Script without SRI')"></script>
+  <script src="https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?resource=script"
+          integrity="sha384-zDCkvKOHXk8mM6Nk07oOGXGME17PA4+ydFw+hq0r9kgF6ZDYFWK3fLGPEy7FoOAo"
+          onload="loaded('Script with SRI')"
+          onerror="blocked('Script with SRI')"></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_sri_signature
@@ -0,0 +1,1 @@
+i5jOnrZWwyNwrTcIjfJ6fUR-8MhhvhtMvQbdrUD7j8aHTybNolv25v9NwJAT6rVU6kgkxmD_st9Kla086CQmzYQdLhKfzgLbTDXz0-1j23fQnyjsP1_4MNIu2xTea11p
\ No newline at end of file
--- a/dom/security/test/contentverifier/file_contentserver.sjs
+++ b/dom/security/test/contentverifier/file_contentserver.sjs
@@ -9,31 +9,40 @@ const path = "browser/dom/security/test/
 
 const goodFileName = "file_about_newtab.html";
 const goodFileBase = path + goodFileName;
 const goodFile = FileUtils.getDir("TmpD", [], true);
 goodFile.append(goodFileName);
 const goodSignature = path + "file_about_newtab_good_signature";
 const goodKeyId = "RemoteNewTab";
 
+const scriptFileName = "script.js";
+const cssFileName = "style.css";
 const badFile = path + "file_about_newtab_bad.html";
 const brokenSignature = path + "file_about_newtab_broken_signature";
 const badSignature = path + "file_about_newtab_bad_signature";
 const badKeyId = "OldRemoteNewTabKey";
 
+const sriFile = path + "file_about_newtab_sri.html";
+const sriSignature = path + "file_about_newtab_sri_signature";
+
+const tempFileNames = [goodFileName, scriptFileName, cssFileName];
+
 // we copy the file to serve as newtab to a temp directory because
 // we modify it during tests.
-setupTestFile();
+setupTestFiles();
 
-function setupTestFile() {
-  let tempFile = FileUtils.getDir("TmpD", [], true);
-  tempFile.append(goodFileName);
-  if (!tempFile.exists()) {
-    let fileIn = getFileName(goodFileBase, "CurWorkD");
-    fileIn.copyTo(FileUtils.getDir("TmpD", [], true), "");
+function setupTestFiles() {
+  for (let fileName of tempFileNames) {
+    let tempFile = FileUtils.getDir("TmpD", [], true);
+    tempFile.append(fileName);
+    if (!tempFile.exists()) {
+      let fileIn = getFileName(path + fileName, "CurWorkD");
+      fileIn.copyTo(FileUtils.getDir("TmpD", [], true), "");
+    }
   }
 }
 
 function getFileName(filePath, dir) {
   // Since it's relative to the cwd of the test runner, we start there and
   // append to get to the actual path of the file.
   let testFile =
     Cc["@mozilla.org/file/directory_service;1"].
@@ -80,16 +89,24 @@ function truncateFile(aFile, length) {
     file.close();
   } catch (e) {
     dump(">>> Error in truncateFile "+e);
     return "Error";
   }
   return "Done";
 }
 
+function cleanupTestFiles() {
+  for (let fileName of tempFileNames) {
+    let tempFile = FileUtils.getDir("TmpD", [], true);
+    tempFile.append(fileName);
+    tempFile.remove(true);
+  }
+}
+
 /*
  * handle requests of the following form:
  * sig=good&key=good&file=good&header=good&cached=no to serve pages with
  * content signatures
  *
  * it further handles invalidateFile=yep and validateFile=yep to change the
  * served file
  */
@@ -97,32 +114,61 @@ function handleRequest(request, response
   let params = new URLSearchParams(request.queryString);
   let keyType = params.get("key");
   let signatureType = params.get("sig");
   let fileType = params.get("file");
   let headerType = params.get("header");
   let cached = params.get("cached");
   let invalidateFile = params.get("invalidateFile");
   let validateFile = params.get("validateFile");
+  let resource = params.get("resource");
+
+  if (params.get("cleanup")) {
+    cleanupTestFiles();
+    response.setHeader("Content-Type", "text/html", false);
+    response.write("Done");
+    return;
+  }
+
+  if (resource) {
+    if (resource == "script") {
+      response.setHeader("Content-Type", "application/javascript", false);
+      response.write(loadFile(getFileName(scriptFileName, "TmpD")));
+    } else { // resource == "css1" || resource == "css2"
+      response.setHeader("Content-Type", "text/css", false);
+      response.write(loadFile(getFileName(cssFileName, "TmpD")));
+    }
+    return;
+  }
 
   // if invalidateFile is set, this doesn't actually return a newtab page
   // but changes the served file to invalidate the signature
   // NOTE: make sure to make the file valid again afterwards!
   if (invalidateFile) {
+    let r = "Done";
+    for (let fileName of tempFileNames) {
+      if (appendToFile(getFileName(fileName, "TmpD"), "!") != "Done") {
+        r = "Error";
+      }
+    }
     response.setHeader("Content-Type", "text/html", false);
-    let r = appendToFile(goodFile, "!");
     response.write(r);
     return;
   }
 
   // if validateFile is set, this doesn't actually return a newtab page
   // but changes the served file to make the signature valid again
   if (validateFile) {
+    let r = "Done";
+    for (let fileName of tempFileNames) {
+      if (truncateFile(getFileName(fileName, "TmpD"), 1) != "Done") {
+        r = "Error";
+      }
+    }
     response.setHeader("Content-Type", "text/html", false);
-    let r = truncateFile(goodFile, 1);
     response.write(r);
     return;
   }
 
   // avoid confusing cache behaviours
   if (!cached) {
     response.setHeader("Cache-Control", "no-cache", false);
   } else {
@@ -142,19 +188,23 @@ function handleRequest(request, response
   let file = goodFile;
   if (keyType == "bad") {
     keyId = badKeyId;
   }
   if (signatureType == "bad") {
     signature = badSignature;
   } else if (signatureType == "broken") {
     signature = brokenSignature;
+  } else if (signatureType == "sri") {
+    signature = sriSignature;
   }
   if (fileType == "bad") {
     file = getFileName(badFile, "CurWorkD");
+  } else if (fileType == "sri") {
+    file = getFileName(sriFile, "CurWorkD");
   }
 
   if (headerType == "good") {
     // a valid content-signature header
     csHeader = "keyid=" + keyId + ";p384ecdsa=" +
                loadFile(getFileName(signature, "CurWorkD"));
   } else if (headerType == "error") {
     // this content-signature header is missing ; before p384ecdsa
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/script.js
@@ -0,0 +1,1 @@
+var load=true;
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/style.css
@@ -0,0 +1,3 @@
+#red-text {
+  color: red;
+}
--- a/dom/security/test/csp/browser_test_web_manifest.js
+++ b/dom/security/test/csp/browser_test_web_manifest.js
@@ -1,30 +1,28 @@
 /*
  * Description of the tests:
  *   These tests check for conformance to the CSP spec as they relate to Web Manifests.
  *
  *   In particular, the tests check that default-src and manifest-src directives are
  *   are respected by the ManifestObtainer.
  */
-/*globals Components*/
+/*globals Cu, is, ok*/
 'use strict';
 requestLongerTimeout(10); // e10s tests take time.
 const {
   ManifestObtainer
 } = Cu.import('resource://gre/modules/ManifestObtainer.jsm', {});
 const path = '/tests/dom/security/test/csp/';
 const testFile = `file=${path}file_web_manifest.html`;
 const remoteFile = `file=${path}file_web_manifest_remote.html`;
 const httpsManifest = `file=${path}file_web_manifest_https.html`;
-const mixedContent = `file=${path}file_web_manifest_mixed_content.html`;
 const server = 'file_testserver.sjs';
 const defaultURL = `http://example.org${path}${server}`;
-const remoteURL = `http://mochi.test:8888`;
-const secureURL = `https://example.com${path}${server}`;
+const secureURL = `https://example.com:443${path}${server}`;
 const tests = [
   // CSP block everything, so trying to load a manifest
   // will result in a policy violation.
   {
     expected: `default-src 'none' blocks fetching manifest.`,
     get tabURL() {
       let queryParts = [
         `csp=default-src 'none'`,
@@ -98,75 +96,44 @@ const tests = [
       is(topic, 'csp-on-violate-policy', this.expected);
     }
   },
   // CSP allows fetching from self, so manifest should load.
   {
     expected: `CSP manifest-src allows self`,
     get tabURL() {
       let queryParts = [
-        `manifest-src 'self'`,
+        `csp=manifest-src 'self'`,
         testFile
       ];
       return `${defaultURL}?${queryParts.join('&')}`;
     },
     run(manifest) {
       is(manifest.name, 'loaded', this.expected);
     }
   },
   // CSP allows fetching from example.org, so manifest should load.
   {
     expected: `CSP manifest-src allows http://example.org`,
     get tabURL() {
       let queryParts = [
-        `manifest-src http://example.org`,
+        `csp=manifest-src http://example.org`,
         testFile
       ];
       return `${defaultURL}?${queryParts.join('&')}`;
     },
     run(manifest) {
       is(manifest.name, 'loaded', this.expected);
     }
   },
-  // Check interaction with default-src and another origin,
-  // CSP allows fetching from example.org, so manifest should load.
-  {
-    expected: `CSP manifest-src overrides default-src of elsewhere.com`,
-    get tabURL() {
-      let queryParts = [
-        `default-src: http://elsewhere.com; manifest-src http://example.org`,
-        testFile
-      ];
-      return `${defaultURL}?${queryParts.join('&')}`;
-    },
-    run(manifest) {
-      is(manifest.name, 'loaded', this.expected);
-    }
-  },
-  // Check interaction with default-src none,
-  // CSP allows fetching manifest from example.org, so manifest should load.
-  {
-    expected: `CSP manifest-src overrides default-src`,
-    get tabURL() {
-      let queryParts = [
-        `default-src: 'none'; manifest-src 'self'`,
-        testFile
-      ];
-      return `${defaultURL}?${queryParts.join('&')}`;
-    },
-    run(manifest) {
-      is(manifest.name, 'loaded', this.expected);
-    }
-  },
-  // CSP allows fetching from mochi.test:8888, which has a
-  // CORS header set to '*'. So the manifest should load.
   {
     expected: `CSP manifest-src allows mochi.test:8888`,
     get tabURL() {
       let queryParts = [
+        `cors=*`,
         `csp=default-src *; manifest-src http://mochi.test:8888`,
         remoteFile
       ];
       return `${defaultURL}?${queryParts.join('&')}`;
     },
     run(manifest) {
       is(manifest.name, 'loaded', this.expected);
     }
@@ -197,68 +164,86 @@ const tests = [
         `csp=manifest-src 'self'`,
         remoteFile
       ];
       return `${defaultURL}?${queryParts.join('&')}`;
     },
     run(topic) {
       is(topic, 'csp-on-violate-policy', this.expected);
     }
-  }
+  },
+  // CSP allows fetching over TLS from example.org, so manifest should load.
+  {
+    expected: `CSP manifest-src allows example.com over TLS`,
+    get tabURL() {
+      let queryParts = [
+        'cors=*',
+        'csp=manifest-src https://example.com:443',
+        httpsManifest
+      ];
+      // secureURL loads https://example.com:443
+      // and gets manifest from https://example.org:443
+      return `${secureURL}?${queryParts.join('&')}`;
+    },
+    run(manifest) {
+      is(manifest.name, 'loaded', this.expected);
+    }
+  },
 ];
 //jscs:disable
 add_task(function* () {
   //jscs:enable
-  for (let test of tests) {
-    let tabOptions = {
-      gBrowser: gBrowser,
-      url: test.tabURL,
-    };
-    yield BrowserTestUtils.withNewTab(
-      tabOptions,
-      browser => testObtainingManifest(browser, test)
+  var testPromises = tests.map(
+      (test) => ([test, {gBrowser, url: test.tabURL, skipAnimation: true}])
+    ).map(
+      ([test, tabOptions]) => BrowserTestUtils.withNewTab(tabOptions, (browser) => testObtainingManifest(browser, test))
     );
-  }
+  yield Promise.all(testPromises);
+});
 
-  function* testObtainingManifest(aBrowser, aTest) {
-    const observer = (/blocks/.test(aTest.expected)) ? new NetworkObserver(aTest) : null;
-    let manifest;
-    // Expect an exception (from promise rejection) if there a content policy
-    // that is violated.
-    try {
-      manifest = yield ManifestObtainer.browserObtainManifest(aBrowser);
-    } catch (e) {
-      const msg = `Expected promise rejection obtaining.`;
-      ok(/blocked the loading of a resource/.test(e.message), msg);
-      if (observer) {
-        yield observer.finished;
-      }
+function* testObtainingManifest(aBrowser, aTest) {
+  const expectsBlocked = aTest.expected.includes('block');
+  const observer = (expectsBlocked) ? createNetObserver(aTest) : null;
+  // Expect an exception (from promise rejection) if there a content policy
+  // that is violated.
+  try {
+    const manifest = yield ManifestObtainer.browserObtainManifest(aBrowser);
+    aTest.run(manifest);
+  } catch (e) {
+    const wasBlocked = e.message.includes('blocked the loading of a resource');
+    ok(wasBlocked,`Expected promise rejection obtaining ${aTest.tabURL}: ${e.message}`);
+    if (observer) {
+      yield observer.untilFinished;
       return;
     }
-    // otherwise, we test manifest's content.
-    if (manifest) {
-      aTest.run(manifest);
-    }
+    throw e;
   }
-});
+}
 
-// Helper object used to observe policy violations. It waits 10 seconds
+// Helper object used to observe policy violations. It waits 1 seconds
 // for a response, and then times out causing its associated test to fail.
-function NetworkObserver(test) {
+function createNetObserver(test) {
   let finishedTest;
   let success = false;
-  this.finished = new Promise((resolver) => {
+  const finished = new Promise((resolver) => {
     finishedTest = resolver;
-  })
-  this.observe = function observer(subject, topic) {
-    SpecialPowers.removeObserver(this, 'csp-on-violate-policy');
-    test.run(topic);
-    finishedTest();
-    success = true;
-  };
-  SpecialPowers.addObserver(this, 'csp-on-violate-policy', false);
-  setTimeout(() => {
+  });
+  const timeoutId = setTimeout(() => {
     if (!success) {
       test.run('This test timed out.');
       finishedTest();
     }
   }, 1000);
+  var observer = {
+    get untilFinished(){
+      return finished;
+    },
+    observe(subject, topic) {
+      SpecialPowers.removeObserver(observer, 'csp-on-violate-policy');
+      test.run(topic);
+      finishedTest();
+      clearTimeout(timeoutId);
+      success = true;
+    },
+  };
+  SpecialPowers.addObserver(observer, 'csp-on-violate-policy', false);
+  return observer;
 }
--- a/dom/telephony/TelephonyDialCallback.cpp
+++ b/dom/telephony/TelephonyDialCallback.cpp
@@ -70,17 +70,17 @@ TelephonyDialCallback::NotifyDialMMISucc
 {
   AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.Init(mWindow))) {
     return NS_ERROR_FAILURE;
   }
 
   JSContext* cx = jsapi.cx();
 
-  MozMMIResult result;
+  RootedDictionary<MozMMIResult> result(cx);
   result.mSuccess = true;
   result.mServiceCode.Assign(mServiceCode);
   result.mStatusMessage.Assign(aStatusMessage);
 
   return NotifyDialMMISuccess(cx, result);
 }
 
 NS_IMETHODIMP
@@ -89,17 +89,17 @@ TelephonyDialCallback::NotifyDialMMISucc
 {
   AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.Init(mWindow))) {
     return NS_ERROR_FAILURE;
   }
 
   JSContext* cx = jsapi.cx();
 
-  MozMMIResult result;
+  RootedDictionary<MozMMIResult> result(cx);
   result.mSuccess = true;
   result.mServiceCode.Assign(mServiceCode);
   result.mStatusMessage.Assign(aStatusMessage);
   result.mAdditionalInformation.Construct().SetAsUnsignedShort() = aAdditionalInformation;
 
   return NotifyDialMMISuccess(cx, result);
 }
 
@@ -212,17 +212,17 @@ TelephonyDialCallback::NotifyDialMMIErro
 {
   AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.Init(mWindow))) {
     return NS_ERROR_FAILURE;
   }
 
   JSContext* cx = jsapi.cx();
 
-  MozMMIResult result;
+  RootedDictionary<MozMMIResult> result(cx);
   result.mSuccess = false;
   result.mServiceCode.Assign(mServiceCode);
   result.mStatusMessage.Assign(aError);
 
   return NotifyDialMMISuccess(cx, result);
 }
 
 NS_IMETHODIMP
@@ -231,16 +231,16 @@ TelephonyDialCallback::NotifyDialMMIErro
 {
   AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.Init(mWindow))) {
     return NS_ERROR_FAILURE;
   }
 
   JSContext* cx = jsapi.cx();
 
-  MozMMIResult result;
+  RootedDictionary<MozMMIResult> result(cx);
   result.mSuccess = false;
   result.mServiceCode.Assign(mServiceCode);
   result.mStatusMessage.Assign(aError);
   result.mAdditionalInformation.Construct().SetAsUnsignedShort() = aInfo;
 
   return NotifyDialMMISuccess(cx, result);
 }
--- a/dom/webidl/CanvasRenderingContext2D.webidl
+++ b/dom/webidl/CanvasRenderingContext2D.webidl
@@ -293,17 +293,19 @@ interface CanvasPathMethods {
   void arcTo(double x1, double y1, double x2, double y2, double radius); 
 // NOT IMPLEMENTED  [LenientFloat] void arcTo(double x1, double y1, double x2, double y2, double radiusX, double radiusY, double rotation);
 
   [LenientFloat]
   void rect(double x, double y, double w, double h);
 
   [Throws, LenientFloat]
   void arc(double x, double y, double radius, double startAngle, double endAngle, optional boolean anticlockwise = false); 
-// NOT IMPLEMENTED  [LenientFloat] void ellipse(double x, double y, double radiusX, double radiusY, double rotation, double startAngle, double endAngle, boolean anticlockwise);
+
+  [Throws, LenientFloat]
+  void ellipse(double x, double y, double radiusX, double radiusY, double rotation, double startAngle, double endAngle, optional boolean anticlockwise = false);
 };
 
 interface CanvasGradient {
   // opaque object
   [Throws]
   // addColorStop should take a double
   void addColorStop(float offset, DOMString color);
 };
--- a/dom/webidl/KeyframeEffect.webidl
+++ b/dom/webidl/KeyframeEffect.webidl
@@ -41,24 +41,32 @@ interface KeyframeEffectReadOnly : Anima
   // KeyframeEffect             clone();
 
   // We use object instead of ComputedKeyframe so that we can put the
   // property-value pairs on the object.
   [Throws] sequence<object> getFrames();
 };
 
 // Non-standard extensions
-dictionary AnimationPropertyState {
-  DOMString property;
-  boolean runningOnCompositor;
-  DOMString? warning;
+dictionary AnimationPropertyValueDetails {
+  required double             offset;
+  required DOMString          value;
+           DOMString          easing;
+  required CompositeOperation composite;
+};
+
+dictionary AnimationPropertyDetails {
+  required DOMString                               property;
+  required boolean                                 runningOnCompositor;
+           DOMString                               warning;
+  required sequence<AnimationPropertyValueDetails> values;
 };
 
 partial interface KeyframeEffectReadOnly {
-  [ChromeOnly] sequence<AnimationPropertyState> getPropertyState();
+  [ChromeOnly, Throws] sequence<AnimationPropertyDetails> getProperties();
 };
 
 [Func="nsDocument::IsWebAnimationsEnabled",
  Constructor ((Element or CSSPseudoElement)? target,
               object? frames,
               optional (unrestricted double or KeyframeEffectOptions) options)]
 interface KeyframeEffect : KeyframeEffectReadOnly {
   // Bug 1067769 - Allow setting KeyframeEffect.target
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -1929,21 +1929,18 @@ RuntimeService::Shutdown()
     AutoTArray<WorkerPrivate*, 100> workers;
     AddAllTopLevelWorkersToArray(workers);
 
     if (!workers.IsEmpty()) {
       // Cancel all top-level workers.
       {
         MutexAutoUnlock unlock(mMutex);
 
-        AutoSafeJSContext cx;
-        JSAutoRequest ar(cx);
-
         for (uint32_t index = 0; index < workers.Length(); index++) {
-          if (!workers[index]->Kill(cx)) {
+          if (!workers[index]->Kill()) {
             NS_WARNING("Failed to cancel worker!");
           }
         }
       }
     }
   }
 }
 
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.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 "ScriptLoader.h"
 
 #include "nsIChannel.h"
 #include "nsIContentPolicy.h"
 #include "nsIContentSecurityPolicy.h"
+#include "nsIDocShell.h"
 #include "nsIHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIInputStreamPump.h"
 #include "nsIIOService.h"
 #include "nsIProtocolHandler.h"
 #include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIStreamLoader.h"
@@ -888,16 +889,38 @@ private:
     nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
     NS_ASSERTION(secMan, "This should never be null!");
 
     ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
     nsresult& rv = loadInfo.mLoadResult;
 
     nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
 
+    // Get the top-level worker.
+    WorkerPrivate* topWorkerPrivate = mWorkerPrivate;
+    WorkerPrivate* parent = topWorkerPrivate->GetParent();
+    while (parent) {
+      topWorkerPrivate = parent;
+      parent = topWorkerPrivate->GetParent();
+    }
+
+    // If the top-level worker is a dedicated worker and has a window, and the
+    // window has a docshell, the caching behavior of this worker should match
+    // that of that docshell.
+    if (topWorkerPrivate->IsDedicatedWorker()) {
+      nsCOMPtr<nsPIDOMWindowInner> window = topWorkerPrivate->GetWindow();
+      if (window) {
+        nsCOMPtr<nsIDocShell> docShell = do_GetInterface(window);
+        if (docShell) {
+          nsresult rv = docShell->GetDefaultLoadFlags(&loadFlags);
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+      }
+    }
+
     // If we are loading a script for a ServiceWorker then we must not
     // try to intercept it.  If the interception matches the current
     // ServiceWorker's scope then we could deadlock the load.
     if (mWorkerPrivate->IsServiceWorker()) {
       loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
     }
 
     if (!channel) {
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -287,17 +287,17 @@ public:
 
   bool
   Cancel()
   {
     return Notify(Canceling);
   }
 
   bool
-  Kill(JSContext* aCx)
+  Kill()
   {
     return Notify(Killing);
   }
 
   // We can assume that an nsPIDOMWindow will be available for Freeze, Thaw
   // as these are only used for globals going in and out of the bfcache.
   //
   // XXXbz: This is a bald-faced lie given the uses in RegisterSharedWorker and
--- a/dom/workers/test/browser.ini
+++ b/dom/workers/test/browser.ini
@@ -1,4 +1,10 @@
 [DEFAULT]
+support-files =
+  bug1047663_tab.html
+  bug1047663_worker.sjs
+  frame_script.js
+  head.js
 
+[browser_bug1047663.js]
 [browser_bug1104623.js]
 run-if = buildapp == 'browser'
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/browser_bug1047663.js
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const TAB_URL = EXAMPLE_URL + "bug1047663_tab.html";
+const WORKER_URL = EXAMPLE_URL + "bug1047663_worker.sjs";
+
+function test() {
+  waitForExplicitFinish();
+
+  Task.spawn(function* () {
+    let tab = yield addTab(TAB_URL);
+
+    // Create a worker. Post a message to it, and check the reply. Since the
+    // server side JavaScript file returns the first source for the first
+    // request, the reply should be "one". If the reply is correct, terminate
+    // the worker.
+    yield createWorkerInTab(tab, WORKER_URL);
+    let message = yield postMessageToWorkerInTab(tab, WORKER_URL, "ping");
+    is(message, "one");
+    yield terminateWorkerInTab(tab, WORKER_URL);
+
+    // Create a second worker with the same URL. Post a message to it, and check
+    // the reply. The server side JavaScript file returns the second source for
+    // all subsequent requests, but since the cache is still enabled, the reply
+    // should still be "one". If the reply is correct, terminate the worker.
+    yield createWorkerInTab(tab, WORKER_URL);
+    message = yield postMessageToWorkerInTab(tab, WORKER_URL, "ping");
+    is(message, "one");
+    yield terminateWorkerInTab(tab, WORKER_URL);
+
+    // Disable the cache in this tab. This should also disable the cache for all
+    // workers in this tab.
+    yield disableCacheInTab(tab);
+
+    // Create a third worker with the same URL. Post a message to it, and check
+    // the reply. Since the server side JavaScript file returns the second
+    // source for all subsequent requests, and the cache is now disabled, the
+    // reply should now be "two". If the reply is correct, terminate the worker.
+    yield createWorkerInTab(tab, WORKER_URL);
+    message = yield postMessageToWorkerInTab(tab, WORKER_URL, "ping");
+    is(message, "two");
+    yield terminateWorkerInTab(tab, WORKER_URL);
+
+    removeTab(tab);
+
+    finish();
+  });
+}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/bug1047663_tab.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8"/>
+  </head>
+  <body>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/bug1047663_worker.sjs
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const WORKER_1 = `
+  "use strict";
+
+  self.onmessage = function () {
+    postMessage("one");
+  };
+`;
+
+const WORKER_2 = `
+  "use strict";
+
+  self.onmessage = function () {
+    postMessage("two");
+  };
+`;
+
+function handleRequest(request, response) {
+  let count = getState("count");
+  if (count === "") {
+    count = "1";
+  }
+
+  // This header is necessary for the cache to trigger.
+  response.setHeader("Cache-control", "max-age=3600");
+
+  // If this is the first request, return the first source.
+  if (count === "1") {
+    response.write(WORKER_1);
+    setState("count", "2");
+  }
+  // For all subsequent requests, return the second source.
+  else {
+    response.write(WORKER_2);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/frame_script.js
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { interfaces: Ci } = Components;
+
+let workers = {};
+
+let methods = {
+  /**
+   * Create a worker with the given `url` in this tab.
+   */
+  createWorker: function (url) {
+    dump("Frame script: creating worker with url '" + url + "'\n");
+
+    workers[url] = new content.Worker(url);
+    return Promise.resolve();
+  },
+
+  /**
+   * Terminate the worker with the given `url` in this tab.
+   */
+  terminateWorker: function (url) {
+    dump("Frame script: terminating worker with url '" + url + "'\n");
+
+    workers[url].terminate();
+    delete workers[url];
+    return Promise.resolve();
+  },
+
+  /**
+   * Post the given `message` to the worker with the given `url` in this tab.
+   */
+  postMessageToWorker: function (url, message) {
+    dump("Frame script: posting message to worker with url '" + url + "'\n");
+
+    let worker = workers[url];
+    worker.postMessage(message);
+    return new Promise(function (resolve) {
+      worker.onmessage = function (event) {
+        worker.onmessage = null;
+        resolve(event.data);
+      };
+    });
+  },
+
+  /**
+   * Disable the cache for this tab.
+   */
+  disableCache: function () {
+    docShell.defaultLoadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE
+                              | Ci.nsIRequest.INHIBIT_CACHING;
+  }
+};
+
+addMessageListener("jsonrpc", function (event) {
+  let { id, method, params } = event.data;
+  Promise.resolve().then(function () {
+    return methods[method].apply(undefined, params);
+  }).then(function (result) {
+    sendAsyncMessage("jsonrpc", {
+      id: id,
+      result: result
+    });
+  }).catch(function (error) {
+    sendAsyncMessage("jsonrpc", {
+      id: id,
+      error: error.toString()
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/head.js
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const EXAMPLE_URL = "http://example.com/browser/dom/workers/test/";
+const FRAME_SCRIPT_URL = getRootDirectory(gTestPath) + "frame_script.js";
+
+/**
+ * Add a tab with given `url`, and load a frame script in it. Returns a promise
+ * that will be resolved when the tab finished loading.
+ */
+function addTab(url) {
+  let tab = gBrowser.addTab(TAB_URL);
+  gBrowser.selectedTab = tab;
+  let linkedBrowser = tab.linkedBrowser;
+  linkedBrowser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
+  return new Promise(function (resolve) {
+    linkedBrowser.addEventListener("load", function onload() {
+      linkedBrowser.removeEventListener("load", onload, true);
+      resolve(tab);
+    }, true);
+  });
+}
+
+/**
+ * Remove the given `tab`.
+ */
+function removeTab(tab) {
+  gBrowser.removeTab(tab);
+}
+
+let nextId = 0;
+
+/**
+ * Send a JSON RPC request to the frame script in the given `tab`, invoking the
+ * given `method` with the given `params`. Returns a promise that will be
+ * resolved with the result of the invocation.
+ */
+function jsonrpc(tab, method, params) {
+  let currentId = nextId++;
+  let messageManager = tab.linkedBrowser.messageManager;
+  messageManager.sendAsyncMessage("jsonrpc", {
+    id: currentId,
+    method: method,
+    params: params
+  });
+  return new Promise(function (resolve, reject) {
+    messageManager.addMessageListener("jsonrpc", function listener(event) {
+      let { id, result, error } = event.data;
+      if (id !== currentId) {
+        return;
+      }
+      messageManager.removeMessageListener("jsonrpc", listener);
+      if (error) {
+        reject(error);
+        return;
+      }
+      resolve(result);
+    });
+  });
+}
+
+/**
+ * Create a worker with the given `url` in the given `tab`.
+ */
+function createWorkerInTab(tab, url) {
+  return jsonrpc(tab, "createWorker", [url]);
+}
+
+/**
+ * Terminate the worker with the given `url` in the given `tab`.
+ */
+function terminateWorkerInTab(tab, url) {
+  return jsonrpc(tab, "terminateWorker", [url]);
+}
+
+/**
+ * Post the given `message` to the worker with the given `url` in the given
+ * `tab`.
+ */
+function postMessageToWorkerInTab(tab, url, message) {
+  return jsonrpc(tab, "postMessageToWorker", [url, message]);
+}
+
+/**
+ * Disable the cache in the given `tab`.
+ */
+function disableCacheInTab(tab) {
+  return jsonrpc(tab, "disableCache", []);
+}
--- a/dom/xslt/xslt/txEXSLTFunctions.cpp
+++ b/dom/xslt/xslt/txEXSLTFunctions.cpp
@@ -163,47 +163,47 @@ static const char * const sTypes[] = {
 // Function implementations
 // ------------------------------------------------------------------
 
 struct txEXSLTFunctionDescriptor
 {
     int8_t mMinParams;
     int8_t mMaxParams;
     Expr::ResultType mReturnType;
+    int32_t mNamespaceID;
     nsIAtom** mName;
-    int32_t mNamespaceID;
     const char* mNamespaceURI;
 };
 
 static const char kEXSLTCommonNS[] = "http://exslt.org/common";
 static const char kEXSLTSetsNS[] = "http://exslt.org/sets";
 static const char kEXSLTStringsNS[] = "http://exslt.org/strings";
 static const char kEXSLTMathNS[] = "http://exslt.org/math";
 static const char kEXSLTDatesAndTimesNS[] = "http://exslt.org/dates-and-times";
 
 // The order of this table must be the same as the
 // txEXSLTFunctionCall::eType enum
 static txEXSLTFunctionDescriptor descriptTable[] =
 {
-    { 1, 1, Expr::NODESET_RESULT, &nsGkAtoms::nodeSet, 0, kEXSLTCommonNS }, // NODE_SET
-    { 1, 1, Expr::STRING_RESULT,  &nsGkAtoms::objectType, 0, kEXSLTCommonNS }, // OBJECT_TYPE
-    { 2, 2, Expr::NODESET_RESULT, &nsGkAtoms::difference, 0, kEXSLTSetsNS }, // DIFFERENCE
-    { 1, 1, Expr::NODESET_RESULT, &nsGkAtoms::distinct, 0, kEXSLTSetsNS }, // DISTINCT
-    { 2, 2, Expr::BOOLEAN_RESULT, &nsGkAtoms::hasSameNode, 0, kEXSLTSetsNS }, // HAS_SAME_NODE
-    { 2, 2, Expr::NODESET_RESULT, &nsGkAtoms::intersection, 0, kEXSLTSetsNS }, // INTERSECTION
-    { 2, 2, Expr::NODESET_RESULT, &nsGkAtoms::leading, 0, kEXSLTSetsNS }, // LEADING
-    { 2, 2, Expr::NODESET_RESULT, &nsGkAtoms::trailing, 0, kEXSLTSetsNS }, // TRAILING
-    { 1, 1, Expr::STRING_RESULT,  &nsGkAtoms::concat, 0, kEXSLTStringsNS }, // CONCAT
-    { 1, 2, Expr::STRING_RESULT,  &nsGkAtoms::split, 0, kEXSLTStringsNS }, // SPLIT
-    { 1, 2, Expr::STRING_RESULT,  &nsGkAtoms::tokenize, 0, kEXSLTStringsNS }, // TOKENIZE
-    { 1, 1, Expr::NUMBER_RESULT,  &nsGkAtoms::max, 0, kEXSLTMathNS }, // MAX
-    { 1, 1, Expr::NUMBER_RESULT,  &nsGkAtoms::min, 0, kEXSLTMathNS }, // MIN
-    { 1, 1, Expr::NODESET_RESULT, &nsGkAtoms::highest, 0, kEXSLTMathNS }, // HIGHEST
-    { 1, 1, Expr::NODESET_RESULT, &nsGkAtoms::lowest, 0, kEXSLTMathNS }, // LOWEST
-    { 0, 0, Expr::STRING_RESULT,  &nsGkAtoms::dateTime, 0, kEXSLTDatesAndTimesNS }, // DATE_TIME
+    { 1, 1, Expr::NODESET_RESULT, 0, &nsGkAtoms::nodeSet, kEXSLTCommonNS }, // NODE_SET
+    { 1, 1, Expr::STRING_RESULT, 0, &nsGkAtoms::objectType, kEXSLTCommonNS }, // OBJECT_TYPE
+    { 2, 2, Expr::NODESET_RESULT, 0, &nsGkAtoms::difference, kEXSLTSetsNS }, // DIFFERENCE
+    { 1, 1, Expr::NODESET_RESULT, 0, &nsGkAtoms::distinct, kEXSLTSetsNS }, // DISTINCT
+    { 2, 2, Expr::BOOLEAN_RESULT, 0, &nsGkAtoms::hasSameNode, kEXSLTSetsNS }, // HAS_SAME_NODE
+    { 2, 2, Expr::NODESET_RESULT, 0, &nsGkAtoms::intersection, kEXSLTSetsNS }, // INTERSECTION
+    { 2, 2, Expr::NODESET_RESULT, 0, &nsGkAtoms::leading, kEXSLTSetsNS }, // LEADING
+    { 2, 2, Expr::NODESET_RESULT, 0, &nsGkAtoms::trailing, kEXSLTSetsNS }, // TRAILING
+    { 1, 1, Expr::STRING_RESULT, 0, &nsGkAtoms::concat, kEXSLTStringsNS }, // CONCAT
+    { 1, 2, Expr::STRING_RESULT, 0, &nsGkAtoms::split, kEXSLTStringsNS }, // SPLIT
+    { 1, 2, Expr::STRING_RESULT, 0, &nsGkAtoms::tokenize, kEXSLTStringsNS }, // TOKENIZE
+    { 1, 1, Expr::NUMBER_RESULT, 0, &nsGkAtoms::max, kEXSLTMathNS }, // MAX
+    { 1, 1, Expr::NUMBER_RESULT, 0, &nsGkAtoms::min, kEXSLTMathNS }, // MIN
+    { 1, 1, Expr::NODESET_RESULT, 0, &nsGkAtoms::highest, kEXSLTMathNS }, // HIGHEST
+    { 1, 1, Expr::NODESET_RESULT, 0, &nsGkAtoms::lowest, kEXSLTMathNS }, // LOWEST
+    { 0, 0, Expr::STRING_RESULT, 0, &nsGkAtoms::dateTime, kEXSLTDatesAndTimesNS }, // DATE_TIME
 
 };
 
 class txEXSLTFunctionCall : public FunctionCall
 {
 public:
     // The order of this enum must be the same as the descriptTable
     // table above
--- a/embedding/components/find/nsWebBrowserFind.cpp
+++ b/embedding/components/find/nsWebBrowserFind.cpp
@@ -812,17 +812,17 @@ nsWebBrowserFind::GetFrameSelection(nsPI
   if (frame) {
     frame->GetSelectionController(presContext, getter_AddRefs(selCon));
     selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
                          getter_AddRefs(sel));
     if (sel) {
       int32_t count = -1;
       sel->GetRangeCount(&count);
       if (count > 0) {
-        return nullptr;
+        return sel.forget();
       }
     }
   }
 
   selCon = do_QueryInterface(presShell);
   selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
                        getter_AddRefs(sel));
   return sel.forget();
--- a/gfx/2d/PathHelpers.h
+++ b/gfx/2d/PathHelpers.h
@@ -29,28 +29,29 @@ inline Float ComputeKappaFactor(Float aA
  * Draws a partial arc <= 90 degrees given exact start and end points.
  * Assumes that it is continuing from an already specified start point.
  */
 template <typename T>
 inline void PartialArcToBezier(T* aSink,
                                const Size& aRadius,
                                const Point& aStartPoint, const Point& aEndPoint,
                                const Point& aStartOffset, const Point& aEndOffset,
-                               Float aKappaFactor = kKappaFactor)
+                               Float aKappaFactor = kKappaFactor,
+                               const Matrix& aTransform = Matrix())
 {
   Float kappaX = aKappaFactor * aRadius.width;
   Float kappaY = aKappaFactor * aRadius.height;
 
   Point cp1 =
     aStartPoint + Point(-aStartOffset.y * kappaX, aStartOffset.x * kappaY);
 
   Point cp2 =
     aEndPoint + Point(aEndOffset.y * kappaX, -aEndOffset.x * kappaY);
 
-  aSink->BezierTo(cp1, cp2, aEndPoint);
+  aSink->BezierTo(aTransform * cp1, aTransform * cp2, aTransform * aEndPoint);
 }
 
 /**
  * Draws an acute arc (<= 90 degrees) given exact start and end points.
  * Specialized version avoiding kappa calculation.
  */
 template <typename T>
 inline void AcuteArcToBezier(T* aSink,
@@ -85,17 +86,18 @@ inline void AcuteArcToBezier(T* aSink,
                              Float aStartAngle, Float aEndAngle)
 {
   AcuteArcToBezier(aSink, aOrigin, aRadius, aStartPoint, aEndPoint,
                    ComputeKappaFactor(aEndAngle - aStartAngle));
 }
 
 template <typename T>
 void ArcToBezier(T* aSink, const Point &aOrigin, const Size &aRadius,
-                 float aStartAngle, float aEndAngle, bool aAntiClockwise)
+                 float aStartAngle, float aEndAngle, bool aAntiClockwise,
+                 float aRotation = 0.0f)
 {
   Float sweepDirection = aAntiClockwise ? -1.0f : 1.0f;
 
   // Calculate the total arc we're going to sweep.
   Float arcSweepLeft = (aEndAngle - aStartAngle) * sweepDirection;
 
   // Clockwise we always sweep from the smaller to the larger angle, ccw
   // it's vice versa.
@@ -106,33 +108,34 @@ void ArcToBezier(T* aSink, const Point &
     aStartAngle = aEndAngle - arcSweepLeft * sweepDirection;
   } else if (arcSweepLeft > Float(2.0f * M_PI)) {
     // Sweeping more than 2 * pi is a full circle.
     arcSweepLeft = Float(2.0f * M_PI);
   }
 
   Float currentStartAngle = aStartAngle;
   Point currentStartOffset(cosf(aStartAngle), sinf(aStartAngle));
-  Point currentStartPoint(aOrigin.x + currentStartOffset.x * aRadius.width,
-                          aOrigin.y + currentStartOffset.y * aRadius.height);
-
-  aSink->LineTo(currentStartPoint);
+  Point currentStartPoint(currentStartOffset.x * aRadius.width,
+                          currentStartOffset.y * aRadius.height);
+  Matrix transform(cosf(aRotation), sinf(aRotation), -sinf(aRotation), cosf(aRotation), aOrigin.x, aOrigin.y);
+  aSink->LineTo(transform * currentStartPoint);
 
   while (arcSweepLeft > 0) {
     Float currentEndAngle =
       currentStartAngle + std::min(arcSweepLeft, Float(M_PI / 2.0f)) * sweepDirection;
 
     Point currentEndOffset(cosf(currentEndAngle), sinf(currentEndAngle));
-    Point currentEndPoint(aOrigin.x + currentEndOffset.x * aRadius.width,
-                          aOrigin.y + currentEndOffset.y * aRadius.height);
+    Point currentEndPoint(currentEndOffset.x * aRadius.width,
+                          currentEndOffset.y * aRadius.height);
 
     PartialArcToBezier(aSink, aRadius,
                        currentStartPoint, currentEndPoint,
                        currentStartOffset, currentEndOffset,
-                       ComputeKappaFactor(currentEndAngle - currentStartAngle));
+                       ComputeKappaFactor(currentEndAngle - currentStartAngle),
+                       transform);
 
     // We guarantee here the current point is the start point of the next
     // curve segment.
     arcSweepLeft -= Float(M_PI / 2.0f);
     currentStartAngle = currentEndAngle;
     currentStartOffset = currentEndOffset;
     currentStartPoint = currentEndPoint;
   }
--- a/gfx/graphite2/README.mozilla
+++ b/gfx/graphite2/README.mozilla
@@ -1,3 +1,6 @@
-This directory contains the Graphite2 library release 1.3.6 from
-https://github.com/silnrsi/graphite/releases/download/1.3.6/graphite-minimal-1.3.6.tgz
-See ./gfx/graphite2/moz-gr-update.sh for update procedure.
+This directory contains the Graphite2 library release 1.3.7 from
+https://github.com/silnrsi/graphite/releases/download/1.3.7/graphite2-minimal-1.3.7.tgz
+See gfx/graphite2/moz-gr-update.sh for update procedure.
+
+Also cherry-picked the post-1.3.7 commit 7dc29f3e4665b17f6e825957b707db6da36ae7d8
+to keep our reftests happy (affects the PigLatin test font).
\ No newline at end of file
--- a/gfx/graphite2/include/graphite2/Font.h
+++ b/gfx/graphite2/include/graphite2/Font.h
@@ -25,17 +25,17 @@
     either version 2 of the License or (at your option) any later version.
 */
 #pragma once
 
 #include "graphite2/Types.h"
 
 #define GR2_VERSION_MAJOR   1
 #define GR2_VERSION_MINOR   3
-#define GR2_VERSION_BUGFIX  6
+#define GR2_VERSION_BUGFIX  7
 
 #ifdef __cplusplus
 extern "C"
 {
 #endif
 
 typedef struct gr_face          gr_face;
 typedef struct gr_font          gr_font;
--- a/gfx/graphite2/moz-gr-update.sh
+++ b/gfx/graphite2/moz-gr-update.sh
@@ -14,17 +14,17 @@
 RELEASE=$1
 
 if [ "x$RELEASE" == "x" ]
 then
     echo "Must provide the version number to be used."
     exit 1
 fi
 
-TARBALL="https://github.com/silnrsi/graphite/releases/download/$RELEASE/graphite-minimal-$RELEASE.tgz"
+TARBALL="https://github.com/silnrsi/graphite/releases/download/$RELEASE/graphite2-minimal-$RELEASE.tgz"
 
 foo=`basename $0`
 TMPFILE=`mktemp -t ${foo}` || exit 1
 
 curl -L "$TARBALL" -o "$TMPFILE"
 tar -x -z -C gfx/graphite2/ --strip-components 1 -f "$TMPFILE" || exit 1
 rm "$TMPFILE"
 
--- a/gfx/graphite2/src/CachedFace.cpp
+++ b/gfx/graphite2/src/CachedFace.cpp
@@ -64,20 +64,20 @@ bool CachedFace::runGraphite(Segment *se
         return false;
 
     assert(m_cacheStore);
     // find where the segment can be broken
     Slot * subSegStartSlot = seg->first();
     Slot * subSegEndSlot = subSegStartSlot;
     uint16 cmapGlyphs[eMaxSpliceSize];
     int subSegStart = 0;
-    for (unsigned int i = 0; i < seg->charInfoCount(); ++i)
+    for (unsigned int i = 0; i < seg->charInfoCount() && subSegEndSlot; ++i)
     {
         const unsigned int length = i - subSegStart + 1;
-        if (length < eMaxSpliceSize)
+        if (length < eMaxSpliceSize && subSegEndSlot->gid() < m_cacheStore->maxCmapGid())
             cmapGlyphs[length-1] = subSegEndSlot->gid();
         else return false;
         const bool spaceOnly = m_cacheStore->isSpaceGlyph(subSegEndSlot->gid());
         // at this stage the character to slot mapping is still 1 to 1
         const int   breakWeight = seg->charinfo(i)->breakWeight(),
                     nextBreakWeight = (i + 1 < seg->charInfoCount())?
                             seg->charinfo(i+1)->breakWeight() : 0;
         const uint8 f = seg->charinfo(i)->flags();
--- a/gfx/graphite2/src/Code.cpp
+++ b/gfx/graphite2/src/Code.cpp
@@ -61,93 +61,88 @@ inline bool is_return(const instr i) {
     const instr pop_ret  = *opmap[POP_RET].impl,
                 ret_zero = *opmap[RET_ZERO].impl,
                 ret_true = *opmap[RET_TRUE].impl;
     return i == pop_ret || i == ret_zero || i == ret_true;
 }
 
 struct context
 {
-    context(uint8 ref=0) : codeRef(ref) {flags.changed=false; flags.referenced=false; flags.inserted=false;}
+    context(uint8 ref=0) : codeRef(ref) {flags.changed=false; flags.referenced=false;}
     struct { 
         uint8   changed:1,
-                referenced:1,
-                inserted:1;
+                referenced:1;
     } flags;
     uint8       codeRef;
 };
 
 } // end namespace
 
 
 class Machine::Code::decoder
 {
 public:
     struct limits;
-    struct analysis
-    {
-        static const int NUMCONTEXTS = 256;
-        uint8     slotref;
-        context   contexts[NUMCONTEXTS];
-        byte      max_ref;
-        
-        analysis() : slotref(0), max_ref(0) {};
-        void set_ref(int index, bool incinsert=false) throw();
-        void set_noref(int index) throw();
-        void set_changed(int index) throw();
-
-    };
+    static const int NUMCONTEXTS = 256;
     
     decoder(limits & lims, Code &code, enum passtype pt) throw();
     
     bool        load(const byte * bc_begin, const byte * bc_end);
     void        apply_analysis(instr * const code, instr * code_end);
-    byte        max_ref() { return _analysis.max_ref; }
-    int         pre_context() const { return _pre_context; }
+    byte        max_ref() { return _max_ref; }
+    int         out_index() const { return _out_index; }
     
 private:
+    void        set_ref(int index) throw();
+    void        set_noref(int index) throw();
+    void        set_changed(int index) throw();
     opcode      fetch_opcode(const byte * bc);
     void        analyse_opcode(const opcode, const int8 * const dp) throw();
     bool        emit_opcode(opcode opc, const byte * & bc);
     bool        validate_opcode(const opcode opc, const byte * const bc);
     bool        valid_upto(const uint16 limit, const uint16 x) const throw();
     bool        test_context() const throw();
+    bool        test_ref(int8 index) const throw();
     void        failure(const status_t s) const throw() { _code.failure(s); }
     
     Code              & _code;
-    int                 _pre_context;
-    uint16              _rule_length;
+    int                 _out_index;
+    uint16              _out_length;
     instr             * _instr;
     byte              * _data;
     limits            & _max;
-    analysis            _analysis;
     enum passtype       _passtype;
     int                 _stack_depth;
     bool                _in_ctxt_item;
+    int16               _slotref;
+    context             _contexts[NUMCONTEXTS];
+    byte                _max_ref;
 };
 
 
 struct Machine::Code::decoder::limits
 {
   const byte       * bytecode;
   const uint8        pre_context;
   const uint16       rule_length,
                      classes,
                      glyf_attrs,
                      features;
   const byte         attrid[gr_slatMax];
 };
    
 inline Machine::Code::decoder::decoder(limits & lims, Code &code, enum passtype pt) throw()
 : _code(code),
-  _pre_context(code._constraint ? 0 : lims.pre_context), 
-  _rule_length(code._constraint ? 1 : lims.rule_length), 
+  _out_index(code._constraint ? 0 : lims.pre_context), 
+  _out_length(code._constraint ? 1 : lims.rule_length), 
   _instr(code._code), _data(code._data), _max(lims), _passtype(pt),
   _stack_depth(0),
-  _in_ctxt_item(false)
+  _in_ctxt_item(false),
+  _slotref(0),
+  _max_ref(0)
 { }
     
 
 
 Machine::Code::Code(bool is_constraint, const byte * bytecode_begin, const byte * const bytecode_end,
            uint8 pre_context, uint16 rule_length, const Silf & silf, const Face & face,
            enum passtype pt, byte * * const _out)
  :  _code(0), _data(0), _data_size(0), _instr_count(0), _max_ref(0), _status(loaded),
@@ -163,17 +158,17 @@ Machine::Code::Code(bool is_constraint, 
       return;
     }
     assert(bytecode_end > bytecode_begin);
     const opcode_t *    op_to_fn = Machine::getOpcodeTable();
     
     // Allocate code and data target buffers, these sizes are a worst case
     // estimate.  Once we know their real sizes the we'll shrink them.
     if (_out)   _code = reinterpret_cast<instr *>(*_out);
-    else        _code = static_cast<instr *>(malloc(estimateCodeDataOut(bytecode_end-bytecode_begin)));
+    else        _code = static_cast<instr *>(malloc(estimateCodeDataOut(bytecode_end-bytecode_begin, 1, is_constraint ? 0 : rule_length)));
     _data = reinterpret_cast<byte *>(_code + (bytecode_end - bytecode_begin));
     
     if (!_code || !_data) {
         failure(alloc_failed);
         return;
     }
     
     decoder::limits lims = {
@@ -319,47 +314,57 @@ opcode Machine::Code::decoder::fetch_opc
         case COND :
             _stack_depth -= 2;
             if (_stack_depth <= 0)
                 failure(underfull_stack);
             break;
         case NEXT :
         case NEXT_N :           // runtime checked
         case COPY_NEXT :
-            test_context();
-            ++_pre_context;
+            ++_out_index;
+            if (_out_index < -1 || _out_index > _out_length || _slotref > _max.rule_length)
+                failure(out_of_range_data);
             break;
         case PUT_GLYPH_8BIT_OBS :
             valid_upto(_max.classes, bc[0]);
             test_context();
             break;
         case PUT_SUBS_8BIT_OBS :
-            valid_upto(_rule_length, _pre_context + int8(bc[0]));
+            test_ref(int8(bc[0]));
             valid_upto(_max.classes, bc[1]);
             valid_upto(_max.classes, bc[2]);
             test_context();
             break;
         case PUT_COPY :
-            valid_upto(_rule_length, _pre_context + int8(bc[0]));
+            test_ref(int8(bc[0]));
             test_context();
             break;
         case INSERT :
             if (_passtype >= PASS_TYPE_POSITIONING)
                 failure(invalid_opcode);
-            else
-                --_pre_context;
+            ++_out_length;
+            if (_out_index < 0) ++_out_index;
+            if (_out_index < -1 || _out_index >= _out_length)
+                failure(out_of_range_data);
             break;
         case DELETE :
             if (_passtype >= PASS_TYPE_POSITIONING)
                 failure(invalid_opcode);
-            test_context();
+            if (_out_index < _max.pre_context)
+                failure(out_of_range_data);
+            --_out_index;
+            --_out_length;
+            if (_out_index < -1 || _out_index > _out_length)
+                failure(out_of_range_data);
             break;
         case ASSOC :
+            if (bc[0] == 0)
+                failure(out_of_range_data);
             for (uint8 num = bc[0]; num; --num)
-                valid_upto(_rule_length, _pre_context + int8(bc[num]));
+                test_ref(int8(bc[num]));
             test_context();
             break;
         case CNTXT_ITEM :
             valid_upto(_max.rule_length, _max.pre_context + int8(bc[0]));
             if (bc + 2 + bc[1] >= _max.bytecode)    failure(jump_past_end);
             if (_in_ctxt_item)                      failure(nested_context_item);
             break;
         case ATTR_SET :
@@ -378,52 +383,43 @@ opcode Machine::Code::decoder::fetch_opc
                 failure(underfull_stack);
             if (valid_upto(gr_slatMax, bc[0]))
                 valid_upto(_max.attrid[bc[0]], bc[1]);
             test_context();
             break;
         case PUSH_SLOT_ATTR :
             ++_stack_depth;
             valid_upto(gr_slatMax, bc[0]);
-            valid_upto(_rule_length, _pre_context + int8(bc[1]));
+            test_ref(int8(bc[1]));
             if (attrCode(bc[0]) == gr_slatUserDefn)     // use IATTR for user attributes
                 failure(out_of_range_data);
             break;
         case PUSH_GLYPH_ATTR_OBS :
+        case PUSH_ATT_TO_GATTR_OBS :
             ++_stack_depth;
             valid_upto(_max.glyf_attrs, bc[0]);
-            valid_upto(_rule_length, _pre_context + int8(bc[1]));
+            test_ref(int8(bc[1]));
             break;
+        case PUSH_ATT_TO_GLYPH_METRIC :
         case PUSH_GLYPH_METRIC :
             ++_stack_depth;
             valid_upto(kgmetDescent, bc[0]);
-            valid_upto(_rule_length, _pre_context + int8(bc[1]));
+            test_ref(int8(bc[1]));
             // level: dp[2] no check necessary
             break;
         case PUSH_FEAT :
             ++_stack_depth;
             valid_upto(_max.features, bc[0]);
-            valid_upto(_rule_length, _pre_context + int8(bc[1]));
-            break;
-        case PUSH_ATT_TO_GATTR_OBS :
-            ++_stack_depth;
-            valid_upto(_max.glyf_attrs, bc[0]);
-            valid_upto(_rule_length, _pre_context + int8(bc[1]));
-            break;
-        case PUSH_ATT_TO_GLYPH_METRIC :
-            ++_stack_depth;
-            valid_upto(kgmetDescent, bc[0]);
-            valid_upto(_rule_length, _pre_context + int8(bc[1]));
-            // level: dp[2] no check necessary
+            test_ref(int8(bc[1]));
             break;
         case PUSH_ISLOT_ATTR :
             ++_stack_depth;
             if (valid_upto(gr_slatMax, bc[0]))
             {
-                valid_upto(_rule_length, _pre_context + int8(bc[1]));
+                test_ref(int8(bc[1]));
                 valid_upto(_max.attrid[bc[0]], bc[2]);
             }
             break;
         case PUSH_IGLYPH_ATTR :// not implemented
             ++_stack_depth;
             break;
         case POP_RET :
             if (--_stack_depth < 0)
@@ -442,118 +438,107 @@ opcode Machine::Code::decoder::fetch_opc
                 valid_upto(_max.attrid[bc[0]], bc[1]);
             test_context();
             break;
         case PUSH_PROC_STATE :  // dummy: dp[0] no check necessary
         case PUSH_VERSION :
             ++_stack_depth;
             break;
         case PUT_SUBS :
-            valid_upto(_rule_length, _pre_context + int8(bc[0]));
+            test_ref(int8(bc[0]));
             valid_upto(_max.classes, uint16(bc[1]<< 8) | bc[2]);
             valid_upto(_max.classes, uint16(bc[3]<< 8) | bc[4]);
             test_context();
             break;
         case PUT_SUBS2 :        // not implemented
         case PUT_SUBS3 :        // not implemented
             break;
         case PUT_GLYPH :
             valid_upto(_max.classes, uint16(bc[0]<< 8) | bc[1]);
             test_context();
             break;
         case PUSH_GLYPH_ATTR :
         case PUSH_ATT_TO_GLYPH_ATTR :
             ++_stack_depth;
             valid_upto(_max.glyf_attrs, uint16(bc[0]<< 8) | bc[1]);
-            valid_upto(_rule_length, _pre_context + int8(bc[2]));
+            test_ref(int8(bc[2]));
+            break;
+        case SET_FEAT :
+            valid_upto(_max.features, bc[0]);
+            test_ref(int8(bc[1]));
             break;
         default:
             failure(invalid_opcode);
             break;
     }
 
     return bool(_code) ? opc : MAX_OPCODE;
 }
 
 
 void Machine::Code::decoder::analyse_opcode(const opcode opc, const int8  * arg) throw()
 {
-  if (_code._constraint) return;
-  
   switch (opc)
   {
     case DELETE :
       _code._delete = true;
       break;
+    case ASSOC :
+      set_changed(0);
+//      for (uint8 num = arg[0]; num; --num)
+//        _analysis.set_noref(num);
+      break;
     case PUT_GLYPH_8BIT_OBS :
     case PUT_GLYPH :
       _code._modify = true;
-      _analysis.set_changed(0);
+      set_changed(0);
       break;
     case ATTR_SET :
     case ATTR_ADD :
+    case ATTR_SUB :
     case ATTR_SET_SLOT :
     case IATTR_SET_SLOT :
     case IATTR_SET :
     case IATTR_ADD :
     case IATTR_SUB :
-      _analysis.set_noref(0);
+      set_noref(0);
       break;
     case NEXT :
     case COPY_NEXT :
-      if (!_analysis.contexts[_analysis.slotref].flags.inserted)
-        ++_analysis.slotref;
-      _analysis.contexts[_analysis.slotref] = context(_code._instr_count+1);
+      ++_slotref;
+      _contexts[_slotref] = context(_code._instr_count+1);
       // if (_analysis.slotref > _analysis.max_ref) _analysis.max_ref = _analysis.slotref;
       break;
     case INSERT :
-      _analysis.contexts[_analysis.slotref].flags.inserted = true;
+      if (_slotref >= 0) --_slotref;
       _code._modify = true;
       break;
     case PUT_SUBS_8BIT_OBS :    // slotref on 1st parameter
     case PUT_SUBS : 
       _code._modify = true;
-      _analysis.set_changed(0);
+      set_changed(0);
       GR_FALLTHROUGH;
       // no break
     case PUT_COPY :
-    {
-      if (arg[0] != 0) { _analysis.set_changed(0); _code._modify = true; }
-      if (arg[0] <= 0 && -arg[0] <= _analysis.slotref - _analysis.contexts[_analysis.slotref].flags.inserted)
-        _analysis.set_ref(arg[0], true);
-      else if (arg[0] > 0)
-        _analysis.set_ref(arg[0], true);
+      if (arg[0] != 0) { set_changed(0); _code._modify = true; }
+      set_ref(arg[0]);
       break;
-    }
-    case PUSH_ATT_TO_GATTR_OBS : // slotref on 2nd parameter
-        if (_code._constraint) return;
-        GR_FALLTHROUGH;
-        // no break
     case PUSH_GLYPH_ATTR_OBS :
     case PUSH_SLOT_ATTR :
     case PUSH_GLYPH_METRIC :
+    case PUSH_ATT_TO_GATTR_OBS :
     case PUSH_ATT_TO_GLYPH_METRIC :
     case PUSH_ISLOT_ATTR :
     case PUSH_FEAT :
-      if (arg[1] <= 0 && -arg[1] <= _analysis.slotref - _analysis.contexts[_analysis.slotref].flags.inserted)
-        _analysis.set_ref(arg[1], true);
-      else if (arg[1] > 0)
-        _analysis.set_ref(arg[1], true);
+    case SET_FEAT :
+      set_ref(arg[1]);
       break;
     case PUSH_ATT_TO_GLYPH_ATTR :
-        if (_code._constraint) return;
-        GR_FALLTHROUGH;
-        // no break
     case PUSH_GLYPH_ATTR :
-      if (arg[2] <= 0 && -arg[2] <= _analysis.slotref - _analysis.contexts[_analysis.slotref].flags.inserted)
-        _analysis.set_ref(arg[2], true);
-      else if (arg[2] > 0)
-        _analysis.set_ref(arg[2], true);
-      break;
-    case ASSOC :                // slotrefs in varargs
+      set_ref(arg[2]);
       break;
     default:
         break;
   }
 }
 
 
 bool Machine::Code::decoder::emit_opcode(opcode opc, const byte * & bc)
@@ -579,57 +564,60 @@ bool Machine::Code::decoder::emit_opcode
         _data            += param_sz;
         _code._data_size += param_sz;
     }
     
     // recursively decode a context item so we can split the skip into 
     // instruction and data portions.
     if (opc == CNTXT_ITEM)
     {
-        assert(_pre_context == 0);
+        assert(_out_index == 0);
         _in_ctxt_item = true;
-        _pre_context = _max.pre_context + int8(_data[-2]);
-        _rule_length = _max.rule_length;
+        _out_index = _max.pre_context + int8(_data[-2]);
+        _slotref = int8(_data[-2]);
+        _out_length = _max.rule_length;
 
         const size_t ctxt_start = _code._instr_count;
         byte & instr_skip = _data[-1];
         byte & data_skip  = *_data++;
         ++_code._data_size;
         const byte *curr_end = _max.bytecode;
 
         if (load(bc, bc + instr_skip))
         {
             bc += instr_skip;
             data_skip  = instr_skip - (_code._instr_count - ctxt_start);
             instr_skip = _code._instr_count - ctxt_start;
             _max.bytecode = curr_end;
 
-            _rule_length = 1;
-            _pre_context = 0;
+            _out_length = 1;
+            _out_index = 0;
+            _slotref = 0;
             _in_ctxt_item = false;
         }
         else
         {
-            _pre_context = 0;
+            _out_index = 0;
+            _slotref = 0;
             return false;
         }
     }
     
     return bool(_code);
 }
 
 
 void Machine::Code::decoder::apply_analysis(instr * const code, instr * code_end)
 {
     // insert TEMP_COPY commands for slots that need them (that change and are referenced later)
     int tempcount = 0;
     if (_code._constraint) return;
 
     const instr temp_copy = Machine::getOpcodeTable()[TEMP_COPY].impl[0];
-    for (const context * c = _analysis.contexts, * const ce = c + _analysis.slotref; c != ce; ++c)
+    for (const context * c = _contexts, * const ce = c + _slotref; c < ce; ++c)
     {
         if (!c->flags.referenced || !c->flags.changed) continue;
         
         instr * const tip = code + c->codeRef + tempcount;        
         memmove(tip+1, tip, (code_end - tip) * sizeof(instr));
         *tip = temp_copy;
         ++code_end;
         ++tempcount;
@@ -644,16 +632,21 @@ inline
 bool Machine::Code::decoder::validate_opcode(const opcode opc, const byte * const bc)
 {
     if (opc >= MAX_OPCODE)
     {
         failure(invalid_opcode);
         return false;
     }
     const opcode_t & op = Machine::getOpcodeTable()[opc];
+    if (op.impl[_code._constraint] == 0)
+    {
+        failure(unimplemented_opcode_used);
+        return false;
+    }
     if (op.param_sz == VARARGS && bc >= _max.bytecode)
     {
         failure(arguments_exhausted);
         return false;
     }
     const size_t param_sz = op.param_sz == VARARGS ? bc[0] + 1 : op.param_sz;
     if (bc - 1 + param_sz >= _max.bytecode)
     {
@@ -666,56 +659,59 @@ bool Machine::Code::decoder::validate_op
 
 bool Machine::Code::decoder::valid_upto(const uint16 limit, const uint16 x) const throw()
 {
     const bool t = (limit != 0) && (x < limit);
     if (!t) failure(out_of_range_data);
     return t;
 }
 
+inline
+bool Machine::Code::decoder::test_ref(int8 index) const throw()
+{
+    return valid_upto(_max.rule_length, _slotref + _max.pre_context + index);
+}
+
 bool Machine::Code::decoder::test_context() const throw()
 {
-    if (_pre_context >= _rule_length || _analysis.slotref >= analysis::NUMCONTEXTS - 1)
+    if (_out_index >= _out_length || _out_index < 0 || _slotref >= NUMCONTEXTS - 1)
     {
         failure(out_of_range_data);
         return false;
     }
     return true;
 }
 
 inline 
 void Machine::Code::failure(const status_t s) throw() {
     release_buffers();
     _status = s;
 }
 
 
 inline
-void Machine::Code::decoder::analysis::set_ref(int index, bool incinsert) throw() {
-    if (incinsert && contexts[slotref].flags.inserted) --index;
-    if (index + slotref < 0 || index + slotref >= NUMCONTEXTS) return;
-    contexts[index + slotref].flags.referenced = true;
-    if ((index > 0 || !contexts[index + slotref].flags.inserted) && index + slotref > max_ref) max_ref = index + slotref;
+void Machine::Code::decoder::set_ref(int index) throw() {
+    if (index + _slotref < 0 || index + _slotref >= NUMCONTEXTS) return;
+    _contexts[index + _slotref].flags.referenced = true;
+    if (index + _slotref > _max_ref) _max_ref = index + _slotref;
 }
 
 
 inline
-void Machine::Code::decoder::analysis::set_noref(int index) throw() {
-    if (contexts[slotref].flags.inserted) --index;
-    if (index + slotref < 0 || index + slotref >= NUMCONTEXTS) return;
-    if ((index > 0 || !contexts[index + slotref].flags.inserted) && index + slotref > max_ref) max_ref = index + slotref;
+void Machine::Code::decoder::set_noref(int index) throw() {
+    if (index + _slotref < 0 || index + _slotref >= NUMCONTEXTS) return;
+    if (index + _slotref > _max_ref) _max_ref = index + _slotref;
 }
 
 
 inline
-void Machine::Code::decoder::analysis::set_changed(int index) throw() {
-    if (contexts[slotref].flags.inserted) --index;
-    if (index + slotref < 0 || index + slotref >= NUMCONTEXTS) return;
-    contexts[index + slotref].flags.changed = true;
-    if ((index > 0 || !contexts[index + slotref].flags.inserted) && index + slotref > max_ref) max_ref = index + slotref;
+void Machine::Code::decoder::set_changed(int index) throw() {
+    if (index + _slotref < 0 || index + _slotref >= NUMCONTEXTS) return;
+    _contexts[index + _slotref].flags.changed= true;
+    if (index + _slotref > _max_ref) _max_ref = index + _slotref;
 }
 
 
 void Machine::Code::release_buffers() throw()
 {
     if (_own)
         free(_code);
     _code = 0;
--- a/gfx/graphite2/src/Face.cpp
+++ b/gfx/graphite2/src/Face.cpp
@@ -178,17 +178,18 @@ bool Face::runGraphite(Segment *seg, con
     if ((seg->dir() & 3) == 3 && aSilf->bidiPass() == 0xFF)
         seg->doMirror(aSilf->aMirror());
     bool res = aSilf->runGraphite(seg, 0, aSilf->positionPass(), true);
     if (res)
     {
         seg->associateChars(0, seg->charInfoCount());
         if (aSilf->flags() & 0x20)
             res &= seg->initCollisions();
-        res &= aSilf->runGraphite(seg, aSilf->positionPass(), aSilf->numPasses(), false);
+        if (res)
+            res &= aSilf->runGraphite(seg, aSilf->positionPass(), aSilf->numPasses(), false);
     }
 
 #if !defined GRAPHITE2_NTRACING
     if (dbgout)
 {
         seg->positionSlots(0, 0, 0, aSilf->dir());
         *dbgout             << json::item
                             << json::close // Close up the passes array
@@ -226,17 +227,17 @@ const Silf *Face::chooseSilf(uint32 scri
         return m_silfs;
 }
 
 uint16 Face::findPseudo(uint32 uid) const
 {
     return (m_numSilf) ? m_silfs[0].findPseudo(uid) : 0;
 }
 
-uint16 Face::getGlyphMetric(uint16 gid, uint8 metric) const
+int32 Face::getGlyphMetric(uint16 gid, uint8 metric) const
 {
     switch (metrics(metric))
     {
         case kgmetAscent : return m_ascent;
         case kgmetDescent : return m_descent;
         default: 
             if (gid >= glyphs().numGlyphs()) return 0;
             return glyphs().glyph(gid)->getMetric(metric);
@@ -277,17 +278,17 @@ Face::Table::Table(const Face & face, co
 : _f(&face), _compressed(false)
 {
     size_t sz = 0;
     _p = static_cast<const byte *>((*_f->m_ops.get_table)(_f->m_appFaceHandle, n, &sz));
     _sz = uint32(sz);
 
     if (!TtfUtil::CheckTable(n, _p, _sz))
     {
-        this->~Table();     // Make sure we release the table buffer even if the table filed it's checks
+        releaseBuffers();     // Make sure we release the table buffer even if the table failed it's checks
         return;
     }
 
     if (be::peek<uint32>(_p) >= version)
         decompress();
 }
 
 void Face::Table::releaseBuffers()
@@ -324,17 +325,18 @@ Error Face::Table::decompress()
     switch(compression(hdr >> 27))
     {
     case NONE: return e;
 
     case LZ4:
     {
         uncompressed_size  = hdr & 0x07ffffff;
         uncompressed_table = gralloc<byte>(uncompressed_size);
-        if (!e.test(!uncompressed_table, E_OUTOFMEM))
+        if (!e.test(!uncompressed_table || uncompressed_size < 4, E_OUTOFMEM))
+            memset(uncompressed_table, 0, 4);   // make sure version number is initialised
             // coverity[forward_null : FALSE] - uncompressed_table has been checked so can't be null
             // coverity[checked_return : FALSE] - we test e later
             e.test(lz4::decompress(p, _sz - 2*sizeof(uint32), uncompressed_table, uncompressed_size) != signed(uncompressed_size), E_SHRINKERFAILED);
         break;
     }
 
     default:
         e.error(E_BADSCHEME);
--- a/gfx/graphite2/src/GlyphCache.cpp
+++ b/gfx/graphite2/src/GlyphCache.cpp
@@ -111,18 +111,20 @@ private:
                     _num_glyphs_attributes,
                     _num_attrs;                    // number of glyph attributes per glyph
 };
 
 
 
 GlyphCache::GlyphCache(const Face & face, const uint32 face_options)
 : _glyph_loader(new Loader(face, bool(face_options & gr_face_dumbRendering))),
-  _glyphs(_glyph_loader && *_glyph_loader ? grzeroalloc<const GlyphFace *>(_glyph_loader->num_glyphs()) : 0),
-  _boxes(_glyph_loader && _glyph_loader->has_boxes() ? grzeroalloc<GlyphBox *>(_glyph_loader->num_glyphs()) : 0),
+  _glyphs(_glyph_loader && *_glyph_loader && _glyph_loader->num_glyphs()
+        ? grzeroalloc<const GlyphFace *>(_glyph_loader->num_glyphs()) : 0),
+  _boxes(_glyph_loader && _glyph_loader->has_boxes() && _glyph_loader->num_glyphs()
+        ? grzeroalloc<GlyphBox *>(_glyph_loader->num_glyphs()) : 0),
   _num_glyphs(_glyphs ? _glyph_loader->num_glyphs() : 0),
   _num_attrs(_glyphs ? _glyph_loader->num_attrs() : 0),
   _upem(_glyphs ? _glyph_loader->units_per_em() : 0)
 {
     if ((face_options & gr_face_preloadGlyphs) && _glyph_loader && _glyphs)
     {
         int numsubs = 0;
         GlyphFace * const glyphs = new GlyphFace [_num_glyphs];
@@ -139,17 +141,17 @@ GlyphCache::GlyphCache(const Face & face
         for (uint16 gid = 1; loaded && gid != _num_glyphs; ++gid)
             _glyphs[gid] = loaded = _glyph_loader->read_glyph(gid, glyphs[gid], &numsubs);
 
         if (!loaded)
         {
             _glyphs[0] = 0;
             delete [] glyphs;
         }
-        else if (numsubs > 0)
+        else if (numsubs > 0 && _boxes)
         {
             GlyphBox * boxes = (GlyphBox *)gralloc<char>(_num_glyphs * sizeof(GlyphBox) + numsubs * 8 * sizeof(float));
             GlyphBox * currbox = boxes;
 
             for (uint16 gid = 0; currbox && gid != _num_glyphs; ++gid)
             {
                 _boxes[gid] = currbox;
                 currbox = _glyph_loader->read_box(gid, currbox, *_glyphs[gid]);
@@ -280,26 +282,27 @@ GlyphCache::Loader::Loader(const Face & 
         _long_fmt              = flags & 1;
         int tmpnumgattrs       = (m_pGloc.size()
                                    - (p - m_pGloc)
                                    - sizeof(uint16)*(flags & 0x2 ? _num_attrs : 0))
                                        / (_long_fmt ? sizeof(uint32) : sizeof(uint16)) - 1;
 
         if (version >= 0x00020000 || tmpnumgattrs < 0 || tmpnumgattrs > 65535
             || _num_attrs == 0 || _num_attrs > 0x3000  // is this hard limit appropriate?
-            || _num_glyphs_graphics > tmpnumgattrs)
+            || _num_glyphs_graphics > tmpnumgattrs
+            || m_pGlat.size() < 4)
         {
             _head = Face::Table();
             return;
         }
 
         _num_glyphs_attributes = static_cast<unsigned short>(tmpnumgattrs);
         p = m_pGlat;
         version = be::read<uint32>(p);
-        if (version >= 0x00040000)       // reject Glat tables that are too new
+        if (version >= 0x00040000 || (version >= 0x00030000 && m_pGlat.size() < 8))       // reject Glat tables that are too new
         {
             _head = Face::Table();
             return;
         }
         else if (version >= 0x00030000)
         {
             unsigned int glatflags = be::read<uint32>(p);
             _has_boxes = glatflags & 1;
@@ -381,21 +384,21 @@ const GlyphFace * GlyphCache::Loader::re
         }
         else
         {
             be::skip<uint16>(gloc, glyphid);
             glocs = be::read<uint16>(gloc);
             gloce = be::peek<uint16>(gloc);
         }
 
-        if (glocs >= m_pGlat.size() || gloce > m_pGlat.size())
+        if (glocs + 1 >= m_pGlat.size() || gloce > m_pGlat.size())
             return 0;
 
         const uint32 glat_version = be::peek<uint32>(m_pGlat);
-        if (glat_version == 0x00030000)
+        if (glat_version >= 0x00030000)
         {
             const byte * p = m_pGlat + glocs;
             uint16 bmap = be::read<uint16>(p);
             int num = bit_set_count((uint32)bmap);
             if (numsubs) *numsubs += num;
             glocs += 6 + 8 * num;
             if (glocs > gloce)
                 return 0;
@@ -449,29 +452,31 @@ GlyphBox * GlyphCache::Loader::read_box(
     }
     else
     {
         be::skip<uint16>(gloc, gid);
         glocs = be::read<uint16>(gloc);
         gloce = be::peek<uint16>(gloc);
     }
 
-    if (glocs >= m_pGlat.size() || gloce > m_pGlat.size())
+    if (gloce > m_pGlat.size() || glocs + 6 >= gloce)
         return 0;
 
     const byte * p = m_pGlat + glocs;
     uint16 bmap = be::read<uint16>(p);
     int num = bit_set_count((uint32)bmap);
 
     Rect bbox = glyph.theBBox();
     Rect diamax(Position(bbox.bl.x + bbox.bl.y, bbox.bl.x - bbox.tr.y),
                 Position(bbox.tr.x + bbox.tr.y, bbox.tr.x - bbox.bl.y));
     Rect diabound = readbox(diamax, p[0], p[2], p[1], p[3]);
     ::new (curr) GlyphBox(num, bmap, &diabound);
     be::skip<uint8>(p, 4);
+    if (glocs + 6 + num * 8 >= gloce)
+        return 0;
 
     for (int i = 0; i < num * 2; ++i)
     {
         Rect box = readbox((i & 1) ? diamax : bbox, p[0], p[2], p[1], p[3]);
         curr->addSubBox(i >> 1, i & 1, &box);
         be::skip<uint8>(p, 4);
     } 
     return (GlyphBox *)((char *)(curr) + sizeof(GlyphBox) + 2 * num * sizeof(Rect));
--- a/gfx/graphite2/src/GlyphFace.cpp
+++ b/gfx/graphite2/src/GlyphFace.cpp
@@ -24,25 +24,25 @@ Mozilla Public License (http://mozilla.o
 License, as published by the Free Software Foundation, either version 2
 of the License or (at your option) any later version.
 */
 #include "inc/GlyphFace.h"
 
 
 using namespace graphite2;
 
-uint16 GlyphFace::getMetric(uint8 metric) const
+int32 GlyphFace::getMetric(uint8 metric) const
 {
     switch (metrics(metric))
     {
-        case kgmetLsb       : return static_cast<uint16>(m_bbox.bl.x);
-        case kgmetRsb       : return static_cast<uint16>(m_advance.x - m_bbox.tr.x);
-        case kgmetBbTop     : return static_cast<uint16>(m_bbox.tr.y);
-        case kgmetBbBottom  : return static_cast<uint16>(m_bbox.bl.y);
-        case kgmetBbLeft    : return static_cast<uint16>(m_bbox.bl.x);
-        case kgmetBbRight   : return static_cast<uint16>(m_bbox.tr.x);
-        case kgmetBbHeight  : return static_cast<uint16>(m_bbox.tr.y - m_bbox.bl.y);
-        case kgmetBbWidth   : return static_cast<uint16>(m_bbox.tr.x - m_bbox.bl.x);
-        case kgmetAdvWidth  : return static_cast<uint16>(m_advance.x);
-        case kgmetAdvHeight : return static_cast<uint16>(m_advance.y);
+        case kgmetLsb       : return m_bbox.bl.x;
+        case kgmetRsb       : return m_advance.x - m_bbox.tr.x;
+        case kgmetBbTop     : return m_bbox.tr.y;
+        case kgmetBbBottom  : return m_bbox.bl.y;
+        case kgmetBbLeft    : return m_bbox.bl.x;
+        case kgmetBbRight   : return m_bbox.tr.x;
+        case kgmetBbHeight  : return m_bbox.tr.y - m_bbox.bl.y;
+        case kgmetBbWidth   : return m_bbox.tr.x - m_bbox.bl.x;
+        case kgmetAdvWidth  : return m_advance.x;
+        case kgmetAdvHeight : return m_advance.y;
         default : return 0;
     }
 }
--- a/gfx/graphite2/src/NameTable.cpp
+++ b/gfx/graphite2/src/NameTable.cpp
@@ -42,25 +42,26 @@ NameTable::NameTable(const void* data, s
     memcpy(pdata, data, length);
     m_table = reinterpret_cast<const TtfUtil::Sfnt::FontNames*>(pdata);
 
     if ((length > sizeof(TtfUtil::Sfnt::FontNames)) &&
         (length > sizeof(TtfUtil::Sfnt::FontNames) +
          sizeof(TtfUtil::Sfnt::NameRecord) * ( be::swap<uint16>(m_table->count) - 1)))
     {
         uint16 offset = be::swap<uint16>(m_table->string_offset);
-        m_nameData = reinterpret_cast<const uint8*>(pdata) + offset;
-        setPlatformEncoding(platformId, encodingID);
-        m_nameDataLength = length - offset;
+        if (offset < length)
+        {
+            m_nameData = reinterpret_cast<const uint8*>(pdata) + offset;
+            setPlatformEncoding(platformId, encodingID);
+            m_nameDataLength = length - offset;
+            return;
+        }
     }
-    else
-    {
-        free(const_cast<TtfUtil::Sfnt::FontNames*>(m_table));
-        m_table = NULL;
-    }
+    free(const_cast<TtfUtil::Sfnt::FontNames*>(m_table));
+    m_table = NULL;
 }
 
 uint16 NameTable::setPlatformEncoding(uint16 platformId, uint16 encodingID)
 {
     if (!m_nameData) return 0;
     uint16 i = 0;
     uint16 count = be::swap<uint16>(m_table->count);
     for (; i < count; i++)
@@ -139,28 +140,36 @@ void* NameTable::getName(uint16& languag
     uint16 offset = be::swap<uint16>(nameRecord.offset);
     if(offset + utf16Length > m_nameDataLength)
     {
         languageId = 0;
         length = 0;
         return NULL;
     }
     utf16Length >>= 1; // in utf16 units
-    utf16::codeunit_t * utf16Name = gralloc<utf16::codeunit_t>(utf16Length);
+    utf16::codeunit_t * utf16Name = gralloc<utf16::codeunit_t>(utf16Length + 1);
     if (!utf16Name)
     {
         languageId = 0;
         length = 0;
         return NULL;
     }
     const uint8* pName = m_nameData + offset;
     for (size_t i = 0; i < utf16Length; i++)
     {
         utf16Name[i] = be::read<uint16>(pName);
     }
+    utf16Name[utf16Length] = 0;
+    if (!utf16::validate(utf16Name, utf16Name + utf16Length))
+    {
+        free(utf16Name);
+        languageId = 0;
+        length = 0;
+        return NULL;
+    }
     switch (enc)
     {
     case gr_utf8:
     {
         utf8::codeunit_t* uniBuffer = gralloc<utf8::codeunit_t>(3 * utf16Length + 1);
         if (!uniBuffer)
         {
             free(utf16Name);
--- a/gfx/graphite2/src/Pass.cpp
+++ b/gfx/graphite2/src/Pass.cpp
@@ -96,17 +96,17 @@ bool Pass::readPass(const byte * const p
     const byte * p              = pass_start,
                * const pass_end = p + pass_length;
     size_t numRanges;
 
     if (e.test(pass_length < 40, E_BADPASSLENGTH)) return face.error(e); 
     // Read in basic values
     const byte flags = be::read<byte>(p);
     if (e.test((flags & 0x1f) && 
-            (pt < PASS_TYPE_POSITIONING || !m_silf->aCollision() || !face.glyphs().hasBoxes()),
+            (pt < PASS_TYPE_POSITIONING || !m_silf->aCollision() || !face.glyphs().hasBoxes() || !(m_silf->flags() & 0x20)),
             E_BADCOLLISIONPASS))
         return face.error(e);
     m_numCollRuns = flags & 0x7;
     m_kernColls   = (flags >> 3) & 0x3;
     m_isReverseDir = (flags >> 5) & 0x1;
     m_iMaxLoop = be::read<byte>(p);
     if (m_iMaxLoop < 1) m_iMaxLoop = 1;
     be::skip<byte>(p,2); // skip maxContext & maxBackup
@@ -226,17 +226,21 @@ bool Pass::readRules(const byte * rule_m
     // Load rules.
     const byte * ac_begin = 0, * rc_begin = 0,
                * ac_end = ac_data + be::peek<uint16>(o_action),
                * rc_end = rc_data + be::peek<uint16>(o_constraint);
 
     // Allocate pools
     m_rules = new Rule [m_numRules];
     m_codes = new Code [m_numRules*2];
-    const size_t prog_pool_sz = vm::Machine::Code::estimateCodeDataOut(ac_end - ac_data + rc_end - rc_data);
+    int totalSlots = 0;
+    const uint16 *tsort = sort_key;
+    for (int i = 0; i < m_numRules; ++i)
+        totalSlots += be::peek<uint16>(--tsort);
+    const size_t prog_pool_sz = vm::Machine::Code::estimateCodeDataOut(ac_end - ac_data + rc_end - rc_data, 2 * m_numRules, totalSlots);
     m_progs = gralloc<byte>(prog_pool_sz);
     byte * prog_pool_free = m_progs,
          * prog_pool_end  = m_progs + prog_pool_sz;
     if (e.test(!(m_rules && m_codes && m_progs), E_OUTOFMEM)) return face.error(e);
 
     Rule * r = m_rules + m_numRules - 1;
     for (size_t n = m_numRules; r >= m_rules; --n, --r, ac_end = ac_begin, rc_end = rc_begin)
     {
@@ -249,17 +253,17 @@ bool Pass::readRules(const byte * rule_m
         if (r->sort > 63 || r->preContext >= r->sort || r->preContext > m_maxPreCtxt || r->preContext < m_minPreCtxt)
             return false;
         ac_begin      = ac_data + be::peek<uint16>(--o_action);
         --o_constraint;
         rc_begin      = be::peek<uint16>(o_constraint) ? rc_data + be::peek<uint16>(o_constraint) : rc_end;
 
         if (ac_begin > ac_end || ac_begin > ac_data_end || ac_end > ac_data_end
                 || rc_begin > rc_end || rc_begin > rc_data_end || rc_end > rc_data_end
-                || vm::Machine::Code::estimateCodeDataOut(ac_end - ac_begin + rc_end - rc_begin) > size_t(prog_pool_end - prog_pool_free))
+                || vm::Machine::Code::estimateCodeDataOut(ac_end - ac_begin + rc_end - rc_begin, 2, r->sort) > size_t(prog_pool_end - prog_pool_free))
             return false;
         r->action     = new (m_codes+n*2-2) vm::Machine::Code(false, ac_begin, ac_end, r->preContext, r->sort, *m_silf, face, pt, &prog_pool_free);
         r->constraint = new (m_codes+n*2-1) vm::Machine::Code(true,  rc_begin, rc_end, r->preContext, r->sort, *m_silf, face, pt, &prog_pool_free);
 
         if (e.test(!r->action || !r->constraint, E_OUTOFMEM)
                 || e.test(r->action->status() != Code::loaded, r->action->status() + E_CODEFAILURE)
                 || e.test(r->constraint->status() != Code::loaded, r->constraint->status() + E_CODEFAILURE)
                 || e.test(!r->constraint->immutable(), E_MUTABLECCODE))
@@ -351,17 +355,18 @@ bool Pass::readStates(const byte * start
         if (e.test(begin >= rule_map_end || end > rule_map_end || begin > end, E_BADRULEMAPPING))
         {
             face.error_context((face.error_context() & 0xFFFF00) + EC_ARULEMAP + (n << 24));
             return face.error(e);
         }
         s->rules = begin;
         s->rules_end = (end - begin <= FiniteStateMachine::MAX_RULES)? end :
             begin + FiniteStateMachine::MAX_RULES;
-        qsort(begin, end - begin, sizeof(RuleEntry), &cmpRuleEntry);
+        if (begin)      // keep UBSan happy can't call qsort with null begin
+            qsort(begin, end - begin, sizeof(RuleEntry), &cmpRuleEntry);
     }
 
     return true;
 }
 
 bool Pass::readRanges(const byte * ranges, size_t num_ranges, Error &e)
 {
     m_cols = gralloc<uint16>(m_numGlyphs);
@@ -449,19 +454,19 @@ bool Pass::runFSM(FiniteStateMachine& fs
     if (fsm.slots.context() < m_minPreCtxt)
         return false;
 
     uint16 state = m_startStates[m_maxPreCtxt - fsm.slots.context()];
     uint8  free_slots = SlotMap::MAX_SLOTS;
     do
     {
         fsm.slots.pushSlot(slot);
-        if (--free_slots == 0
-         || slot->gid() >= m_numGlyphs
+        if (slot->gid() >= m_numGlyphs
          || m_cols[slot->gid()] == 0xffffU
+         || --free_slots == 0
          || state >= m_numTransition)
             return free_slots != 0;
 
         const uint16 * transitions = m_transitions + state*m_numColumns;
         state = transitions[m_cols[slot->gid()]];
         if (state >= m_successStart)
             fsm.rules.accumulate_rules(m_states[state]);
 
@@ -627,37 +632,40 @@ bool Pass::testPassConstraint(Machine & 
 }
 
 
 bool Pass::testConstraint(const Rule & r, Machine & m) const
 {
     const uint16 curr_context = m.slotMap().context();
     if (unsigned(r.sort - r.preContext) > m.slotMap().size() - curr_context
         || curr_context - r.preContext < 0) return false;
+
+    vm::slotref * map = m.slotMap().begin() + curr_context - r.preContext;
+    if (map[r.sort - 1] == 0)
+        return false;
+
     if (!*r.constraint) return true;
     assert(r.constraint->constraint());
-
-    vm::slotref * map = m.slotMap().begin() + curr_context - r.preContext;
     for (int n = r.sort; n && map; --n, ++map)
     {
         if (!*map) continue;
         const int32 ret = r.constraint->run(m, map);
         if (!ret || m.status() != Machine::finished)
             return false;
     }
 
     return true;
 }
 
 
 void SlotMap::collectGarbage(Slot * &aSlot)
 {
     for(Slot **s = begin(), *const *const se = end() - 1; s != se; ++s) {
         Slot *& slot = *s;
-        if(slot->isDeleted() || slot->isCopied())
+        if(slot && (slot->isDeleted() || slot->isCopied()))
         {
             if (slot == aSlot)
                 aSlot = slot->prev() ? slot->prev() : slot->next();
             segment.freeSlot(slot);
         }
     }
 }
 
--- a/gfx/graphite2/src/Segment.cpp
+++ b/gfx/graphite2/src/Segment.cpp
@@ -419,16 +419,19 @@ Position Segment::positionSlots(const Fo
         reverseSlots();
         temp = iStart;
         iStart = iEnd;
         iEnd = temp;
     }
     if (!iStart)    iStart = m_first;
     if (!iEnd)      iEnd   = m_last;
 
+    if (!iStart || !iEnd)   // only true for empty segments
+        return currpos;
+
     if (isRtl)
     {
         for (Slot * s = iEnd, * const end = iStart->prev(); s && s != end; s = s->prev())
         {
             if (s->isBase())
                 currpos = s->finalise(this, font, currpos, bbox, 0, clusterMin = currpos.x, isRtl, isFinal);
         }
     }
@@ -526,11 +529,14 @@ void Segment::doMirror(uint16 aMirror)
 }
 
 bool Segment::initCollisions()
 {
     m_collisions = grzeroalloc<SlotCollision>(slotCount());
     if (!m_collisions) return false;
 
     for (Slot *p = m_first; p; p = p->next())
-        ::new (collisionInfo(p)) SlotCollision(this, p);
+        if (p->index() < slotCount())
+            ::new (collisionInfo(p)) SlotCollision(this, p);
+        else
+            return false;
     return true;
 }
--- a/gfx/graphite2/src/Silf.cpp
+++ b/gfx/graphite2/src/Silf.cpp
@@ -350,20 +350,20 @@ uint16 Silf::getClassGlyph(uint16 cid, u
     }
     return 0;
 }
 
 
 bool Silf::runGraphite(Segment *seg, uint8 firstPass, uint8 lastPass, int dobidi) const
 {
     assert(seg != 0);
-    SlotMap            map(*seg, m_dir);
+    unsigned int       maxSize = seg->slotCount() * MAX_SEG_GROWTH_FACTOR;
+    SlotMap            map(*seg, m_dir, maxSize);
     FiniteStateMachine fsm(map, seg->getFace()->logger());
     vm::Machine        m(map);
-    unsigned int       initSize = seg->slotCount();
     uint8              lbidi = m_bPass;
 #if !defined GRAPHITE2_NTRACING
     json * const dbgout = seg->getFace()->logger();
 #endif
 
     if (lastPass == 0)
     {
         if (firstPass == lastPass && lbidi == 0xFF)
@@ -419,13 +419,13 @@ bool Silf::runGraphite(Segment *seg, uin
 
         // test whether to reorder, prepare for positioning
         bool reverse = (lbidi == 0xFF) && (seg->currdir() != ((m_dir & 1) ^ m_passes[i].reverseDir()));
         if ((i >= 32 || (seg->passBits() & (1 << i)) == 0 || m_passes[i].collisionLoops())
                 && !m_passes[i].runGraphite(m, fsm, reverse))
             return false;
         // only subsitution passes can change segment length, cached subsegments are short for their text
         if (m.status() != vm::Machine::finished
-            || (seg->slotCount() && seg->slotCount() * MAX_SEG_GROWTH_FACTOR < initSize))
+            || (seg->slotCount() && seg->slotCount() > maxSize))
             return false;
     }
     return true;
 }
--- a/gfx/graphite2/src/Slot.cpp
+++ b/gfx/graphite2/src/Slot.cpp
@@ -80,20 +80,20 @@ void Slot::set(const Slot & orig, int ch
 
 void Slot::update(int /*numGrSlots*/, int numCharInfo, Position &relpos)
 {
     m_before += numCharInfo;
     m_after += numCharInfo;
     m_position = m_position + relpos;
 }
 
-Position Slot::finalise(const Segment *seg, const Font *font, Position & base, Rect & bbox, uint8 attrLevel, float & clusterMin, bool rtl, bool isFinal)
+Position Slot::finalise(const Segment *seg, const Font *font, Position & base, Rect & bbox, uint8 attrLevel, float & clusterMin, bool rtl, bool isFinal, int depth)
 {
     SlotCollision *coll = NULL;
-    if (attrLevel && m_attLevel > attrLevel) return Position(0, 0);
+    if (depth > 100 || (attrLevel && m_attLevel > attrLevel)) return Position(0, 0);
     float scale = font ? font->scale() : 1.0f;
     Position shift(m_shift.x * (rtl * -2 + 1) + m_just, m_shift.y);
     float tAdvance = m_advance.x + m_just;
     if (isFinal && (coll = seg->collisionInfo(this)))
     {
         const Position &collshift = coll->offset();
         if (!(coll->flags() & SlotCollision::COLL_KERN) || rtl)
             shift = shift + collshift;
@@ -128,23 +128,23 @@ Position Slot::finalise(const Segment *s
     if (glyphFace)
     {
         Rect ourBbox = glyphFace->theBBox() * scale + m_position;
         bbox = bbox.widen(ourBbox);
     }
 
     if (m_child && m_child != this && m_child->attachedTo() == this)
     {
-        Position tRes = m_child->finalise(seg, font, m_position, bbox, attrLevel, clusterMin, rtl, isFinal);
+        Position tRes = m_child->finalise(seg, font, m_position, bbox, attrLevel, clusterMin, rtl, isFinal, depth + 1);
         if ((!m_parent || m_advance.x >= 0.5f) && tRes.x > res.x) res = tRes;
     }
 
     if (m_parent && m_sibling && m_sibling != this && m_sibling->attachedTo() == m_parent)
     {
-        Position tRes = m_sibling->finalise(seg, font, base, bbox, attrLevel, clusterMin, rtl, isFinal);
+        Position tRes = m_sibling->finalise(seg, font, base, bbox, attrLevel, clusterMin, rtl, isFinal, depth + 1);
         if (tRes.x > res.x) res = tRes;
     }
     
     if (!m_parent && clusterMin < base.x)
     {
         Position adj = Position(m_position.x - clusterMin, 0.);
         res += adj;
         m_position += adj;
@@ -160,35 +160,35 @@ int32 Slot::clusterMetric(const Segment 
         return 0;
     Rect bbox = seg->theGlyphBBoxTemporary(glyph());
     float clusterMin = 0.;
     Position res = finalise(seg, NULL, base, bbox, attrLevel, clusterMin, rtl, false);
 
     switch (metrics(metric))
     {
     case kgmetLsb :
-        return static_cast<uint32>(bbox.bl.x);
+        return bbox.bl.x;
     case kgmetRsb :
-        return static_cast<uint32>(res.x - bbox.tr.x);
+        return res.x - bbox.tr.x;
     case kgmetBbTop :
-        return static_cast<uint32>(bbox.tr.y);
+        return bbox.tr.y;
     case kgmetBbBottom :
-        return static_cast<uint32>(bbox.bl.y);
+        return bbox.bl.y;
     case kgmetBbLeft :
-        return static_cast<uint32>(bbox.bl.x);
+        return bbox.bl.x;
     case kgmetBbRight :
-        return static_cast<uint32>(bbox.tr.x);
+        return bbox.tr.x;
     case kgmetBbWidth :
-        return static_cast<uint32>(bbox.tr.x - bbox.bl.x);
+        return bbox.tr.x - bbox.bl.x;
     case kgmetBbHeight :
-        return static_cast<uint32>(bbox.tr.y - bbox.bl.y);
+        return bbox.tr.y - bbox.bl.y;
     case kgmetAdvWidth :
-        return static_cast<uint32>(res.x);
+        return res.x;
     case kgmetAdvHeight :
-        return static_cast<uint32>(res.y);
+        return res.y;
     default :
         return 0;
     }
 }
 
 #define SLOTGETCOLATTR(x) { SlotCollision *c = seg->collisionInfo(this); return c ? int(c-> x) : 0; }
 
 int Slot::getAttr(const Segment *seg, attrCode ind, uint8 subindex) const
@@ -290,19 +290,32 @@ void Slot::setAttr(Segment *seg, attrCod
     case gr_slatAdvX :  m_advance.x = value; break;
     case gr_slatAdvY :  m_advance.y = value; break;
     case gr_slatAttTo :
     {
         const uint16 idx = uint16(value);
         if (idx < map.size() && map[idx])
         {
             Slot *other = map[idx];
-            if (other == this || other == m_parent) break;
-            if (m_parent) m_parent->removeChild(this);
-            if (!other->isChildOf(this) && other->child(this))
+            if (other == this || other == m_parent || other->isCopied()) break;
+            if (m_parent) { m_parent->removeChild(this); attachTo(NULL); }
+            Slot *pOther = other;
+            int count = 0;
+            bool foundOther = false;
+            while (pOther)
+            {
+                ++count;
+                if (pOther == this) foundOther = true;
+                pOther = pOther->attachedTo();
+            }
+            for (pOther = m_child; pOther; pOther = pOther->m_child)
+                ++count;
+            for (pOther = m_sibling; pOther; pOther = pOther->m_sibling)
+                ++count;
+            if (count < 100 && !foundOther && other->child(this))
             {
                 attachTo(other);
                 if ((map.dir() != 0) ^ (idx > subindex))
                     m_with = Position(advance(), 0);
                 else        // normal match to previous root
                     m_attach = Position(other->advance(), 0);
             }
         }
@@ -416,41 +429,34 @@ bool Slot::sibling(Slot *ap)
         m_sibling = ap;
     else
         return m_sibling->sibling(ap);
     return true;
 }
 
 bool Slot::removeChild(Slot *ap)
 {
-    if (this == ap || !m_child) return false;
+    if (this == ap || !m_child || !ap) return false;
     else if (ap == m_child)
     {
         Slot *nSibling = m_child->nextSibling();
-        m_child->removeSibling(nSibling);
+        m_child->nextSibling(NULL);
         m_child = nSibling;
         return true;
     }
-    else
-        return m_child->removeSibling(ap);
-    return true;
-}
-
-bool Slot::removeSibling(Slot *ap)
-{
-    if (this == ap || !m_sibling) return false;
-    else if (ap == m_sibling)
+    for (Slot *p = m_child; p; p = p->m_sibling)
     {
-        m_sibling = m_sibling->nextSibling();
-        if (m_sibling) ap->removeSibling(m_sibling);
-        return true;
+        if (p->m_sibling && p->m_sibling == ap)
+        {
+            p->m_sibling = p->m_sibling->m_sibling;
+            ap->nextSibling(NULL);
+            return true;
+        }
     }
-    else
-        return m_sibling->removeSibling(ap);
-    return true;
+    return false;
 }
 
 void Slot::setGlyph(Segment *seg, uint16 glyphid, const GlyphFace * theGlyph)
 {
     m_glyphid = glyphid;
     m_bidiCls = -1;
     if (!theGlyph)
     {
@@ -475,21 +481,23 @@ void Slot::setGlyph(Segment *seg, uint16
     if (seg->silf()->aPassBits())
     {
         seg->mergePassBits(theGlyph->attrs()[seg->silf()->aPassBits()]);
         if (seg->silf()->numPasses() > 16)
             seg->mergePassBits(theGlyph->attrs()[seg->silf()->aPassBits()+1] << 16);
     }
 }
 
-void Slot::floodShift(Position adj)
+void Slot::floodShift(Position adj, int depth)
 {
+    if (depth > 100)
+        return;
     m_position += adj;
-    if (m_child) m_child->floodShift(adj);
-    if (m_sibling) m_sibling->floodShift(adj);
+    if (m_child) m_child->floodShift(adj, depth + 1);
+    if (m_sibling) m_sibling->floodShift(adj, depth + 1);
 }
 
 void SlotJustify::LoadSlot(const Slot *s, const Segment *seg)
 {
     for (int i = seg->silf()->numJustLevels() - 1; i >= 0; --i)
     {
         Justinfo *justs = seg->silf()->justAttrs() + i;
         int16 *v = values + i * NUMJUSTPARAMS;
@@ -514,15 +522,14 @@ Slot * Slot::nextInCluster(const Slot *s
             return base->nextSibling();
         s = base;
     }
     return NULL;
 }
 
 bool Slot::isChildOf(const Slot *base) const
 {
-    if (m_parent == base)
-        return true;
-    else if (!m_parent)
-        return false;
-    else
-        return m_parent->isChildOf(base);
+    for (Slot *p = m_parent; p; p = p->m_parent)
+        if (p == base)
+            return true;
+    return false;
 }
+
--- a/gfx/graphite2/src/TtfUtil.cpp
+++ b/gfx/graphite2/src/TtfUtil.cpp
@@ -891,25 +891,27 @@ const void * FindCmapSubtable(const void
 ----------------------------------------------------------------------------------------------*/
 bool CheckCmapSubtable4(const void * pCmapSubtable4, const void * pCmapEnd /*, unsigned int maxgid*/)
 {
     size_t table_len = (const byte *)pCmapEnd - (const byte *)pCmapSubtable4;
     if (!pCmapSubtable4) return false;
     const Sfnt::CmapSubTable * pTable = reinterpret_cast<const Sfnt::CmapSubTable *>(pCmapSubtable4);
     // Bob H say some freeware TT fonts have version 1 (eg, CALIGULA.TTF) 
     // so don't check subtable version. 21 Mar 2002 spec changes version to language.
-    if (be::swap(pTable->format) != 4) return false;
+    if (table_len < sizeof(*pTable) || be::swap(pTable->format) != 4) return false;
     const Sfnt::CmapSubTableFormat4 * pTable4 = reinterpret_cast<const Sfnt::CmapSubTableFormat4 *>(pCmapSubtable4);
+    if (table_len < sizeof(*pTable4))
+        return false;
     uint16 length = be::swap(pTable4->length);
     if (length > table_len)
         return false;
     if (length < sizeof(Sfnt::CmapSubTableFormat4))
         return false;
     uint16 nRanges = be::swap(pTable4->seg_count_x2) >> 1;
-    if (length < sizeof(Sfnt::CmapSubTableFormat4) + 4 * nRanges * sizeof(uint16))
+    if (!nRanges || length < sizeof(Sfnt::CmapSubTableFormat4) + 4 * nRanges * sizeof(uint16))
         return false;
     // check last range is properly terminated
     uint16 chEnd = be::peek<uint16>(pTable4->end_code + nRanges - 1);
     if (chEnd != 0xFFFF)
         return false;
 #if 0
     int lastend = -1;
     for (int i = 0; i < nRanges; ++i)
@@ -999,17 +1001,17 @@ gid16 CmapSubtable4Lookup(const void * p
         uint16 idRangeOffset = be::peek<uint16>(pMid += nSeg);
 
         if (idRangeOffset == 0)
             return (uint16)(idDelta + nUnicodeId); // must use modulus 2^16
 
         // Look up value in glyphIdArray
         const ptrdiff_t offset = (nUnicodeId - chStart) + (idRangeOffset >> 1) +
                 (pMid - reinterpret_cast<const uint16 *>(pTable));
-        if (offset * 2 >= be::swap<uint16>(pTable->length))
+        if (offset * 2 + 1 >= be::swap<uint16>(pTable->length))
             return 0;
         gid16 nGlyphId = be::peek<uint16>(reinterpret_cast<const uint16 *>(pTable)+offset);
         // If this value is 0, return 0. Else add the idDelta
         return nGlyphId ? nGlyphId + idDelta : 0;
     }
 
     return 0;
 }
@@ -1081,19 +1083,21 @@ unsigned int CmapSubtable4NextCodepoint(
 /*----------------------------------------------------------------------------------------------
     Check the Microsoft UCS-4 subtable for expected values.
 ----------------------------------------------------------------------------------------------*/
 bool CheckCmapSubtable12(const void *pCmapSubtable12, const void *pCmapEnd /*, unsigned int maxgid*/)
 {
     size_t table_len = (const byte *)pCmapEnd - (const byte *)pCmapSubtable12;
     if (!pCmapSubtable12)  return false;
     const Sfnt::CmapSubTable * pTable = reinterpret_cast<const Sfnt::CmapSubTable *>(pCmapSubtable12);
-    if (be::swap(pTable->format) != 12)
+    if (table_len < sizeof(*pTable) || be::swap(pTable->format) != 12)
         return false;
     const Sfnt::CmapSubTableFormat12 * pTable12 = reinterpret_cast<const Sfnt::CmapSubTableFormat12 *>(pCmapSubtable12);
+    if (table_len < sizeof(*pTable12))
+        return false;
     uint32 length = be::swap(pTable12->length);
     if (length > table_len)
         return false;
     if (length < sizeof(Sfnt::CmapSubTableFormat12))
         return false;
     uint32 num_groups = be::swap(pTable12->num_groups);
     if (num_groups > 0x10000000 || length != (sizeof(Sfnt::CmapSubTableFormat12) + (num_groups - 1) * sizeof(uint32) * 3))
         return false;
--- a/gfx/graphite2/src/inc/Code.h
+++ b/gfx/graphite2/src/inc/Code.h
@@ -81,17 +81,17 @@ private:
                 _modify,
                 _delete;
     mutable bool _own;
 
     void release_buffers() throw ();
     void failure(const status_t) throw();
 
 public:
-    static size_t estimateCodeDataOut(size_t num_bytecodes);
+    static size_t estimateCodeDataOut(size_t num_bytecodes, int nRules, int nSlots);
 
     Code() throw();
     Code(bool is_constraint, const byte * bytecode_begin, const byte * const bytecode_end,
          uint8 pre_context, uint16 rule_length, const Silf &, const Face &,
          enum passtype pt, byte * * const _out = 0);
     Code(const Machine::Code &) throw();
     ~Code() throw();
     
@@ -107,19 +107,21 @@ public:
     void          externalProgramMoved(ptrdiff_t) throw();
 
     int32 run(Machine &m, slotref * & map) const;
     
     CLASS_NEW_DELETE;
 };
 
 inline
-size_t  Machine::Code::estimateCodeDataOut(size_t n_bc)
+size_t  Machine::Code::estimateCodeDataOut(size_t n_bc, int nRules, int nSlots)
 {
-    return (n_bc + 1) * (sizeof(instr)+sizeof(byte));
+    // max is: all codes are instructions + 1 for each rule + max tempcopies
+    // allocate space for separate maximal code and data then merge them later
+    return (n_bc + nRules + nSlots) * sizeof(instr) + n_bc * sizeof(byte);
 }
 
 
 inline Machine::Code::Code() throw()
 : _code(0), _data(0), _data_size(0), _instr_count(0), _max_ref(0),
   _status(loaded), _constraint(false), _modify(false), _delete(false),
   _own(false)
 {
--- a/gfx/graphite2/src/inc/Face.h
+++ b/gfx/graphite2/src/inc/Face.h
@@ -82,17 +82,17 @@ public:
     uint16              languageForLocale(const char * locale) const;
 
     // Features
     uint16              numFeatures() const;
     const FeatureRef  * featureById(uint32 id) const;
     const FeatureRef  * feature(uint16 index) const;
 
     // Glyph related
-    uint16 getGlyphMetric(uint16 gid, uint8 metric) const;
+    int32  getGlyphMetric(uint16 gid, uint8 metric) const;
     uint16 findPseudo(uint32 uid) const;
 
     // Errors
     unsigned int        error() const { return m_error; }
     bool                error(Error e) { m_error = e.error(); return false; }
     unsigned int        error_context() const { return m_error; }
     void                error_context(unsigned int errcntxt) { m_errcntxt = errcntxt; }
 
--- a/gfx/graphite2/src/inc/GlyphFace.h
+++ b/gfx/graphite2/src/inc/GlyphFace.h
@@ -46,17 +46,17 @@ class GlyphFace
 public:
     GlyphFace();
     template<typename I>
     GlyphFace(const Rect & bbox, const Position & adv, I first, const I last);
 
     const Position    & theAdvance() const;
     const Rect        & theBBox() const { return m_bbox; }
     const sparse      & attrs() const { return m_attrs; }
-    uint16              getMetric(uint8 metric) const;
+    int32               getMetric(uint8 metric) const;
 
     CLASS_NEW_DELETE;
 private:
     Rect     m_bbox;        // bounding box metrics in design units
     Position m_advance;     // Advance width and height in design units
     sparse   m_attrs;
 };
 
--- a/gfx/graphite2/src/inc/Rule.h
+++ b/gfx/graphite2/src/inc/Rule.h
@@ -97,17 +97,17 @@ bool State::empty() const
     return rules_end == rules;
 }
 
 
 class SlotMap
 {
 public:
   enum {MAX_SLOTS=64};
-  SlotMap(Segment & seg, uint8 direction);
+  SlotMap(Segment & seg, uint8 direction, int maxSize);
   
   Slot       * * begin();
   Slot       * * end();
   size_t         size() const;
   unsigned short context() const;
   void           reset(Slot &, unsigned short);
   
   Slot * const & operator[](int n) const;
@@ -116,23 +116,25 @@ public:
   void           collectGarbage(Slot *& aSlot);
 
   Slot         * highwater() { return m_highwater; }
   void           highwater(Slot *s) { m_highwater = s; m_highpassed = false; }
   bool           highpassed() const { return m_highpassed; }
   void           highpassed(bool v) { m_highpassed = v; }
 
   uint8          dir() const { return m_dir; }
+  int            decMax() { return --m_maxSize; }
 
   Segment &    segment;
 private:
   Slot         * m_slot_map[MAX_SLOTS+1];
   unsigned short m_size;
   unsigned short m_precontext;
   Slot         * m_highwater;
+  int            m_maxSize;
   uint8          m_dir;
   bool           m_highpassed;
 };
 
 
 class FiniteStateMachine
 {
 public:
@@ -237,18 +239,19 @@ void FiniteStateMachine::Rules::accumula
       return;
     }
   }
   while (rre != rrend && out != lrend) { *out++ = *rre++; }
   m_end = out;
 }
 
 inline
-SlotMap::SlotMap(Segment & seg, uint8 direction)
-: segment(seg), m_size(0), m_precontext(0), m_highwater(0), m_dir(direction), m_highpassed(false)
+SlotMap::SlotMap(Segment & seg, uint8 direction, int maxSize)
+: segment(seg), m_size(0), m_precontext(0), m_highwater(0),
+    m_maxSize(maxSize), m_dir(direction), m_highpassed(false)
 {
     m_slot_map[0] = 0;
 }
 
 inline
 Slot * * SlotMap::begin()
 {
   return &m_slot_map[1]; // allow map to go 1 before slot_map when inserting
--- a/gfx/graphite2/src/inc/Segment.h
+++ b/gfx/graphite2/src/inc/Segment.h
@@ -35,17 +35,17 @@ of the License or (at your option) any l
 #include "inc/FeatureVal.h"
 #include "inc/GlyphCache.h"
 #include "inc/GlyphFace.h"
 #include "inc/Slot.h"
 #include "inc/Position.h"
 #include "inc/List.h"
 #include "inc/Collider.h"
 
-#define MAX_SEG_GROWTH_FACTOR  256
+#define MAX_SEG_GROWTH_FACTOR  64
 
 namespace graphite2 {
 
 typedef Vector<Features>        FeatureList;
 typedef Vector<Slot *>          SlotRope;
 typedef Vector<int16 *>         AttributeRope;
 typedef Vector<SlotJustify *>   JustifyRope;
 
@@ -154,17 +154,17 @@ public:
     int8 getSlotBidiClass(Slot *s) const;
     void doMirror(uint16 aMirror);
     Slot *addLineEnd(Slot *nSlot);
     void delLineEnd(Slot *s);
     bool hasJustification() const { return m_justifies.size() != 0; }
     void reverseSlots();
 
     bool isWhitespace(const int cid) const;
-    bool hasCollisionInfo() const { return (m_flags & SEG_HASCOLLISIONS); }
+    bool hasCollisionInfo() const { return (m_flags & SEG_HASCOLLISIONS) && m_collisions; }
     SlotCollision *collisionInfo(const Slot *s) const { return m_collisions ? m_collisions + s->index() : 0; }
     CLASS_NEW_DELETE
 
 public:       //only used by: GrSegment* makeAndInitialize(const GrFont *font, const GrFace *face, uint32 script, const FeaturesHandle& pFeats/*must not be IsNull*/, encform enc, const void* pStart, size_t nChars, int dir);
     bool read_text(const Face *face, const Features* pFeats/*must not be NULL*/, gr_encform enc, const void*pStart, size_t nChars);
     void finalise(const Font *font, bool reverse=false);
     float justify(Slot *pSlot, const Font *font, float width, enum justFlags flags, Slot *pFirst, Slot *pLast);
     bool initCollisions();
--- a/gfx/graphite2/src/inc/Slot.h
+++ b/gfx/graphite2/src/inc/Slot.h
@@ -92,17 +92,17 @@ public:
     void adjKern(const Position &pos) { m_shift = m_shift + pos; m_advance = m_advance + pos; }
     void origin(const Position &pos) { m_position = pos + m_shift; }
     void originate(int ind) { m_original = ind; }
     int original() const { return m_original; }
     void before(int ind) { m_before = ind; }
     void after(int ind) { m_after = ind; }
     bool isBase() const { return (!m_parent); }
     void update(int numSlots, int numCharInfo, Position &relpos);
-    Position finalise(const Segment* seg, const Font* font, Position & base, Rect & bbox, uint8 attrLevel, float & clusterMin, bool rtl, bool isFinal);
+    Position finalise(const Segment* seg, const Font* font, Position & base, Rect & bbox, uint8 attrLevel, float & clusterMin, bool rtl, bool isFinal, int depth = 0);
     bool isDeleted() const { return (m_flags & DELETED) ? true : false; }
     void markDeleted(bool state) { if (state) m_flags |= DELETED; else m_flags &= ~DELETED; }
     bool isCopied() const { return (m_flags & COPIED) ? true : false; }
     void markCopied(bool state) { if (state) m_flags |= COPIED; else m_flags &= ~COPIED; }
     bool isPositioned() const { return (m_flags & POSITIONED) ? true : false; }
     void markPositioned(bool state) { if (state) m_flags |= POSITIONED; else m_flags &= ~POSITIONED; }
     bool isInsertBefore() const { return !(m_flags & INSERTED); }
     uint8 getBidiLevel() const { return m_bidiLevel; }
@@ -123,20 +123,19 @@ public:
     Position attachOffset() const { return m_attach - m_with; }
     Slot* firstChild() const { return m_child; }
     void firstChild(Slot *ap) { m_child = ap; }
     bool child(Slot *ap);
     Slot* nextSibling() const { return m_sibling; }
     void nextSibling(Slot *ap) { m_sibling = ap; }
     bool sibling(Slot *ap);
     bool removeChild(Slot *ap);
-    bool removeSibling(Slot *ap);
     int32 clusterMetric(const Segment* seg, uint8 metric, uint8 attrLevel, bool rtl);
     void positionShift(Position a) { m_position += a; }
-    void floodShift(Position adj);
+    void floodShift(Position adj, int depth = 0);
     float just() const { return m_just; }
     void just(float j) { m_just = j; }
     Slot *nextInCluster(const Slot *s) const;
     bool isChildOf(const Slot *base) const;
 
     CLASS_NEW_DELETE
 
 private:
--- a/gfx/graphite2/src/inc/UtfCodec.h
+++ b/gfx/graphite2/src/inc/UtfCodec.h
@@ -35,16 +35,17 @@ typedef uint32  uchar_t;
 
 template <int N>
 struct _utf_codec
 {
     typedef uchar_t codeunit_t;
 
     static void     put(codeunit_t * cp, const uchar_t , int8 & len) throw();
     static uchar_t  get(const codeunit_t * cp, int8 & len) throw();
+    static bool     validate(const codeunit_t * s, const codeunit_t * e) throw();
 };
 
 
 template <>
 struct _utf_codec<32>
 {
 private:
     static const uchar_t    limit = 0x110000;
@@ -58,16 +59,22 @@ public:
     }
 
     inline
     static uchar_t get(const codeunit_t * cp, int8 & l) throw()
     {
         if (cp[0] < limit)  { l = 1;  return cp[0]; }
         else                { l = -1; return 0xFFFD; }
     }
+
+    inline
+    static bool validate(codeunit_t * s, codeunit_t * e) throw()
+    {
+        return e > s;
+    }
 };
 
 
 template <>
 struct _utf_codec<16>
 {
 private:
     static const int32  lead_offset      = 0xD800 - (0x10000 >> 10);
@@ -88,22 +95,31 @@ public:
     }
 
     inline
     static uchar_t get(const codeunit_t * cp, int8 & l) throw()
     {
         const uint32    uh = cp[0];
         l = 1;
 
-        if (0xD800 > uh || uh > 0xDFFF) { return uh; }
+        if (uh < 0xD800|| uh > 0xDFFF) { return uh; }
         const uint32 ul = cp[1];
-        if (uh > 0xDBFF || 0xDC00 > ul || ul > 0xDFFF) { l = -1; return 0xFFFD; }
+        if (uh > 0xDBFF || ul < 0xDC00 || ul > 0xDFFF) { l = -1; return 0xFFFD; }
         ++l;
         return (uh<<10) + ul + surrogate_offset;
     }
+
+    inline
+    static bool validate(codeunit_t * s, codeunit_t * e) throw()
+    {
+        const ptrdiff_t n = e-s;
+        if (n <= 0) return n == 0;
+        const uint32 u = *(s+(n-1)); // Get the last codepoint
+        return (u < 0xD800 || u > 0xDBFF);
+    }
 };
 
 
 template <>
 struct _utf_codec<8>
 {
 private:
     static const int8 sz_lut[16];
@@ -143,16 +159,34 @@ public:
 
         if (l != seq_sz || toolong)
         {
             l = -l;
             return 0xFFFD;
         }
         return u;
     }
+
+    inline
+    static bool validate(codeunit_t * s, codeunit_t * e) throw()
+    {
+        const ptrdiff_t n = e-s;
+        if (n <= 0) return n == 0;
+        s += (n-1);
+        if (*s < 0x80) return true;
+        if (*s >= 0xC0) return false;
+        if (n == 1) return true;
+        if (*--s < 0x80) return true;
+        if (*s >= 0xe0) return false;
+        if (n == 2 || *s >= 0xC0) return true;
+        if (*--s < 0x80) return true;
+        if (*s >= 0xF0) return false;
+        return true;
+    }
+
 };
 
 
 template <typename C>
 class _utf_iterator
 {
     typedef _utf_codec<sizeof(C)*8> codec;
 
@@ -195,16 +229,21 @@ public:
 
 template <typename C>
 struct utf
 {
     typedef typename _utf_codec<sizeof(C)*8>::codeunit_t codeunit_t;
 
     typedef _utf_iterator<C>        iterator;
     typedef _utf_iterator<const C>  const_iterator;
+
+    inline
+    static bool validate(codeunit_t * s, codeunit_t * e) throw() {
+        return _utf_codec<sizeof(C)*8>::validate(s,e);
+    }
 };
 
 
 typedef utf<uint32> utf32;
 typedef utf<uint16> utf16;
 typedef utf<uint8>  utf8;
 
 } // namespace graphite2
--- a/gfx/graphite2/src/inc/opcode_table.h
+++ b/gfx/graphite2/src/inc/opcode_table.h
@@ -113,13 +113,13 @@ static const opcode_t opcode_table[] =
     {{NILOP,NILOP},                                 0, "PUT_SUBS3"},
     {{do_(put_glyph), NILOP},                       2, "PUT_GLYPH"},                // output_class output_class
     {{do2(push_glyph_attr)},                        3, "PUSH_GLYPH_ATTR"},          // gattrnum gattrnum slot
     {{do2(push_att_to_glyph_attr)},                 3, "PUSH_ATT_TO_GLYPH_ATTR"},   // gattrnum gattrnum slot
     {{do2(bor)},                                    0, "BITOR"},
     {{do2(band)},                                   0, "BITAND"},
     {{do2(bnot)},                                   0, "BITNOT"},   // 0x40
     {{do2(setbits)},                                4, "BITSET"},
-    {{do2(set_feat)},                               2, "SET_FEAT"},
+    {{do_(set_feat), NILOP},                        2, "SET_FEAT"},                 // featidx slot
     // private opcodes for internal use only, comes after all other on disk opcodes.
     {{do_(temp_copy), NILOP},                       0, "TEMP_COPY"}
 };
 
--- a/gfx/graphite2/src/inc/opcodes.h
+++ b/gfx/graphite2/src/inc/opcodes.h
@@ -237,17 +237,17 @@ STARTOP(put_subs_8bit_obs)
         index = seg.findClassIndex(input_class, slot->gid());
         is->setGlyph(&seg, seg.getClassGlyph(output_class, index));
     }
 ENDOP
 
 STARTOP(put_copy)
     declare_params(1);
     const int  slot_ref = int8(*param);
-    if (is)
+    if (is && !is->isDeleted())
     {
         slotref ref = slotat(slot_ref);
         if (ref && ref != is)
         {
             int16 *tempUserAttrs = is->userAttrs();
             if (is->attachedTo() || is->firstChild()) DIE
             Slot *prev = is->prev();
             Slot *next = is->next();
@@ -262,16 +262,17 @@ STARTOP(put_copy)
                 is->attachedTo()->child(is);
         }
         is->markCopied(false);
         is->markDeleted(false);
     }
 ENDOP
 
 STARTOP(insert)
+    if (smap.decMax() <= 0) DIE;
     Slot *newSlot = seg.newSlot();
     if (!newSlot) DIE;
     Slot *iss = is;
     while (iss && iss->isDeleted()) iss = iss->next();
     if (!iss)
     {
         if (seg.last())
         {
@@ -550,31 +551,31 @@ ENDOP
 
 STARTOP(iattr_add)
     declare_params(2);
     const attrCode      slat = attrCode(uint8(param[0]));
     const size_t        idx  = uint8(param[1]);
     const          int  val  = int(pop());
     if ((slat == gr_slatPosX || slat == gr_slatPosY) && (flags & POSITIONED) == 0)
     {
-        seg.positionSlots(0, *smap.begin(), *(smap.end()-1), dir);
+        seg.positionSlots(0, *smap.begin(), *(smap.end()-1), seg.currdir());
         flags |= POSITIONED;
     }
     int res = is->getAttr(&seg, slat, idx);
     is->setAttr(&seg, slat, idx, val + res, smap);
 ENDOP
 
 STARTOP(iattr_sub)
     declare_params(2);
     const attrCode      slat = attrCode(uint8(param[0]));
     const size_t        idx  = uint8(param[1]);
     const          int  val  = int(pop());
     if ((slat == gr_slatPosX || slat == gr_slatPosY) && (flags & POSITIONED) == 0)
     {
-        seg.positionSlots(0, *smap.begin(), *(smap.end()-1), dir);
+        seg.positionSlots(0, *smap.begin(), *(smap.end()-1), seg.currdir());
         flags |= POSITIONED;
     }
     int res = is->getAttr(&seg, slat, idx);
     is->setAttr(&seg, slat, idx, res - val, smap);
 ENDOP
 
 STARTOP(push_proc_state)
     use_params(1);
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -1382,20 +1382,27 @@ ContainerLayer::DefaultComputeEffectiveT
   if (HasMaskLayers() ||
       GetForceIsolatedGroup()) {
     useIntermediateSurface = true;
 #ifdef MOZ_DUMP_PAINTING
   } else if (gfxEnv::DumpPaintIntermediate() && !Extend3DContext()) {
     useIntermediateSurface = true;
 #endif
   } else {
+    /* Don't use an intermediate surface for opacity when it's within a 3d
+     * context, since we'd rather keep the 3d effects. This matches the
+     * WebKit/blink behaviour, but is changing in the latest spec.
+     */
     float opacity = GetEffectiveOpacity();
     CompositionOp blendMode = GetEffectiveMixBlendMode();
-    if (((opacity != 1.0f || blendMode != CompositionOp::OP_OVER) && (HasMultipleChildren() || Creates3DContextWithExtendingChildren())) ||
-        (!idealTransform.Is2D() && Creates3DContextWithExtendingChildren())) {
+    if ((HasMultipleChildren() || Creates3DContextWithExtendingChildren()) &&
+        ((opacity != 1.0f && !Extend3DContext()) ||
+         (blendMode != CompositionOp::OP_OVER))) {
+      useIntermediateSurface = true;
+    } else if (!idealTransform.Is2D() && Creates3DContextWithExtendingChildren()) {
       useIntermediateSurface = true;
     } else {
       useIntermediateSurface = false;
       gfx::Matrix contTransform;
       bool checkClipRect = false;
       bool checkMaskLayers = false;
 
       if (!idealTransform.Is2D(&contTransform)) {
@@ -1698,17 +1705,18 @@ void WriteSnapshotToDumpFile(Compositor*
 {
   RefPtr<SourceSurface> surf = aTarget->Snapshot();
   RefPtr<DataSourceSurface> dSurf = surf->GetDataSurface();
   WriteSnapshotToDumpFile_internal(aCompositor, dSurf);
 }
 #endif
 
 void
-Layer::Dump(std::stringstream& aStream, const char* aPrefix, bool aDumpHtml)
+Layer::Dump(std::stringstream& aStream, const char* aPrefix,
+            bool aDumpHtml, bool aSorted)
 {
 #ifdef MOZ_DUMP_PAINTING
   bool dumpCompositorTexture = gfxEnv::DumpCompositorTextures() && AsLayerComposite() &&
                                AsLayerComposite()->GetCompositableHost();
   bool dumpClientTexture = gfxEnv::DumpPaint() && AsShadowableLayer() &&
                            AsShadowableLayer()->GetCompositableClient();
   nsCString layerId(Name());
   layerId.Append('-');
@@ -1765,33 +1773,43 @@ Layer::Dump(std::stringstream& aStream, 
 
 #ifdef MOZ_DUMP_PAINTING
   for (size_t i = 0; i < mExtraDumpInfo.Length(); i++) {
     const nsCString& str = mExtraDumpInfo[i];
     aStream << aPrefix << "  Info:\n" << str.get();
   }
 #endif
 
-  if (Layer* kid = GetFirstChild()) {
+  if (ContainerLayer* container = AsContainerLayer()) {
+    AutoTArray<Layer*, 12> children;
+    if (aSorted) {
+      container->SortChildrenBy3DZOrder(children);
+    } else {
+      for (Layer* l = container->GetFirstChild(); l; l = l->GetNextSibling()) {
+        children.AppendElement(l);
+      }
+    }
     nsAutoCString pfx(aPrefix);
     pfx += "  ";
     if (aDumpHtml) {
       aStream << "<ul>";
     }
-    kid->Dump(aStream, pfx.get(), aDumpHtml);
+
+    for (Layer* child : children) {
+      child->Dump(aStream, pfx.get(), aDumpHtml, aSorted);
+    }
+
     if (aDumpHtml) {
       aStream << "</ul>";
     }
   }
 
   if (aDumpHtml) {
     aStream << "</li>";
   }
-  if (Layer* next = GetNextSibling())
-    next->Dump(aStream, aPrefix, aDumpHtml);
 }
 
 void
 Layer::DumpSelf(std::stringstream& aStream, const char* aPrefix)
 {
   PrintInfo(aStream, aPrefix);
   aStream << "\n";
 }
@@ -1915,16 +1933,25 @@ Layer::PrintInfo(std::stringstream& aStr
     aStream << " [opaqueContent]";
   }
   if (GetContentFlags() & CONTENT_COMPONENT_ALPHA) {
     aStream << " [componentAlpha]";
   }
   if (GetContentFlags() & CONTENT_BACKFACE_HIDDEN) {
     aStream << " [backfaceHidden]";
   }
+  if (Extend3DContext()) {
+    aStream << " [extend3DContext]";
+  }
+  if (Combines3DTransformWithAncestors()) {
+    aStream << " [combines3DTransformWithAncestors]";
+  }
+  if (Is3DContextLeaf()) {
+    aStream << " [is3DContextLeaf]";
+  }
   if (GetScrollbarDirection() == VERTICAL) {
     aStream << nsPrintfCString(" [vscrollbar=%lld]", GetScrollbarTargetContainerId()).get();
   }
   if (GetScrollbarDirection() == HORIZONTAL) {
     aStream << nsPrintfCString(" [hscrollbar=%lld]", GetScrollbarTargetContainerId()).get();
   }
   if (GetIsFixedPosition()) {
     LayerPoint anchor = GetFixedPositionAnchor();
@@ -2305,24 +2332,25 @@ ReadbackLayer::DumpPacket(layerscope::La
   size->set_w(mSize.width);
   size->set_h(mSize.height);
 }
 
 //--------------------------------------------------
 // LayerManager
 
 void
-LayerManager::Dump(std::stringstream& aStream, const char* aPrefix, bool aDumpHtml)
+LayerManager::Dump(std::stringstream& aStream, const char* aPrefix,
+                   bool aDumpHtml, bool aSorted)
 {
 #ifdef MOZ_DUMP_PAINTING
   if (aDumpHtml) {
     aStream << "<ul><li>";
   }
 #endif
-  DumpSelf(aStream, aPrefix);
+  DumpSelf(aStream, aPrefix, aSorted);
 
   nsAutoCString pfx(aPrefix);
   pfx += "  ";
   if (!GetRoot()) {
     aStream << nsPrintfCString("%s(null)", pfx.get()).get();
     if (aDumpHtml) {
       aStream << "</li></ul>";
     }
@@ -2335,27 +2363,28 @@ LayerManager::Dump(std::stringstream& aS
   GetRoot()->Dump(aStream, pfx.get(), aDumpHtml);
   if (aDumpHtml) {
     aStream << "</ul></li></ul>";
   }
   aStream << "\n";
 }
 
 void
-LayerManager::DumpSelf(std::stringstream& aStream, const char* aPrefix)
+LayerManager::DumpSelf(std::stringstream& aStream, const char* aPrefix, bool aSorted)
 {
   PrintInfo(aStream, aPrefix);
+  aStream << " --- in " << (aSorted ? "3D-sorted rendering order" : "content order");
   aStream << "\n";
 }
 
 void
-LayerManager::Dump()
+LayerManager::Dump(bool aSorted)
 {
   std::stringstream ss;
-  Dump(ss);
+  Dump(ss, "", false, aSorted);
   print_stderr(ss);
 }
 
 void
 LayerManager::Dump(layerscope::LayersPacket* aPacket)
 {
   DumpPacket(aPacket);
 
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -559,22 +559,23 @@ public:
   virtual bool NeedsWidgetInvalidation() { return true; }
 
   virtual const char* Name() const { return "???"; }
 
   /**
    * Dump information about this layer manager and its managed tree to
    * aStream.
    */
-  void Dump(std::stringstream& aStream, const char* aPrefix="", bool aDumpHtml=false);
+  void Dump(std::stringstream& aStream, const char* aPrefix="",
+            bool aDumpHtml=false, bool aSorted=false);
   /**
    * Dump information about just this layer manager itself to aStream
    */
-  void DumpSelf(std::stringstream& aStream, const char* aPrefix="");
-  void Dump();
+  void DumpSelf(std::stringstream& aStream, const char* aPrefix="", bool aSorted=false);
+  void Dump(bool aSorted=false);
 
   /**
    * Dump information about this layer manager and its managed tree to
    * layerscope packet.
    */
   void Dump(layerscope::LayersPacket* aPacket);
 
   /**
@@ -1582,17 +1583,18 @@ public:
   void SetParent(ContainerLayer* aParent) { mParent = aParent; }
   void SetNextSibling(Layer* aSibling) { mNextSibling = aSibling; }
   void SetPrevSibling(Layer* aSibling) { mPrevSibling = aSibling; }
 
   /**
    * Dump information about this layer manager and its managed tree to
    * aStream.
    */
-  void Dump(std::stringstream& aStream, const char* aPrefix="", bool aDumpHtml=false);
+  void Dump(std::stringstream& aStream, const char* aPrefix="",
+            bool aDumpHtml=false, bool aSorted=false);
   /**
    * Dump information about just this layer manager itself to aStream.
    */
   void DumpSelf(std::stringstream& aStream, const char* aPrefix="");
 
   /**
    * Dump information about this layer and its child & sibling layers to
    * layerscope packet.
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -2345,17 +2345,28 @@ bool AsyncPanZoomController::AttemptScro
                                            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
-  {
+
+  // If the direction of panning is reversed within the same input block,
+  // a later event in the block could potentially scroll an APZC earlier
+  // in the handoff chain, than an earlier event in the block (because
+  // the earlier APZC was scrolled to its extent in the original direction).
+  // If immediate handoff is disallowed, we want to disallow this (to
+  // preserve the property that a single input block only scrolls one APZC),
+  // so we skip the earlier APZC.
+  bool scrollThisApzc = gfxPrefs::APZAllowImmediateHandoff() ||
+      (CurrentInputBlock() && (!CurrentInputBlock()->GetScrolledApzc() || this == CurrentInputBlock()->GetScrolledApzc()));
+
+  if (scrollThisApzc) {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     ParentLayerPoint adjustedDisplacement;
     bool forceVerticalOverscroll =
       (aOverscrollHandoffState.mScrollSource == ScrollSource::Wheel &&
        !mFrameMetrics.AllowVerticalScrollWithWheel());
     bool yChanged = mY.AdjustDisplacement(displacement.y, adjustedDisplacement.y, overscroll.y,
                                           forceVerticalOverscroll);
@@ -2370,16 +2381,18 @@ bool AsyncPanZoomController::AttemptScro
       if (!gfxPrefs::APZAllowImmediateHandoff()) {
         if (InputBlockState* block = CurrentInputBlock()) {
           block->SetScrolledApzc(this);
         }
       }
       ScheduleCompositeAndMaybeRepaint();
       UpdateSharedCompositorFrameMetrics();
     }
+  } else {
+    overscroll = displacement;
   }
 
   // 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;
--- a/gfx/layers/apz/src/InputBlockState.cpp
+++ b/gfx/layers/apz/src/InputBlockState.cpp
@@ -106,19 +106,18 @@ InputBlockState::IsAncestorOf(AsyncPanZo
   }
   return false;
 }
 
 
 void
 InputBlockState::SetScrolledApzc(AsyncPanZoomController* aApzc)
 {
-  // With immediate handoff disabled, the scrolled APZC cannot move down the handoff chain
-  // but it can move up the handoff chain if we change scrolling directions.
-  MOZ_ASSERT(!mScrolledApzc || IsAncestorOf(aApzc, mScrolledApzc));
+  // An input block should only have one scrolled APZC.
+  MOZ_ASSERT(!mScrolledApzc || mScrolledApzc == aApzc);
 
   mScrolledApzc = aApzc;
 }
 
 AsyncPanZoomController*
 InputBlockState::GetScrolledApzc() const
 {
   return mScrolledApzc;
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -20,22 +20,20 @@ skip-if = (os == 'android') || (os == 'b
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_bug1151667.html]
 skip-if = (os == 'android') || (os == 'b2g') # wheel events not supported on mobile
 [test_layerization.html]
 skip-if = (os == 'android') || (os == 'b2g') # uses wheel events which are not supported on mobile
 [test_basic_pan.html]
 # Windows touch injection doesn't work in automation, but this test can be run locally on a windows touch device.
 # On OS X we don't support touch events at all.
-# Android 2.3: bug 1252121
-skip-if = (toolkit == 'windows') || (toolkit == 'cocoa') || (android_version == '10')
+skip-if = (toolkit == 'windows') || (toolkit == 'cocoa')
 [test_scroll_inactive_flattened_frame.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_scroll_inactive_bug1190112.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_scroll_subframe_scrollbar.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_frame_reconstruction.html]
 [test_tap.html]
 # Windows touch injection doesn't work in automation, but this test can be run locally on a windows touch device.
 # On OS X we don't support touch events at all.
-# Android 2.3: bug 1252121
-skip-if = (toolkit == 'windows') || (toolkit == 'cocoa') || (android_version == '10')
+skip-if = (toolkit == 'windows') || (toolkit == 'cocoa')
--- a/gfx/layers/basic/BasicContainerLayer.cpp
+++ b/gfx/layers/basic/BasicContainerLayer.cpp
@@ -78,17 +78,17 @@ BasicContainerLayer::ComputeEffectiveTra
    * via GetEffectiveOpacity.
    * Having a mask layer always forces our own push group
    * Having a blend mode also always forces our own push group
    */
   mUseIntermediateSurface =
     GetMaskLayer() ||
     GetForceIsolatedGroup() ||
     (GetMixBlendMode() != CompositionOp::OP_OVER && HasMultipleChildren()) ||
-    (GetEffectiveOpacity() != 1.0 && (HasMultipleChildren() || hasSingleBlendingChild));
+    (GetEffectiveOpacity() != 1.0 && ((HasMultipleChildren() && !Extend3DContext()) || hasSingleBlendingChild));
 
   if (!Extend3DContext()) {
     idealTransform.ProjectTo2D();
   }
   mEffectiveTransform =
     !mUseIntermediateSurface ?
     idealTransform : SnapTransformTranslation(idealTransform, &residual);
   Matrix4x4 childTransformToSurface =
--- a/gfx/layers/client/CanvasClient.cpp
+++ b/gfx/layers/client/CanvasClient.cpp
@@ -66,75 +66,77 @@ CanvasClientBridge::UpdateAsync(AsyncCan
     ->AttachAsyncCompositable(asyncID, mLayer);
   mAsyncID = asyncID;
 }
 
 void
 CanvasClient2D::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer)
 {
   AutoRemoveTexture autoRemove(this);
-  if (mBuffer &&
-      (mBuffer->IsImmutable() || mBuffer->GetSize() != aSize)) {
-    autoRemove.mTexture = mBuffer;
-    mBuffer = nullptr;
+  if (mBackBuffer &&
+      (mBackBuffer->IsImmutable() || mBackBuffer->GetSize() != aSize)) {
+    autoRemove.mTexture = mBackBuffer;
+    mBackBuffer = nullptr;
   }
 
   bool bufferCreated = false;
-  if (!mBuffer) {
+  if (!mBackBuffer) {
     bool isOpaque = (aLayer->GetContentFlags() & Layer::CONTENT_OPAQUE);
     gfxContentType contentType = isOpaque
                                                 ? gfxContentType::COLOR
                                                 : gfxContentType::COLOR_ALPHA;
     gfx::SurfaceFormat surfaceFormat
       = gfxPlatform::GetPlatform()->Optimal2DFormatForContent(contentType);
     TextureFlags flags = TextureFlags::DEFAULT;
     if (mTextureFlags & TextureFlags::ORIGIN_BOTTOM_LEFT) {
       flags |= TextureFlags::ORIGIN_BOTTOM_LEFT;
     }
 
-    mBuffer = CreateTextureClientForCanvas(surfaceFormat, aSize, flags, aLayer);
-    if (!mBuffer) {
+    mBackBuffer = CreateTextureClientForCanvas(surfaceFormat, aSize, flags, aLayer);
+    if (!mBackBuffer) {
       NS_WARNING("Failed to allocate the TextureClient");
       return;
     }
-    MOZ_ASSERT(mBuffer->CanExposeDrawTarget());
+    MOZ_ASSERT(mBackBuffer->CanExposeDrawTarget());
 
     bufferCreated = true;
   }
 
   bool updated = false;
   {
-    TextureClientAutoLock autoLock(mBuffer, OpenMode::OPEN_WRITE_ONLY);
+    TextureClientAutoLock autoLock(mBackBuffer, OpenMode::OPEN_WRITE_ONLY);
     if (!autoLock.Succeeded()) {
-      mBuffer = nullptr;
+      mBackBuffer = nullptr;
       return;
     }
 
-    RefPtr<DrawTarget> target = mBuffer->BorrowDrawTarget();
+    RefPtr<DrawTarget> target = mBackBuffer->BorrowDrawTarget();
     if (target) {
       aLayer->UpdateTarget(target);
       updated = true;
     }
   }
 
-  if (bufferCreated && !AddTextureClient(mBuffer)) {
-    mBuffer = nullptr;
+  if (bufferCreated && !AddTextureClient(mBackBuffer)) {
+    mBackBuffer = nullptr;
     return;
   }
 
   if (updated) {
     AutoTArray<CompositableForwarder::TimedTextureClient,1> textures;
     CompositableForwarder::TimedTextureClient* t = textures.AppendElement();
-    t->mTextureClient = mBuffer;
-    t->mPictureRect = nsIntRect(nsIntPoint(0, 0), mBuffer->GetSize());
+    t->mTextureClient = mBackBuffer;
+    t->mPictureRect = nsIntRect(nsIntPoint(0, 0), mBackBuffer->GetSize());
     t->mFrameID = mFrameID;
     t->mInputFrameID = VRManagerChild::Get()->GetInputFrameID();
     GetForwarder()->UseTextures(this, textures);
-    mBuffer->SyncWithObject(GetForwarder()->GetSyncObject());
+    mBackBuffer->SyncWithObject(GetForwarder()->GetSyncObject());
   }
+
+  mBackBuffer.swap(mFrontBuffer);
 }
 
 already_AddRefed<TextureClient>
 CanvasClient2D::CreateTextureClientForCanvas(gfx::SurfaceFormat aFormat,
                                              gfx::IntSize aSize,
                                              TextureFlags aFlags,
                                              ClientCanvasLayer* aLayer)
 {
--- a/gfx/layers/client/CanvasClient.h
+++ b/gfx/layers/client/CanvasClient.h
@@ -94,40 +94,41 @@ public:
 
   TextureInfo GetTextureInfo() const override
   {
     return TextureInfo(CompositableType::IMAGE, mTextureFlags);
   }
 
   virtual void Clear() override
   {
-    mBuffer = nullptr;
+    mBackBuffer = mFrontBuffer = nullptr;
   }
 
   virtual void Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) override;
 
   virtual bool AddTextureClient(TextureClient* aTexture) override
   {
     MOZ_ASSERT((mTextureFlags & aTexture->GetFlags()) == mTextureFlags);
     return CanvasClient::AddTextureClient(aTexture);
   }
 
   virtual void OnDetach() override
   {
-    mBuffer = nullptr;
+    mBackBuffer = mFrontBuffer = nullptr;
   }
 
 private:
   already_AddRefed<TextureClient>
     CreateTextureClientForCanvas(gfx::SurfaceFormat aFormat,
                                  gfx::IntSize aSize,
                                  TextureFlags aFlags,
                                  ClientCanvasLayer* aLayer);
 
-  RefPtr<TextureClient> mBuffer;
+  RefPtr<TextureClient> mBackBuffer;
+  RefPtr<TextureClient> mFrontBuffer;
 };
 
 // Used for GL canvases where we don't need to do any readback, i.e., with a
 // GL backend.
 class CanvasClientSharedSurface : public CanvasClient
 {
 private:
   RefPtr<SharedSurfaceTextureClient> mShSurfClient;
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -836,17 +836,17 @@ LayerManagerComposite::Render(const nsIn
   float contrastVal = gfxPrefs::LayersEffectContrast();
   bool haveLayerEffects = (invertVal || grayscaleVal || contrastVal != 0.0);
 
   // Set LayerScope begin/end frame
   LayerScopeAutoFrame frame(PR_Now());
 
   // Dump to console
   if (gfxPrefs::LayersDump()) {
-    this->Dump();
+    this->Dump(/* aSorted= */true);
   } else if (profiler_feature_active("layersdump")) {
     std::stringstream ss;
     Dump(ss);
     profiler_log(ss.str().c_str());
   }
 
   // Dump to LayerScope Viewer
   if (LayerScope::CheckSendable()) {
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -1275,17 +1275,17 @@ CompositorParent::CompositeToTarget(Draw
     ScheduleComposition();
   }
 
   RenderTraceLayers(mLayerManager->GetRoot(), "0000");
 
 #ifdef MOZ_DUMP_PAINTING
   if (gfxPrefs::DumpHostLayers()) {
     printf_stderr("Painting --- compositing layer tree:\n");
-    mLayerManager->Dump();
+    mLayerManager->Dump(/* aSorted = */ true);
   }
 #endif
   mLayerManager->SetDebugOverlayWantsNextFrame(false);
   mLayerManager->EndTransaction(time);
 
   if (!aTarget) {
     TimeStamp end = TimeStamp::Now();
     DidComposite(start, end);
--- a/gfx/skia/skia/src/core/SkTime.cpp
+++ b/gfx/skia/skia/src/core/SkTime.cpp
@@ -23,17 +23,17 @@ void SkTime::DateTime::toISO8601(SkStrin
                     static_cast<unsigned>(fSecond), timezoneSign, timeZoneHours,
                     timeZoneMinutes);
     }
 }
 
 
 #ifdef SK_BUILD_FOR_WIN32
 
-#include "Windows.h"
+#include "windows.h"
 void SkTime::GetDateTime(DateTime* dt) {
     if (dt) {
         SYSTEMTIME st;
         GetSystemTime(&st);
         dt->fTimeZoneMinutes = 0;
         dt->fYear       = st.wYear;
         dt->fMonth      = SkToU8(st.wMonth);
         dt->fDayOfWeek  = SkToU8(st.wDayOfWeek);
--- a/gfx/tests/reftest/reftest.list
+++ b/gfx/tests/reftest/reftest.list
@@ -1,9 +1,9 @@
 # 468496-1 will also detect bugs in video drivers.
 == 468496-1.html 468496-1-ref.html
 fuzzy-if(winWidget,175,443) == 611498-1.html 611498-ref.html
-skip-if(B2G) fuzzy-if(Android&&AndroidVersion>=15,8,1000) == 709477-1.html 709477-1-ref.html # bug 773482
+skip-if(B2G) fuzzy-if(Android,8,1000) == 709477-1.html 709477-1-ref.html # bug 773482
 skip-if(!asyncPan) == 1086723.html 1086723-ref.html
 == 853889-1.html 853889-1-ref.html
 skip-if(Android) fuzzy-if(skiaContent,1,587) == 1143303-1.svg pass.svg
 fuzzy(100,30) == 1149923.html 1149923-ref.html # use fuzzy due to few distorted pixels caused by border-radius
 == 1131264-1.svg pass.svg
--- a/image/test/mochitest/mochitest.ini
+++ b/image/test/mochitest/mochitest.ini
@@ -65,36 +65,30 @@ support-files =
 skip-if = buildapp == 'b2g' && debug # Bug 1182951
 # [test_bug435296.html]
 # disabled - See bug 578591
 [test_bug466586.html]
 [test_bug468160.html]
 # [test_bug478398.html]
 # disabled - See bug 579139
 [test_bug490949.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_bug496292.html]
 [test_bug497665.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_bug552605-1.html]
 [test_bug552605-2.html]
 [test_bug553982.html]
 [test_bug601470.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_bug614392.html]
 [test_bug657191.html]
 [test_bug671906.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_bug733553.html]
 [test_bug767779.html]
 [test_bug865919.html]
 [test_bug89419-1.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_bug89419-2.html]
-skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_bug1180105.html]
 [test_bug1217571.html]
 [test_animation_operators.html]
 [test_drawDiscardedImage.html]
 skip-if = toolkit == "gonk" #Bug 997034 - canvas.toDataURL() often causes lost connection to device.
 [test_error_events.html]
 [test_short_gif_header.html]
 [test_image_buffer_limit.html]
--- a/image/test/unit/xpcshell.ini
+++ b/image/test/unit/xpcshell.ini
@@ -30,18 +30,18 @@ support-files =
   image4gif16x16bmp32bpp.ico
   image4gif32x32bmp24bpp.ico
   image4gif32x32bmp32bpp.ico
   image_load_helpers.js
 
 
 [test_async_notification.js]
 # Bug 1156452: frequent crash on Android 4.3
-skip-if = android_version == "18"
+skip-if = os == "android"
 [test_async_notification_404.js]
 [test_async_notification_animated.js]
 [test_encoder_apng.js]
 [test_encoder_png.js]
 [test_imgtools.js]
-# Bug 676968: test fails consistently on Android; crashes on 4.3 (bug 1156452)
+# Bug 676968
 skip-if = os == "android"
 [test_moz_icon_uri.js]
 [test_private_channel.js]
--- a/ipc/chromium/src/base/thread_local.h
+++ b/ipc/chromium/src/base/thread_local.h
@@ -102,17 +102,18 @@ class ThreadLocalBoolean {
   ThreadLocalBoolean() { }
   ~ThreadLocalBoolean() { }
 
   bool Get() {
     return tlp_.Get() != NULL;
   }
 
   void Set(bool val) {
-    tlp_.Set(reinterpret_cast<void*>(val ? 1 : 0));
+    uintptr_t intVal = val ? 1 : 0;
+    tlp_.Set(reinterpret_cast<void*>(intVal));
   }
 
  private:
   ThreadLocalPointer<void> tlp_;
 
   DISALLOW_COPY_AND_ASSIGN(ThreadLocalBoolean);
 };
 
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -240,16 +240,17 @@ LoadInfoToLoadInfoArgs(nsILoadInfo *aLoa
     LoadInfoArgs(
       requestingPrincipalInfo,
       triggeringPrincipalInfo,
       aLoadInfo->GetSecurityFlags(),
       aLoadInfo->InternalContentPolicyType(),
       static_cast<uint32_t>(aLoadInfo->GetTainting()),
       aLoadInfo->GetUpgradeInsecureRequests(),
       aLoadInfo->GetVerifySignedContent(),
+      aLoadInfo->GetEnforceSRI(),
       aLoadInfo->GetInnerWindowID(),
       aLoadInfo->GetOuterWindowID(),
       aLoadInfo->GetParentOuterWindowID(),
       aLoadInfo->GetEnforceSecurity(),
       aLoadInfo->GetInitialSecurityCheckDone(),
       aLoadInfo->GetIsInThirdPartyContext(),
       aLoadInfo->GetOriginAttributes(),
       redirectChainIncludingInternalRedirects,
@@ -300,16 +301,17 @@ LoadInfoArgsToLoadInfo(const OptionalLoa
   nsCOMPtr<nsILoadInfo> loadInfo =
     new mozilla::LoadInfo(requestingPrincipal,
                           triggeringPrincipal,
                           loadInfoArgs.securityFlags(),
                           loadInfoArgs.contentPolicyType(),
                           static_cast<LoadTainting>(loadInfoArgs.tainting()),
                           loadInfoArgs.upgradeInsecureRequests(),
                           loadInfoArgs.verifySignedContent(),
+                          loadInfoArgs.enforceSRI(),
                           loadInfoArgs.innerWindowID(),
                           loadInfoArgs.outerWindowID(),
                           loadInfoArgs.parentOuterWindowID(),
                           loadInfoArgs.enforceSecurity(),
                           loadInfoArgs.initialSecurityCheckDone(),
                           loadInfoArgs.isInThirdPartyContext(),
                           loadInfoArgs.originAttributes(),
                           redirectChainIncludingInternalRedirects,
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -63,8 +63,60 @@ def disable_export_js(value):
     # DISABLE_EXPORT_JS=1 gets us an empty PositiveOptionValue
     if value and not len(value):
         suggestion = '--disable-export-js'
     else:
         suggestion = '--enable-export-js'
 
     error('Setting %s is deprecated, use %s instead.'
           % (value.format('DISABLE_EXPORT_JS'), suggestion))
+
+
+# Profiling
+# =======================================================
+js_option('--enable-instruments', env='MOZ_INSTRUMENTS',
+          help='Enable instruments remote profiling')
+
+@depends('--enable-instruments', target)
+def instruments(value, target):
+    if value and target.os != 'OSX':
+        error('--enable-instruments cannot be used when targeting %s'
+              % target.os)
+    if value:
+        set_config('MOZ_INSTRUMENTS', '1')
+        set_define('MOZ_INSTRUMENTS', '1')
+        add_old_configure_assignment('MOZ_INSTRUMENTS', '1')
+        imply_option('--enable-profiling', reason='--enable-instruments')
+
+
+js_option('--enable-callgrind', env='MOZ_CALLGRIND',
+          help='Enable callgrind profiling')
+
+@depends('--enable-callgrind')
+def callgrind(value):
+    if value:
+        set_define('MOZ_CALLGRIND', '1')
+        imply_option('--enable-profiling')
+
+
+js_option('--enable-profiling', env='MOZ_PROFILING',
+          help='Set compile flags necessary for using sampling profilers '
+               '(e.g. shark, perf)')
+
+@depends('--enable-profiling', target)
+def profiling(value, target):
+    if value:
+        set_config('MOZ_PROFILING', '1')
+        set_define('MOZ_PROFILING', '1')
+        add_old_configure_assignment('MOZ_PROFILING', '1')
+
+        if target.kernel == 'WINNT' or (target.kernel == 'Linux' and
+                                        target.os == 'GNU'):
+            imply_option('--enable-vtune', reason='--enable-profiling')
+
+
+js_option('--enable-vtune', env='MOZ_VTUNE', help='Enable vtune profiling')
+
+@depends('--enable-vtune')
+def vtune(value):
+    if value:
+        set_config('MOZ_VTUNE', '1')
+        set_define('MOZ_VTUNE', '1')
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -4117,17 +4117,18 @@ Parser<ParseHandler>::bindVar(BindData<P
         // (Ideally, the 'e' in 'e = 42' can be linked up as a use to the
         // def of the catch parameter. However, in practice this is messy
         // because we then need to emit the synthesized var name node to
         // ensure that functionless scopes get the proper DEFVAR emits.)
         parser->handler.setFlag(pn, PND_DEOPTIMIZED);
 
         // Synthesize a new 'var' binding if one does not exist.
         DefinitionNode last = pc->decls().lookupLast(name);
-        if (last && parser->handler.getDefinitionKind(last) != Definition::VAR) {
+        Definition::Kind lastKind = parser->handler.getDefinitionKind(last);
+        if (last && lastKind != Definition::VAR && lastKind != Definition::ARG) {
             parser->handler.setFlag(parser->handler.getDefinitionNode(last), PND_CLOSED);
 
             Node synthesizedVarName = parser->newName(name);
             if (!synthesizedVarName)
                 return false;
             if (!pc->define(parser->tokenStream, name, synthesizedVarName, Definition::VAR,
                             /* declaringVarInCatchBody = */ true))
             {
--- a/js/src/gc/Iteration.cpp
+++ b/js/src/gc/Iteration.cpp
@@ -96,37 +96,37 @@ js::IterateScripts(JSRuntime* rt, JSComp
 {
     MOZ_ASSERT(!rt->mainThread.suppressGC);
     rt->gc.evictNursery();
     MOZ_ASSERT(rt->gc.nursery.isEmpty());
 
     AutoPrepareForTracing prep(rt, SkipAtoms);
 
     if (compartment) {
-        for (ZoneCellIterUnderGC i(compartment->zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) {
+        for (ZoneCellIter i(compartment->zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) {
             JSScript* script = i.get<JSScript>();
             if (script->compartment() == compartment)
                 scriptCallback(rt, data, script);
         }
     } else {
         for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
-            for (ZoneCellIterUnderGC i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next())
+            for (ZoneCellIter i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next())
                 scriptCallback(rt, data, i.get<JSScript>());
         }
     }
 }
 
 void
 js::IterateGrayObjects(Zone* zone, GCThingCallback cellCallback, void* data)
 {
     zone->runtimeFromMainThread()->gc.evictNursery();
     AutoPrepareForTracing prep(zone->runtimeFromMainThread(), SkipAtoms);
 
     for (auto thingKind : ObjectAllocKinds()) {
-        for (ZoneCellIterUnderGC i(zone, thingKind); !i.done(); i.next()) {
+        for (ZoneCellIter i(zone, thingKind); !i.done(); i.next()) {
             JSObject* obj = i.get<JSObject>();
             if (obj->asTenured().isMarked(GRAY))
                 cellCallback(data, JS::GCCellPtr(obj));
         }
     }
 }
 
 JS_PUBLIC_API(void)
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -141,17 +141,17 @@ Zone::sweepBreakpoints(FreeOp* fop)
         return;
 
     /*
      * Sweep all compartments in a zone at the same time, since there is no way
      * to iterate over the scripts belonging to a single compartment in a zone.
      */
 
     MOZ_ASSERT(isGCSweepingOrCompacting());
-    for (ZoneCellIterUnderGC i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
+    for (ZoneCellIter i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
         JSScript* script = i.get<JSScript>();
         if (!script->hasAnyBreakpointsOrStepMode())
             continue;
 
         bool scriptGone = IsAboutToBeFinalizedUnbarriered(&script);
         MOZ_ASSERT(script == i.get<JSScript>());
         for (unsigned i = 0; i < script->length(); i++) {
             BreakpointSite* site = script->getBreakpointSite(script->offsetToPC(i));
@@ -195,29 +195,29 @@ Zone::discardJitCode(FreeOp* fop)
         return;
 
     if (isPreservingCode()) {
         PurgeJITCaches(this);
     } else {
 
 #ifdef DEBUG
         /* Assert no baseline scripts are marked as active. */
-        for (ZoneCellIterUnderGC i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
+        for (ZoneCellIter i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
             JSScript* script = i.get<JSScript>();
             MOZ_ASSERT_IF(script->hasBaselineScript(), !script->baselineScript()->active());
         }
 #endif
 
         /* Mark baseline scripts on the stack as active. */
         jit::MarkActiveBaselineScripts(this);
 
         /* Only mark OSI points if code is being discarded. */
         jit::InvalidateAll(fop, this);
 
-        for (ZoneCellIterUnderGC i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
+        for (ZoneCellIter i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
             JSScript* script = i.get<JSScript>();
             jit::FinishInvalidation(fop, script);
 
             /*
              * Discard baseline script if it's not marked as active. Note that
              * this also resets the active flag.
              */
             jit::FinishDiscardBaselineScript(fop, script);
--- a/js/src/jit-test/tests/gc/incremental-state.js
+++ b/js/src/jit-test/tests/gc/incremental-state.js
@@ -24,35 +24,35 @@ assertEq(gcstate(), "mark");
 gcslice(1000000);
 while (gcstate() == "finalize") { gcslice(1); }
 assertEq(gcstate(), "none");
 
 // Zeal mode 8: Incremental GC in two main slices:
 //   1) mark roots
 //   2) mark and sweep
 //   *) finalize.
-gczeal(8);
+gczeal(8, 0);
 gcslice(1);
 assertEq(gcstate(), "mark");
 gcslice(1);
 while (gcstate() == "finalize") { gcslice(1); }
 assertEq(gcstate(), "none");
 
 // Zeal mode 9: Incremental GC in two main slices:
 //   1) mark roots and marking
 //   2) new marking and sweeping
 //   *) finalize.
-gczeal(9);
+gczeal(9, 0);
 gcslice(1);
 assertEq(gcstate(), "mark");
 gcslice(1);
 while (gcstate() == "finalize") { gcslice(1); }
 assertEq(gcstate(), "none");
 
 // Zeal mode 10: Incremental GC in multiple slices (always yeilds before
 // sweeping). This test uses long slices to prove that this zeal mode yields
 // in sweeping, where normal IGC (above) does not.
-gczeal(10);
+gczeal(10, 0);
 gcslice(1000000);
 assertEq(gcstate(), "sweep");
 gcslice(1000000);
 while (gcstate() == "finalize") { gcslice(1); }
 assertEq(gcstate(), "none");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parser/bug-1250192.js
@@ -0,0 +1,5 @@
+(function * YearFromTime(x, ... get)  {
+try {} catch (x) {
+  for (var x;;);
+}
+})();
--- a/js/src/jit/AtomicOperations.h
+++ b/js/src/jit/AtomicOperations.h
@@ -329,16 +329,18 @@ AtomicOperations::isLockfree(int32_t siz
   // You can disable the JIT with --disable-ion but you must still
   // provide the atomic operations that will be used by the JS engine.
   // When the JIT is disabled the operations are simply safe-for-races
   // C++ realizations of atomics.  These operations cannot be written
   // in portable C++, hence the default here is to crash.  See the
   // top of the file for more guidance.
 # if defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)
 #  include "jit/none/AtomicOperations-ppc.h"
+# elif defined(__aarch64__)
+#  include "jit/arm64/AtomicOperations-arm64.h"
 # else
 #  include "jit/none/AtomicOperations-none.h" // These MOZ_CRASH() always
 # endif
 #elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
 # include "jit/x86-shared/AtomicOperations-x86-shared.h"
 #else
 # error "Atomic operations must be defined for this platform"
 #endif
new file mode 100644
--- /dev/null
+++ b/js/src/jit/BaselineCacheIR.cpp
@@ -0,0 +1,1061 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jit/BaselineCacheIR.h"
+
+#include "jit/CacheIR.h"
+#include "jit/Linker.h"
+#include "jit/SharedICHelpers.h"
+
+#include "jit/MacroAssembler-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+// OperandLocation represents the location of an OperandId. The operand is
+// either in a register or on the stack, and is either boxed or unboxed.
+class OperandLocation
+{
+  public:
+    enum Kind {
+        Uninitialized = 0,
+        PayloadReg,
+        ValueReg,
+        PayloadStack,
+        ValueStack,
+    };
+