Merge autoland to mozilla-central. a=merge
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Thu, 21 Jun 2018 12:46:57 +0300
changeset 423217 e834d23a292972ab4250a8be00e6740c43e41db2
parent 423204 a79434a2511b80f92554c15317e2d6271849ace9 (current diff)
parent 423216 a0c124cdf70fae2889fa93ac3c0d08f4a0b4fe68 (diff)
child 423218 1c33a38da75d550750923358e73d7af2102b9c1d
child 423224 776ba1aa0feee265e8b7ee7a570bac7114433a9a
child 423259 50ea4de900701dc7c65334a8798d1f4a3b23dcb5
push id34166
push usernbeleuzu@mozilla.com
push dateThu, 21 Jun 2018 09:47:30 +0000
treeherdermozilla-central@e834d23a2929 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone62.0a1
first release with
nightly linux32
e834d23a2929 / 62.0a1 / 20180621100048 / files
nightly linux64
e834d23a2929 / 62.0a1 / 20180621100048 / files
nightly mac
e834d23a2929 / 62.0a1 / 20180621100048 / files
nightly win32
e834d23a2929 / 62.0a1 / 20180621100048 / files
nightly win64
e834d23a2929 / 62.0a1 / 20180621100048 / 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 autoland to mozilla-central. a=merge
--- a/browser/components/payments/res/containers/basic-card-form.js
+++ b/browser/components/payments/res/containers/basic-card-form.js
@@ -113,18 +113,19 @@ export default class BasicCardForm exten
 
     this.cancelButton.textContent = this.dataset.cancelButtonLabel;
     this.backButton.textContent = this.dataset.backButtonLabel;
     this.saveButton.textContent = this.dataset.saveButtonLabel;
     this.persistCheckbox.label = this.dataset.persistCheckboxLabel;
     this.addressAddLink.textContent = this.dataset.addressAddLinkLabel;
     this.addressEditLink.textContent = this.dataset.addressEditLinkLabel;
 
-    // The back button is temporarily hidden(See Bug 1462461).
-    this.backButton.hidden = !!page.onboardingWizard;
+    // The next line needs an onboarding check since we don't set previousId
+    // when navigating to add/edit directly from the summary page.
+    this.backButton.hidden = !page.previousId && page.onboardingWizard;
     this.cancelButton.hidden = !page.onboardingWizard;
 
     let record = {};
     let basicCards = paymentRequest.getBasicCards(state);
     let addresses = paymentRequest.getAddresses(state);
 
     this.genericErrorText.textContent = page.error;
 
@@ -205,21 +206,49 @@ export default class BasicCardForm exten
         if (evt.target == this.addressEditLink && selectedOption && selectedOption.value) {
           nextState["address-page"].title = this.dataset.billingAddressTitleEdit;
           nextState["address-page"].guid = selectedOption.value;
         }
         this.requestStore.setState(nextState);
         break;
       }
       case this.backButton: {
-        this.requestStore.setState({
+        let {
+          page,
+          request,
+          "address-page": addressPage,
+          "basic-card-page": basicCardPage,
+          selectedShippingAddress,
+        } = this.requestStore.getState();
+
+        let nextState = {
           page: {
-            id: "payment-summary",
+            id: page.previousId || "payment-summary",
+            onboardingWizard: page.onboardingWizard,
           },
-        });
+        };
+
+        let addressPageState;
+        if (page.onboardingWizard) {
+          if (request.paymentOptions.requestShipping) {
+            addressPageState = Object.assign({}, addressPage, {guid: selectedShippingAddress});
+          } else {
+            addressPageState =
+              Object.assign({}, addressPage, {guid: basicCardPage.billingAddressGUID});
+          }
+
+          let basicCardPageState = Object.assign({}, basicCardPage, {preserveFieldValues: true});
+
+          Object.assign(nextState, {
+            "address-page": addressPageState,
+            "basic-card-page": basicCardPageState,
+          });
+        }
+
+        this.requestStore.setState(nextState);
         break;
       }
       case this.saveButton: {
         this.saveRecord();
         break;
       }
       default: {
         throw new Error("Unexpected click target");
--- a/browser/components/payments/test/browser/browser_payments_onboarding_wizard.js
+++ b/browser/components/payments/test/browser/browser_payments_onboarding_wizard.js
@@ -401,8 +401,103 @@ add_task(async function test_on_boarding
 
     info("Closing the payment dialog");
     spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
 
     cleanupFormAutofillStorage();
   });
 });
