Merge mozilla-inbound to mozilla-central. a=merge
authorCosmin Sabou <csabou@mozilla.com>
Thu, 04 Oct 2018 00:56:04 +0300
changeset 495190 07aa30516b54bf2644120c51a73cac2f9cf84e6f
parent 495179 cdea5981e6eb02f73d36674fd57ea0964c180827 (current diff)
parent 495189 bd37a0888c5220dd112f9a887951303aa2389a41 (diff)
child 495191 520bcd79bd20bcd9eb3903d4d531a90231101eab
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central. a=merge
testing/web-platform/meta/css/css-images/multiple-position-color-stop-linear.html.ini
testing/web-platform/meta/css/css-images/multiple-position-color-stop-radial.html.ini
testing/web-platform/meta/webvr/webvr-enabled-by-feature-policy-attribute.https.sub.html.ini
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-stepping.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-stepping.js
@@ -19,11 +19,11 @@ add_task(async function test() {
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
   await stepIn(dbg);
 
-  assertDebugLine(dbg, 42308);
+  assertDebugLine(dbg, 42267);
   assertPausedLocation(dbg);
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_split-console-keypress.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_split-console-keypress.js
@@ -44,22 +44,23 @@ function test() {
 
     // Information for sub-tests. When 'key' is synthesized 'keyRepeat' times,
     // cursor should be at 'caretLine' of this test..
     let stepTests = [
       {key: "KEY_F11", keyRepeat: 1, caretLine: 16},
       {key: "KEY_F11", keyRepeat: 2, caretLine: 18},
       {key: "KEY_F11", keyRepeat: 2, caretLine: 27},
       {key: "KEY_F10", keyRepeat: 1, caretLine: 27},
-      {key: "KEY_F11", keyRepeat: 1, caretLine: 18},
-      {key: "KEY_F11", keyRepeat: 5, caretLine: 32},
-      {key: "KEY_F11", modifier:"Shift", keyRepeat: 1, caretLine: 29},
-      {key: "KEY_F11", modifier:"Shift", keyRepeat: 2, caretLine: 34},
-      {key: "KEY_F11", modifier:"Shift", keyRepeat: 2, caretLine: 34}
+      {key: "KEY_F11", keyRepeat: 1, caretLine: 19},
+      {key: "KEY_F11", keyRepeat: 5, caretLine: 29},
+      {key: "KEY_F11", modifier:"Shift", keyRepeat: 1, caretLine: 32},
+      {key: "KEY_F11", modifier:"Shift", keyRepeat: 1, caretLine: 34},
+      {key: "KEY_F11", modifier:"Shift", keyRepeat: 1, caretLine: 34}
     ];
+
     // Trigger script that stops at debugger statement
     executeSoon(() => generateMouseClickInTab(gTab,
       "content.document.getElementById('start')"));
     yield waitForPause(gThreadClient);
 
     // Focus the console and add event listener to track whether it loses focus
     // (Must happen after generateMouseClickInTab() call)
     let consoleLostFocus = false;
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -83,16 +83,17 @@ const ThreadActor = ActorClassWithSpec(t
 
     this.global = global;
 
     this._allEventsListener = this._allEventsListener.bind(this);
     this.onNewSourceEvent = this.onNewSourceEvent.bind(this);
     this.onUpdatedSourceEvent = this.onUpdatedSourceEvent.bind(this);
 
     this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
+    this.createCompletionGrip = this.createCompletionGrip.bind(this);
     this.onDebuggerStatement = this.onDebuggerStatement.bind(this);
     this.onNewScript = this.onNewScript.bind(this);
     this.objectGrip = this.objectGrip.bind(this);
     this.pauseObjectGrip = this.pauseObjectGrip.bind(this);
     this._onWindowReady = this._onWindowReady.bind(this);
     this._onOpeningRequest = this._onOpeningRequest.bind(this);
     EventEmitter.on(this._parent, "window-ready", this._onWindowReady);
 
@@ -517,45 +518,53 @@ const ThreadActor = ActorClassWithSpec(t
       if (this.sources.isBlackBoxed(url)) {
         return undefined;
       }
 
       return pauseAndRespond(frame);
     };
   },
 
-  _makeOnPop: function({ thread, pauseAndRespond, createValueGrip: createValueGripHook,
-                          startLocation }) {
+  _makeOnPop: function({ thread, pauseAndRespond, startLocation, steppingType }) {
     const result = function(completion) {
       // onPop is called with 'this' set to the current frame.
       const generatedLocation = thread.sources.getFrameLocation(this);
-      const { originalSourceActor } = thread.unsafeSynchronize(
+      const originalLocation = thread.unsafeSynchronize(
         thread.sources.getOriginalLocation(generatedLocation)
       );
 
+      const { originalSourceActor } = originalLocation;
       const url = originalSourceActor.url;
 
       if (thread.sources.isBlackBoxed(url)) {
         return undefined;
       }
 
       // Note that we're popping this frame; we need to watch for
       // subsequent step events on its caller.
       this.reportedPop = true;
 
+      if (steppingType == "finish") {
+        const parentFrame = thread._getNextStepFrame(this);
+        if (parentFrame && parentFrame.script) {
+          const { onStep } = thread._makeSteppingHooks(
+            originalLocation, "next", false, completion
+          );
+          parentFrame.onStep = onStep;
+          return undefined;
+        }
+      }
+
       return pauseAndRespond(this, packet => {
-        packet.why.frameFinished = {};
-        if (!completion) {
-          packet.why.frameFinished.terminated = true;
-        } else if (completion.hasOwnProperty("return")) {
-          packet.why.frameFinished.return = createValueGripHook(completion.return);
-        } else if (completion.hasOwnProperty("yield")) {
-          packet.why.frameFinished.return = createValueGripHook(completion.yield);
+        if (completion) {
+          thread.createCompletionGrip(packet, completion);
         } else {
-          packet.why.frameFinished.throw = createValueGripHook(completion.throw);
+          packet.why.frameFinished = {
+            terminated: true
+          };
         }
         return packet;
       });
     };
 
     // When stepping out, we don't want to stop at a breakpoint that
     // happened to be set exactly at the spot where we stepped out.
     // See bug 970469.  We record the original location here and check
@@ -564,17 +573,17 @@ const ThreadActor = ActorClassWithSpec(t
     // frame, if we did we'd also have to find the appropriate spot to
     // clear it.
     result.originalLocation = startLocation;
 
     return result;
   },
 
   // Return whether reaching a script offset should be considered a distinct
-  // "step" from another location in the same frame.
+  // "step" from another location.
   _intraFrameLocationIsStepTarget: function(startLocation, script, offset) {
     // Only allow stepping stops at entry points for the line.
     if (!script.getOffsetLocation(offset).isEntryPoint) {
       return false;
     }
 
     // Cases when we have executed enough within a frame to consider a "step"
     // to have occured:
@@ -616,17 +625,17 @@ const ThreadActor = ActorClassWithSpec(t
     }
 
     // NOTE: if we do not find a pause point we want to
     // fall back on the old behavior (Case 3)
     return lineChanged;
   },
 
   _makeOnStep: function({ thread, pauseAndRespond, startFrame,
-                          startLocation, steppingType }) {
+                          startLocation, steppingType, completion, rewinding }) {
     // Breaking in place: we should always pause.
     if (steppingType === "break") {
       return () => pauseAndRespond(this);
     }
 
     // Otherwise take what a "step" means into consideration.
     return function() {
       // onStep is called with 'this' set to the current frame.
@@ -641,33 +650,55 @@ const ThreadActor = ActorClassWithSpec(t
       // 1. We are in a source mapped region, but inside a null mapping
       //    (doesn't correlate to any region of original source)
       // 2. The source we are in is black boxed.
       if (newLocation.originalUrl == null
           || thread.sources.isBlackBoxed(newLocation.originalUrl)) {
         return undefined;
       }
 
-      // A step has occurred if we have changed frames.
-      if (this !== startFrame) {
+      // A step has occurred if we are rewinding and have changed frames.
+      if (rewinding && this !== startFrame) {
         return pauseAndRespond(this);
       }
 
       // A step has occurred if we reached a step target.
       if (thread._intraFrameLocationIsStepTarget(startLocation,
                                                  this.script, this.offset)) {
-        return pauseAndRespond(this);
+        return pauseAndRespond(
+          this,
+          packet => thread.createCompletionGrip(packet, completion)
+        );
       }
 
       // Otherwise, let execution continue (we haven't executed enough code to
       // consider this a "step" yet).
       return undefined;
     };
   },
 
+  createCompletionGrip: function(packet, completion) {
+    if (!completion) {
+      return packet;
+    }
+
+    const createGrip = value => createValueGrip(value, this._pausePool, this.objectGrip);
+    packet.why.frameFinished = {};
+
+    if (completion.hasOwnProperty("return")) {
+      packet.why.frameFinished.return = createGrip(completion.return);
+    } else if (completion.hasOwnProperty("yield")) {
+      packet.why.frameFinished.return = createGrip(completion.yield);
+    } else if (completion.hasOwnProperty("throw")) {
+      packet.why.frameFinished.throw = createGrip(completion.throw);
+    }
+
+    return packet;
+  },
+
   /**
    * When replaying, we need to specify the offsets where a frame's onStep hook
    * should fire. Given that we are stepping forward (rewind == false) or
    * backwards (rewinding == true), return an array of all the step targets
    * that could be reached next from startLocation.
    */
   _findReplayingStepOffsets: function(startLocation, frame, rewinding) {
     const worklist = [frame.offset], seen = [], result = [];
@@ -691,33 +722,33 @@ const ThreadActor = ActorClassWithSpec(t
       }
     }
     return result;
   },
 
   /**
    * Define the JS hook functions for stepping.
    */
-  _makeSteppingHooks: function(startLocation, steppingType, rewinding) {
+  _makeSteppingHooks: function(startLocation, steppingType, rewinding, completion) {
     // Bind these methods and state because some of the hooks are called
     // with 'this' set to the current frame. Rather than repeating the
     // binding in each _makeOnX method, just do it once here and pass it
     // in to each function.
     const steppingHookState = {
       pauseAndRespond: (frame, onPacket = k=>k) => this._pauseAndRespond(
         frame,
         { type: "resumeLimit" },
         onPacket
       ),
-      createValueGrip: v => createValueGrip(v, this._pausePool, this.objectGrip),
       thread: this,
       startFrame: this.youngestFrame,
       startLocation: startLocation,
       steppingType: steppingType,
-      rewinding: rewinding
+      rewinding: rewinding,
+      completion
     };
 
     return {
       onEnterFrame: this._makeOnEnterFrame(steppingHookState),
       onPop: this._makeOnPop(steppingHookState),
       onStep: this._makeOnStep(steppingHookState)
     };
   },
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/completions.js
@@ -0,0 +1,24 @@
+"use strict";
+/* exported global doRet doThrow */
+
+function ret() {
+  return 2;
+}
+
+function throws() {
+  throw new Error("yo");
+}
+
+function doRet() {
+  debugger;
+  const r = ret();
+  return r;
+}
+
+function doThrow() {
+  debugger;
+  try {
+    throws();
+  } catch (e) {
+  }
+}
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -841,8 +841,37 @@ function getInflatedStackLocations(threa
     const frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]];
     locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]);
     stackIndex = stackEntry[STACK_PREFIX_SLOT];
   }
 
   // The profiler tree is inverted, so reverse the array.
   return locations.reverse();
 }
+
+async function setupTestFromUrl(url) {
+  do_test_pending();
+
+  const { createRootActor } = require("xpcshell-test/testactors");
+  DebuggerServer.setRootActor(createRootActor);
+  DebuggerServer.init(() => true);
+
+  const global = createTestGlobal("test");
+  DebuggerServer.addTestGlobal(global);
+
+  const debuggerClient = new DebuggerClient(DebuggerServer.connectPipe());
+  await connect(debuggerClient);
+
+  const { tabs } = await listTabs(debuggerClient);
+  const tab = findTab(tabs, "test");
+  const [, tabClient] = await attachTarget(debuggerClient, tab);
+
+  const [, threadClient] = await attachThread(tabClient);
+  await resume(threadClient);
+
+  const sourceUrl = getFileUrl(url);
+  const promise = waitForNewSource(threadClient, sourceUrl);
+  loadSubScript(sourceUrl, global);
+  const { source } = await promise;
+
+  const sourceClient = threadClient.source(source);
+  return { global, debuggerClient, threadClient, sourceClient };
+}
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/stepping.js
@@ -0,0 +1,27 @@
+"use strict";
+/* exported global arithmetic composition chaining */
+
+const obj = { b };
+
+function a() {
+  return obj;
+}
+
+function b() {
+  return 2;
+}
+
+function arithmetic() {
+  debugger;
+  a() + b();
+}
+
+function composition() {
+  debugger;
+  b(a());
+}
+
+function chaining() {
+  debugger;
+  a().b();
+}
--- a/devtools/server/tests/unit/test_breakpoint-13.js
+++ b/devtools/server/tests/unit/test_breakpoint-13.js
@@ -63,28 +63,23 @@ function test_simple_breakpoint() {
           // Check that the breakpoint wasn't the reason for this pause, but
           // that the frame is about to be popped while stepping.
           Assert.equal(packet.frame.where.line, gDebuggee.line0 + 3);
           Assert.notEqual(packet.why.type, "breakpoint");
           Assert.equal(packet.why.type, "resumeLimit");
           Assert.equal(packet.why.frameFinished.return.type, "undefined");
         },
         function(packet) {
-          // The foo function call frame was just popped from the stack.
+          // Check that the debugger statement wasn't the reason for this pause.
           Assert.equal(gDebuggee.a, 1);
           Assert.equal(gDebuggee.b, undefined);
-          Assert.equal(packet.frame.where.line, gDebuggee.line0 + 5);
-          Assert.equal(packet.why.type, "resumeLimit");
-          Assert.equal(packet.poppedFrames.length, 1);
-        },
-        function(packet) {
-          // Check that the debugger statement wasn't the reason for this pause.
           Assert.equal(packet.frame.where.line, gDebuggee.line0 + 6);
           Assert.notEqual(packet.why.type, "debuggerStatement");
           Assert.equal(packet.why.type, "resumeLimit");
+          Assert.equal(packet.poppedFrames.length, 1);
         },
         function(packet) {
           // Check that the debugger statement wasn't the reason for this pause.
           Assert.equal(packet.frame.where.line, gDebuggee.line0 + 7);
           Assert.notEqual(packet.why.type, "debuggerStatement");
           Assert.equal(packet.why.type, "resumeLimit");
         },
       ];
--- a/devtools/server/tests/unit/test_breakpoint-14.js
+++ b/devtools/server/tests/unit/test_breakpoint-14.js
@@ -61,28 +61,23 @@ function test_simple_breakpoint() {
         function(packet) {
           // The frame is about to be popped while stepping.
           Assert.equal(packet.frame.where.line, gDebuggee.line0 + 3);
           Assert.notEqual(packet.why.type, "breakpoint");
           Assert.equal(packet.why.type, "resumeLimit");
           Assert.equal(packet.why.frameFinished.return.type, "undefined");
         },
         function(packet) {
-          // The foo function call frame was just popped from the stack.
+          // Check that the debugger statement wasn't the reason for this pause.
           Assert.equal(gDebuggee.a, 1);
           Assert.equal(gDebuggee.b, undefined);
-          Assert.equal(packet.frame.where.line, gDebuggee.line0 + 5);
-          Assert.equal(packet.why.type, "resumeLimit");
-          Assert.equal(packet.poppedFrames.length, 1);
-        },
-        function(packet) {
-          // Check that the debugger statement wasn't the reason for this pause.
           Assert.equal(packet.frame.where.line, gDebuggee.line0 + 6);
           Assert.notEqual(packet.why.type, "debuggerStatement");
           Assert.equal(packet.why.type, "resumeLimit");
+          Assert.equal(packet.poppedFrames.length, 1);
         },
         function(packet) {
           // Check that the debugger statement wasn't the reason for this pause.
           Assert.equal(packet.frame.where.line, gDebuggee.line0 + 7);
           Assert.notEqual(packet.why.type, "debuggerStatement");
           Assert.equal(packet.why.type, "resumeLimit");
         },
       ];
--- a/devtools/server/tests/unit/test_stepping-01.js
+++ b/devtools/server/tests/unit/test_stepping-01.js
@@ -1,81 +1,91 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint-disable no-shadow, max-nested-callbacks */
 
 "use strict";
 
 /**
- * Check basic step-over functionality.
+ * Check scenarios where we're leaving function a and
+ * going to the function b's call-site.
  */
 
-var gDebuggee;
-var gClient;
-var gCallback;
+async function testFinish({threadClient, debuggerClient}) {
+  await resume(threadClient);
+  await close(debuggerClient);
+
+  do_test_finished();
+}
 
-function run_test() {
-  do_test_pending();
-  run_test_with_server(DebuggerServer, function() {
-    run_test_with_server(WorkerDebuggerServer, do_test_finished);
-  });
+async function invokeAndPause({global, debuggerClient}, expression) {
+  return executeOnNextTickAndWaitForPause(
+    () => Cu.evalInSandbox(expression, global),
+    debuggerClient
+  );
+}
+
+async function step({threadClient, debuggerClient}, cmd) {
+  return cmd(debuggerClient, threadClient);
 }
 
-function run_test_with_server(server, callback) {
-  gCallback = callback;
-  initTestDebuggerServer(server);
-  gDebuggee = addTestGlobal("test-stepping", server);
-  gClient = new DebuggerClient(server.connectPipe());
-  gClient.connect(test_simple_stepping);
+function getPauseLocation(packet) {
+  const {line, column} = packet.frame.where;
+  return {line, column};
+}
+
+function getPauseReturn(packet) {
+  dump(`>> getPauseReturn yo ${JSON.stringify(packet.why)}\n`);
+  return packet.why.frameFinished.return;
+}
+
+async function steps(dbg, sequence) {
+  const locations = [];
+  for (const cmd of sequence) {
+    const packet = await step(dbg, cmd);
+    locations.push(getPauseLocation(packet));
+  }
+  return locations;
 }
 
-async function test_simple_stepping() {
-  const [attachResponse,, threadClient] = await attachTestTabAndResume(gClient,
-                                                                       "test-stepping");
-  ok(!attachResponse.error, "Should not get an error attaching");
-
-  dumpn("Evaluating test code and waiting for first debugger statement");
-  const dbgStmt = await executeOnNextTickAndWaitForPause(evaluateTestCode, gClient);
-  equal(dbgStmt.frame.where.line, 2, "Should be at debugger statement on line 2");
-  equal(gDebuggee.a, undefined);
-  equal(gDebuggee.b, undefined);
+async function stepOutOfA(dbg, func, expectedLocation) {
+  await invokeAndPause(dbg, `${func}()`);
+  await steps(dbg, [stepOver, stepIn]);
 
-  dumpn("Step Over to line 3");
-  const step1 = await stepOver(gClient, threadClient);
-  equal(step1.type, "paused");
-  equal(step1.why.type, "resumeLimit");
-  equal(step1.frame.where.line, 3);
-  equal(gDebuggee.a, undefined);
-  equal(gDebuggee.b, undefined);
+  dump(`>>> oof\n`);
+  const packet = await step(dbg, stepOut);
+  dump(`>>> foo\n`);
 
-  dumpn("Step Over to line 4");
-  const step3 = await stepOver(gClient, threadClient);
-  equal(step3.type, "paused");
-  equal(step3.why.type, "resumeLimit");
-  equal(step3.frame.where.line, 4);
-  equal(gDebuggee.a, 1);
-  equal(gDebuggee.b, undefined);
+  deepEqual(getPauseLocation(packet), expectedLocation, `step out location in ${func}`);
 
-  dumpn("Step Over to line 4 to leave the frame");
-  const step4 = await stepOver(gClient, threadClient);
-  equal(step4.type, "paused");
-  equal(step4.why.type, "resumeLimit");
-  equal(step4.frame.where.line, 4);
-  equal(gDebuggee.a, 1);
-  equal(gDebuggee.b, 2);
-
-  finishClient(gClient, gCallback);
+  await resume(dbg.threadClient);
 }
 
-function evaluateTestCode() {
-  /* eslint-disable */
-  Cu.evalInSandbox(
-    `                                   // 1
-    debugger;                           // 2
-    var a = 1;                          // 3
-    var b = 2;`,                        // 4
-    gDebuggee,
-    "1.8",
-    "test_stepping-01-test-code.js",
-    1
-  );
-  /* eslint-disable */
-}
\ No newline at end of file
+async function stepOverInA(dbg, func, expectedLocation) {
+  await invokeAndPause(dbg, `${func}()`);
+  await steps(dbg, [stepOver, stepIn, stepOver]);
+
+  let packet = await step(dbg, stepOver);
+  dump(`>> stepOverInA hi\n`);
+  equal(getPauseReturn(packet).ownPropertyLength, 1, "a() is returning obj");
+
+  packet = await step(dbg, stepOver);
+  deepEqual(getPauseLocation(packet), expectedLocation, `step out location in ${func}`);
+
+  await resume(dbg.threadClient);
+}
+
+async function testStep(dbg, func, expectedLocation) {
+  await stepOverInA(dbg, func, expectedLocation);
+  await stepOutOfA(dbg, func, expectedLocation);
+}
+
+function run_test() {
+  return (async function() {
+    const dbg = await setupTestFromUrl("stepping.js");
+
+    await testStep(dbg, "arithmetic", {line: 16, column: 8});
+    await testStep(dbg, "composition", {line: 21, column: 2});
+    await testStep(dbg, "chaining", {line: 26, column: 6});
+
+    await testFinish(dbg);
+  })();
+}
--- a/devtools/server/tests/unit/test_stepping-03.js
+++ b/devtools/server/tests/unit/test_stepping-03.js
@@ -32,17 +32,17 @@ async function test_simple_stepping() {
                                                                        "test-stepping");
   ok(!attachResponse.error, "Should not get an error attaching");
 
   dumpn("Evaluating test code and waiting for first debugger statement");
   await executeOnNextTickAndWaitForPause(evaluateTestCode, gClient);
 
   const step1 = await stepOut(gClient, threadClient);
   equal(step1.type, "paused");
-  equal(step1.frame.where.line, 6);
+  equal(step1.frame.where.line, 8);
   equal(step1.why.type, "resumeLimit");
 
   equal(gDebuggee.a, 1);
   equal(gDebuggee.b, 2);
 
   finishClient(gClient, gCallback);
 }
 
@@ -58,9 +58,9 @@ function evaluateTestCode() {
     f();                                // 7
     `,                                  // 8
     gDebuggee,
     "1.8",
     "test_stepping-01-test-code.js",
     1
   );
   /* eslint-disable */
-}
\ No newline at end of file
+}
--- a/devtools/server/tests/unit/test_stepping-06.js
+++ b/devtools/server/tests/unit/test_stepping-06.js
@@ -3,108 +3,145 @@
 /* eslint-disable no-shadow, max-nested-callbacks */
 
 "use strict";
 
 /**
  * Check that stepping out of a function returns the right return value.
  */
 
-var gDebuggee;
-var gClient;
-var gThreadClient;
-var gCallback;
+async function invokeAndPause({global, debuggerClient}, expression) {
+  return executeOnNextTickAndWaitForPause(
+    () => Cu.evalInSandbox(expression, global),
+    debuggerClient
+  );
+}
 
-function run_test() {
-  run_test_with_server(DebuggerServer, function() {
-    run_test_with_server(WorkerDebuggerServer, do_test_finished);
-  });
-  do_test_pending();
+async function step({threadClient, debuggerClient}, cmd) {
+  return cmd(debuggerClient, threadClient);
+}
+
+function getPauseLocation(packet) {
+  const {line, column} = packet.frame.where;
+  return {line, column};
 }
 
-function run_test_with_server(server, callback) {
-  gCallback = callback;
-  initTestDebuggerServer(server);
-  gDebuggee = addTestGlobal("test-stack", server);
-  gClient = new DebuggerClient(server.connectPipe());
-  gClient.connect().then(function() {
-    attachTestTabAndResume(
-      gClient, "test-stack",
-      function(response, tabClient, threadClient) {
-        gThreadClient = threadClient;
-        // XXX: We have to do an executeSoon so that the error isn't caught and
-        // reported by DebuggerClient.requester (because we are using the local
-        // transport and share a stack) which causes the test to fail.
-        Services.tm.dispatchToMainThread({
-          run: test_simple_stepping
-        });
-      });
-  });
+function getFrameFinished(packet) {
+  return packet.why.frameFinished;
+}
+
+async function steps(dbg, sequence) {
+  const locations = [];
+  for (const cmd of sequence) {
+    const packet = await step(dbg, cmd);
+    locations.push(getPauseLocation(packet));
+  }
+  return locations;
+}
+
+async function testFinish({threadClient, debuggerClient}) {
+  await resume(threadClient);
+  await close(debuggerClient);
+
+  do_test_finished();
 }
 
-async function test_simple_stepping() {
-  await executeOnNextTickAndWaitForPause(evaluateTestCode, gClient);
+async function testRet(dbg) {
+  let packet;
+
+  info(`1. Test returning from doRet via stepping over`);
+  await invokeAndPause(dbg, `doRet()`);
+  await steps(dbg, [stepOver, stepIn, stepOver]);
+  packet = await step(dbg, stepOver);
 
-  const step1 = await stepOut(gClient, gThreadClient);
-  equal(step1.type, "paused");
-  equal(step1.frame.where.line, 6);
-  equal(step1.why.type, "resumeLimit");
-  equal(step1.why.frameFinished.return, 10);
+  deepEqual(
+    getPauseLocation(packet),
+    {line: 6, column: 0},
+    `completion location in doRet`
+  );
+  deepEqual(
+    getFrameFinished(packet),
+    {"return": 2}, `completion value`);
 
-  gThreadClient.resume();
-  const step2 = await waitForPause(gThreadClient);
-  equal(step2.type, "paused");
-  equal(step2.frame.where.line, 8);
-  equal(step2.why.type, "debuggerStatement");
+  await resume(dbg.threadClient);
 
-  gThreadClient.stepOut();
-  const step3 = await waitForPause(gThreadClient);
-  equal(step3.type, "paused");
-  equal(step3.frame.where.line, 9);
-  equal(step3.why.type, "resumeLimit");
-  equal(step3.why.frameFinished.return.type, "undefined");
+  info(`2. Test leaving from doRet via stepping out`);
+  await invokeAndPause(dbg, `doRet()`);
+  await steps(dbg, [stepOver, stepIn]);
 
-  gThreadClient.resume();
-  const step4 = await waitForPause(gThreadClient);
+  packet = await step(dbg, stepOut);
 
-  equal(step4.type, "paused");
-  equal(step4.frame.where.line, 11);
+  deepEqual(
+    getPauseLocation(packet),
+    {line: 15, column: 2},
+    `completion location in doThrow`
+  );
 
-  gThreadClient.stepOut();
-  const step5 = await waitForPause(gThreadClient);
-  equal(step5.type, "paused");
-  equal(step5.frame.where.line, 12);
-  equal(step5.why.type, "resumeLimit");
-  equal(step5.why.frameFinished.throw, "ah");
+  deepEqual(
+    getFrameFinished(packet),
+    {"return": 2},
+    `completion completion value`
+  );
 
-  finishClient(gClient, gCallback);
+  await resume(dbg.threadClient);
 }
 
-function evaluateTestCode() {
-  /* eslint-disable */
-  Cu.evalInSandbox(
-    `                                   //  1
-    function f() {                      //  2
-      debugger;                         //  3
-      var a = 10;                       //  4
-      return a;                         //  5
-    }                                   //  6
-    function g() {                      //  7
-      debugger;                         //  8
-    }                                   //  9
-    function h() {                      // 10
-      debugger;                         // 11
-      throw 'ah';                       // 12
-      return 2;                         // 13
-    }                                   // 14
-    f()                                 // 15
-    g()                                 // 16
-    try {                               // 17
-      h();                              // 18
-    } catch (ex) { };                   // 19
-    `,                                  // 20
-    gDebuggee,
-    "1.8",
-    "test_stepping-07-test-code.js",
-    1
+async function testThrow(dbg) {
+  let packet;
+
+  info(`3. Test leaving from doThrow via stepping over`);
+  await invokeAndPause(dbg, `doThrow()`);
+  await steps(dbg, [stepOver, stepOver, stepIn]);
+  packet = await step(dbg, stepOver);
+
+  deepEqual(
+    getPauseLocation(packet),
+    {line: 9, column: 8},
+    `completion location in doThrow`
+  );
+
+  deepEqual(
+    getFrameFinished(packet).throw.class,
+    "Error",
+    `completion value class`
+  );
+  deepEqual(
+    getFrameFinished(packet).throw.preview.message,
+    "yo",
+    `completion value preview`
   );
-  /* eslint-enable */
+
+  await resume(dbg.threadClient);
+
+  info(`4. Test leaving from doThrow via stepping out`);
+  await invokeAndPause(dbg, `doThrow()`);
+  await steps(dbg, [stepOver, stepOver, stepIn]);
+
+  packet = await step(dbg, stepOut);
+  deepEqual(
+    getPauseLocation(packet),
+    {line: 22, column: 14},
+    `completion location in doThrow`
+  );
+
+  deepEqual(
+    getFrameFinished(packet).throw.class,
+    "Error",
+    `completion completion value class`
+  );
+  deepEqual(
+    getFrameFinished(packet).throw.preview.message,
+    "yo",
+    `completion completion value preview`
+  );
+  await resume(dbg.threadClient);
 }
+
+function run_test() {
+  return (async function() {
+    const dbg = await setupTestFromUrl("completions.js");
+
+    await testRet(dbg);
+    await testThrow(dbg);
+
+    await testFinish(dbg);
+  })();
+}
--- a/devtools/server/tests/unit/test_stepping-08.js
+++ b/devtools/server/tests/unit/test_stepping-08.js
@@ -41,17 +41,17 @@ async function testStepOutWithBreakpoint
 
   dumpn("Step in to innerFunction");
   const step1 = await stepIn(gClient, threadClient);
   equal(step1.frame.where.line, 7);
 
   dumpn("Step out of innerFunction");
   const step2 = await stepOut(gClient, threadClient);
   // The bug was that we'd stop again at the breakpoint on line 7.
-  equal(step2.frame.where.line, 10);
+  equal(step2.frame.where.line, 4);
 
   finishClient(gClient, gCallback);
 }
 
 function evaluateTestCode() {
   /* eslint-disable */
   Cu.evalInSandbox(
     `                                   //  1
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -1,26 +1,28 @@
 [DEFAULT]
 tags = devtools
 head = head_dbg.js
 firefox-appdir = browser
 skip-if = toolkit == 'android'
 
 support-files =
   babel_and_browserify_script_with_source_map.js
+  completions.js
   source-map-data/sourcemapped.coffee
   source-map-data/sourcemapped.map
   post_init_global_actors.js
   post_init_target_scoped_actors.js
   pre_init_global_actors.js
   pre_init_target_scoped_actors.js
   registertestactors-lazy.js
   sourcemapped.js
   testactors.js
   hello-actor.js
+  stepping.js
   setBreakpoint-on-column.js
   setBreakpoint-on-column-in-gcd-script.js
   setBreakpoint-on-column-with-no-offsets.js
   setBreakpoint-on-column-with-no-offsets-in-gcd-script.js
   setBreakpoint-on-line.js
   setBreakpoint-on-line-in-gcd-script.js
   setBreakpoint-on-line-with-multiple-offsets.js
   setBreakpoint-on-line-with-multiple-statements.js
--- a/dom/security/featurepolicy/FeaturePolicyUtils.cpp
+++ b/dom/security/featurepolicy/FeaturePolicyUtils.cpp
@@ -21,31 +21,31 @@ struct FeatureMap {
 };
 
 /*
  * IMPORTANT: Do not change this list without review from a DOM peer _AND_ a
  * DOM Security peer!
  */
 static FeatureMap sSupportedFeatures[] = {
   // TODO: not supported yet!!!
-  { "autoplay", FeatureMap::eSelf },
+  { "autoplay", FeatureMap::eAll },
   // TODO: not supported yet!!!
-  { "camera", FeatureMap::eSelf  },
-  { "encrypted-media", FeatureMap::eSelf  },
+  { "camera", FeatureMap::eAll  },
+  { "encrypted-media", FeatureMap::eAll  },
   // TODO: not supported yet!!!
-  { "fullscreen", FeatureMap::eSelf  },
+  { "fullscreen", FeatureMap::eAll  },
   // TODO: not supported yet!!!
-  { "geolocation", FeatureMap::eSelf  },
+  { "geolocation", FeatureMap::eAll  },
   // TODO: not supported yet!!!
-  { "microphone", FeatureMap::eSelf  },
-  { "midi", FeatureMap::eSelf  },
-  { "payment", FeatureMap::eSelf  },
+  { "microphone", FeatureMap::eAll  },
+  { "midi", FeatureMap::eAll  },
+  { "payment", FeatureMap::eAll  },
   // TODO: not supported yet!!!
-  { "speaker", FeatureMap::eSelf  },
-  { "vr", FeatureMap::eSelf  },
+  { "speaker", FeatureMap::eAll  },
+  { "vr", FeatureMap::eAll  },
 };
 
 /* static */ bool
 FeaturePolicyUtils::IsSupportedFeature(const nsAString& aFeatureName)
 {
   uint32_t numFeatures = (sizeof(sSupportedFeatures) / sizeof(sSupportedFeatures[0]));
   for (uint32_t i = 0; i < numFeatures; ++i) {
     if (aFeatureName.LowerCaseEqualsASCII(sSupportedFeatures[i].mFeatureName)) {
--- a/dom/security/featurepolicy/test/mochitest/test_parser.html
+++ b/dom/security/featurepolicy/test/mochitest/test_parser.html
@@ -21,17 +21,17 @@ function test_document() {
   ok(document.policy.allowsFeature("camera"), "Camera is always enabled");
   ok(document.policy.allowsFeature("camera", "http://foo.bar"), "Camera is always enabled");
   let allowed = document.policy.getAllowlistForFeature("camera");
   is(allowed.length, 1, "Only 1 entry in allowlist for camera");
   is(allowed[0], "*", "allowlist is *");
 
   ok(document.policy.allowsFeature("geolocation"), "Geolocation is enabled for self");
   ok(document.policy.allowsFeature("geolocation", location.origin), "Geolocation is enabled for self");
-  ok(!document.policy.allowsFeature("geolocation", "http://foo.bar"), "Geolocation is not enabled for anything else");
+  ok(!document.policy.allowsFeature("geolocation", "http://foo.bar"), "Geolocation is not enabled for any random URL");
   allowed = document.policy.getAllowlistForFeature("geolocation");
   is(allowed.length, 1, "Only 1 entry in allowlist for geolocation");
   is(allowed[0], location.origin, "allowlist is self");
 
   ok(!document.policy.allowsFeature("microphone"), "Microphone is disabled for self");
   ok(!document.policy.allowsFeature("microphone", location.origin), "Microphone is disabled for self");
   ok(!document.policy.allowsFeature("microphone", "http://foo.bar"), "Microphone is disabled for foo.bar");
   ok(document.policy.allowsFeature("microphone", "http://example.com"), "Microphone is enabled for example.com");
@@ -63,29 +63,29 @@ function test_document() {
 function test_iframe_without_allow() {
   info("Checking HTMLIFrameElement.policy");
   let ifr = document.getElementById("ifr");
   ok("policy" in ifr, "HTMLIFrameElement.policy exists");
 
   ok(!ifr.policy.allowsFeature("foobar"), "Random feature");
   ok(!ifr.policy.allowsFeature("foobar", "http://www.something.net"), "Random feature");
 
-  ok(ifr.policy.allowsFeature("camera"), "Camera is always enabled for self");
-  ok(ifr.policy.allowsFeature("camera", location.origin), "Camera is allowed for self");
-  ok(!ifr.policy.allowsFeature("camera", "http://foo.bar"), "Camera is not allowed for a random URL");
+  ok(ifr.policy.allowsFeature("camera"), "Camera is always allowed");
+  ok(ifr.policy.allowsFeature("camera", location.origin), "Camera is always allowed");
+  ok(ifr.policy.allowsFeature("camera", "http://foo.bar"), "Camera is always allowed");
   let allowed = ifr.policy.getAllowlistForFeature("camera");
   is(allowed.length, 1, "Only 1 entry in allowlist for camera");
-  is(allowed[0], location.origin, "allowlist is 'self'");
+  is(allowed[0], "*", "allowlist is '*'");
 
-  ok(ifr.policy.allowsFeature("geolocation"), "Geolocation is enabled for self");
-  ok(ifr.policy.allowsFeature("geolocation", location.origin), "Geolocation is enabled for self");
-  ok(!ifr.policy.allowsFeature("geolocation", "http://foo.bar"), "Geolocation is not enabled for anything else");
+  ok(ifr.policy.allowsFeature("geolocation"), "Geolocation is enabled for all");
+  ok(ifr.policy.allowsFeature("geolocation", location.origin), "Geolocation is enabled for all");
+  ok(ifr.policy.allowsFeature("geolocation", "http://foo.bar"), "Geolocation is allowed for any random URL");
   allowed = ifr.policy.getAllowlistForFeature("geolocation");
   is(allowed.length, 1, "Only 1 entry in allowlist for geolocation");
-  is(allowed[0], location.origin, "allowlist is self");
+  is(allowed[0], "*", "allowlist is '*'");
 
   ok(!ifr.policy.allowsFeature("microphone"), "Microphone is disabled for self");
   ok(!ifr.policy.allowsFeature("microphone", location.origin), "Microphone is disabled for self");
   ok(!ifr.policy.allowsFeature("microphone", "http://foo.bar"), "Microphone is disabled for foo.bar");
   ok(!ifr.policy.allowsFeature("microphone", "http://example.com"), "Microphone is disabled for example.com");
   ok(!ifr.policy.allowsFeature("microphone", "http://example.org"), "Microphone is disabled for example.org");
   allowed = ifr.policy.getAllowlistForFeature("microphone");
   is(allowed.length, 0, "No allowlist for microphone");
@@ -115,22 +115,22 @@ function test_iframe_with_allow() {
 
   ok(!ifr.policy.allowsFeature("foobar"), "Random feature");
   ok(!ifr.policy.allowsFeature("foobar", "http://www.something.net"), "Random feature");
 
   ok(!ifr.policy.allowsFeature("camera"), "Camera is not enabled");
   let allowed = ifr.policy.getAllowlistForFeature("camera");
   is(allowed.length, 0, "Camera has an empty allowlist");
 
-  ok(ifr.policy.allowsFeature("geolocation"), "Geolocation is enabled for self");
-  ok(ifr.policy.allowsFeature("geolocation", location.origin), "Geolocation is enabled for self");
-  ok(!ifr.policy.allowsFeature("geolocation", "http://foo.bar"), "Geolocation is not enabled for anything else");
+  ok(ifr.policy.allowsFeature("geolocation"), "Geolocation is enabled for all");
+  ok(ifr.policy.allowsFeature("geolocation", location.origin), "Geolocation is enabled for all");
+  ok(ifr.policy.allowsFeature("geolocation", "http://foo.bar"), "Geolocation is enabled for all");
   allowed = ifr.policy.getAllowlistForFeature("geolocation");
   is(allowed.length, 1, "Only 1 entry in allowlist for geolocation");
-  is(allowed[0], location.origin, "allowlist is self");
+  is(allowed[0], "*", "allowlist is '*'");
 
   ok(!ifr.policy.allowsFeature("microphone"), "Microphone is disabled for self");
   ok(!ifr.policy.allowsFeature("microphone", location.origin), "Microphone is disabled for self");
   ok(!ifr.policy.allowsFeature("microphone", "http://foo.bar"), "Microphone is disabled for foo.bar");
   ok(!ifr.policy.allowsFeature("microphone", "http://example.com"), "Microphone is disabled for example.com");
   ok(!ifr.policy.allowsFeature("microphone", "http://example.org"), "Microphone is disabled for example.org");
   allowed = ifr.policy.getAllowlistForFeature("microphone");
   is(allowed.length, 0, "No allowlist for microphone");
@@ -152,28 +152,28 @@ function test_iframe_contentDocument() {
   let ifr = document.createElement("iframe");
   ifr.setAttribute("src", "empty.html");
   ifr.onload = function() {
     ok("policy" in ifr.contentDocument, "We have ifr.contentDocument.policy");
 
     ok(!ifr.contentDocument.policy.allowsFeature("foobar"), "Random feature");
     ok(!ifr.contentDocument.policy.allowsFeature("foobar", "http://www.something.net"), "Random feature");
 
-    ok(ifr.contentDocument.policy.allowsFeature("camera"), "Camera is always enabled for self");
-    ok(!ifr.contentDocument.policy.allowsFeature("camera", "http://foo.bar"), "Camera is not allowed for a random URL");
+    ok(ifr.contentDocument.policy.allowsFeature("camera"), "Camera is always allowed");
+    ok(ifr.contentDocument.policy.allowsFeature("camera", "http://foo.bar"), "Camera is always allowed");
     let allowed = ifr.contentDocument.policy.getAllowlistForFeature("camera");
     is(allowed.length, 1, "Only 1 entry in allowlist for camera");
-    is(allowed[0], location.origin, "allowlist is self");
+    is(allowed[0], "*", "allowlist is '*'");
 
-    ok(ifr.contentDocument.policy.allowsFeature("geolocation"), "Geolocation is enabled for self");
-    ok(ifr.contentDocument.policy.allowsFeature("geolocation", location.origin), "Geolocation is enabled for self");
-    ok(!ifr.contentDocument.policy.allowsFeature("geolocation", "http://foo.bar"), "Geolocation is not enabled for anything else");
+    ok(ifr.contentDocument.policy.allowsFeature("geolocation"), "Geolocation is enabled for all");
+    ok(ifr.contentDocument.policy.allowsFeature("geolocation", location.origin), "Geolocation is enabled for all");
+    ok(ifr.contentDocument.policy.allowsFeature("geolocation", "http://foo.bar"), "Geolocation is enabled for any random URL");
     allowed = ifr.contentDocument.policy.getAllowlistForFeature("geolocation");
     is(allowed.length, 1, "Only 1 entry in allowlist for geolocation");
-    is(allowed[0], location.origin, "allowlist is self");
+    is(allowed[0], "*", "allowlist is '*'");
 
     ok(!ifr.contentDocument.policy.allowsFeature("microphone"), "Microphone is disabled for self");
     ok(!ifr.contentDocument.policy.allowsFeature("microphone", location.origin), "Microphone is disabled for self");
     ok(!ifr.contentDocument.policy.allowsFeature("microphone", "http://foo.bar"), "Microphone is disabled for foo.bar");
     ok(!ifr.contentDocument.policy.allowsFeature("microphone", "http://example.com"), "Microphone is enabled for example.com");
     ok(!ifr.contentDocument.policy.allowsFeature("microphone", "http://example.org"), "Microphone is enabled for example.org");
     allowed = ifr.contentDocument.policy.getAllowlistForFeature("microphone");
     is(allowed.length, 0, "No allowlist for microphone");
--- a/js/src/jit/mips32/MacroAssembler-mips32.cpp
+++ b/js/src/jit/mips32/MacroAssembler-mips32.cpp
@@ -218,70 +218,103 @@ MacroAssemblerMIPS::ma_liPatchable(Regis
 }
 
 // Arithmetic-based ops.
 
 // Add.
 void
 MacroAssemblerMIPS::ma_addTestOverflow(Register rd, Register rs, Register rt, Label* overflow)
 {
-    Label goodAddition;
+    MOZ_ASSERT_IF(rs == rd, rs != rt);
+    MOZ_ASSERT(rs != ScratchRegister);
+    MOZ_ASSERT(rt != ScratchRegister);
+    MOZ_ASSERT(rd != rt);
+    MOZ_ASSERT(rd != ScratchRegister);
+    MOZ_ASSERT(rd != SecondScratchReg);
+
+    if (rs == rt) {
+       as_addu(rd, rs, rs);
+       as_xor(SecondScratchReg, rs, rd);
+       ma_b(SecondScratchReg, Imm32(0), overflow, Assembler::LessThan);
+       return;
+    }
+
+    // If different sign, no overflow
+    as_xor(ScratchRegister, rs, rt);
+
     as_addu(rd, rs, rt);
-
-    as_xor(ScratchRegister, rs, rt); // If different sign, no overflow
-    ma_b(ScratchRegister, Imm32(0), &goodAddition, Assembler::LessThan, ShortJump);
-
+    as_nor(ScratchRegister, ScratchRegister, zero);
     // If different sign, then overflow
-    as_xor(ScratchRegister, rs, rd);
-    ma_b(ScratchRegister, Imm32(0), overflow, Assembler::LessThan);
-
-    bind(&goodAddition);
+    as_xor(SecondScratchReg, rt, rd);
+    as_and(SecondScratchReg, SecondScratchReg, ScratchRegister);
+    ma_b(SecondScratchReg, Imm32(0), overflow, Assembler::LessThan);
+
 }
 
 void
 MacroAssemblerMIPS::ma_addTestOverflow(Register rd, Register rs, Imm32 imm, Label* overflow)
 {
-    // Check for signed range because of as_addiu
-    // Check for unsigned range because of as_xori
-    if (Imm16::IsInSignedRange(imm.value) && Imm16::IsInUnsignedRange(imm.value)) {
-        Label goodAddition;
+    MOZ_ASSERT(rs != ScratchRegister);
+    MOZ_ASSERT(rs != SecondScratchReg);
+    MOZ_ASSERT(rd != ScratchRegister);
+    MOZ_ASSERT(rd != SecondScratchReg);
+
+    Register rs_copy = rs;
+
+    if (imm.value > 0) {
+        as_nor(ScratchRegister, rs, zero);
+    } else if (rs == rd) {
+        ma_move(ScratchRegister, rs);
+        rs_copy = ScratchRegister;
+    }
+
+    if (Imm16::IsInSignedRange(imm.value)) {
         as_addiu(rd, rs, imm.value);
-
-        // If different sign, no overflow
-        as_xori(ScratchRegister, rs, imm.value);
-        ma_b(ScratchRegister, Imm32(0), &goodAddition, Assembler::LessThan, ShortJump);
-
-        // If different sign, then overflow
-        as_xor(ScratchRegister, rs, rd);
-        ma_b(ScratchRegister, Imm32(0), overflow, Assembler::LessThan);
-
-        bind(&goodAddition);
     } else {
-        ma_li(ScratchRegister, imm);
-        ma_addTestOverflow(rd, rs, ScratchRegister, overflow);
+        ma_li(SecondScratchReg, imm);
+        as_addu(rd, rs, SecondScratchReg);
     }
+
+    if (imm.value > 0) {
+        as_and(ScratchRegister, ScratchRegister, rd);
+    } else {
+        as_nor(SecondScratchReg, rd, zero);
+        as_and(ScratchRegister, rs_copy, SecondScratchReg);
+    }
+
+    ma_b(ScratchRegister, Imm32(0), overflow, Assembler::LessThan);
 }
 
 // Subtract.
 void
 MacroAssemblerMIPS::ma_subTestOverflow(Register rd, Register rs, Register rt, Label* overflow)
 {
-    Label goodSubtraction;
-    // Use second scratch. The instructions generated by ma_b don't use the
-    // second scratch register.
+    // The rs == rt case should probably be folded at MIR stage.
+    // Happens for Number_isInteger*. Not worth specializing here.
+    MOZ_ASSERT_IF(rs == rd, rs != rt);
+    MOZ_ASSERT(rs != SecondScratchReg);
+    MOZ_ASSERT(rt != SecondScratchReg);
+    MOZ_ASSERT(rd != rt);
+    MOZ_ASSERT(rd != ScratchRegister);
+    MOZ_ASSERT(rd != SecondScratchReg);
+
+    Register rs_copy = rs;
+
+    if (rs == rd) {
+       ma_move(SecondScratchReg, rs);
+       rs_copy = SecondScratchReg;
+    }
+
     as_subu(rd, rs, rt);
-
-    as_xor(ScratchRegister, rs, rt); // If same sign, no overflow
-    ma_b(ScratchRegister, Imm32(0), &goodSubtraction, Assembler::GreaterThanOrEqual, ShortJump);
-
+    // If same sign, no overflow
+    as_xor(ScratchRegister, rs_copy, rt);
     // If different sign, then overflow
-    as_xor(ScratchRegister, rs, rd);
-    ma_b(ScratchRegister, Imm32(0), overflow, Assembler::LessThan);
-
-    bind(&goodSubtraction);
+    as_xor(SecondScratchReg, rs_copy, rd);
+    as_and(SecondScratchReg, SecondScratchReg, ScratchRegister);
+    ma_b(SecondScratchReg, Imm32(0), overflow, Assembler::LessThan);
 }
 
 // Memory.
 
 void
 MacroAssemblerMIPS::ma_load(Register dest, Address address,
                             LoadStoreSize size, LoadStoreExtension extension)
 {
@@ -1771,21 +1804,21 @@ MacroAssemblerMIPSCompat::loadValue(Addr
         ma_lw(val.typeReg(), Address(src.base, src.offset + TAG_OFFSET));
         ma_lw(val.payloadReg(), Address(src.base, src.offset + PAYLOAD_OFFSET));
     }
 }
 
 void
 MacroAssemblerMIPSCompat::tagValue(JSValueType type, Register payload, ValueOperand dest)
 {
-    MOZ_ASSERT(payload != dest.typeReg());
-    ma_li(dest.typeReg(), ImmType(type));
+    MOZ_ASSERT(dest.typeReg() != dest.payloadReg());
     if (payload != dest.payloadReg()) {
         ma_move(dest.payloadReg(), payload);
     }
+    ma_li(dest.typeReg(), ImmType(type));
 }
 
 void
 MacroAssemblerMIPSCompat::pushValue(ValueOperand val)
 {
     // Allocate stack slots for type and payload. One for each.
     asMasm().subPtr(Imm32(sizeof(Value)), StackPointer);
     // Store type and payload.
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -125,16 +125,22 @@ var validGradientAndElementValues = [
   "linear-gradient(.414rad, red 50%, 50%, blue 50%)",
   "linear-gradient(.414rad, red 50%, 20%, blue 50%)",
   "linear-gradient(.414rad, red 50%, 30%, blue 10%)",
   "linear-gradient(to right bottom, red, 20%, green 50%, 65%, blue)",
   "linear-gradient(to right bottom, red, 20%, green 10%, blue)",
   "linear-gradient(to right bottom, red, 50%, green 50%, 50%, blue)",
   "linear-gradient(to right bottom, red, 0%, green 50%, 100%, blue)",
 
+  "linear-gradient(red 0% 100%)",
+  "linear-gradient(red 0% 50%, blue 50%)",
+  "linear-gradient(red 0% 50%, blue 50% 100%)",
+  "linear-gradient(red 0% 50%, 0%, blue 50%)",
+  "linear-gradient(red 0% 50%, 0%, blue 50% 100%)",
+
   /* Unitless 0 is valid as an <angle> */
   "linear-gradient(0, red, blue)",
 
   "radial-gradient(red, blue)",
   "radial-gradient(red, yellow, blue)",
   "radial-gradient(red 1px, yellow 20%, blue 24em, green)",
   "radial-gradient(red, yellow, green, blue 50%)",
   "radial-gradient(red -50%, yellow -25%, green, blue)",
@@ -178,16 +184,22 @@ var validGradientAndElementValues = [
   "radial-gradient(farthest-corner circle at 4em, red, blue)",
 
   "radial-gradient(30% 40% at top left, red, blue)",
   "radial-gradient(50px 60px at 15% 20%, red, blue)",
   "radial-gradient(7em 8em at 45px, red, blue)",
 
   "radial-gradient(circle at 15% 20%, red, blue)",
 
+  "radial-gradient(red 0% 100%)",
+  "radial-gradient(red 0% 50%, blue 50%)",
+  "radial-gradient(red 0% 50%, blue 50% 100%)",
+  "radial-gradient(red 0% 50%, 0%, blue 50%)",
+  "radial-gradient(red 0% 50%, 0%, blue 50% 100%)",
+
   "repeating-radial-gradient(red, blue)",
   "repeating-radial-gradient(red, yellow, blue)",
   "repeating-radial-gradient(red 1px, yellow 20%, blue 24em, green)",
   "repeating-radial-gradient(red, yellow, green, blue 50%)",
   "repeating-radial-gradient(red -50%, yellow -25%, green, blue)",
   "repeating-radial-gradient(red -99px, yellow, green, blue 120%)",
   "repeating-radial-gradient(#ffff00, #ef3, rgba(10, 20, 30, 0.4))",
   "repeating-radial-gradient(rgba(10, 20, 30, 0.4), #ffff00, #ef3)",
@@ -323,16 +335,26 @@ var invalidGradientAndElementValues = [
   "radial-gradient(399grad, ellipse closest-corner, red, blue)",
   "radial-gradient(399grad, farthest-side circle, red, blue)",
 
   "radial-gradient(top left 99deg, cover, red, blue)",
   "radial-gradient(15% 20% -1.2345rad, circle, red, blue)",
   "radial-gradient(45px 399grad, ellipse closest-corner, red, blue)",
   "radial-gradient(45px 399grad, farthest-side circle, red, blue)",
   "radial-gradient(circle red, blue)",
+
+  /* don't allow more than two positions with multi-position syntax */
+  "linear-gradient(red 0% 50% 100%)",
+  "linear-gradient(red 0% 50% 75%, blue 75%)",
+  "linear-gradient(to bottom, red 0% 50% 100%)",
+  "linear-gradient(to bottom, red 0% 50% 75%, blue 75%)",
+  "radial-gradient(red 0% 50% 100%)",
+  "radial-gradient(red 0% 50% 75%, blue 75%)",
+  "radial-gradient(center, red 0% 50% 100%)",
+  "radial-gradient(center, red 0% 50% 75%, blue 75%)",
 ];
 var unbalancedGradientAndElementValues = [
   "-moz-element(#a()",
 ];
 
 var basicShapeSVGBoxValues = [
   "fill-box",
   "stroke-box",
--- a/servo/components/style/values/specified/image.rs
+++ b/servo/components/style/values/specified/image.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! CSS handling for the specified value of
 //! [`image`][image]s
 //!
 //! [image]: https://drafts.csswg.org/css-images/#image-values
 
 use Atom;
-use cssparser::{Parser, Token};
+use cssparser::{Parser, Token, Delimiter};
 use custom_properties::SpecifiedValue;
 use parser::{Parse, ParserContext};
 use selectors::parser::SelectorParseErrorKind;
 #[cfg(feature = "servo")]
 use servo_url::ServoUrl;
 use std::cmp::Ordering;
 use std::f32::consts::PI;
 use std::fmt::{self, Write};
@@ -951,27 +951,53 @@ impl ShapeExtent {
     }
 }
 
 impl GradientItem {
     fn parse_comma_separated<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Vec<Self>, ParseError<'i>> {
+        let mut items = Vec::new();
         let mut seen_stop = false;
-        let items = input.parse_comma_separated(|input| {
-            if seen_stop {
-                if let Ok(hint) = input.try(|i| LengthOrPercentage::parse(context, i)) {
-                    seen_stop = false;
-                    return Ok(generic::GradientItem::InterpolationHint(hint));
+
+        loop {
+            input.parse_until_before(Delimiter::Comma, |input| {
+                if seen_stop {
+                    if let Ok(hint) = input.try(|i| LengthOrPercentage::parse(context, i)) {
+                        seen_stop = false;
+                        items.push(generic::GradientItem::InterpolationHint(hint));
+                        return Ok(());
+                    }
                 }
+
+                let stop = ColorStop::parse(context, input)?;
+
+                if let Ok(multi_position) = input.try(|i| LengthOrPercentage::parse(context, i)) {
+                    let stop_color = stop.color.clone();
+                    items.push(generic::GradientItem::ColorStop(stop));
+                    items.push(generic::GradientItem::ColorStop(ColorStop {
+                        color: stop_color,
+                        position: Some(multi_position),
+                    }));
+                } else {
+                    items.push(generic::GradientItem::ColorStop(stop));
+                }
+
+                seen_stop = true;
+                Ok(())
+            })?;
+
+            match input.next() {
+                Err(_) => break,
+                Ok(&Token::Comma) => continue,
+                Ok(_) => unreachable!(),
             }
-            seen_stop = true;
-            ColorStop::parse(context, input).map(generic::GradientItem::ColorStop)
-        })?;
+        }
+
         if !seen_stop || items.len() < 2 {
             return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
         }
         Ok(items)
     }
 }
 
 impl Parse for ColorStop {
--- a/testing/mozharness/mozharness/mozilla/testing/android.py
+++ b/testing/mozharness/mozharness/mozilla/testing/android.py
@@ -238,8 +238,33 @@ class AndroidMixin(object):
             for p in glob.glob(os.path.join(xre_dir, 'host-utils-*')):
                 if os.path.isdir(p) and os.path.isfile(os.path.join(p, 'xpcshell')):
                     xre_path = p
             if not xre_path:
                 self.fatal("xre path not found in %s" % xre_dir)
         else:
             self.fatal("configure hostutils_manifest_path!")
         return xre_path
+
+    def query_package_name(self):
+        if self.app_name is None:
+            # For convenience, assume geckoview.test/geckoview_example when install
+            # target looks like geckoview.
+            if 'androidTest' in self.installer_path:
+                self.app_name = 'org.mozilla.geckoview.test'
+            elif 'geckoview' in self.installer_path:
+                self.app_name = 'org.mozilla.geckoview_example'
+        if self.app_name is None:
+            # Find appname from package-name.txt - assumes download-and-extract
+            # has completed successfully.
+            # The app/package name will typically be org.mozilla.fennec,
+            # but org.mozilla.firefox for release builds, and there may be
+            # other variations. 'aapt dump badging <apk>' could be used as an
+            # alternative to package-name.txt, but introduces a dependency
+            # on aapt, found currently in the Android SDK build-tools component.
+            apk_dir = self.abs_dirs['abs_work_dir']
+            self.apk_path = os.path.join(apk_dir, self.installer_path)
+            unzip = self.query_exe("unzip")
+            package_path = os.path.join(apk_dir, 'package-name.txt')
+            unzip_cmd = [unzip, '-q', '-o', self.apk_path]
+            self.run_command(unzip_cmd, cwd=apk_dir, halt_on_failure=True)
+            self.app_name = str(self.read_from_file(package_path, verbose=True)).rstrip()
+        return self.app_name
--- a/testing/mozharness/scripts/android_emulator_unittest.py
+++ b/testing/mozharness/scripts/android_emulator_unittest.py
@@ -168,41 +168,16 @@ class AndroidEmulatorTest(TestingMixin, 
     def _query_tests_dir(self, test_suite):
         dirs = self.query_abs_dirs()
         try:
             test_dir = self.config["suite_definitions"][test_suite]["testsdir"]
         except Exception:
             test_dir = test_suite
         return os.path.join(dirs['abs_test_install_dir'], test_dir)
 
-    def _query_package_name(self):
-        if self.app_name is None:
-            # For convenience, assume geckoview.test/geckoview_example when install
-            # target looks like geckoview.
-            if 'androidTest' in self.installer_path:
-                self.app_name = 'org.mozilla.geckoview.test'
-            elif 'geckoview' in self.installer_path:
-                self.app_name = 'org.mozilla.geckoview_example'
-        if self.app_name is None:
-            # Find appname from package-name.txt - assumes download-and-extract
-            # has completed successfully.
-            # The app/package name will typically be org.mozilla.fennec,
-            # but org.mozilla.firefox for release builds, and there may be
-            # other variations. 'aapt dump badging <apk>' could be used as an
-            # alternative to package-name.txt, but introduces a dependency
-            # on aapt, found currently in the Android SDK build-tools component.
-            apk_dir = self.abs_dirs['abs_work_dir']
-            self.apk_path = os.path.join(apk_dir, self.installer_path)
-            unzip = self.query_exe("unzip")
-            package_path = os.path.join(apk_dir, 'package-name.txt')
-            unzip_cmd = [unzip, '-q', '-o', self.apk_path]
-            self.run_command(unzip_cmd, cwd=apk_dir, halt_on_failure=True)
-            self.app_name = str(self.read_from_file(package_path, verbose=True)).rstrip()
-        return self.app_name
-
     def _launch_emulator(self):
         env = self.query_env()
 
         # Write a default ddms.cfg to avoid unwanted prompts
         avd_home_dir = self.abs_dirs['abs_avds_dir']
         DDMS_FILE = os.path.join(avd_home_dir, "ddms.cfg")
         with open(DDMS_FILE, 'w') as f:
             f.write("pingOptIn=false\npingId=0\n")
@@ -371,17 +346,17 @@ class AndroidEmulatorTest(TestingMixin, 
             opt = option.split('=')[0]
             # override configured chunk options with script args, if specified
             if opt in ('--this-chunk', '--total-chunks'):
                 if user_paths or getattr(self, opt.replace('-', '_').strip('_'), None) is not None:
                     continue
 
             if '%(app)' in option:
                 # only query package name if requested
-                cmd.extend([option % {'app': self._query_package_name()}])
+                cmd.extend([option % {'app': self.query_package_name()}])
             else:
                 option = option % str_format_values
                 if option:
                     cmd.extend([option])
 
         if not (self.verify_enabled or self.per_test_coverage):
             if user_paths:
                 cmd.extend(user_paths.split(':'))
@@ -549,28 +524,17 @@ class AndroidEmulatorTest(TestingMixin, 
         """
         super(AndroidEmulatorTest, self).download_and_extract(
             suite_categories=self._query_suite_categories())
         dirs = self.query_abs_dirs()
         if self.test_suite and self.test_suite.startswith('robocop'):
             robocop_url = self.installer_url[:self.installer_url.rfind('/')] + '/robocop.apk'
             self.info("Downloading robocop...")
             self.download_file(robocop_url, 'robocop.apk', dirs['abs_work_dir'], error_level=FATAL)
-        self.rmtree(dirs['abs_xre_dir'])
-        self.mkdir_p(dirs['abs_xre_dir'])
-        if self.config["hostutils_manifest_path"]:
-            url = self._get_repo_url(self.config["hostutils_manifest_path"])
-            self._tooltool_fetch(url, dirs['abs_xre_dir'])
-            for p in glob.glob(os.path.join(dirs['abs_xre_dir'], 'host-utils-*')):
-                if os.path.isdir(p) and os.path.isfile(os.path.join(p, 'xpcshell')):
-                    self.xre_path = p
-            if not self.xre_path:
-                self.fatal("xre path not found in %s" % dirs['abs_xre_dir'])
-        else:
-            self.fatal("configure hostutils_manifest_path!")
+        self.xre_path = self.download_hostutils(dirs['abs_xre_dir'])
 
     def install(self):
         """
         Install APKs on the device.
         """
         install_needed = (not self.test_suite) or \
             self.config["suite_definitions"][self.test_suite].get("install")
         if install_needed is False:
--- a/testing/mozharness/scripts/android_hardware_unittest.py
+++ b/testing/mozharness/scripts/android_hardware_unittest.py
@@ -2,17 +2,16 @@
 # ***** BEGIN LICENSE BLOCK *****
 # 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/.
 # ***** END LICENSE BLOCK *****
 
 import copy
 import datetime
-import glob
 import os
 import re
 import sys
 import subprocess
 
 # load modules from parent dir
 sys.path.insert(1, os.path.dirname(sys.path[0]))
 
@@ -153,41 +152,16 @@ class AndroidHardwareTest(TestingMixin, 
     def _query_tests_dir(self):
         dirs = self.query_abs_dirs()
         try:
             test_dir = self.config["suite_definitions"][self.test_suite]["testsdir"]
         except Exception:
             test_dir = self.test_suite
         return os.path.join(dirs['abs_test_install_dir'], test_dir)
 
-    def _query_package_name(self):
-        if self.app_name is None:
-            # For convenience, assume geckoview.test/geckoview_example when install
-            # target looks like geckoview.
-            if 'androidTest' in self.installer_path:
-                self.app_name = 'org.mozilla.geckoview.test'
-            elif 'geckoview' in self.installer_path:
-                self.app_name = 'org.mozilla.geckoview_example'
-        if self.app_name is None:
-            # Find appname from package-name.txt - assumes download-and-extract
-            # has completed successfully.
-            # The app/package name will typically be org.mozilla.fennec,
-            # but org.mozilla.firefox for release builds, and there may be
-            # other variations. 'aapt dump badging <apk>' could be used as an
-            # alternative to package-name.txt, but introduces a dependency
-            # on aapt, found currently in the Android SDK build-tools component.
-            apk_dir = self.abs_dirs['abs_work_dir']
-            self.apk_path = os.path.join(apk_dir, self.installer_path)
-            unzip = self.query_exe("unzip")
-            package_path = os.path.join(apk_dir, 'package-name.txt')
-            unzip_cmd = [unzip, '-q', '-o', self.apk_path]
-            self.run_command(unzip_cmd, cwd=apk_dir, halt_on_failure=True)
-            self.app_name = str(self.read_from_file(package_path, verbose=True)).rstrip()
-        return self.app_name
-
     def _build_command(self):
         c = self.config
         dirs = self.query_abs_dirs()
 
         if self.test_suite not in self.config["suite_definitions"]:
             self.fatal("Key '%s' not defined in the config!" % self.test_suite)
 
         cmd = [
@@ -237,17 +211,17 @@ class AndroidHardwareTest(TestingMixin, 
             opt = option.split('=')[0]
             # override configured chunk options with script args, if specified
             if opt in ('--this-chunk', '--total-chunks'):
                 if user_paths or getattr(self, opt.replace('-', '_').strip('_'), None) is not None:
                     continue
 
             if '%(app)' in option:
                 # only query package name if requested
-                cmd.extend([option % {'app': self._query_package_name()}])
+                cmd.extend([option % {'app': self.query_package_name()}])
             else:
                 option = option % str_format_values
                 if option:
                     cmd.extend([option])
 
         if user_paths:
             cmd.extend(user_paths.split(':'))
         elif not self.verify_enabled:
@@ -332,28 +306,17 @@ class AndroidHardwareTest(TestingMixin, 
         """
         super(AndroidHardwareTest, self).download_and_extract(
             suite_categories=self._query_suite_categories())
         dirs = self.query_abs_dirs()
         if self.test_suite and self.test_suite.startswith('robocop'):
             robocop_url = self.installer_url[:self.installer_url.rfind('/')] + '/robocop.apk'
             self.info("Downloading robocop...")
             self.download_file(robocop_url, 'robocop.apk', dirs['abs_work_dir'], error_level=FATAL)
-        self.rmtree(dirs['abs_xre_dir'])
-        self.mkdir_p(dirs['abs_xre_dir'])
-        if self.config["hostutils_manifest_path"]:
-            url = self._get_repo_url(self.config["hostutils_manifest_path"])
-            self._tooltool_fetch(url, dirs['abs_xre_dir'])
-            for p in glob.glob(os.path.join(dirs['abs_xre_dir'], 'host-utils-*')):
-                if os.path.isdir(p) and os.path.isfile(os.path.join(p, 'xpcshell')):
-                    self.xre_path = p
-            if not self.xre_path:
-                self.fatal("xre path not found in %s" % dirs['abs_xre_dir'])
-        else:
-            self.fatal("configure hostutils_manifest_path!")
+        self.xre_path = self.download_hostutils(dirs['abs_xre_dir'])
 
     def install(self):
         """
         Install APKs on the device.
         """
         install_needed = (not self.test_suite) or \
             self.config["suite_definitions"][self.test_suite].get("install")
         if install_needed is False:
--- a/testing/web-platform/meta/css/css-images/gradient/color-stops-parsing.html.ini
+++ b/testing/web-platform/meta/css/css-images/gradient/color-stops-parsing.html.ini
@@ -1,57 +1,9 @@
 [color-stops-parsing.html]
-  [linear-gradient(black 0% 50%, white) [ parsable \]]
-    expected: FAIL
-
-  [linear-gradient(black 0% 50%, white 50% 100%) [ parsable \]]
-    expected: FAIL
-
-  [linear-gradient(black 0% 50%, green 25% 75%, white 50% 100%) [ parsable \]]
-    expected: FAIL
-
-  [linear-gradient(black 0% calc(100% / 5), 25%, green 30% 60%, calc(100% * 3 / 4), white calc(100% - 20%) 100%) [ parsable \]]
-    expected: FAIL
-
-  [repeating-linear-gradient(black 0% 50%, white) [ parsable \]]
-    expected: FAIL
-
-  [repeating-linear-gradient(black 0% 50%, white 50% 100%) [ parsable \]]
-    expected: FAIL
-
-  [repeating-linear-gradient(black 0% 50%, green 25% 75%, white 50% 100%) [ parsable \]]
-    expected: FAIL
-
-  [repeating-linear-gradient(black 0% calc(100% / 5), 25%, green 30% 60%, calc(100% * 3 / 4), white calc(100% - 20%) 100%) [ parsable \]]
-    expected: FAIL
-
-  [radial-gradient(black 0% 50%, white) [ parsable \]]
-    expected: FAIL
-
-  [radial-gradient(black 0% 50%, white 50% 100%) [ parsable \]]
-    expected: FAIL
-
-  [radial-gradient(black 0% 50%, green 25% 75%, white 50% 100%) [ parsable \]]
-    expected: FAIL
-
-  [radial-gradient(black 0% calc(100% / 5), 25%, green 30% 60%, calc(100% * 3 / 4), white calc(100% - 20%) 100%) [ parsable \]]
-    expected: FAIL
-
-  [repeating-radial-gradient(black 0% 50%, white) [ parsable \]]
-    expected: FAIL
-
-  [repeating-radial-gradient(black 0% 50%, white 50% 100%) [ parsable \]]
-    expected: FAIL
-
-  [repeating-radial-gradient(black 0% 50%, green 25% 75%, white 50% 100%) [ parsable \]]
-    expected: FAIL
-
-  [repeating-radial-gradient(black 0% calc(100% / 5), 25%, green 30% 60%, calc(100% * 3 / 4), white calc(100% - 20%) 100%) [ parsable \]]
-    expected: FAIL
-
   [conic-gradient(black, white) [ parsable \]]
     expected: FAIL
 
   [conic-gradient(black 0, white) [ parsable \]]
     expected: FAIL
 
   [conic-gradient(black 0%, white) [ parsable \]]
     expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-images/multiple-position-color-stop-linear.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[multiple-position-color-stop-linear.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-images/multiple-position-color-stop-radial.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[multiple-position-color-stop-radial.html]
-  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/encrypted-media/clearkey-mp4-unique-origin.https.html.ini
@@ -0,0 +1,4 @@
+[clearkey-mp4-unique-origin.https.html]
+  [Unique origin is unable to create MediaKeys]
+    expected: FAIL
+
--- a/testing/web-platform/meta/encrypted-media/encrypted-media-default-feature-policy.https.sub.html.ini
+++ b/testing/web-platform/meta/encrypted-media/encrypted-media-default-feature-policy.https.sub.html.ini
@@ -1,8 +1,7 @@
 [encrypted-media-default-feature-policy.https.sub.html]
   expected: TIMEOUT
   [Default "encrypted-media" feature policy ["self"\] allows same-origin iframes.]
     expected: TIMEOUT
 
-  [Feature policy "encrypted-media" can be enabled in cross-origin iframes using "allow" attribute.]
+  [Default "encrypted-media" feature policy ["self"\] disallows cross-origin iframes.]
     expected: FAIL
-
--- a/testing/web-platform/meta/feature-policy/payment-default-feature-policy.https.sub.html.ini
+++ b/testing/web-platform/meta/feature-policy/payment-default-feature-policy.https.sub.html.ini
@@ -7,10 +7,11 @@
     expected:
       if not e10s: FAIL
 
   [Default "payment" feature policy ["self"\] allowpaymentrequest=true allows same-origin iframes.]
     expected:
       if not e10s: FAIL
 
   [Default "payment" feature policy ["self"\] allowpaymentrequest=true allows cross-origin iframes.]
-    expected: FAIL
+    expected:
+      if not e10s: FAIL
 
deleted file mode 100644
--- a/testing/web-platform/meta/webvr/webvr-enabled-by-feature-policy-attribute.https.sub.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[webvr-enabled-by-feature-policy-attribute.https.sub.html]
-  [Feature-Policy allow="vr" attribute allows cross-origin iframe]
-    expected: FAIL
-
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-images/multiple-position-color-stop-linear-2-ref.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+    <div style="background: linear-gradient(to bottom, red 0%, red 25%, blue 25%, blue 75%, red 75%, red 100%); width: 100px; height: 100px;"><br></div>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-images/multiple-position-color-stop-linear-2.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Linear gradient with a two position color stops</title>
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#color-stop-syntax">
+<meta name="assert" content="Color stops with two positions are equivalent to two color stops with the same color">
+<link rel=match href=/css/css-images/multiple-position-color-stop-linear-2-ref.html>
+<body>
+    <div style="background: linear-gradient(to bottom, red 0% 25%, blue 25% 75%, red 75% 100%); width: 100px; height: 100px;"><br></div>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-images/multiple-position-color-stop-radial-2-ref.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+    <div style="background: radial-gradient(center, red 0%, red 25%, blue 25%, blue 75%, red 75%, red 100%); width: 100px; height: 100px;"><br></div>
+</body>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-images/multiple-position-color-stop-radial-2.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Radial gradient with a two position color stops</title>
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#color-stop-syntax">
+<meta name="assert" content="Color stops with two positions are equivalent to two color stops with the same color">
+<link rel=match href=/css/css-images/multiple-position-color-stop-radial-2-ref.html>
+<body>
+    <div style="background: radial-gradient(center, red 0% 25%, blue 25% 75%, red 75% 100%); width: 100px; height: 100px;"><br></div>
+</body>
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/fennec.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/fennec.py
@@ -91,17 +91,17 @@ def browser_kwargs(test_type, run_info_d
             "device_serial": kwargs["device_serial"],
             "prefs_root": kwargs["prefs_root"],
             "extra_prefs": kwargs["extra_prefs"],
             "test_type": test_type,
             "debug_info": kwargs["debug_info"],
             "symbols_path": kwargs["symbols_path"],
             "stackwalk_binary": kwargs["stackwalk_binary"],
             "certutil_binary": kwargs["certutil_binary"],
-            "ca_certificate_path": kwargs["ssl_env"].ca_cert_path(),
+            "ca_certificate_path": config.ssl_config["ca_cert_path"],
             "stackfix_dir": kwargs["stackfix_dir"],
             "binary_args": kwargs["binary_args"],
             "timeout_multiplier": get_timeout_multiplier(test_type,
                                                          run_info_data,
                                                          **kwargs),
             "leak_check": kwargs["leak_check"],
             "stylo_threads": kwargs["stylo_threads"],
             "chaos_mode_flags": kwargs["chaos_mode_flags"],