+
+add_task(async function test_back_button_on_basic_card_page_during_onboarding() {
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: BLANK_PAGE_URL,
+  }, async browser => {
+    cleanupFormAutofillStorage();
+
+    info("Opening the payment dialog");
+    let {win, frame} =
+        await setupPaymentDialog(browser, {
+          methodData: [PTU.MethodData.basicCard],
+          details: PTU.Details.total60USD,
+          merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
+        });
+
+    await spawnPaymentDialogTask(frame, async function() {
+      let {
+        PaymentTestUtils: PTU,
+      } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+
+      await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return state.page.id == "address-page";
+      }, "Address page is shown first if there are saved addresses during on boarding");
+
+      info("Checking if the address page has been rendered");
+      let addressSaveButton = content.document.querySelector("address-form .save-button");
+      ok(content.isVisible(addressSaveButton), "Address save button is rendered");
+
+      for (let [key, val] of Object.entries(PTU.Addresses.TimBL2)) {
+        let field = content.document.getElementById(key);
+        if (!field) {
+          ok(false, `${key} field not found`);
+        }
+        field.value = val;
+        ok(!field.disabled, `Field #${key} shouldn't be disabled`);
+      }
+      content.document.querySelector("address-form .save-button").click();
+
+      await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return state.page.id == "basic-card-page";
+      }, "Basic card page is shown next");
+
+      info("Checking if basic card page is rendered");
+      let basicCardBackButton = content.document.querySelector("basic-card-form .back-button");
+      ok(content.isVisible(basicCardBackButton), "Back button is visible on the basic card page");
+
+      info("Partially fill basic card form");
+      let field = content.document.getElementById("cc-number");
+      field.value = PTU.BasicCards.JohnDoe["cc-number"];
+
+      info("Clicking on the back button to edit address saved in the previous step");
+      basicCardBackButton.click();
+
+      await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return state.page.id == "address-page" &&
+               state["address-page"].guid == state["basic-card-page"].billingAddressGUID;
+      }, "Address page is shown again");
+
+      info("Checking if the address page has been rendered");
+      addressSaveButton = content.document.querySelector("address-form .save-button");
+      ok(content.isVisible(addressSaveButton), "Address save button is rendered");
+
+      info("Checking if the address saved in the last step is correctly loaded in the form");
+      field = content.document.getElementById("given-name");
+      ok(field.value, PTU.Addresses.TimBL2["given-name"],
+         "Given name field value is correctly loaded");
+
+      info("Editing the address and saving again");
+      field.value = "John";
+      addressSaveButton.click();
+
+      info("Checking if the address was correctly edited");
+      await PTU.DialogContentUtils.waitForState(content, (state) => {
+        return state.page.id == "basic-card-page" &&
+               // eslint-disable-next-line max-len
+               state.savedAddresses[state["basic-card-page"].billingAddressGUID]["given-name"] == "John";
+      }, "Address was correctly edited and saved");
+
+      // eslint-disable-next-line max-len
+      info("Checking if the basic card form is now rendered and if the field values from before are preserved");
+      let basicCardCancelButton = content.document.querySelector("basic-card-form .cancel-button");
+      ok(content.isVisible(basicCardCancelButton),
+         "Cancel button is visible on the basic card page");
+      field = content.document.getElementById("cc-number");
+      ok(field.value, PTU.BasicCards.JohnDoe["cc-number"], "Values in the form are preserved");
+    });
+
+    info("Closing the payment dialog");
+    spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
+    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
+
+    cleanupFormAutofillStorage();
+  });
+});
--- a/devtools/client/inspector/animation/components/AnimationItem.js
+++ b/devtools/client/inspector/animation/components/AnimationItem.js
@@ -29,35 +29,37 @@ class AnimationItem extends Component {
       timeScale: PropTypes.object.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {
-      isSelected: false,
+      isSelected: this.isSelected(props),
     };
   }
 
   componentWillReceiveProps(nextProps) {
-    const { animation } = this.props;
-
     this.setState({
-      isSelected: nextProps.selectedAnimation &&
-                  animation.actorID === nextProps.selectedAnimation.actorID
+      isSelected: this.isSelected(nextProps),
     });
   }
 
   shouldComponentUpdate(nextProps, nextState) {
     return this.state.isSelected !== nextState.isSelected ||
            this.props.animation !== nextProps.animation ||
            this.props.timeScale !== nextProps.timeScale;
   }
 
+  isSelected(props) {
+    return props.selectedAnimation &&
+           props.animation.actorID === props.selectedAnimation.actorID;
+  }
+
   render() {
     const {
       animation,
       emitEventForTest,
       getAnimatedPropertyMap,
       getNodeFromActor,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
--- a/devtools/client/inspector/animation/reducers/animations.js
+++ b/devtools/client/inspector/animation/reducers/animations.js
@@ -43,18 +43,22 @@ const reducers = {
       animations,
       detailVisibility,
       selectedAnimation,
       timeScale: new TimeScale(animations),
     });
   },
 
   [UPDATE_DETAIL_VISIBILITY](state, { detailVisibility }) {
+    const selectedAnimation =
+      detailVisibility ? state.selectedAnimation : null;
+
     return Object.assign({}, state, {
-      detailVisibility
+      detailVisibility,
+      selectedAnimation,
     });
   },
 
   [UPDATE_ELEMENT_PICKER_ENABLED](state, { elementPickerEnabled }) {
     return Object.assign({}, state, {
       elementPickerEnabled
     });
   },
--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -21,16 +21,18 @@ support-files =
 
 [browser_animation_animated-property-list.js]
 [browser_animation_animated-property-list_unchanged-items.js]
 [browser_animation_animated-property-name.js]
 [browser_animation_animation-detail_close-button.js]
 [browser_animation_animation-detail_title.js]
 [browser_animation_animation-detail_visibility.js]
 [browser_animation_animation-list.js]
+[browser_animation_animation-list_one-animation-select.js]
+[browser_animation_animation-list_select.js]
 [browser_animation_animation-target.js]
 [browser_animation_animation-target_highlight.js]
 [browser_animation_animation-target_select.js]
 [browser_animation_animation-timeline-tick.js]
 [browser_animation_css-transition-with-playstate-idle.js]
 [browser_animation_current-time-label.js]
 [browser_animation_current-time-scrubber.js]
 [browser_animation_current-time-scrubber_each-different-creation-time-animations.js]
--- a/devtools/client/inspector/animation/test/browser_animation_animated-property-list.js
+++ b/devtools/client/inspector/animation/test/browser_animation_animated-property-list.js
@@ -33,22 +33,10 @@ add_task(async function() {
                                            panel, `.${ targetClass }`);
     ok(panel.querySelector(".animated-property-list"),
       `The animated-property-list should be in the DOM at ${ targetClass }`);
     const itemEls =
       panel.querySelectorAll(".animated-property-list .animated-property-item");
     is(itemEls.length, expectedNumber,
        `The number of animated-property-list should be ${ expectedNumber } ` +
        `at ${ targetClass }`);
-
-    if (itemEls.length < 2) {
-      continue;
-    }
-
-    info("Checking the background color for " +
-         `the animated property item at ${ targetClass }`);
-    const evenColor = panel.ownerGlobal.getComputedStyle(itemEls[0]).backgroundColor;
-    const oddColor = panel.ownerGlobal.getComputedStyle(itemEls[1]).backgroundColor;
-    isnot(evenColor, oddColor,
-          "Background color of an even animated property item " +
-          "should be different from odd");
   }
 });
--- a/devtools/client/inspector/animation/test/browser_animation_animation-list.js
+++ b/devtools/client/inspector/animation/test/browser_animation_animation-list.js
@@ -12,22 +12,13 @@ add_task(async function() {
 
   info("Checking animation list and items existence");
   ok(panel.querySelector(".animation-list"),
     "The animation-list is in the DOM");
   is(panel.querySelectorAll(".animation-list .animation-item").length,
      animationInspector.state.animations.length,
      "The number of animations displayed matches the number of animations");
 
-  info("Checking the background color for the animation list items");
-  const animationItemEls = panel.querySelectorAll(".animation-list .animation-item");
-  const evenColor =
-    panel.ownerGlobal.getComputedStyle(animationItemEls[0]).backgroundColor;
-  const oddColor =
-    panel.ownerGlobal.getComputedStyle(animationItemEls[1]).backgroundColor;
-  isnot(evenColor, oddColor,
-    "Background color of an even animation should be different from odd");
-
   info("Checking list and items existence after select a element which has an animation");
   await selectNodeAndWaitForAnimations(".animated", inspector);
   is(panel.querySelectorAll(".animation-list .animation-item").length, 1,
     "The number of animations displayed should be 1 for .animated element");
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_animation-list_one-animation-select.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test whether the animation item has been selected from first time
+// if count of the animations is one.
+
+add_task(async function() {
+  await addTab(URL_ROOT + "doc_simple_animation.html");
+  await removeAnimatedElementsExcept([".animated"]);
+  const { panel } = await openAnimationInspector();
+
+  info("Checking whether an item element has been selected");
+  is(panel.querySelector(".animation-item").classList.contains("selected"), true,
+     "The animation item should have 'selected' class");
+
+  info("Checking whether the element will be unselected after closing the detail pane");
+  clickOnDetailCloseButton(panel);
+  is(panel.querySelector(".animation-item").classList.contains("selected"), false,
+     "The animation item should not have 'selected' class");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_animation-list_select.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test whether the animation items in the list were selectable.
+
+add_task(async function() {
+  await addTab(URL_ROOT + "doc_simple_animation.html");
+  await removeAnimatedElementsExcept([".animated", ".long"]);
+  const { animationInspector, panel } = await openAnimationInspector();
+
+  info("Checking whether 1st element will be selected");
+  await clickOnAnimation(animationInspector, panel, 0);
+  assertSelection(panel, [true, false]);
+
+  info("Checking whether 2nd element will be selected");
+  await clickOnAnimation(animationInspector, panel, 1);
+  assertSelection(panel, [false, true]);
+
+  info("Checking whether all elements will be unselected after closing the detail pane");
+  clickOnDetailCloseButton(panel);
+  assertSelection(panel, [false, false]);
+});
+
+function assertSelection(panel, expectedResult) {
+  panel.querySelectorAll(".animation-item").forEach((item, index) => {
+    const shouldSelected = expectedResult[index];
+    is(item.classList.contains("selected"), shouldSelected,
+       `Animation item[${ index }] should ` +
+       `${ shouldSelected ? "" : "not" } have 'selected' class`);
+  });
+}
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -1,40 +1,42 @@
 /* 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/. */
 
 /* Animation-inspector specific theme variables */
 
 :root {
-  --animation-even-background-color: rgba(0, 0, 0, 0.05);
+  --animation-item-hover-color: var(--grey-30-a40);
+  --animation-item-selected-color: var(--grey-30-a90);
   --command-pick-image: url(chrome://devtools/skin/images/command-pick.svg);
+  --cssanimation-color: var(--purple-50);
+  --csstransition-color: var(--blue-55);
   --devtools-toolbar-height: 24px;
   --fast-track-image: url("images/animation-fast-track.svg");
-  --fill-color-cssanimation: var(--theme-contrast-background);
-  --fill-color-csstransition: var(--theme-highlight-blue);
-  --fill-color-scriptanimation: var(--theme-graphs-green);
   --graph-height: 30px;
   --graph-right-offset: 10px;
   --keyframe-marker-shadow-color: #c4c4c4;
   --pause-image: url(chrome://devtools/skin/images/pause.svg);
-  --progress-bar-color: #909090;
+  --progress-bar-color: var(--grey-40);
   --resume-image: url(chrome://devtools/skin/images/play.svg);
   --rewind-image: url(chrome://devtools/skin/images/rewind.svg);
-  --scrubber-color: #dd00a9;
+  --scriptanimation-color: var(--green-60);
+  --scrubber-color: var(--magenta-65);
   --sidebar-width: 200px;
-  --stroke-color-cssanimation: var(--theme-highlight-lightorange);
-  --stroke-color-csstransition: var(--theme-highlight-bluegrey);
-  --stroke-color-scriptanimation: var(--theme-highlight-green);
-  --tick-line-style: 0.5px solid rgba(128, 136, 144, 0.5);
+  --tick-line-style: 0.5px solid var(--theme-splitter-color);
 }
 
 :root.theme-dark {
-  --animation-even-background-color: rgba(255, 255, 255, 0.05);
+  --animation-item-hover-color: var(--grey-60-a50);
+  --animation-item-selected-color: var(--grey-60);
+  --csstransition-color: var(--blue-50);
   --keyframe-marker-shadow-color: #818181;
+  --progress-bar-color: var(--grey-50);
+  --scrubber-color: var(--magenta-50);
 }
 
 /* Root element of animation inspector */
 #animation-container {
   cursor: default;
   display: flex;
   flex-direction: column;
   height: 100%;
@@ -127,37 +129,37 @@ select.playback-rate-selector.devtools-b
 }
 
 .indication-bar.current-time-scrubber::after {
   border-left-color: var(--scrubber-color);
   left: 5px;
 }
 
 /* Animation Item */
-.animation-item:nth-child(2n+1) {
-  background-color: var(--animation-even-background-color);
-}
-
 .animation-item.cssanimation {
-  --computed-timing-graph-color: var(--fill-color-cssanimation);
-  --effect-timing-graph-color: var(--stroke-color-cssanimation);
+  --graph-color: var(--cssanimation-color);
+  --graph-opacity: 0.7;
 }
 
 .animation-item.csstransition {
-  --computed-timing-graph-color: var(--fill-color-csstransition);
-  --effect-timing-graph-color: var(--stroke-color-csstransition);
+  --graph-color: var(--csstransition-color);
+  --graph-opacity: 0.8;
 }
 
 .animation-item.scriptanimation {
-  --computed-timing-graph-color: var(--fill-color-scriptanimation);
-  --effect-timing-graph-color: var(--stroke-color-scriptanimation);
+  --graph-color: var(--scriptanimation-color);
+  --graph-opacity: 0.5;
+}
+
+.animation-item:hover {
+  background-color: var(--animation-item-hover-color);
 }
 
 .animation-item.selected {
-  background-color: var(--theme-selection-background-hover);
+  background-color: var(--animation-item-selected-color);
 }
 
 /* Animation Target */
 .animation-target {
   align-items: center;
   display: flex;
   grid-column: 1 / 2;
   height: var(--graph-height);
@@ -186,17 +188,18 @@ select.playback-rate-selector.devtools-b
   background-color: var(--theme-highlight-blue);
 }
 
 /* Summary Graph */
 .animation-summary-graph {
   cursor: pointer;
   grid-column: 2 / 3;
   height: var(--graph-height);
-  padding-top: 5px;
+  padding-bottom: 3px;
+  padding-top: 3px;
   position: relative;
 }
 
 .animation-summary-graph.compositor::after {
   background-image: var(--fast-track-image);
   background-repeat: no-repeat;
   content: "";
   display: block;
@@ -210,28 +213,29 @@ select.playback-rate-selector.devtools-b
 }
 
 .animation-summary-graph-path {
   height: 100%;
   width: 100%;
 }
 
 .animation-computed-timing-path path {
-  fill: var(--computed-timing-graph-color);
+  fill: var(--graph-color);
+  fill-opacity: var(--graph-opacity);
   vector-effect: non-scaling-stroke;
   transform: scale(1, -1);
 }
 
 .animation-computed-timing-path path.infinity:nth-child(n+2) {
   opacity: 0.3;
 }
 
 .animation-effect-timing-path path {
   fill: none;
-  stroke: var(--effect-timing-graph-color);
+  stroke: var(--graph-color);
   stroke-dasharray: 2px 2px;
   transform: scale(1, -1);
   vector-effect: non-scaling-stroke;
 }
 
 .animation-effect-timing-path path.infinity:nth-child(n+2) {
   opacity: 0.3;
 }
@@ -245,33 +249,33 @@ select.playback-rate-selector.devtools-b
   vector-effect: non-scaling-stroke;
 }
 
 .animation-delay-sign,
 .animation-end-delay-sign {
   background-color: var(--theme-graphs-grey);
   height: 3px;
   position: absolute;
-  top: calc(100% - 1.5px);
+  bottom: 2px;
 }
 
 .animation-delay-sign::before,
 .animation-end-delay-sign::before {
   background-color: inherit;
   border-radius: 50%;
   content: "";
   height: 6px;
   position: absolute;
   top: -1.5px;
   width: 6px;
 }
 
 .animation-delay-sign.fill,
 .animation-end-delay-sign.fill {
-  background-color: var(--effect-timing-graph-color);
+  background-color: var(--graph-color);
 }
 
 .animation-delay-sign.negative::before {
   left: unset;
   right: -3px;
 }
 
 .animation-end-delay-sign::before {
@@ -300,17 +304,17 @@ select.playback-rate-selector.devtools-b
   stroke-linejoin: round;
   stroke-opacity: .5;
   stroke-width: 4;
   text-anchor: end;
 }
 
 /* Animation Detail */
 .animation-detail-container {
-  background-color: var(--theme-body-background);
+  background-color: var(--theme-sidebar-background);
   display: flex;
   flex-direction: column;
   height: 100%;
   overflow: hidden;
   width: 100%;
   z-index: 2;
 }
 
@@ -360,20 +364,16 @@ select.playback-rate-selector.devtools-b
   border-top-color: var(--progress-bar-color);
 }
 
 .indication-bar.keyframes-progress-bar::after {
   border-left-color: var(--progress-bar-color);
 }
 
 /* Animated Property Item */
-.animated-property-item:nth-child(2n+1) {
-  background-color: var(--animation-even-background-color);
-}
-
 .animated-property-item.unchanged {
   opacity: 0.6;
 }
 
 /* Animated Property Name */
 .animated-property-name {
   align-items: center;
   display: flex;
@@ -383,25 +383,25 @@ select.playback-rate-selector.devtools-b
 }
 
 .animated-property-name.compositor span {
   padding-left: 15px;
   position: relative;
 }
 
 .animated-property-list-container.cssanimation .animated-property-name.compositor {
-  --fast-track-color: var(--stroke-color-cssanimation);
+  --fast-track-color: var(--cssanimation-color);
 }
 
 .animated-property-list-container.csstransition .animated-property-name.compositor {
-  --fast-track-color: var(--stroke-color-csstransition);
+  --fast-track-color: var(--csstransition-color);
 }
 
 .animated-property-list-container.scriptanimation .animated-property-name.compositor {
-  --fast-track-color: var(--stroke-color-scriptanimation);
+  --fast-track-color: var(--scriptanimation-color);
 }
 
 .animated-property-name.compositor span::before {
   background-image: var(--fast-track-image);
   background-repeat: no-repeat;
   background-size: contain;
   content: "";
   fill: var(--fast-track-color);
@@ -425,30 +425,31 @@ select.playback-rate-selector.devtools-b
 }
 
 .keyframes-graph-path {
   height: 100%;
   width: 100%;
 }
 
 .keyframes-graph-path path {
-  fill: #00b0bd88;
-  stroke: #00b0bd;
+  fill: var(--teal-60);
+  fill-opacity: 0.5;
+  stroke: var(--teal-70);
   vector-effect: non-scaling-stroke;
   transform: scale(1, -1);
 }
 
 .keyframes-graph.opacity .keyframes-graph-path path {
-  fill: #df00a988;
-  stroke: #df00a9;
+  fill: var(--magenta-50);
+  stroke: var(--magenta-70);
 }
 
 .keyframes-graph.transform .keyframes-graph-path path {
-  fill: #ea800088;
-  stroke: #ea8000;
+  fill: var(--yellow-50);
+  stroke: var(--yellow-60);
 }
 
 .keyframes-graph-path .color-path path {
   stroke: none;
 }
 
 .keyframes-graph .keyframes-graph-path .hint path {
   fill: none;
@@ -491,25 +492,25 @@ select.playback-rate-selector.devtools-b
   position: absolute;
   top: 50%;
   height: 10px;
   transform: translate(-5px, -3px);
   width: 10px;
 }
 
 .animated-property-list-container.cssanimation .keyframe-marker-item {
-  background-color: var(--fill-color-cssanimation);
+  background-color: var(--cssanimation-color);
 }
 
 .animated-property-list-container.csstransition .keyframe-marker-item {
-  background-color: var(--fill-color-csstransition);
+  background-color: var(--csstransition-color);
 }
 
 .animated-property-list-container.scriptanimation .keyframe-marker-item {
-  background-color: var(--fill-color-scriptanimation);
+  background-color: var(--scriptanimation-color);
 }
 
 /* Common Components */
 /* Progress Inspection Panel */
 .progress-inspection-panel {
   height: 100%;
   overflow-y: auto;
   overflow-x: hidden;
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -208,38 +208,49 @@
   --toolbarbutton-checked-border-color: var(--toolbarbutton-border-color);
   --toolbarbutton-checked-focus-background: var(--blue-60);
 
   /* The photon animation curve */
   --animation-curve: cubic-bezier(.07,.95,0,1);
 
   /* Firefox Colors CSS Variables v1.0.3
    * Colors are taken from: https://github.com/FirefoxUX/design-tokens */
+  --magenta-50: #ff1ad9;
   --magenta-65: #dd00a9;
+  --magenta-70: #b5007f;
 
+  --purple-50: #9400ff;
   --purple-60: #8000d7;
 
   --blue-40: #45a1ff;
   --blue-50: #0a84ff;
   --blue-55: #0074e8;
   --blue-60: #0060df;
   --blue-70: #003eaa;
 
+  --teal-60: #00c8d7;
+  --teal-70: #008ea4;
+
   --red-70: #a4000f;
 
   --green-50: #30e60b;
   --green-60: #12bc00;
   --green-70: #058b00;
 
+  --yellow-50: #ffe900;
+  --yellow-60: #d7b600;
   --yellow-80: #715100;
 
   --grey-10: #f9f9fa;
   --grey-20: #ededf0;
   --grey-30: #d7d7db;
+  --grey-30-a40: rgba(215, 215, 219, 0.4);
+  --grey-30-a90: rgba(215, 215, 219, 0.9);
   --grey-40: #b1b1b3;
   --grey-50: #737373;
   --grey-60: #4a4a4f;
+  --grey-60-a50: rgba(74, 74, 79, 0.5);
   --grey-70: #38383d;
   --grey-80: #2a2a2e;
   --grey-90: #0c0c0d;
   --grey-90-a10: rgba(12, 12, 13, 0.1);
   --grey-90-a80: rgba(12, 12, 13, 0.8);
 }
--- a/dom/payments/PaymentRequest.cpp
+++ b/dom/payments/PaymentRequest.cpp
@@ -644,33 +644,31 @@ already_AddRefed<Promise>
 PaymentRequest::CanMakePayment(ErrorResult& aRv)
 {
   if (mState != eCreated) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   if (mResultPromise) {
+    // XXX This doesn't match the spec but does match Chromium.
     aRv.Throw(NS_ERROR_DOM_NOT_ALLOWED_ERR);
     return nullptr;
   }
 
-  nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+  nsIGlobalObject* global = GetOwnerGlobal();
   ErrorResult result;
   RefPtr<Promise> promise = Promise::Create(global, result);
   if (result.Failed()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
-  if (NS_WARN_IF(!manager)) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
-  }
+  MOZ_ASSERT(manager);
   nsresult rv = manager->CanMakePayment(this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     promise->MaybeReject(NS_ERROR_FAILURE);
     return promise.forget();
   }
   mResultPromise = promise;
   return promise.forget();
 }
@@ -682,48 +680,51 @@ PaymentRequest::RespondCanMakePayment(bo
   mResultPromise->MaybeResolve(aResult);
   mResultPromise = nullptr;
 }
 
 already_AddRefed<Promise>
 PaymentRequest::Show(const Optional<OwningNonNull<Promise>>& aDetailsPromise,
                      ErrorResult& aRv)
 {
-  if (mState != eCreated) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return nullptr;
-  }
-
   if (!EventStateManager::IsHandlingUserInput()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
-  nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+  nsIGlobalObject* global = GetOwnerGlobal();
+  nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
+  MOZ_ASSERT(win);
+  nsIDocument* doc = win->GetExtantDoc();
+  if (!doc || !doc->IsCurrentActiveDocument()) {
+    aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+    return nullptr;
+  }
+
+  if (mState != eCreated) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
+
   ErrorResult result;
   RefPtr<Promise> promise = Promise::Create(global, result);
   if (result.Failed()) {
     mState = eClosed;
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
-  if (NS_WARN_IF(!manager)) {
-    mState = eClosed;
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
-  }
-
   if (aDetailsPromise.WasPassed()) {
     aDetailsPromise.Value().AppendNativeHandler(this);
     mUpdating = true;
     mDeferredShow = true;
   }
 
+  RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
+  MOZ_ASSERT(manager);
   nsresult rv = manager->ShowPayment(this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     if (rv == NS_ERROR_ABORT) {
       promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     } else {
       promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
     }
     mState = eClosed;
@@ -789,35 +790,31 @@ already_AddRefed<Promise>
 PaymentRequest::Abort(ErrorResult& aRv)
 {
   if (mState != eInteractive) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   if (mAbortPromise) {
-    aRv.Throw(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
-  nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+  nsIGlobalObject* global = GetOwnerGlobal();
   ErrorResult result;
   RefPtr<Promise> promise = Promise::Create(global, result);
   if (result.Failed()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
-  if (NS_WARN_IF(!manager)) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
-  }
-
-  // It's possible for to call this between show and its promise resolving.
+  MOZ_ASSERT(manager);
+  // It's possible to be called between show and its promise resolving.
   nsresult rv = manager->AbortPayment(this, mDeferredShow);
   mDeferredShow = false;
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   mAbortPromise = promise;
@@ -1055,17 +1052,19 @@ PaymentRequest::RejectedCallback(JSConte
   mUpdating = false;
   AbortUpdate(NS_ERROR_DOM_ABORT_ERR, mDeferredShow);
   mDeferredShow = false;
 }
 
 PaymentRequest::~PaymentRequest()
 {
   if (mIPC) {
-    mIPC->MaybeDelete();
+    // If we're being destroyed, the PaymentRequestManager isn't holding any
+    // references to us and we can't be waiting for any replies.
+    mIPC->MaybeDelete(false);
   }
 }
 
 JSObject*
 PaymentRequest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PaymentRequestBinding::Wrap(aCx, this, aGivenProto);
 }
--- a/dom/payments/PaymentRequestManager.cpp
+++ b/dom/payments/PaymentRequestManager.cpp
@@ -304,16 +304,27 @@ PaymentRequestManager::NotifyRequestDone
   MOZ_ASSERT(entry.Data() > 0);
 
   uint32_t count = --entry.Data();
   if (count == 0) {
     entry.Remove();
   }
 }
 
+void
+PaymentRequestManager::RequestIPCOver(PaymentRequest* aRequest)
+{
+  // This must only be called from ActorDestroy or if we're sure we won't
+  // receive any more IPC for aRequest.
+  mActivePayments.Remove(aRequest);
+  if (aRequest == mShowingRequest) {
+    mShowingRequest = nullptr;
+  }
+}
+
 already_AddRefed<PaymentRequestManager>
 PaymentRequestManager::GetSingleton()
 {
   if (!gPaymentManager) {
     gPaymentManager = new PaymentRequestManager();
     ClearOnShutdown(&gPaymentManager);
   }
   RefPtr<PaymentRequestManager> manager = gPaymentManager;
--- a/dom/payments/PaymentRequestManager.h
+++ b/dom/payments/PaymentRequestManager.h
@@ -59,16 +59,20 @@ public:
 
   nsresult RespondPayment(PaymentRequest* aRequest,
                           const IPCPaymentActionResponse& aResponse);
   nsresult ChangeShippingAddress(PaymentRequest* aRequest,
                                  const IPCPaymentAddress& aAddress);
   nsresult ChangeShippingOption(PaymentRequest* aRequest,
                                 const nsAString& aOption);
 
+  // Called to ensure that we don't "leak" aRequest if we shut down while it had
+  // an active request to the parent.
+  void RequestIPCOver(PaymentRequest* aRequest);
+
 private:
   PaymentRequestManager() = default;
   ~PaymentRequestManager()
   {
     MOZ_ASSERT(mActivePayments.Count() == 0);
   }
 
   PaymentRequestChild* GetPaymentChild(PaymentRequest* aRequest);
--- a/dom/payments/ipc/PaymentRequestChild.cpp
+++ b/dom/payments/ipc/PaymentRequestChild.cpp
@@ -81,43 +81,40 @@ PaymentRequestChild::RecvChangeShippingO
   }
   return IPC_OK();
 }
 
 void
 PaymentRequestChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   if (mRequest) {
-    DetachFromRequest();
+    DetachFromRequest(true);
   }
 }
 
 void
-PaymentRequestChild::MaybeDelete()
+PaymentRequestChild::MaybeDelete(bool aCanBeInManager)
 {
   if (mRequest) {
-    DetachFromRequest();
+    DetachFromRequest(aCanBeInManager);
     Send__delete__(this);
   }
 }
 
-bool
-PaymentRequestChild::SendRequestPayment(const IPCPaymentActionRequest& aAction)
-{
-  return PPaymentRequestChild::SendRequestPayment(aAction);
-}
-
 void
-PaymentRequestChild::DetachFromRequest()
+PaymentRequestChild::DetachFromRequest(bool aCanBeInManager)
 {
   MOZ_ASSERT(mRequest);
-  nsAutoString id;
-  mRequest->GetInternalId(id);
 
-  RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
-  MOZ_ASSERT(manager);
+  if (aCanBeInManager) {
+    RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
+    MOZ_ASSERT(manager);
+
+    RefPtr<PaymentRequest> request(mRequest);
+    manager->RequestIPCOver(request);
+  }
 
   mRequest->SetIPC(nullptr);
   mRequest = nullptr;
 }
 
 } // end of namespace dom
 } // end of namespace mozilla
--- a/dom/payments/ipc/PaymentRequestChild.h
+++ b/dom/payments/ipc/PaymentRequestChild.h
@@ -14,17 +14,17 @@ namespace dom {
 
 class PaymentRequest;
 
 class PaymentRequestChild final : public PPaymentRequestChild
 {
 public:
   explicit PaymentRequestChild(PaymentRequest* aRequest);
 
-  void MaybeDelete();
+  void MaybeDelete(bool aCanBeInManager);
 
   nsresult RequestPayment(const IPCPaymentActionRequest& aAction);
 
 protected:
   mozilla::ipc::IPCResult
   RecvRespondPayment(const IPCPaymentActionResponse& aResponse) override;
 
   mozilla::ipc::IPCResult
@@ -35,17 +35,17 @@ protected:
   RecvChangeShippingOption(const nsString& aRequestId,
                            const nsString& aOption) override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
 private:
   ~PaymentRequestChild() = default;
 
-  bool SendRequestPayment(const IPCPaymentActionRequest& aAction);
-  void DetachFromRequest();
+  void DetachFromRequest(bool aCanBeInManager);
+
   PaymentRequest* MOZ_NON_OWNING_REF mRequest;
 };
 
 } // end of namespace dom
 } // end of namespace mozilla
 
 #endif
--- a/dom/vr/VRDisplay.cpp
+++ b/dom/vr/VRDisplay.cpp
@@ -258,27 +258,25 @@ VRDisplayCapabilities::WrapObject(JSCont
 {
   return VRDisplayCapabilitiesBinding::Wrap(aCx, this, aGivenProto);
 }
 
 VRPose::VRPose(nsISupports* aParent, const gfx::VRHMDSensorState& aState)
   : Pose(aParent)
   , mVRState(aState)
 {
-  mFrameId = aState.inputFrameID;
   mozilla::HoldJSObjects(this);
 }
 
 VRPose::VRPose(nsISupports* aParent)
   : Pose(aParent)
 {
   mVRState.inputFrameID = 0;
   mVRState.timestamp = 0.0;
   mVRState.flags = gfx::VRDisplayCapabilityFlags::Cap_None;
-  mFrameId = 0;
   mozilla::HoldJSObjects(this);
 }
 
 VRPose::~VRPose()
 {
   mozilla::DropJSObjects(this);
 }
 
--- a/dom/vr/VRDisplay.h
+++ b/dom/vr/VRDisplay.h
@@ -98,18 +98,16 @@ protected:
 
 class VRPose final : public Pose
 {
 
 public:
   VRPose(nsISupports* aParent, const gfx::VRHMDSensorState& aState);
   explicit VRPose(nsISupports* aParent);
 
-  uint64_t FrameID() const { return mFrameId; }
-
   virtual void GetPosition(JSContext* aCx,
                            JS::MutableHandle<JSObject*> aRetval,
                            ErrorResult& aRv) override;
   virtual void GetLinearVelocity(JSContext* aCx,
                                  JS::MutableHandle<JSObject*> aRetval,
                                  ErrorResult& aRv) override;
   virtual void GetLinearAcceleration(JSContext* aCx,
                                      JS::MutableHandle<JSObject*> aRetval,
@@ -124,17 +122,16 @@ public:
                                       JS::MutableHandle<JSObject*> aRetval,
                                       ErrorResult& aRv) override;
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
 protected:
   ~VRPose();
 
-  uint64_t mFrameId;
   gfx::VRHMDSensorState mVRState;
 };
 
 struct VRFrameInfo
 {
   VRFrameInfo();
 
   void Update(const gfx::VRDisplayInfo& aInfo,
--- a/taskcluster/mach_commands.py
+++ b/taskcluster/mach_commands.py
@@ -267,53 +267,45 @@ class MachCommands(MachCommandBase):
                      help='Action input (.yml or .json)')
     @CommandArgument('--task', default=None,
                      help='Task definition (.yml or .json; if omitted, the task will be'
                           'fetched from the queue)')
     @CommandArgument('callback', default=None,
                      help='Action callback name (Python function name)')
     def test_action_callback(self, **options):
         import taskgraph.parameters
-        from taskgraph.util.taskcluster import get_task_definition
         import taskgraph.actions
         import yaml
 
         def load_data(filename):
             with open(filename) as f:
                 if filename.endswith('.yml'):
                     return yaml.safe_load(f)
                 elif filename.endswith('.json'):
                     return json.load(f)
                 else:
                     raise Exception("unknown filename {}".format(filename))
 
         try:
             self.setup_logging()
             task_id = options['task_id']
-            if options['task']:
-                task = load_data(options['task'])
-            elif task_id:
-                task = get_task_definition(task_id)
-            else:
-                task = None
 
             if options['input']:
                 input = load_data(options['input'])
             else:
                 input = None
 
             parameters = taskgraph.parameters.load_parameters_file(options['parameters'])
             parameters.check()
 
             root = options['root']
 
             return taskgraph.actions.trigger_action_callback(
                     task_group_id=options['task_group_id'],
                     task_id=task_id,
-                    task=task,
                     input=input,
                     callback=options['callback'],
                     parameters=parameters,
                     root=root,
                     test=True)
         except Exception:
             traceback.print_exc()
             sys.exit(1)
--- a/taskcluster/taskgraph/actions/backfill.py
+++ b/taskcluster/taskgraph/actions/backfill.py
@@ -39,27 +39,45 @@ logger = logging.getLogger(__name__)
             'depth': {
                 'type': 'integer',
                 'default': 5,
                 'minimum': 1,
                 'maximum': 10,
                 'title': 'Depth',
                 'description': ('The number of previous pushes before the current '
                                 'push to attempt to trigger this task on.')
+            },
+            'inclusive': {
+                'type': 'boolean',
+                'default': False,
+                'title': 'Inclusive Range',
+                'description': ('If true, the backfill will also retrigger the task '
+                                'on the selected push.')
+            },
+            'addGeckoProfile': {
+                'type': 'boolean',
+                'default': False,
+                'title': 'Add Gecko Profile',
+                'description': 'If true, appends --geckoProfile to mozharness options.'
+            },
+            'testPath': {
+                'type': 'string',
+                'title': 'Test Path',
             }
         },
         'additionalProperties': False
     },
     available=lambda parameters: parameters.get('project', None) != 'try'
 )
 def backfill_action(parameters, graph_config, input, task_group_id, task_id, task):
     label = task['metadata']['name']
     pushes = []
-    depth = input.get('depth', 5)
-    end_id = int(parameters['pushlog_id']) - 1
+    inclusive_tweak = 1 if input.get('inclusive') else 0
+    depth = input.get('depth', 5) + inclusive_tweak
+    end_id = int(parameters['pushlog_id']) - (1 - inclusive_tweak)
 
     while True:
         start_id = max(end_id - depth, 0)
         pushlog_url = PUSHLOG_TMPL.format(parameters['head_repository'], start_id, end_id)
         r = requests.get(pushlog_url)
         r.raise_for_status()
         pushes = pushes + r.json()['pushes'].keys()
         if len(pushes) >= depth:
@@ -85,13 +103,27 @@ def backfill_action(parameters, graph_co
                     INDEX_TMPL.format(parameters['project'], push),
                     'public/parameters.yml')
             push_decision_task_id = find_decision_task(push_params, graph_config)
         except HTTPError as e:
             logger.info('Skipping {} due to missing index artifacts! Error: {}'.format(push, e))
             continue
 
         if label in full_task_graph.tasks.keys():
-            create_tasks(
-                    [label], full_task_graph, label_to_taskid,
-                    push_params, push_decision_task_id, push)
+            def modifier(task):
+                if task.label != label:
+                    return task
+                if input.get('addGeckoProfile'):
+                    mh = task.task['payload'].setdefault('env', {}) \
+                                                    .get('MOZHARNESS_OPTIONS', '')
+                    task.task['payload']['env']['MOZHARNESS_OPTIONS'] = mh + ' --geckoProfile'
+                    task.task['extra']['treeherder']['symbol'] += '-p'
+
+                if input.get('testPath'):
+                    env = task.task['payload'].setdefault('env', {})
+                    env['MOZHARNESS_TEST_PATHS'] = input.get('testPath')
+                    task.task['extra']['treeherder']['symbol'] += '-b'
+                return task
+
+            create_tasks([label], full_task_graph, label_to_taskid,
+                         push_params, push_decision_task_id, push, modifier=modifier)
         else:
             logging.info('Could not find {} on {}. Skipping.'.format(label, push))
--- a/taskcluster/taskgraph/actions/util.py
+++ b/taskcluster/taskgraph/actions/util.py
@@ -106,35 +106,40 @@ def create_task_from_def(task_id, task_d
 
 
 def update_parent(task, graph):
     task.task.setdefault('extra', {})['parent'] = os.environ.get('TASK_ID', '')
     return task
 
 
 def create_tasks(to_run, full_task_graph, label_to_taskid,
-                 params, decision_task_id=None, suffix=''):
+                 params, decision_task_id=None, suffix='', modifier=lambda t: t):
     """Create new tasks.  The task definition will have {relative-datestamp':
     '..'} rendered just like in a decision task.  Action callbacks should use
     this function to create new tasks,
     allowing easy debugging with `mach taskgraph action-callback --test`.
     This builds up all required tasks to run in order to run the tasks requested.
 
+    Optionally this function takes a `modifier` function that is passed in each
+    task before it is put into a new graph. It should return a valid task. Note
+    that this is passed _all_ tasks in the graph, not just the set in to_run. You
+    may want to skip modifying tasks not in your to_run list.
+
     If you wish to create the tasks in a new group, leave out decision_task_id."""
     if suffix != '':
         suffix = '-{}'.format(suffix)
     to_run = set(to_run)
 
     #  Copy to avoid side-effects later
     full_task_graph = copy.deepcopy(full_task_graph)
     label_to_taskid = label_to_taskid.copy()
 
     target_graph = full_task_graph.graph.transitive_closure(to_run)
     target_task_graph = TaskGraph(
-        {l: full_task_graph[l] for l in target_graph.nodes},
+        {l: modifier(full_task_graph[l]) for l in target_graph.nodes},
         target_graph)
     target_task_graph.for_each_task(update_parent)
     optimized_task_graph, label_to_taskid = optimize_task_graph(target_task_graph,
                                                                 params,
                                                                 to_run,
                                                                 label_to_taskid)
     write_artifact('task-graph{}.json'.format(suffix), optimized_task_graph.to_json())
     write_artifact('label-to-taskid{}.json'.format(suffix), label_to_taskid)
--- a/taskcluster/taskgraph/task.py
+++ b/taskcluster/taskgraph/task.py
@@ -16,17 +16,17 @@ class Task(object):
     - optimization: optimization to apply to the task (see taskgraph.optimize)
     - dependencies: tasks this one depends on, in the form {name: label}, for example
       {'build': 'build-linux64/opt', 'docker-image': 'build-docker-image-desktop-test'}
 
     And later, as the task-graph processing proceeds:
 
     - task_id -- TaskCluster taskId under which this task will be created
 
-    This class is just a convenience wraper for the data type and managing
+    This class is just a convenience wrapper for the data type and managing
     display, comparison, serialization, etc. It has no functionality of its own.
     """
     def __init__(self, kind, label, attributes, task,
                  optimization=None, dependencies=None):
         self.kind = kind
         self.label = label
         self.attributes = attributes
         self.task = task
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -597458,17 +597458,17 @@
    "2280f0ef821cdc3093e10c2162d3756f5eeb78de",
    "testharness"
   ],
   "payment-request/payment-request-abort-method.https.html": [
    "a9d811dc41487ba7a8e5d55319574364b93362aa",
    "testharness"
   ],
   "payment-request/payment-request-canmakepayment-method-manual.https.html": [
-   "7531ea3b11f6e8de8cf71666b19861dbc5267cf7",
+   "11df3310832e043e533a11245b012b9de1c0bb26",
    "manual"
   ],
   "payment-request/payment-request-constructor-crash.https.html": [
    "383d1c3f9505ee63d504bee87e13efa90ba49f3d",
    "testharness"
   ],
   "payment-request/payment-request-constructor.https.html": [
    "511c4d25939d344f187fe4a8daa4a8ff926b613d",
--- a/testing/web-platform/tests/payment-request/payment-request-canmakepayment-method-manual.https.html
+++ b/testing/web-platform/tests/payment-request/payment-request-canmakepayment-method-manual.https.html
@@ -121,19 +121,22 @@ promise_test(async t => {
       );
       break;
     }
   }
 }, `Optionally, at the user agent's discretion, return a promise rejected with a "NotAllowedError" DOMException.`);
 
 function manualTest1(elem){
   elem.disabled = true;
+
+  // NB: request.show has to be called outside of promise_test to ensure the
+  // user's click is still visible to PaymentRequest.show.
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  const acceptPromise = request.show(); // Sets state to "interactive"
   promise_test(async t => {
-    const request = new PaymentRequest(defaultMethods, defaultDetails);
-    const acceptPromise = request.show(); // Sets state to "interactive"
     const canMakePaymentPromise = request.canMakePayment();
     try {
       const result = await canMakePaymentPromise;
       assert_true(
         false,
         `canMakePaymentPromise should have thrown InvalidStateError`
       );
     } catch (err) {
@@ -143,21 +146,24 @@ function manualTest1(elem){
       await promise_rejects(t, "AbortError", acceptPromise);
     }
     // The state should be "closed"
     await promise_rejects(t, "InvalidStateError", request.canMakePayment());
   }, elem.textContent.trim());
 }
 
 function manualTest2(elem){
- elem.disabled = true;
+  elem.disabled = true;
+
+  // See above for why it's important for these lines to be outside of
+  // promise_test.
+  const request = new PaymentRequest(defaultMethods, defaultDetails);
+  const acceptPromise = request.show(); // The state is now "interactive"
+  acceptPromise.catch(() => {}); // no-op, just to silence unhandled rejection in devtools.
   promise_test(async t => {
-    const request = new PaymentRequest(defaultMethods, defaultDetails);
-    const acceptPromise = request.show(); // The state is now "interactive"
-    acceptPromise.catch(() => {}); // no-op, just to silence unhandled rejection in devtools.
     await request.abort(); // The state is now "closed"
     await promise_rejects(t, "InvalidStateError", request.canMakePayment());
     try {
       const result = await request.canMakePayment();
       assert_true(
         false,
         `should have thrown InvalidStateError, but instead returned "${result}"`
       );
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -19,16 +19,18 @@ def Libxul(name):
         GeckoFramework(name, linkage=None)
         SHARED_LIBRARY_NAME = 'XUL'
     else:
         GeckoSharedLibrary(name, linkage=None)
         SHARED_LIBRARY_NAME = 'xul'
 
     DELAYLOAD_DLLS += [
         'comdlg32.dll',
+        'hid.dll',
+        'msimg32.dll',
         'netapi32.dll',
         'secur32.dll',
         'wininet.dll',
         'winspool.drv'
     ]
 
     if CONFIG['ACCESSIBILITY']:
         DELAYLOAD_DLLS += ['oleacc.dll']