Merge autoland to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Thu, 27 Oct 2016 19:18:41 -0700
changeset 319816 ab7970b0eba3e2f13d08814438a0c87dac5a8e8a
parent 319781 10a2b6ebcd44a3516673f51da14b760de7fd3bc0 (current diff)
parent 319815 ed22ec2c58924267a99e6d81658c4dc387f19fc3 (diff)
child 319839 944cb0fd05526894fcd90fbe7d1e625ee53cd73d
push id20748
push userphilringnalda@gmail.com
push dateFri, 28 Oct 2016 03:39:55 +0000
treeherderfx-team@715360440695 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone52.0a1
Merge autoland to m-c, a=merge MozReview-Commit-ID: HtjVCRveaGW
devtools/client/locales/en-US/promisedebugger.dtd
devtools/client/promisedebugger/moz.build
devtools/client/promisedebugger/promise-controller.js
devtools/client/promisedebugger/promise-debugger.xhtml
devtools/client/promisedebugger/promise-panel.js
devtools/client/promisedebugger/test/.eslintrc.js
devtools/client/promisedebugger/test/head.js
editor/composer/test/chrome.ini
editor/composer/test/mochitest.ini
editor/composer/test/test_bug1205983.html
editor/libeditor/tests/chrome.ini
editor/libeditor/tests/mochitest.ini
--- a/.eslintignore
+++ b/.eslintignore
@@ -60,17 +60,16 @@ browser/app/**
 browser/branding/**/firefox-branding.js
 browser/base/content/browser-social.js
 browser/base/content/nsContextMenu.js
 browser/base/content/sanitizeDialog.js
 browser/base/content/test/general/file_csp_block_all_mixedcontent.html
 browser/base/content/test/urlbar/file_blank_but_not_blank.html
 browser/base/content/newtab/**
 browser/components/downloads/**
-browser/components/feeds/**
 browser/components/privatebrowsing/**
 browser/components/sessionstore/**
 browser/components/tabview/**
 # generated files in cld2
 browser/components/translation/cld2/cld-worker.js
 browser/extensions/pdfjs/**
 # generated or library files in pocket
 browser/extensions/pocket/content/panels/js/tmpl.js
--- a/browser/components/feeds/FeedConverter.js
+++ b/browser/components/feeds/FeedConverter.js
@@ -1,9 +1,9 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ 
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/debug.js");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
@@ -222,17 +222,17 @@ FeedConverter.prototype = {
             case "client":
             case "default":
               try {
                 let title = feed.title ? feed.title.plainText() : "";
                 let desc = feed.subtitle ? feed.subtitle.plainText() : "";
                 let feedReader = safeGetCharPref(getPrefActionForType(feedType), "bookmarks");
                 feedService.addToClientReader(result.uri.spec, title, desc, feed.type, feedReader);
                 return;
-              } catch(ex) { /* fallback to preview mode */ }
+              } catch (ex) { /* fallback to preview mode */ }
           }
         }
       }
 
       let ios =
           Cc["@mozilla.org/network/io-service;1"].
           getService(Ci.nsIIOService);
       let chromeChannel;
--- a/browser/components/feeds/FeedWriter.js
+++ b/browser/components/feeds/FeedWriter.js
@@ -606,17 +606,17 @@ FeedWriter.prototype = {
   _setAlwaysUseCheckedState(feedType) {
     let checkbox = this._document.getElementById("alwaysUse");
     if (checkbox) {
       let alwaysUse = false;
       try {
         if (Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask")
           alwaysUse = true;
       }
-      catch(ex) { }
+      catch (ex) { }
       this._setCheckboxCheckedState(checkbox, alwaysUse);
     }
   },
 
   _setSubscribeUsingLabel() {
     let stringLabel = "subscribeFeedUsing";
     switch (this._getFeedType()) {
       case Ci.nsIFeed.TYPE_VIDEO:
@@ -944,17 +944,17 @@ FeedWriter.prototype = {
     prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, false);
     prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, false);
     prefs.addObserver(PREF_AUDIO_SELECTED_APP, this, false);
 
     this._mm.addMessageListener("FeedWriter:SetApplicationLauncherMenuItem", this);
   },
 
   receiveMessage(msg) {
-    switch(msg.name) {
+    switch (msg.name) {
       case "FeedWriter:SetApplicationLauncherMenuItem":
         let menuItem = null;
 
         if (msg.data.type == "DefaultAppMenuItem") {
           menuItem = this._defaultHandlerMenuItem;
         } else {
           // Most likely SelectedAppMenuItem
           menuItem = this._selectedAppMenuItem;
--- a/browser/components/feeds/WebContentConverter.js
+++ b/browser/components/feeds/WebContentConverter.js
@@ -915,17 +915,17 @@ WebContentConverterRegistrarContent.prot
     }).filter(child => !!child)
       .sort();
 
     // now register them
     for (num of nums) {
       let branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + num + ".");
       try {
         this._registerContentHandlerHavingBranch(branch);
-      } catch(ex) {
+      } catch (ex) {
         // do nothing, the next branch might have values
       }
     }
   },
 
   _typeIsRegistered(contentType, uri) {
     return this._contentTypes[contentType]
                .some(e => e.uri == uri);
--- a/browser/components/feeds/content/subscribe.js
+++ b/browser/components/feeds/content/subscribe.js
@@ -3,17 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var SubscribeHandler = {
   /**
    * The nsIFeedWriter object that produces the UI
    */
   _feedWriter: null,
-  
+
   init: function SH_init() {
     this._feedWriter = new BrowserFeedWriter();
   },
 
   writeContent: function SH_writeContent() {
     this._feedWriter.writeContent();
   },
 
--- a/browser/components/feeds/test/test_bug436801.html
+++ b/browser/components/feeds/test/test_bug436801.html
@@ -81,29 +81,29 @@ function checkNode(node, schema) {
     var text = schema.shift();
     is(node.data, text, "Text should match");
     return;
   }
   // type == Node.ELEMENT_NODE
   var tag = schema.shift();
   is(node.localName, tag, "Element should have expected tag");
   while (schema.length) {
-    var val = schema.shift();
+    let val = schema.shift();
     if (Array.isArray(val))
       var childSchema = val;
     else
       var attrSchema = val;
   }
   if (attrSchema) {
     var nsTable = {
       xml: "http://www.w3.org/XML/1998/namespace",
     };
     for (var name in attrSchema) {
       var [ns, nsName] = name.split(":");
-      var val = nsName ? node.getAttributeNS(nsTable[ns], nsName) :
+      let val = nsName ? node.getAttributeNS(nsTable[ns], nsName) :
                 node.getAttribute(name);
       is(val, attrSchema[name], "Attribute " + name + " should match");
     }
   }
   if (childSchema) {
     var numChildren = node.childNodes.length;
     is(childSchema.length, numChildren,
        "Element should have expected number of children");
--- a/browser/components/feeds/test/test_registerHandler.html
+++ b/browser/components/feeds/test/test_registerHandler.html
@@ -23,17 +23,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   function testRegisterHandler(aIsProtocol, aTxt, aUri, aTitle)
   {
     try {
       if (aIsProtocol)
         navigator.registerProtocolHandler(aTxt, aUri, aTitle);
       else
         navigator.registerContentHandler(aTxt, aUri, aTitle);
     }
-    catch(e) {
+    catch (e) {
       return false;
     }
 
     return true;
   }
 
   ok(navigator.registerProtocolHandler, "navigator.registerProtocolHandler should be defined");
   ok(navigator.registerContentHandler, "navigator.registerContentHandler should be defined");
@@ -56,20 +56,20 @@ https://bugzilla.mozilla.org/show_bug.cg
 
   // restriction to http(s) for the uri of the handler (bug 401343)
   // https should work (http already tested in the generic case)
   is(testRegisterHandler(true, "foo", "https://mochi.test:8888/%s", "Foo handler"), true, "registering a foo protocol handler with https scheme should work");
   is(testRegisterHandler(false, "application/rss+xml", "https://mochi.test:8888/%s", "Foo handler"), true, "registering a foo content handler with https scheme should work");
   // ftp should not work
   is(testRegisterHandler(true, "foo", "ftp://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with ftp scheme should not work");
   is(testRegisterHandler(false, "application/rss+xml", "ftp://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with ftp scheme should not work");
-  // chrome should not work 
+  // chrome should not work
   is(testRegisterHandler(true, "foo", "chrome://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with chrome scheme should not work");
   is(testRegisterHandler(false, "application/rss+xml", "chrome://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with chrome scheme should not work");
-  // foo should not work 
+  // foo should not work
   is(testRegisterHandler(true, "foo", "foo://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with foo scheme should not work");
   is(testRegisterHandler(false, "application/rss+xml", "foo://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with foo scheme should not work");
 
   // for security reasons, protocol handlers should never be registered for some schemes (chrome, vbscript, ...) (bug 402788)
   is(testRegisterHandler(true, "chrome", "http://mochi.test:8888/%s", "chrome handler"), false, "registering a chrome protocol handler should not work");
   is(testRegisterHandler(true, "vbscript", "http://mochi.test:8888/%s", "vbscript handler"), false, "registering a vbscript protocol handler should not work");
   is(testRegisterHandler(true, "javascript", "http://mochi.test:8888/%s", "javascript handler"), false, "registering a javascript protocol handler should not work");
   is(testRegisterHandler(true, "moz-icon", "http://mochi.test:8888/%s", "moz-icon handler"), false, "registering a moz-icon protocol handler should not work");
--- a/devtools/client/animationinspector/components/animation-time-block.js
+++ b/devtools/client/animationinspector/components/animation-time-block.js
@@ -7,16 +7,33 @@
 "use strict";
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const {createNode, TimeScale} = require("devtools/client/animationinspector/utils");
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/locale/animationinspector.properties");
 
+// In the createPathSegments function, an animation duration is divided by
+// DURATION_RESOLUTION in order to draw the way the animation progresses.
+// But depending on the timing-function, we may be not able to make the graph
+// smoothly progress if this resolution is not high enough.
+// So, if the difference of animation progress between 2 divisions is more than
+// MIN_PROGRESS_THRESHOLD, then createPathSegments re-divides
+// by DURATION_RESOLUTION.
+// DURATION_RESOLUTION shoud be integer and more than 2.
+const DURATION_RESOLUTION = 4;
+// MIN_PROGRESS_THRESHOLD shoud be between more than 0 to 1.
+const MIN_PROGRESS_THRESHOLD = 0.1;
+// Show max 10 iterations for infinite animations
+// to give users a clue that the animation does repeat.
+const MAX_INFINITE_ANIMATIONS_ITERATIONS = 10;
+// SVG namespace
+const SVG_NS = "http://www.w3.org/2000/svg";
+
 /**
  * UI component responsible for displaying a single animation timeline, which
  * basically looks like a rectangle that shows the delay and iterations.
  */
 function AnimationTimeBlock() {
   EventEmitter.decorate(this);
   this.onClick = this.onClick.bind(this);
 }
@@ -46,85 +63,200 @@ AnimationTimeBlock.prototype = {
     this.unrender();
 
     this.animation = animation;
     let {state} = this.animation;
 
     // Create a container element to hold the delay and iterations.
     // It is positioned according to its delay (divided by the playbackrate),
     // and its width is according to its duration (divided by the playbackrate).
-    let {x, iterationW, delayX, delayW, negativeDelayW, endDelayX, endDelayW} =
+    const {x, delayX, delayW, endDelayX, endDelayW} =
       TimeScale.getAnimationDimensions(animation);
 
-    // background properties for .iterations element
-    let backgroundIterations = TimeScale.getIterationsBackgroundData(animation);
-
-    createNode({
+    // Animation summary graph element.
+    const summaryEl = createNode({
       parent: this.containerEl,
+      namespace: "http://www.w3.org/2000/svg",
+      nodeType: "svg",
       attributes: {
-        "class": "iterations" + (state.iterationCount ? "" : " infinite"),
-        // Individual iterations are represented by setting the size of the
-        // repeating linear-gradient.
-        // The background-size, background-position, background-repeat represent
-        // iterationCount and iterationStart.
-        "style": `left:${x}%;
-                  width:${iterationW}%;
-                  background-size:${backgroundIterations.size}% 100%;
-                  background-position:${backgroundIterations.position}% 0;
-                  background-repeat:${backgroundIterations.repeat};`
+        "class": "summary",
+        "preserveAspectRatio": "none",
+        "style": `left: ${ x - (state.delay > 0 ? delayW : 0) }%`
       }
     });
 
-    // The animation name is displayed over the iterations.
-    // Note that in case of negative delay, it is pushed towards the right so
-    // the delay element does not overlap.
+    // Total displayed duration
+    const totalDisplayedDuration = state.playbackRate * TimeScale.getDuration();
+
+    // Calculate stroke height in viewBox to display stroke of path.
+    const strokeHeightForViewBox = 0.5 / this.containerEl.clientHeight;
+
+    // Set viewBox
+    summaryEl.setAttribute("viewBox",
+                           `${ state.delay < 0 ? state.delay : 0 }
+                            -${ 1 + strokeHeightForViewBox }
+                            ${ totalDisplayedDuration }
+                            ${ 1 + strokeHeightForViewBox * 2 }`);
+
+    // Get a helper function that returns the path segment of timing-function.
+    const segmentHelper = getSegmentHelper(state, this.win);
+
+    // Minimum segment duration is the duration of one pixel.
+    const minSegmentDuration =
+      totalDisplayedDuration / this.containerEl.clientWidth;
+    // Minimum progress threshold.
+    let minProgressThreshold = MIN_PROGRESS_THRESHOLD;
+    // If the easing is step function,
+    // minProgressThreshold should be changed by the steps.
+    const stepFunction = state.easing.match(/steps\((\d+)/);
+    if (stepFunction) {
+      minProgressThreshold = 1 / (parseInt(stepFunction[1], 10) + 1);
+    }
+
+    // Starting time of main iteration.
+    let mainIterationStartTime = 0;
+    let iterationStart = state.iterationStart;
+    let iterationCount = state.iterationCount ? state.iterationCount : Infinity;
+
+    // Append delay.
+    if (state.delay > 0) {
+      renderDelay(summaryEl, state, segmentHelper);
+      mainIterationStartTime = state.delay;
+    } else {
+      const negativeDelayCount = -state.delay / state.duration;
+      // Move to forward the starting point for negative delay.
+      iterationStart += negativeDelayCount;
+      // Consume iteration count by negative delay.
+      if (iterationCount !== Infinity) {
+        iterationCount -= negativeDelayCount;
+      }
+    }
+
+    // Append 1st section of iterations,
+    // This section is only useful in cases where iterationStart has decimals.
+    // e.g.
+    // if { iterationStart: 0.25, iterations: 3 }, firstSectionCount is 0.75.
+    const firstSectionCount =
+      iterationStart % 1 === 0
+      ? 0 : Math.min(iterationCount, 1) - iterationStart % 1;
+    if (firstSectionCount) {
+      renderFirstIteration(summaryEl, state, mainIterationStartTime,
+                           firstSectionCount, minSegmentDuration,
+                           minProgressThreshold, segmentHelper);
+    }
+
+    if (iterationCount === Infinity) {
+      // If the animation repeats infinitely,
+      // we fill the remaining area with iteration paths.
+      renderInfinity(summaryEl, state, mainIterationStartTime,
+                     firstSectionCount, totalDisplayedDuration,
+                     minSegmentDuration, minProgressThreshold, segmentHelper);
+    } else {
+      // Otherwise, we show remaining iterations, endDelay and fill.
+
+      // Append forwards fill-mode.
+      if (state.fill === "both" || state.fill === "forwards") {
+        renderForwardsFill(summaryEl, state, mainIterationStartTime,
+                           iterationCount, totalDisplayedDuration,
+                           segmentHelper);
+      }
+
+      // Append middle section of iterations.
+      // e.g.
+      // if { iterationStart: 0.25, iterations: 3 }, middleSectionCount is 2.
+      const middleSectionCount =
+        Math.floor(iterationCount - firstSectionCount);
+      renderMiddleIterations(summaryEl, state, mainIterationStartTime,
+                             firstSectionCount, middleSectionCount,
+                             minSegmentDuration, minProgressThreshold,
+                             segmentHelper);
+
+      // Append last section of iterations, if there is remaining iteration.
+      // e.g.
+      // if { iterationStart: 0.25, iterations: 3 }, lastSectionCount is 0.25.
+      const lastSectionCount =
+        iterationCount - middleSectionCount - firstSectionCount;
+      if (lastSectionCount) {
+        renderLastIteration(summaryEl, state, mainIterationStartTime,
+                            firstSectionCount, middleSectionCount,
+                            lastSectionCount, minSegmentDuration,
+                            minProgressThreshold, segmentHelper);
+      }
+
+      // Append endDelay.
+      if (state.endDelay > 0) {
+        renderEndDelay(summaryEl, state,
+                       mainIterationStartTime, iterationCount, segmentHelper);
+      }
+    }
+
+    // Append negative delay (which overlap the animation).
+    if (state.delay < 0) {
+      segmentHelper.animation.effect.timing.fill = "both";
+      segmentHelper.asOriginalBehavior = false;
+      renderNegativeDelayHiddenProgress(summaryEl, state, minSegmentDuration,
+                                        minProgressThreshold, segmentHelper);
+    }
+    // Append negative endDelay (which overlap the animation).
+    if (state.iterationCount && state.endDelay < 0) {
+      if (segmentHelper.asOriginalBehavior) {
+        segmentHelper.animation.effect.timing.fill = "both";
+        segmentHelper.asOriginalBehavior = false;
+      }
+      renderNegativeEndDelayHiddenProgress(summaryEl, state,
+                                           minSegmentDuration,
+                                           minProgressThreshold,
+                                           segmentHelper);
+    }
+
+    // The animation name is displayed over the animation.
     createNode({
       parent: createNode({
         parent: this.containerEl,
         attributes: {
           "class": "name",
-          "title": this.getTooltipText(state),
-          // Place the name at the same position as the iterations, but make
-          // space for the negative delay if any.
-          "style": `left:${x + negativeDelayW}%;
-                    width:${iterationW - negativeDelayW}%;`
+          "title": this.getTooltipText(state)
         },
       }),
       textContent: state.name
     });
 
     // Delay.
     if (state.delay) {
       // Negative delays need to start at 0.
       createNode({
         parent: this.containerEl,
         attributes: {
-          "class": "delay" + (state.delay < 0 ? " negative" : ""),
-          "style": `left:${delayX}%;
-                    width:${delayW}%;`
+          "class": "delay"
+                   + (state.delay < 0 ? " negative" : " positive")
+                   + (state.fill === "both" ||
+                      state.fill === "backwards" ? " fill" : ""),
+          "style": `left:${ delayX }%; width:${ delayW }%;`
         }
       });
     }
 
     // endDelay
-    if (state.endDelay) {
+    if (state.iterationCount && state.endDelay) {
       createNode({
         parent: this.containerEl,
         attributes: {
-          "class": "end-delay" + (state.endDelay < 0 ? " negative" : ""),
-          "style": `left:${endDelayX}%;
-                    width:${endDelayW}%;`
+          "class": "end-delay"
+                   + (state.endDelay < 0 ? " negative" : " positive")
+                   + (state.fill === "both" ||
+                      state.fill === "forwards" ? " fill" : ""),
+          "style": `left:${ endDelayX }%; width:${ endDelayW }%;`
         }
       });
     }
   },
 
   getTooltipText: function (state) {
     let getTime = time => L10N.getFormatStr("player.timeLabel",
-                            L10N.numberWithDecimals(time / 1000, 2));
+                                            L10N.numberWithDecimals(time / 1000, 2));
 
     let text = "";
 
     // Adding the name.
     text += getFormattedAnimationTitle({state});
     text += "\n";
 
     // Adding the delay.
@@ -158,43 +290,68 @@ AnimationTimeBlock.prototype = {
     if (state.iterationStart !== 0) {
       let iterationStartTime = state.iterationStart * state.duration / 1000;
       text += L10N.getFormatStr("player.animationIterationStartLabel",
                                 state.iterationStart,
                                 L10N.numberWithDecimals(iterationStartTime, 2));
       text += "\n";
     }
 
+    // Adding the easing.
+    if (state.easing) {
+      text += L10N.getStr("player.animationEasingLabel") + " ";
+      text += state.easing;
+      text += "\n";
+    }
+
+    // Adding the fill mode.
+    if (state.fill) {
+      text += L10N.getStr("player.animationFillLabel") + " ";
+      text += state.fill;
+      text += "\n";
+    }
+
+    // Adding the direction mode.
+    if (state.direction) {
+      text += L10N.getStr("player.animationDirectionLabel") + " ";
+      text += state.direction;
+      text += "\n";
+    }
+
     // Adding the playback rate if it's different than 1.
     if (state.playbackRate !== 1) {
       text += L10N.getStr("player.animationRateLabel") + " ";
       text += state.playbackRate;
       text += "\n";
     }
 
     // Adding a note that the animation is running on the compositor thread if
     // needed.
     if (state.propertyState) {
       if (state.propertyState
-          .every(propState => propState.runningOnCompositor)) {
+               .every(propState => propState.runningOnCompositor)) {
         text += L10N.getStr("player.allPropertiesOnCompositorTooltip");
       } else if (state.propertyState
-                 .some(propState => propState.runningOnCompositor)) {
+                      .some(propState => propState.runningOnCompositor)) {
         text += L10N.getStr("player.somePropertiesOnCompositorTooltip");
       }
     } else if (state.isRunningOnCompositor) {
       text += L10N.getStr("player.runningOnCompositorTooltip");
     }
 
     return text;
   },
 
   onClick: function (e) {
     e.stopPropagation();
     this.emit("selected", this.animation);
+  },
+
+  get win() {
+    return this.containerEl.ownerDocument.defaultView;
   }
 };
 
 /**
  * Get a formatted title for this animation. This will be either:
  * "some-name", "some-name : CSS Transition", "some-name : CSS Animation",
  * "some-name : Script Animation", or "Script Animation", depending
  * if the server provides the type, what type it is and if the animation
@@ -211,8 +368,351 @@ function getFormattedAnimationTitle({sta
 
   // Script-generated animations may not have a name.
   if (state.type === "scriptanimation" && !state.name) {
     return L10N.getStr("timeline.scriptanimation.unnamedLabel");
   }
 
   return L10N.getFormatStr(`timeline.${state.type}.nameLabel`, state.name);
 }
+
+/**
+ * Render delay section.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderDelay(parentEl, state, segmentHelper) {
+  const startSegment = segmentHelper.getSegment(0);
+  const endSegment = { x: state.delay, y: startSegment.y };
+  appendPathElement(parentEl, [startSegment, endSegment], "delay-path");
+}
+
+/**
+ * Render first iteration section.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} mainIterationStartTime - Starting time of main iteration.
+ * @param {Number} firstSectionCount - Iteration count of first section.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderFirstIteration(parentEl, state, mainIterationStartTime,
+                              firstSectionCount, minSegmentDuration,
+                              minProgressThreshold, segmentHelper) {
+  const startTime = mainIterationStartTime;
+  const endTime = startTime + firstSectionCount * state.duration;
+  const segments =
+    createPathSegments(startTime, endTime, minSegmentDuration,
+                       minProgressThreshold, segmentHelper);
+  appendPathElement(parentEl, segments, "iteration-path");
+}
+
+/**
+ * Render middle iterations section.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} mainIterationStartTime - Starting time of main iteration.
+ * @param {Number} firstSectionCount - Iteration count of first section.
+ * @param {Number} middleSectionCount - Iteration count of middle section.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderMiddleIterations(parentEl, state, mainIterationStartTime,
+                                firstSectionCount, middleSectionCount,
+                                minSegmentDuration, minProgressThreshold,
+                                segmentHelper) {
+  const offset = mainIterationStartTime + firstSectionCount * state.duration;
+  for (let i = 0; i < middleSectionCount; i++) {
+    // Get the path segments of each iteration.
+    const startTime = offset + i * state.duration;
+    const endTime = startTime + state.duration;
+    const segments =
+      createPathSegments(startTime, endTime, minSegmentDuration,
+                         minProgressThreshold, segmentHelper);
+    appendPathElement(parentEl, segments, "iteration-path");
+  }
+}
+
+/**
+ * Render last iteration section.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} mainIterationStartTime - Starting time of main iteration.
+ * @param {Number} firstSectionCount - Iteration count of first section.
+ * @param {Number} middleSectionCount - Iteration count of middle section.
+ * @param {Number} lastSectionCount - Iteration count of last section.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderLastIteration(parentEl, state, mainIterationStartTime,
+                             firstSectionCount, middleSectionCount,
+                             lastSectionCount, minSegmentDuration,
+                             minProgressThreshold, segmentHelper) {
+  const startTime = mainIterationStartTime +
+                      (firstSectionCount + middleSectionCount) * state.duration;
+  const endTime = startTime + lastSectionCount * state.duration;
+  const segments =
+    createPathSegments(startTime, endTime, minSegmentDuration,
+                       minProgressThreshold, segmentHelper);
+  appendPathElement(parentEl, segments, "iteration-path");
+}
+
+/**
+ * Render Infinity iterations.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} mainIterationStartTime - Starting time of main iteration.
+ * @param {Number} firstSectionCount - Iteration count of first section.
+ * @param {Number} totalDuration - Displayed max duration.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderInfinity(parentEl, state, mainIterationStartTime,
+                        firstSectionCount, totalDuration, minSegmentDuration,
+                        minProgressThreshold, segmentHelper) {
+  // Calculate the number of iterations to display,
+  // with a maximum of MAX_INFINITE_ANIMATIONS_ITERATIONS
+  let uncappedInfinityIterationCount =
+    (totalDuration - firstSectionCount * state.duration) / state.duration;
+  // If there is a small floating point error resulting in, e.g. 1.0000001
+  // ceil will give us 2 so round first.
+  uncappedInfinityIterationCount =
+    parseFloat(uncappedInfinityIterationCount.toPrecision(6));
+  const infinityIterationCount =
+    Math.min(MAX_INFINITE_ANIMATIONS_ITERATIONS,
+             Math.ceil(uncappedInfinityIterationCount));
+
+  // Append first full iteration path.
+  const firstStartTime =
+    mainIterationStartTime + firstSectionCount * state.duration;
+  const firstEndTime = firstStartTime + state.duration;
+  const firstSegments =
+    createPathSegments(firstStartTime, firstEndTime, minSegmentDuration,
+                       minProgressThreshold, segmentHelper);
+  appendPathElement(parentEl, firstSegments, "iteration-path infinity");
+
+  // Append other iterations. We can copy first segments.
+  const isAlternate = state.direction.match(/alternate/);
+  for (let i = 1; i < infinityIterationCount; i++) {
+    const startTime = firstStartTime + i * state.duration;
+    let segments;
+    if (isAlternate && i % 2) {
+      // Copy as reverse.
+      segments = firstSegments.map(segment => {
+        return { x: firstEndTime - segment.x + startTime, y: segment.y };
+      });
+    } else {
+      // Copy as is.
+      segments = firstSegments.map(segment => {
+        return { x: segment.x - firstStartTime + startTime, y: segment.y };
+      });
+    }
+    appendPathElement(parentEl, segments, "iteration-path infinity copied");
+  }
+}
+
+/**
+ * Render endDelay section.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} mainIterationStartTime - Starting time of main iteration.
+ * @param {Number} iterationCount - Whole iteration count.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderEndDelay(parentEl, state,
+                        mainIterationStartTime, iterationCount, segmentHelper) {
+  const startTime = mainIterationStartTime + iterationCount * state.duration;
+  const startSegment = segmentHelper.getSegment(startTime);
+  const endSegment = { x: startTime + state.endDelay, y: startSegment.y };
+  appendPathElement(parentEl, [startSegment, endSegment], "enddelay-path");
+}
+
+/**
+ * Render forwards fill section.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} mainIterationStartTime - Starting time of main iteration.
+ * @param {Number} iterationCount - Whole iteration count.
+ * @param {Number} totalDuration - Displayed max duration.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderForwardsFill(parentEl, state, mainIterationStartTime,
+                            iterationCount, totalDuration, segmentHelper) {
+  const startTime = mainIterationStartTime + iterationCount * state.duration +
+                      (state.endDelay > 0 ? state.endDelay : 0);
+  const startSegment = segmentHelper.getSegment(startTime);
+  const endSegment = { x: totalDuration, y: startSegment.y };
+  appendPathElement(parentEl, [startSegment, endSegment], "fill-forwards-path");
+}
+
+/**
+ * Render hidden progress of negative delay.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderNegativeDelayHiddenProgress(parentEl, state, minSegmentDuration,
+                                           minProgressThreshold,
+                                           segmentHelper) {
+  const startTime = state.delay;
+  const endTime = 0;
+  const segments =
+    createPathSegments(startTime, endTime, minSegmentDuration,
+                       minProgressThreshold, segmentHelper);
+  appendPathElement(parentEl, segments, "delay-path negative");
+}
+
+/**
+ * Render hidden progress of negative endDelay.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Object} state - State of animation.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object returned by getSegmentHelper.
+ */
+function renderNegativeEndDelayHiddenProgress(parentEl, state,
+                                              minSegmentDuration,
+                                              minProgressThreshold,
+                                              segmentHelper) {
+  const endTime = state.delay + state.iterationCount * state.duration;
+  const startTime = endTime + state.endDelay;
+  const segments =
+    createPathSegments(startTime, endTime, minSegmentDuration,
+                       minProgressThreshold, segmentHelper);
+  appendPathElement(parentEl, segments, "enddelay-path negative");
+}
+
+/**
+ * Get a helper function which returns the segment coord from given time.
+ * @param {Object} state - animation state
+ * @param {Object} win - window object
+ * @return {Object} A segmentHelper object that has the following properties:
+ * - animation: The script animation used to get the progress
+ * - endTime: The end time of the animation
+ * - asOriginalBehavior: The spec is that the progress of animation is changed
+ *                       if the time of setCurrentTime is during the endDelay.
+ *                       Likewise, in case the time is less than 0.
+ *                       If this flag is true, we prevent the time
+ *                       to make the same animation behavior as the original.
+ * - getSegment: Helper function that, given a time,
+ *               will calculate the progress through the dummy animation.
+ */
+function getSegmentHelper(state, win) {
+  // Create a dummy Animation timing data as the
+  // state object we're being passed in.
+  const timing = Object.assign({}, state, {
+    iterations: state.iterationCount ? state.iterationCount : Infinity
+  });
+
+  // Create a dummy Animation with the given timing.
+  const dummyAnimation =
+    new win.Animation(new win.KeyframeEffect(null, null, timing), null);
+
+  // Returns segment helper object.
+  return {
+    animation: dummyAnimation,
+    endTime: dummyAnimation.effect.getComputedTiming().endTime,
+    asOriginalBehavior: true,
+    getSegment: function (time) {
+      if (this.asOriginalBehavior) {
+        // If the given time is less than 0, returned progress is 0.
+        if (time < 0) {
+          return { x: time, y: 0 };
+        }
+        // Avoid to apply over endTime.
+        this.animation.currentTime = time < this.endTime ? time : this.endTime;
+      } else {
+        this.animation.currentTime = time;
+      }
+      const progress = this.animation.effect.getComputedTiming().progress;
+      return { x: time, y: Math.max(progress, 0) };
+    }
+  };
+}
+
+/**
+ * Create the path segments from given parameters.
+ * @param {Number} startTime - Starting time of animation.
+ * @param {Number} endTime - Ending time of animation.
+ * @param {Number} minSegmentDuration - Minimum segment duration.
+ * @param {Number} minProgressThreshold - Minimum progress threshold.
+ * @param {Object} segmentHelper - The object of getSegmentHelper.
+ * @return {Array} path segments -
+ *                 [{x: {Number} time, y: {Number} progress}, ...]
+ */
+function createPathSegments(startTime, endTime, minSegmentDuration,
+                            minProgressThreshold, segmentHelper) {
+  // If the duration is too short, early return.
+  if (endTime - startTime < minSegmentDuration) {
+    return [segmentHelper.getSegment(startTime),
+            segmentHelper.getSegment(endTime)];
+  }
+
+  // Otherwise, start creating segments.
+  let pathSegments = [];
+
+  // Append the segment for the startTime position.
+  const startTimeSegment = segmentHelper.getSegment(startTime);
+  pathSegments.push(startTimeSegment);
+  let previousSegment = startTimeSegment;
+
+  // Split the duration in equal intervals, and iterate over them.
+  // See the definition of DURATION_RESOLUTION for more information about this.
+  const interval = (endTime - startTime) / DURATION_RESOLUTION;
+  for (let index = 1; index <= DURATION_RESOLUTION; index++) {
+    // Create a segment for this interval.
+    const currentSegment =
+      segmentHelper.getSegment(startTime + index * interval);
+
+    // If the distance between the Y coordinate (the animation's progress) of
+    // the previous segment and the Y coordinate of the current segment is too
+    // large, then recurse with a smaller duration to get more details
+    // in the graph.
+    if (Math.abs(currentSegment.y - previousSegment.y) > minProgressThreshold) {
+      // Divide the current interval (excluding start and end bounds
+      // by adding/subtracting 1ms).
+      pathSegments = pathSegments.concat(
+        createPathSegments(previousSegment.x + 1, currentSegment.x - 1,
+                           minSegmentDuration, minProgressThreshold,
+                           segmentHelper));
+    }
+
+    pathSegments.push(currentSegment);
+    previousSegment = currentSegment;
+  }
+
+  return pathSegments;
+}
+
+/**
+ * Append path element.
+ * @param {Element} parentEl - Parent element of this appended path element.
+ * @param {Array} pathSegments - Path segments. Please see createPathSegments.
+ * @param {String} cls - Class name.
+ * @return {Element} path element.
+ */
+function appendPathElement(parentEl, pathSegments, cls) {
+  // Create path string.
+  let path = `M${ pathSegments[0].x },0`;
+  pathSegments.forEach(pathSegment => {
+    path += ` L${ pathSegment.x },${ pathSegment.y }`;
+  });
+  path += ` L${ pathSegments[pathSegments.length - 1].x },0 Z`;
+  // Append and return the path element.
+  return createNode({
+    parent: parentEl,
+    namespace: SVG_NS,
+    nodeType: "path",
+    attributes: {
+      "d": path,
+      "class": cls,
+      "vector-effect": "non-scaling-stroke",
+      "transform": "scale(1, -1)"
+    }
+  });
+}
--- a/devtools/client/animationinspector/test/browser.ini
+++ b/devtools/client/animationinspector/test/browser.ini
@@ -7,16 +7,17 @@ support-files =
   doc_frame_script.js
   doc_keyframes.html
   doc_modify_playbackRate.html
   doc_negative_animation.html
   doc_pseudo_elements.html
   doc_script_animation.html
   doc_simple_animation.html
   doc_multiple_animation_types.html
+  doc_timing_combination_animation.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
 
@@ -51,16 +52,17 @@ skip-if = os == "linux" && !debug # Bug 
 [browser_animation_timeline_pause_button_01.js]
 [browser_animation_timeline_pause_button_02.js]
 [browser_animation_timeline_pause_button_03.js]
 [browser_animation_timeline_rate_selector.js]
 [browser_animation_timeline_rewind_button.js]
 [browser_animation_timeline_scrubber_exists.js]
 [browser_animation_timeline_scrubber_movable.js]
 [browser_animation_timeline_scrubber_moves.js]
+[browser_animation_timeline_setCurrentTime.js]
 [browser_animation_timeline_shows_delay.js]
 [browser_animation_timeline_shows_endDelay.js]
 [browser_animation_timeline_shows_iterations.js]
 [browser_animation_timeline_shows_time_info.js]
 [browser_animation_timeline_takes_rate_into_account.js]
 [browser_animation_timeline_ui.js]
 [browser_animation_toggle_button_resets_on_navigate.js]
 [browser_animation_toggle_button_toggles_animations.js]
--- a/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_iterationStart.js
@@ -16,17 +16,17 @@ add_task(function* () {
   for (let i = 0; i < timeBlockComponents.length; i++) {
     info(`Expand time block ${i} so its keyframes are visible`);
     yield clickOnAnimation(panel, i);
 
     info(`Check the state of time block ${i}`);
     let {containerEl, animation: {state}} = timeBlockComponents[i];
 
     checkAnimationTooltip(containerEl, state);
-    checkIterationBackground(containerEl, state);
+    checkProgressAtStartingTime(containerEl, state);
 
     // Get the first set of keyframes (there's only one animated property
     // anyway), and the first frame element from there, we're only interested in
     // its offset.
     let keyframeComponent = detailsComponents[i].keyframeComponents[0];
     let frameEl = keyframeComponent.keyframesEl.querySelector(".frame");
     checkKeyframeOffset(containerEl, frameEl, state);
   }
@@ -43,37 +43,24 @@ function checkAnimationTooltip(el, {iter
   }).replace(".", "\\.");
   let iterationStartString = iterationStart.toString().replace(".", "\\.");
 
   let regex = new RegExp("Iteration start: " + iterationStartString +
                          " \\(" + iterationStartTimeString + "s\\)");
   ok(title.match(regex), "The tooltip shows the expected iteration start");
 }
 
-function checkIterationBackground(el, {iterationCount, iterationStart}) {
-  info("Check the background-image used to display iterations is offset " +
-       "correctly to represent the iterationStart");
-
-  let iterationsEl = el.querySelector(".iterations");
-  let start = getIterationStartFromBackground(iterationsEl, iterationCount);
-  is(start, iterationStart % 1,
-     "The right background-position for iteration start");
-}
-
-function getIterationStartFromBackground(el, iterationCount) {
-  if (iterationCount == 1) {
-    let size = parseFloat(/([.\d]+)%/.exec(el.style.backgroundSize)[1]);
-    return 1 - size / 100;
-  }
-
-  let size = parseFloat(/([.\d]+)%/.exec(el.style.backgroundSize)[1]);
-  let position = parseFloat(/([-\d]+)%/.exec(el.style.backgroundPosition)[1]);
-  let iterationStartW = -position / size * (100 - size);
-  let rounded = Math.round(iterationStartW * 100);
-  return rounded / 10000;
+function checkProgressAtStartingTime(el, { iterationStart }) {
+  info("Check the progress of starting time");
+  const pathEl = el.querySelector(".iteration-path");
+  const pathSegList = pathEl.pathSegList;
+  const pathSeg = pathSegList.getItem(1);
+  const progress = pathSeg.y;
+  is(progress, iterationStart % 1,
+     `The progress at starting point should be ${ iterationStart % 1 }`);
 }
 
 function checkKeyframeOffset(timeBlockEl, frameEl, {iterationStart}) {
   info("Check that the first keyframe is offset correctly");
 
   let start = getIterationStartFromLeft(frameEl);
   is(start, iterationStart % 1, "The frame offset for iteration start");
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_setCurrentTime.js
@@ -0,0 +1,88 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Animation.currentTime ignores neagtive delay and positive/negative endDelay
+// during fill-mode, even if they are set.
+// For example, when the animation timing is
+// { duration: 1000, iterations: 1, endDelay: -500, easing: linear },
+// the animation progress is 0.5 at 700ms because the progress stops as 0.5 at
+// 500ms in original animation. However, if you set as
+// animation.currentTime = 700 manually, the progress will be 0.7.
+// So we modify setCurrentTime method since
+// AnimationInspector should re-produce same as original animation.
+// In these tests,
+// we confirm the behavior of setCurrentTime by delay and endDelay.
+
+add_task(function* () {
+  yield addTab(URL_ROOT + "doc_timing_combination_animation.html");
+  const { panel, controller } = yield openAnimationInspector();
+
+  yield clickTimelinePlayPauseButton(panel);
+
+  const timelineComponent = panel.animationsTimelineComponent;
+  const timeBlockComponents = timelineComponent.timeBlocks;
+
+  // Test -5000ms.
+  let time = -5000;
+  yield controller.setCurrentTimeAll(time, true);
+  for (let i = 0; i < timeBlockComponents.length; i++) {
+    yield timeBlockComponents[i].animation.refreshState();
+    const state = yield timeBlockComponents[i].animation.state;
+    info(`Check the state at ${ time }ms with `
+         + `delay:${ state.delay } and endDelay:${ state.endDelay }`);
+    is(state.currentTime, 0,
+       `The currentTime should be 0 at setCurrentTime(${ time })`);
+  }
+
+  // Test 10000ms.
+  time = 10000;
+  yield controller.setCurrentTimeAll(time, true);
+  for (let i = 0; i < timeBlockComponents.length; i++) {
+    yield timeBlockComponents[i].animation.refreshState();
+    const state = yield timeBlockComponents[i].animation.state;
+    info(`Check the state at ${ time }ms with `
+         + `delay:${ state.delay } and endDelay:${ state.endDelay }`);
+    const expected = state.delay < 0 ? 0 : time;
+    is(state.currentTime, expected,
+       `The currentTime should be ${ expected } at setCurrentTime(${ time }).`
+       + ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`);
+  }
+
+  // Test 60000ms.
+  time = 60000;
+  yield controller.setCurrentTimeAll(time, true);
+  for (let i = 0; i < timeBlockComponents.length; i++) {
+    yield timeBlockComponents[i].animation.refreshState();
+    const state = yield timeBlockComponents[i].animation.state;
+    info(`Check the state at ${ time }ms with `
+         + `delay:${ state.delay } and endDelay:${ state.endDelay }`);
+    const expected = state.delay < 0 ? time + state.delay : time;
+    is(state.currentTime, expected,
+       `The currentTime should be ${ expected } at setCurrentTime(${ time }).`
+       + ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`);
+  }
+
+  // Test 150000ms.
+  time = 150000;
+  yield controller.setCurrentTimeAll(time, true);
+  for (let i = 0; i < timeBlockComponents.length; i++) {
+    yield timeBlockComponents[i].animation.refreshState();
+    const state = yield timeBlockComponents[i].animation.state;
+    info(`Check the state at ${ time }ms with `
+         + `delay:${ state.delay } and endDelay:${ state.endDelay }`);
+    const currentTime = state.delay < 0 ? time + state.delay : time;
+    const endTime =
+      state.delay + state.iterationCount * state.duration + state.endDelay;
+    const expected =
+      state.endDelay < 0 && state.fill === "both" && currentTime > endTime
+      ? endTime : currentTime;
+    is(state.currentTime, expected,
+       `The currentTime should be ${ expected } at setCurrentTime(${ time }).`
+       + ` delay: ${ state.delay } and endDelay: ${ state.endDelay }`);
+  }
+});
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_delay.js
@@ -14,42 +14,83 @@ requestLongerTimeout(2);
 add_task(function* () {
   yield addTab(URL_ROOT + "doc_simple_animation.html");
   let {inspector, panel} = yield openAnimationInspector();
 
   info("Selecting a delayed animated node");
   yield selectNodeAndWaitForAnimations(".delayed", inspector);
   let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
   checkDelayAndName(timelineEl, true);
+  let animationEl = timelineEl.querySelector(".animation");
+  let state = panel.animationsTimelineComponent.timeBlocks[0].animation.state;
+  checkPath(animationEl, state);
 
   info("Selecting a no-delay animated node");
   yield selectNodeAndWaitForAnimations(".animated", inspector);
   checkDelayAndName(timelineEl, false);
+  animationEl = timelineEl.querySelector(".animation");
+  state = panel.animationsTimelineComponent.timeBlocks[0].animation.state;
+  checkPath(animationEl, state);
 
   info("Selecting a negative-delay animated node");
   yield selectNodeAndWaitForAnimations(".negative-delay", inspector);
   checkDelayAndName(timelineEl, true);
+  animationEl = timelineEl.querySelector(".animation");
+  state = panel.animationsTimelineComponent.timeBlocks[0].animation.state;
+  checkPath(animationEl, state);
 });
 
 function checkDelayAndName(timelineEl, hasDelay) {
   let delay = timelineEl.querySelector(".delay");
 
   is(!!delay, hasDelay, "The timeline " +
                         (hasDelay ? "contains" : "does not contain") +
                         " a delay element, as expected");
 
   if (hasDelay) {
-    let name = timelineEl.querySelector(".name");
     let targetNode = timelineEl.querySelector(".target");
 
     // Check that the delay element does not cause the timeline to overflow.
     let delayLeft = Math.round(delay.getBoundingClientRect().x);
     let sidebarWidth = Math.round(targetNode.getBoundingClientRect().width);
     ok(delayLeft >= sidebarWidth,
        "The delay element isn't displayed over the sidebar");
-
-    // Check that the delay is not displayed on top of the name.
-    let delayRight = Math.round(delay.getBoundingClientRect().right);
-    let nameLeft = Math.round(name.getBoundingClientRect().left);
-    ok(delayRight <= nameLeft,
-       "The delay element does not span over the name element");
   }
 }
+
+function checkPath(animationEl, state) {
+  // Check existance of delay path.
+  const delayPathEl = animationEl.querySelector(".delay-path");
+  if (!state.iterationCount && state.delay < 0) {
+    // Infinity
+    ok(!delayPathEl, "The delay path for Infinity should not exist");
+    return;
+  }
+  if (state.delay === 0) {
+    ok(!delayPathEl, "The delay path for zero delay should not exist");
+    return;
+  }
+  ok(delayPathEl, "The delay path should exist");
+
+  // Check delay path coordinates.
+  const pathSegList = delayPathEl.pathSegList;
+  const startingPathSeg = pathSegList.getItem(0);
+  const endingPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
+  if (state.delay < 0) {
+    ok(delayPathEl.classList.contains("negative"),
+       "The delay path should have 'negative' class");
+    const startingX = state.delay;
+    const endingX = 0;
+    is(startingPathSeg.x, startingX,
+       `The x of starting point should be ${ startingX }`);
+    is(endingPathSeg.x, endingX,
+       `The x of ending point should be ${ endingX }`);
+  } else {
+    ok(!delayPathEl.classList.contains("negative"),
+       "The delay path should not have 'negative' class");
+    const startingX = 0;
+    const endingX = state.delay;
+    is(startingPathSeg.x, startingX,
+       `The x of starting point should be ${ startingX }`);
+    is(endingPathSeg.x, endingX,
+       `The x of ending point should be ${ endingX }`);
+  }
+}
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js
@@ -15,18 +15,21 @@ add_task(function* () {
   yield addTab(URL_ROOT + "doc_end_delay.html");
   let {inspector, panel} = yield openAnimationInspector();
 
   let selectors = ["#target1", "#target2", "#target3", "#target4"];
   for (let i = 0; i < selectors.length; i++) {
     let selector = selectors[i];
     yield selectNode(selector, inspector);
     let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
-    let animationEl = timelineEl.querySelectorAll(".animation")[0];
+    let animationEl = timelineEl.querySelector(".animation");
     checkEndDelayAndName(animationEl);
+    const state =
+      panel.animationsTimelineComponent.timeBlocks[0].animation.state;
+    checkPath(animationEl, state);
   }
 });
 
 function checkEndDelayAndName(animationEl) {
   let endDelay = animationEl.querySelector(".end-delay");
   let name = animationEl.querySelector(".name");
   let targetNode = animationEl.querySelector(".target");
 
@@ -37,8 +40,39 @@ function checkEndDelayAndName(animationE
      "The endDelay element isn't displayed over the sidebar");
 
   // Check that the endDelay is not displayed on top of the name.
   let endDelayRight = Math.round(endDelay.getBoundingClientRect().right);
   let nameLeft = Math.round(name.getBoundingClientRect().left);
   ok(endDelayRight >= nameLeft,
      "The endDelay element does not span over the name element");
 }
+
+function checkPath(animationEl, state) {
+  // Check existance of enddelay path.
+  const endDelayPathEl = animationEl.querySelector(".enddelay-path");
+  ok(endDelayPathEl, "The endDelay path should exist");
+
+  // Check enddelay path coordinates.
+  const pathSegList = endDelayPathEl.pathSegList;
+  const startingPathSeg = pathSegList.getItem(0);
+  const endingPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
+  if (state.endDelay < 0) {
+    ok(endDelayPathEl.classList.contains("negative"),
+       "The endDelay path should have 'negative' class");
+    const endingX = state.delay + state.iterationCount * state.duration;
+    const startingX = endingX + state.endDelay;
+    is(startingPathSeg.x, startingX,
+       `The x of starting point should be ${ startingX }`);
+    is(endingPathSeg.x, endingX,
+       `The x of ending point should be ${ endingX }`);
+  } else {
+    ok(!endDelayPathEl.classList.contains("negative"),
+       "The endDelay path should not have 'negative' class");
+    const startingX =
+      state.delay + state.iterationCount * state.duration;
+    const endingX = startingX + state.endDelay;
+    is(startingPathSeg.x, startingX,
+       `The x of starting point should be ${ startingX }`);
+    is(endingPathSeg.x, endingX,
+       `The x of ending point should be ${ endingX }`);
+  }
+}
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_iterations.js
@@ -12,41 +12,36 @@ requestLongerTimeout(2);
 add_task(function* () {
   yield addTab(URL_ROOT + "doc_simple_animation.html");
   let {inspector, panel} = yield openAnimationInspector();
 
   info("Selecting the test node");
   yield selectNodeAndWaitForAnimations(".delayed", inspector);
 
   info("Getting the animation element from the panel");
-  let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
+  const timelineComponent = panel.animationsTimelineComponent;
+  const timelineEl = timelineComponent.rootWrapperEl;
   let animation = timelineEl.querySelector(".time-block");
-  let iterations = animation.querySelector(".iterations");
-
-  // Iterations are rendered with a repeating linear-gradient, so we need to
-  // calculate how many iterations are represented by looking at the background
-  // size.
-  let iterationCount = getIterationCountFromBackground(iterations);
+  // Get iteration count from summary graph path.
+  let iterationCount = getIterationCount(animation);
 
   is(iterationCount, 10,
      "The animation timeline contains the right number of iterations");
-  ok(!iterations.classList.contains("infinite"),
-     "The iteration element doesn't have the infinite class");
+  ok(!animation.querySelector(".infinity"),
+     "The summary graph does not have any elements "
+     + " that have infinity class");
 
   info("Selecting another test node with an infinite animation");
   yield selectNodeAndWaitForAnimations(".animated", inspector);
 
   info("Getting the animation element from the panel again");
   animation = timelineEl.querySelector(".time-block");
-  iterations = animation.querySelector(".iterations");
-
-  iterationCount = getIterationCountFromBackground(iterations);
+  iterationCount = getIterationCount(animation);
 
   is(iterationCount, 1,
-     "The animation timeline contains just one iteration");
-  ok(iterations.classList.contains("infinite"),
-     "The iteration element has the infinite class");
+     "The animation timeline contains one iteration");
+  ok(animation.querySelector(".infinity"),
+     "The summary graph has an element that has infinity class");
 });
 
-function getIterationCountFromBackground(el) {
-  let backgroundSize = parseFloat(el.style.backgroundSize.split(" ")[0]);
-  return Math.round(100 / backgroundSize);
+function getIterationCount(timeblockEl) {
+  return timeblockEl.querySelectorAll(".iteration-path").length;
 }
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js
@@ -30,12 +30,21 @@ add_task(function* () {
     if (controller.animationPlayers[i].state.endDelay) {
       ok(title.match(/End delay: [\d.-]+s/), "The tooltip shows the endDelay");
     }
     if (controller.animationPlayers[i].state.iterationCount !== 1) {
       ok(title.match(/Repeats: /), "The tooltip shows the iterations");
     } else {
       ok(!title.match(/Repeats: /), "The tooltip doesn't show the iterations");
     }
+    if (controller.animationPlayers[i].state.easing) {
+      ok(title.match(/Easing: /), "The tooltip shows the easing");
+    }
+    if (controller.animationPlayers[i].state.fill) {
+      ok(title.match(/Fill: /), "The tooltip shows the fill");
+    }
+    if (controller.animationPlayers[i].state.direction) {
+      ok(title.match(/Direction: /), "The tooltip shows the direction");
+    }
     ok(!title.match(/Iteration start:/),
       "The tooltip doesn't show the iteration start");
   });
 });
--- a/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_takes_rate_into_account.js
@@ -19,25 +19,63 @@ add_task(function* () {
   let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
 
   let timeBlocks = timelineEl.querySelectorAll(".time-block");
   is(timeBlocks.length, 2, "2 animations are displayed");
 
   info("The first animation has its rate set to 1, let's measure it");
 
   let el = timeBlocks[0];
-  let duration = parseInt(el.querySelector(".iterations").style.width, 10);
+  let duration = getDuration(el.querySelector("path"));
   let delay = parseInt(el.querySelector(".delay").style.width, 10);
 
   info("The second animation has its rate set to 2, so should be shorter");
 
   let el2 = timeBlocks[1];
-  let duration2 = parseInt(el2.querySelector(".iterations").style.width, 10);
+  let duration2 = getDuration(el2.querySelector("path"));
   let delay2 = parseInt(el2.querySelector(".delay").style.width, 10);
 
   // The width are calculated by the animation-inspector dynamically depending
   // on the size of the panel, and therefore depends on the test machine/OS.
   // Let's not try to be too precise here and compare numbers.
   let durationDelta = (2 * duration2) - duration;
   ok(durationDelta <= 1, "The duration width is correct");
   let delayDelta = (2 * delay2) - delay;
   ok(delayDelta <= 1, "The delay width is correct");
 });
+
+function getDuration(pathEl) {
+  const pathSegList = pathEl.pathSegList;
+  // Find the index of starting iterations.
+  let startingIterationIndex = 0;
+  const firstPathSeg = pathSegList.getItem(1);
+  for (let i = 2, n = pathSegList.numberOfItems - 2; i < n; i++) {
+    // Changing point of the progress acceleration is the time.
+    const pathSeg = pathSegList.getItem(i);
+    if (firstPathSeg.y != pathSeg.y) {
+      startingIterationIndex = i;
+      break;
+    }
+  }
+  // Find the index of ending iterations.
+  let endingIterationIndex = 0;
+  let previousPathSegment = pathSegList.getItem(startingIterationIndex);
+  for (let i = startingIterationIndex + 1, n = pathSegList.numberOfItems - 2;
+       i < n; i++) {
+    // Find forwards fill-mode.
+    const pathSeg = pathSegList.getItem(i);
+    if (previousPathSegment.y == pathSeg.y) {
+      endingIterationIndex = i;
+      break;
+    }
+    previousPathSegment = pathSeg;
+  }
+  if (endingIterationIndex) {
+    // Not forwards fill-mode
+    endingIterationIndex = pathSegList.numberOfItems - 2;
+  }
+  // Return the distance of starting and ending
+  const startingIterationPathSegment =
+    pathSegList.getItem(startingIterationIndex);
+  const endingIterationPathSegment =
+    pathSegList.getItem(startingIterationIndex);
+  return endingIterationPathSegment.x - startingIterationPathSegment.x;
+}
--- a/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_ui.js
@@ -32,12 +32,12 @@ add_task(function* () {
 
     ok(animationEl.querySelector(".target"),
        "The animated node target element is in the DOM");
     ok(animationEl.querySelector(".time-block"),
        "The timeline element is in the DOM");
     is(animationEl.querySelector(".name").textContent,
        animation.state.name,
        "The name on the timeline is correct");
-    ok(animationEl.querySelector(".iterations"),
-       "The timeline has iterations displayed");
+    ok(animationEl.querySelector("svg path"),
+       "The timeline has svg and path element as summary graph");
   }
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/doc_timing_combination_animation.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <style>
+    div {
+      display: inline-block;
+      width: 100px;
+      height: 100px;
+      background-color: lime;
+    }
+    </style>
+  </head>
+  <body>
+    <script>
+    "use strict";
+
+    const delayList = [0, 50000, -50000];
+    const endDelayList = [0, 50000, -50000];
+
+    delayList.forEach(delay => {
+      endDelayList.forEach(endDelay => {
+        const el = document.createElement("div");
+        document.body.appendChild(el);
+        el.animate({ opacity: [0, 1] },
+                   { duration: 200000,
+                     iterations: 1,
+                     fill: "both",
+                     delay: delay,
+                     endDelay: endDelay });
+      });
+    });
+    </script>
+  </body>
+</html>
--- a/devtools/client/animationinspector/utils.js
+++ b/devtools/client/animationinspector/utils.js
@@ -22,25 +22,29 @@ const MILLIS_TIME_FORMAT_MAX_DURATION = 
 /**
  * DOM node creation helper function.
  * @param {Object} Options to customize the node to be created.
  * - nodeType {String} Optional, defaults to "div",
  * - attributes {Object} Optional attributes object like
  *   {attrName1:value1, attrName2: value2, ...}
  * - parent {DOMNode} Mandatory node to append the newly created node to.
  * - textContent {String} Optional text for the node.
+ * - namespace {String} Optional namespace
  * @return {DOMNode} The newly created node.
  */
 function createNode(options) {
   if (!options.parent) {
     throw new Error("Missing parent DOMNode to create new node");
   }
 
   let type = options.nodeType || "div";
-  let node = options.parent.ownerDocument.createElement(type);
+  let node =
+    options.namespace
+    ? options.parent.ownerDocument.createElementNS(options.namespace, type)
+    : options.parent.ownerDocument.createElement(type);
 
   for (let name in options.attributes || {}) {
     let value = options.attributes[name];
     node.setAttribute(name, value);
   }
 
   if (options.textContent) {
     node.textContent = options.textContent;
@@ -259,37 +263,12 @@ var TimeScale = {
     // The width of the endDelay.
     let endDelayW = this.durationToDistance(Math.abs(endDelay) / rate);
     // The start position of the endDelay.
     let endDelayX = endDelay < 0 ? x + iterationW - endDelayW
                                  : x + iterationW;
 
     return {x, w, iterationW, delayX, delayW, negativeDelayW,
             endDelayX, endDelayW};
-  },
-
-  /**
-   * Given an animation, get the background data for .iterations element.
-   * This background represents iterationCount and iterationStart.
-   * Returns three properties.
-   * 1. size: x of background-size (%)
-   * 2. position: x of background-position (%)
-   * 3. repeat: background-repeat (string)
-   */
-  getIterationsBackgroundData: function ({state}) {
-    let iterationCount = state.iterationCount || 1;
-    let iterationStartW = state.iterationStart % 1 * 100;
-    let background = {};
-    if (iterationCount == 1) {
-      background.size = 100 - iterationStartW;
-      background.position = 0;
-      background.repeat = "no-repeat";
-    } else {
-      background.size = 1 / iterationCount * 100;
-      background.position = -iterationStartW * background.size /
-                              (100 - background.size);
-      background.repeat = "repeat-x";
-    }
-    return background;
   }
 };
 
 exports.TimeScale = TimeScale;
--- a/devtools/client/debugger/content/views/sources-view.js
+++ b/devtools/client/debugger/content/views/sources-view.js
@@ -186,17 +186,16 @@ SourcesView.prototype = Heritage.extend(
   _addCommands: function () {
     XULUtils.addCommands(this._commandset, {
       addBreakpointCommand: e => this._onCmdAddBreakpoint(e),
       addConditionalBreakpointCommand: e => this._onCmdAddConditionalBreakpoint(e),
       blackBoxCommand: () => this.toggleBlackBoxing(),
       unBlackBoxButton: () => this._onStopBlackBoxing(),
       prettyPrintCommand: () => this.togglePrettyPrint(),
       toggleBreakpointsCommand: () =>this.toggleBreakpoints(),
-      togglePromiseDebuggerCommand: () => this.togglePromiseDebugger(),
       nextSourceCommand: () => this.selectNextItem(),
       prevSourceCommand: () => this.selectPrevItem()
     });
   },
 
   /**
    * Sets the preferred location to be selected in this sources container.
    * @param string aUrl
@@ -609,27 +608,16 @@ SourcesView.prototype = Heritage.extend(
       this._toggleBreakpointsButton.setAttribute("checked", true);
       this._onDisableAll();
     } else {
       this._toggleBreakpointsButton.removeAttribute("checked");
       this._onEnableAll();
     }
   },
 
-  togglePromiseDebugger: function () {
-    if (Prefs.promiseDebuggerEnabled) {
-      let promisePane = this.DebuggerView._promisePane;
-      promisePane.hidden = !promisePane.hidden;
-
-      if (!this.DebuggerView._promiseDebuggerIframe) {
-        this.DebuggerView._initializePromiseDebugger();
-      }
-    }
-  },
-
   hidePrettyPrinting: function () {
     this._prettyPrintButton.style.display = "none";
 
     if (this._blackBoxButton.style.display === "none") {
       let sep = document.querySelector("#sources-toolbar .devtools-separator");
       sep.style.display = "none";
     }
   },
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -1206,17 +1206,16 @@ var Prefs = new PrefsHelper("devtools", 
   pauseOnExceptions: ["Bool", "debugger.pause-on-exceptions"],
   ignoreCaughtExceptions: ["Bool", "debugger.ignore-caught-exceptions"],
   sourceMapsEnabled: ["Bool", "debugger.source-maps-enabled"],
   prettyPrintEnabled: ["Bool", "debugger.pretty-print-enabled"],
   autoPrettyPrint: ["Bool", "debugger.auto-pretty-print"],
   workersEnabled: ["Bool", "debugger.workers"],
   editorTabSize: ["Int", "editor.tabsize"],
   autoBlackBox: ["Bool", "debugger.auto-black-box"],
-  promiseDebuggerEnabled: ["Bool", "debugger.promise"]
 });
 
 /**
  * Convenient way of emitting events from the panel window.
  */
 EventEmitter.decorate(this);
 
 /**
--- a/devtools/client/debugger/debugger-view.js
+++ b/devtools/client/debugger/debugger-view.js
@@ -18,18 +18,16 @@ const FUNCTION_SEARCH_ACTION_MAX_DELAY =
 const SEARCH_GLOBAL_FLAG = "!";
 const SEARCH_FUNCTION_FLAG = "@";
 const SEARCH_TOKEN_FLAG = "#";
 const SEARCH_LINE_FLAG = ":";
 const SEARCH_VARIABLE_FLAG = "*";
 const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG];
 const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
 const RESIZE_REFRESH_RATE = 50; // ms
-const PROMISE_DEBUGGER_URL =
-  "chrome://devtools/content/promisedebugger/promise-debugger.xhtml";
 
 const EventListenersView = require("./content/views/event-listeners-view");
 const SourcesView = require("./content/views/sources-view");
 var actions = Object.assign(
   {},
   require("./content/globalActions"),
   require("./content/actions/breakpoints"),
   require("./content/actions/sources"),
@@ -130,17 +128,16 @@ var DebuggerView = {
     this.Filtering.destroy();
     this.StackFrames.destroy();
     this.StackFramesClassicList.destroy();
     this.Sources.destroy();
     this.VariableBubble.destroy();
     this.WatchExpressions.destroy();
     this.EventListeners.destroy();
     this.GlobalSearch.destroy();
-    this._destroyPromiseDebugger();
     this._destroyPanes();
 
     this.editor.destroy();
     this.editor = null;
 
     this.controller.dispatch(actions.removeAllBreakpoints());
   },
 
@@ -150,17 +147,16 @@ var DebuggerView = {
   _initializePanes: function () {
     dumpn("Initializing the DebuggerView panes");
 
     this._body = document.getElementById("body");
     this._editorDeck = document.getElementById("editor-deck");
     this._workersAndSourcesPane = document.getElementById("workers-and-sources-pane");
     this._instrumentsPane = document.getElementById("instruments-pane");
     this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
-    this._promisePane = document.getElementById("promise-debugger-pane");
 
     this.showEditor = this.showEditor.bind(this);
     this.showBlackBoxMessage = this.showBlackBoxMessage.bind(this);
     this.showProgressBar = this.showProgressBar.bind(this);
 
     this._onTabSelect = this._onInstrumentsPaneTabSelect.bind(this);
     this._instrumentsPane.tabpanels.addEventListener("select", this._onTabSelect);
 
@@ -186,17 +182,16 @@ var DebuggerView = {
     if (gHostType != "side") {
       Prefs.workersAndSourcesWidth = this._workersAndSourcesPane.getAttribute("width");
       Prefs.instrumentsWidth = this._instrumentsPane.getAttribute("width");
     }
 
     this._workersAndSourcesPane = null;
     this._instrumentsPane = null;
     this._instrumentsPaneToggleButton = null;
-    this._promisePane = null;
   },
 
   /**
    * Initializes the VariablesView instance and attaches a controller.
    */
   _initializeVariablesView: function () {
     this.Variables = new VariablesView(document.getElementById("variables"), {
       searchPlaceholder: L10N.getStr("emptyVariablesFilterText"),
@@ -234,51 +229,16 @@ var DebuggerView = {
         case "properties":
           window.emit(EVENTS.FETCHED_PROPERTIES);
           break;
       }
     });
   },
 
   /**
-   * Initialie the Promise Debugger instance.
-   */
-  _initializePromiseDebugger: function () {
-    let iframe = this._promiseDebuggerIframe = document.createElement("iframe");
-    iframe.setAttribute("flex", 1);
-
-    let onLoad = (event) => {
-      iframe.removeEventListener("load", onLoad, true);
-
-      let doc = event.target;
-      let win = doc.defaultView;
-
-      win.setPanel(DebuggerController._toolbox);
-    };
-
-    iframe.addEventListener("load", onLoad, true);
-    iframe.setAttribute("src", PROMISE_DEBUGGER_URL);
-    this._promisePane.appendChild(iframe);
-  },
-
-  /**
-   * Destroy the Promise Debugger instance.
-   */
-  _destroyPromiseDebugger: function () {
-    if (this._promiseDebuggerIframe) {
-      this._promiseDebuggerIframe.contentWindow.destroy();
-
-      this._promiseDebuggerIframe.parentNode.removeChild(
-        this._promiseDebuggerIframe);
-
-      this._promiseDebuggerIframe = null;
-    }
-  },
-
-  /**
    * Initializes the Editor instance.
    *
    * @param function aCallback
    *        Called after the editor finishes initializing.
    */
   _initializeEditor: function (callback) {
     dumpn("Initializing the DebuggerView editor");
 
--- a/devtools/client/debugger/debugger.xul
+++ b/devtools/client/debugger/debugger.xul
@@ -336,21 +336,16 @@
                                  command="prettyPrintCommand"
                                  hidden="true"/>
                 </hbox>
                 <vbox class="devtools-separator"/>
                 <toolbarbutton id="toggle-breakpoints"
                                class="devtools-toolbarbutton"
                                tooltiptext="&debuggerUI.sources.toggleBreakpoints;"
                                command="toggleBreakpointsCommand"/>
-                <toolbarbutton id="toggle-promise-debugger"
-                               class="devtools-toolbarbutton"
-                               tooltiptext="&debuggerUI.sources.togglePromiseDebugger;"
-                               command="togglePromiseDebuggerCommand"
-                               hidden="true"/>
               </toolbar>
             </tabpanel>
             <tabpanel id="callstack-tabpanel">
               <vbox id="callstack-list" flex="1"/>
             </tabpanel>
           </tabpanels>
         </tabbox>
       </vbox>
@@ -398,22 +393,16 @@
                 <vbox id="variables" flex="1"/>
               </tabpanel>
               <tabpanel id="events-tabpanel">
                 <vbox id="event-listeners" flex="1"/>
               </tabpanel>
             </tabpanels>
           </tabbox>
         </hbox>
-        <splitter id="editor-and-promise-splitter"
-                class="devtools-horizontal-splitter"/>
-        <vbox id="promise-debugger-pane"
-              flex="1"
-              hidden="true">
-        </vbox>
       </vbox>
       <splitter id="vertical-layout-splitter"
                 class="devtools-horizontal-splitter"/>
       <hbox id="vertical-layout-panes-container">
         <splitter id="sources-and-instruments-splitter"
                   class="devtools-side-splitter"/>
         <!-- The sources-pane and instruments-pane will be moved in this
              container if the toolbox's host requires it. -->
--- a/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js
@@ -120,49 +120,43 @@ function testHost(aPanel, aHostType, aLa
   is(gView._body.getAttribute("layout"), aLayoutType,
     "The default host type is present as an attribute on the panel's body.");
 
   if (aLayoutType == "horizontal") {
     is(gView._workersAndSourcesPane.parentNode.id, "debugger-widgets",
       "The workers and sources pane's parent is correct for the horizontal layout.");
     is(gView._instrumentsPane.parentNode.id, "editor-and-instruments-pane",
       "The instruments pane's parent is correct for the horizontal layout.");
-    is(gDebugger.document.getElementById("promise-debugger-pane").parentNode.id,
-      "debugger-content",
-      "The promise pane's parent is correct for the horizontal layout.");
   } else {
     is(gView._workersAndSourcesPane.parentNode.id, "vertical-layout-panes-container",
       "The workers and sources pane's parent is correct for the vertical layout.");
     is(gView._instrumentsPane.parentNode.id, "vertical-layout-panes-container",
       "The instruments pane's parent is correct for the vertical layout.");
-    is(gDebugger.document.getElementById("promise-debugger-pane").parentNode.id,
-      "debugger-content",
-      "The promise pane's parent is correct for the horizontal layout.");
   }
 
   let widgets = gDebugger.document.getElementById("debugger-widgets").childNodes;
   let content = gDebugger.document.getElementById("debugger-content").childNodes;
   let editorPane =
     gDebugger.document.getElementById("editor-and-instruments-pane").childNodes;
   let verticalPane =
     gDebugger.document.getElementById("vertical-layout-panes-container").childNodes;
 
   if (aLayoutType == "horizontal") {
     is(widgets.length, 5, // 1 pane, 1 content box, 2 splitters and a phantom box.
       "Found the correct number of debugger widgets.");
-    is(content.length, 3, // 2 panes, 1 splitter.
+    is(content.length, 1, // 1 pane
       "Found the correct number of debugger content.");
     is(editorPane.length, 3, // 2 panes, 1 splitter
       "Found the correct number of debugger panes.");
     is(verticalPane.length, 1, // 1 lonely splitter in the phantom box.
       "Found the correct number of debugger panes.");
   } else {
     is(widgets.length, 4, // 1 content box, 2 splitters and a phantom box.
       "Found the correct number of debugger widgets.");
-    is(content.length, 3, // 2 panes, 1 splitter.
+    is(content.length, 1, // 1 pane
       "Found the correct number of debugger content.");
     is(editorPane.length, 2, // 1 pane, 1 splitter
       "Found the correct number of debugger panes.");
     is(verticalPane.length, 3, // 2 panes and 1 splitter in the phantom box.
       "Found the correct number of debugger panes.");
   }
 }
 
--- a/devtools/client/framework/test/browser_toolbox_select_event.js
+++ b/devtools/client/framework/test/browser_toolbox_select_event.js
@@ -38,39 +38,64 @@ add_task(function* () {
   yield testSelectEvent("inspector");
   yield testSelectEvent("webconsole");
   yield testSelectEvent("styleeditor");
   yield testSelectEvent("inspector");
   yield testSelectEvent("webconsole");
   yield testSelectEvent("styleeditor");
   yield toolbox.destroy();
 
+  yield testSelectToolRace();
+
   /**
    * Assert that selecting the given toolId raises a select event
    * @param {toolId} Id of the tool to test
    */
-  function testSelectEvent(toolId) {
-    return new Promise(resolve => {
-      toolbox.once("select", (event, id) => {
-        is(id, toolId, toolId + " selected");
-        resolve();
-      });
-      toolbox.selectTool(toolId);
-    });
+  function* testSelectEvent(toolId) {
+    let onSelect = toolbox.once("select");
+    toolbox.selectTool(toolId);
+    let id = yield onSelect;
+    is(id, toolId, toolId + " selected");
   }
 
   /**
    * Assert that selecting the given toolId raises its corresponding
    * selected event
    * @param {toolId} Id of the tool to test
    */
-  function testToolSelectEvent(toolId) {
-    return new Promise(resolve => {
-      toolbox.once(toolId + "-selected", () => {
-        let msg = toolId + " tool selected";
-        is(toolbox.currentToolId, toolId, msg);
-        resolve();
-      });
-      toolbox.selectTool(toolId);
-    });
+  function* testToolSelectEvent(toolId) {
+    let onSelected = toolbox.once(toolId + "-selected");
+    toolbox.selectTool(toolId);
+    yield onSelected;
+    is(toolbox.currentToolId, toolId, toolId + " tool selected");
+  }
+
+  /**
+   * Assert that two calls to selectTool won't race
+   */
+  function* testSelectToolRace() {
+    let toolbox = yield openToolboxForTab(tab, "webconsole");
+    let selected = false;
+    let onSelect = (event, id) => {
+      if (selected) {
+        ok(false, "Got more than one 'select' event");
+      } else {
+        selected = true;
+      }
+    };
+    toolbox.once("select", onSelect);
+    let p1 = toolbox.selectTool("inspector")
+    let p2 = toolbox.selectTool("inspector");
+    // Check that both promises don't resolve too early
+    let checkSelectToolResolution = panel => {
+      ok(selected, "selectTool resolves only after 'select' event is fired");
+      let inspector = toolbox.getPanel("inspector");
+      is(panel, inspector, "selecTool resolves to the panel instance");
+    };
+    p1.then(checkSelectToolResolution);
+    p2.then(checkSelectToolResolution);
+    yield p1;
+    yield p2;
+
+    yield toolbox.destroy();
   }
 });
 
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1446,21 +1446,29 @@ Toolbox.prototype = {
     let sep = this.doc.getElementById("toolbox-controls-separator");
     if (id === "options") {
       sep.setAttribute("invisible", "true");
     } else {
       sep.removeAttribute("invisible");
     }
 
     if (this.currentToolId == id) {
-      // re-focus tool to get key events again
-      this.focusTool(id);
+      let panel = this._toolPanels.get(id);
+      if (panel) {
+        // We have a panel instance, so the tool is already fully loaded.
+
+        // re-focus tool to get key events again
+        this.focusTool(id);
 
-      // Return the existing panel in order to have a consistent return value.
-      return promise.resolve(this._toolPanels.get(id));
+        // Return the existing panel in order to have a consistent return value.
+        return promise.resolve(panel);
+      }
+      // Otherwise, if there is no panel instance, it is still loading,
+      // so we are racing another call to selectTool with the same id.
+      return this.once("select").then(() => promise.resolve(this._toolPanels.get(id)));
     }
 
     if (!this.isReady) {
       throw new Error("Can't select tool, wait for toolbox 'ready' event");
     }
 
     let tab = this.doc.getElementById("toolbox-tab-" + id);
 
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -96,19 +96,16 @@ devtools.jar:
     content/performance/views/details-waterfall.js (performance/views/details-waterfall.js)
     content/performance/views/details-js-call-tree.js (performance/views/details-js-call-tree.js)
     content/performance/views/details-js-flamegraph.js (performance/views/details-js-flamegraph.js)
     content/performance/views/details-memory-call-tree.js (performance/views/details-memory-call-tree.js)
     content/performance/views/details-memory-flamegraph.js (performance/views/details-memory-flamegraph.js)
     content/performance/views/recordings.js (performance/views/recordings.js)
     content/memory/memory.xhtml (memory/memory.xhtml)
     content/memory/initializer.js (memory/initializer.js)
-    content/promisedebugger/promise-controller.js (promisedebugger/promise-controller.js)
-    content/promisedebugger/promise-panel.js (promisedebugger/promise-panel.js)
-    content/promisedebugger/promise-debugger.xhtml (promisedebugger/promise-debugger.xhtml)
     content/commandline/commandline.css (commandline/commandline.css)
     content/commandline/commandlineoutput.xhtml (commandline/commandlineoutput.xhtml)
     content/commandline/commandlinetooltip.xhtml (commandline/commandlinetooltip.xhtml)
     content/framework/toolbox-window.xul (framework/toolbox-window.xul)
     content/framework/toolbox-options.xhtml (framework/toolbox-options.xhtml)
     content/framework/toolbox.xul (framework/toolbox.xul)
     content/framework/toolbox-init.js (framework/toolbox-init.js)
     content/framework/options-panel.css (framework/options-panel.css)
--- a/devtools/client/locales/en-US/animationinspector.properties
+++ b/devtools/client/locales/en-US/animationinspector.properties
@@ -65,16 +65,34 @@ player.infiniteIterationCountText=∞
 # LOCALIZATION NOTE (player.animationIterationStartLabel):
 # This string is displayed in a tooltip that appears when hovering over
 # animations in the timeline. It is the label displayed before the animation
 # iterationStart value.
 # %1$S will be replaced by the original iteration start value
 # %2$S will be replaced by the actual time of iteration start
 player.animationIterationStartLabel=Iteration start: %1$S (%2$Ss)
 
+# LOCALIZATION NOTE (player.animationEasingLabel):
+# This string is displayed in a tooltip that appears when hovering over
+# animations in the timeline. It is the label displayed before the animation
+# easing value.
+player.animationEasingLabel=Easing:
+
+# LOCALIZATION NOTE (player.animationFillLabel):
+# This string is displayed in a tooltip that appears when hovering over
+# animations in the timeline. It is the label displayed before the animation
+# fill mode value.
+player.animationFillLabel=Fill:
+
+# LOCALIZATION NOTE (player.animationDirectionLabel):
+# This string is displayed in a tooltip that appears when hovering over
+# animations in the timeline. It is the label displayed before the animation
+# direction value.
+player.animationDirectionLabel=Direction:
+
 # LOCALIZATION NOTE (player.timeLabel):
 # This string is displayed in each animation player widget, to indicate either
 # how long (in seconds) the animation lasts, or what is the animation's current
 # time (in seconds too);
 player.timeLabel=%Ss
 
 # LOCALIZATION NOTE (player.playbackRateLabel):
 # This string is displayed in each animation player widget, as the label of
--- a/devtools/client/locales/en-US/debugger.dtd
+++ b/devtools/client/locales/en-US/debugger.dtd
@@ -45,20 +45,16 @@
   -  checkbox that toggles auto pretty print. -->
 <!ENTITY debuggerUI.autoPrettyPrint     "Auto Prettify Minified Sources">
 <!ENTITY debuggerUI.autoPrettyPrint.accesskey "P">
 
 <!-- LOCALIZATION NOTE (debuggerUI.sources.toggleBreakpoints): This is the tooltip for the
   -  button that toggles all breakpoints for all sources. -->
 <!ENTITY debuggerUI.sources.toggleBreakpoints "Enable/disable all breakpoints">
 
-<!-- LOCALIZATION NOTE (debuggerUI.sources.togglePromiseDebugger): This is the
-  -  tooltip for the button that toggles the promise debugger. -->
-<!ENTITY debuggerUI.sources.togglePromiseDebugger "Toggle Promise Debugger">
-
 <!-- LOCALIZATION NOTE (debuggerUI.clearButton): This is the label for
   -  the button that clears the collected tracing data in the tracing tab. -->
 <!ENTITY debuggerUI.clearButton "Clear">
 
 <!-- LOCALIZATION NOTE (debuggerUI.clearButton.tooltip): This is the tooltip for
   -  the button that clears the collected tracing data in the tracing tab. -->
 <!ENTITY debuggerUI.clearButton.tooltip "Clear the collected traces">
 
deleted file mode 100644
--- a/devtools/client/locales/en-US/promisedebugger.dtd
+++ /dev/null
@@ -1,15 +0,0 @@
-<!-- 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/. -->
-
-<!-- LOCALIZATION NOTE : FILE This file contains the Promise debugger panel
-     strings. The Promise debugger panel is part of the debugger -->
-<!-- LOCALIZATION NOTE : FILE Do not translate commandkey -->
-
-<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
-  - keep it in English, or another language commonly spoken among web developers.
-  - You want to make that choice consistent across the developer tools.
-  - A good criteria is the language in which you'd find the best
-  - documentation on web development on the web. -->
-
-<!ENTITY title "Promise Debugger">
--- a/devtools/client/moz.build
+++ b/devtools/client/moz.build
@@ -17,17 +17,16 @@ DIRS += [
     'inspector',
     'jsonview',
     'locales',
     'memory',
     'netmonitor',
     'performance',
     'preferences',
     'projecteditor',
-    'promisedebugger',
     'responsive.html',
     'responsivedesign',
     'scratchpad',
     'shadereditor',
     'shared',
     'shims',
     'sourceeditor',
     'storage',
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -95,17 +95,16 @@ pref("devtools.debugger.remote-timeout",
 pref("devtools.debugger.pause-on-exceptions", false);
 pref("devtools.debugger.ignore-caught-exceptions", true);
 pref("devtools.debugger.source-maps-enabled", true);
 pref("devtools.debugger.client-source-maps-enabled", true);
 pref("devtools.debugger.pretty-print-enabled", true);
 pref("devtools.debugger.auto-pretty-print", false);
 pref("devtools.debugger.auto-black-box", true);
 pref("devtools.debugger.workers", false);
-pref("devtools.debugger.promise", false);
 
 #if defined(NIGHTLY_BUILD)
 pref("devtools.debugger.new-debugger-frontend", true);
 #else
 pref("devtools.debugger.new-debugger-frontend", false);
 #endif
 
 // The default Debugger UI settings
deleted file mode 100644
--- a/devtools/client/promisedebugger/moz.build
+++ /dev/null
@@ -1,8 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-DevToolsModules(
-)
deleted file mode 100644
--- a/devtools/client/promisedebugger/promise-controller.js
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* global PromisesPanel */
-
-"use strict";
-
-var { utils: Cu } = Components;
-const { loader, require } =
-  Cu.import("resource://devtools/shared/Loader.jsm", {});
-
-const { Task } = require("devtools/shared/task");
-
-loader.lazyRequireGetter(this, "promise");
-loader.lazyRequireGetter(this, "EventEmitter",
-  "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "DevToolsUtils",
-  "devtools/shared/DevToolsUtils");
-loader.lazyRequireGetter(this, "PromisesFront",
-  "devtools/server/actors/promises", true);
-
-// Global toolbox, set when startup is called.
-var gToolbox;
-
-/**
- * Initialize the promise debugger controller and view upon loading the iframe.
- */
-var startup = Task.async(function* (toolbox) {
-  gToolbox = toolbox;
-
-  yield PromisesController.initialize(toolbox);
-  yield PromisesPanel.initialize();
-});
-
-/**
- * Destroy the promise debugger controller and view when unloading the iframe.
- */
-var shutdown = Task.async(function* () {
-  yield PromisesController.destroy();
-  yield PromisesPanel.destroy();
-
-  gToolbox = null;
-});
-
-function setPanel(toolbox) {
-  return startup(toolbox).catch(e =>
-    DevToolsUtils.reportException("setPanel", e));
-}
-
-function destroy() {
-  return shutdown().catch(e => DevToolsUtils.reportException("destroy", e));
-}
-
-/**
- * The promisedebugger controller's job is to retrieve PromisesFronts from the
- * server.
- */
-var PromisesController = {
-  initialize: Task.async(function* () {
-    if (this.initialized) {
-      return this.initialized.promise;
-    }
-
-    this.initialized = promise.defer();
-
-    let target = gToolbox.target;
-    this.promisesFront = new PromisesFront(target.client, target.form);
-    yield this.promisesFront.attach();
-
-    if (this.destroyed) {
-      console.warn("Could not fully initialize the PromisesController");
-      return null;
-    }
-
-    this.initialized.resolve();
-  }),
-
-  destroy: Task.async(function* () {
-    if (!this.initialized) {
-      return null;
-    }
-
-    if (this.destroyed) {
-      return this.destroyed.promise;
-    }
-
-    this.destroyed = promise.defer();
-
-    if (this.promisesFront) {
-      yield this.promisesFront.detach();
-      this.promisesFront.destroy();
-      this.promisesFront = null;
-    }
-
-    this.destroyed.resolve();
-  }),
-};
-
-EventEmitter.decorate(PromisesController);
deleted file mode 100644
--- a/devtools/client/promisedebugger/promise-debugger.xhtml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mkozilla 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/. -->
-
-<!DOCTYPE html [
-  <!ENTITY % promisedebuggerDTD SYSTEM "chrome://devtools/locale/promisedebugger.dtd">
-  %promisedebuggerDTD;
-]>
-
-<html xmlns="http://www.w3.org/1999/xhtml">
-  <head>
-    <title>&title;</title>
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
-  </head>
-  <body class="devtools-monospace" role="application">
-    <script type="application/javascript;version=1.8" src="promise-controller.js"></script>
-    <script type="application/javascript;version=1.8" src="promise-panel.js"></script>
-  </body>
-</html>
deleted file mode 100644
--- a/devtools/client/promisedebugger/promise-panel.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* global PromisesController, promise */
-/* import-globals-from promise-controller.js */
-
-"use strict";
-
-/**
- * The main promise debugger UI.
- */
-var PromisesPanel = {
-  PANEL_INITIALIZED: "panel-initialized",
-
-  initialize: Task.async(function* () {
-    if (PromisesController.destroyed) {
-      return null;
-    }
-    if (this.initialized) {
-      return this.initialized.promise;
-    }
-    this.initialized = promise.defer();
-
-    this.initialized.resolve();
-
-    this.emit(this.PANEL_INITIALIZED);
-  }),
-
-  destroy: Task.async(function* () {
-    if (!this.initialized) {
-      return null;
-    }
-    if (this.destroyed) {
-      return this.destroyed.promise;
-    }
-    this.destroyed = promise.defer();
-
-    this.destroyed.resolve();
-  }),
-};
-
-EventEmitter.decorate(PromisesPanel);
deleted file mode 100644
--- a/devtools/client/promisedebugger/test/.eslintrc.js
+++ /dev/null
@@ -1,6 +0,0 @@
-"use strict";
-
-module.exports = {
-  // Extend from the shared list of defined globals for mochitests.
-  "extends": "../../../.eslintrc.mochitests.js"
-};
deleted file mode 100644
--- a/devtools/client/promisedebugger/test/head.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
--- a/devtools/client/responsive.html/browser/tunnel.js
+++ b/devtools/client/responsive.html/browser/tunnel.js
@@ -104,16 +104,27 @@ function tunnelToInnerBrowser(outer, inn
             return outer[FRAME_LOADER];
           }
           return inner.frameLoader;
         },
         configurable: true,
         enumerable: true,
       });
 
+      // The `outerWindowID` of the content is used by browser actions like view source
+      // and print.  They send the ID down to the client to find the right content frame
+      // to act on.
+      Object.defineProperty(outer, "outerWindowID", {
+        get() {
+          return inner.outerWindowID;
+        },
+        configurable: true,
+        enumerable: true,
+      });
+
       // The `permanentKey` property on a <xul:browser> is used to index into various maps
       // held by the session store.  When you swap content around with
       // `_swapBrowserDocShells`, these keys are also swapped so they follow the content.
       // This means the key that matches the content is on the inner browser.  Since we
       // want the browser UI to believe the page content is part of the outer browser, we
       // copy the content's `permanentKey` up to the outer browser.
       copyPermanentKey(outer, inner);
 
@@ -269,16 +280,17 @@ function tunnelToInnerBrowser(outer, inn
       mmTunnel.destroy();
       mmTunnel = null;
 
       // Reset overridden XBL properties and methods.  Deleting the override
       // means it will fallback to the original XBL binding definitions which
       // are on the prototype.
       delete outer.frameLoader;
       delete outer[FRAME_LOADER];
+      delete outer.outerWindowID;
 
       // Invalidate outer's permanentKey so that SessionStore stops associating
       // things that happen to the outer browser with the content inside in the
       // inner browser.
       outer.permanentKey = { id: "zombie" };
 
       browserWindow = null;
       gBrowser = null;
@@ -401,31 +413,55 @@ MessageManagerTunnel.prototype = {
     "PageVisibility:Show",
     // Messages sent to SessionStore.jsm
     "SessionStore:update",
     // Messages sent to BrowserTestUtils.jsm
     "browser-test-utils:loadEvent",
   ],
 
   OUTER_TO_INNER_MESSAGE_PREFIXES: [
+    // Messages sent from nsContextMenu.js
+    "ContextMenu:",
     // Messages sent from DevTools
     "debug:",
     // Messages sent from findbar.xml
     "Findbar:",
     // Messages sent from RemoteFinder.jsm
     "Finder:",
+    // Messages sent from InlineSpellChecker.jsm
+    "InlineSpellChecker:",
+    // Messages sent from pageinfo.js
+    "PageInfo:",
+    // Messsage sent from printUtils.js
+    "Printing:",
+    // Messages sent from browser-social.js
+    "Social:",
+    "PageMetadata:",
+    // Messages sent from viewSourceUtils.js
+    "ViewSource:",
   ],
 
   INNER_TO_OUTER_MESSAGE_PREFIXES: [
+    // Messages sent to nsContextMenu.js
+    "ContextMenu:",
     // Messages sent to DevTools
     "debug:",
     // Messages sent to findbar.xml
     "Findbar:",
     // Messages sent to RemoteFinder.jsm
     "Finder:",
+    // Messages sent to pageinfo.js
+    "PageInfo:",
+    // Messsage sent from printUtils.js
+    "Printing:",
+    // Messages sent to browser-social.js
+    "Social:",
+    "PageMetadata:",
+    // Messages sent to viewSourceUtils.js
+    "ViewSource:",
   ],
 
   OUTER_TO_INNER_FRAME_SCRIPTS: [
     // DevTools server for OOP frames
     "resource://devtools/server/child.js"
   ],
 
   get outerParentMM() {
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -40,16 +40,22 @@
   --keyframes-marker-size: 10px;
   /* The color of the time graduation borders */
   --time-graduation-border-color: rgba(128, 136, 144, .5);
 }
 
 .animation {
   --timeline-border-color: var(--theme-body-color);
   --timeline-background-color: var(--theme-splitter-color);
+  /* The color of the endDelay hidden progress */
+  --enddelay-hidden-progress-color: var(--theme-graphs-grey);
+  /* The color of none fill mode */
+  --fill-none-color: var(--theme-highlight-gray);
+  /* The color of enable fill mode */
+  --fill-enable-color: var(--timeline-border-color);
 }
 
 .animation.cssanimation {
   --timeline-border-color: var(--theme-highlight-lightorange);
   --timeline-background-color: var(--theme-contrast-background);
 }
 
 .animation.csstransition {
@@ -339,76 +345,61 @@ body {
 .animation-timeline .animation-target {
   background-color: transparent;
 }
 
 .animation-timeline .animation .time-block {
   cursor: pointer;
 }
 
-/* Animation iterations */
-
-.animation-timeline .animation .iterations {
+/* Animation summary graph */
+.animation-timeline .animation .summary {
   position: absolute;
+  width: 100%;
   height: 100%;
-  box-sizing: border-box;
-
-  /* Iterations of the animation are displayed with a repeating linear-gradient
-     which size is dynamically changed from JS. The gradient only draws 1px
-     borders between each iteration. These borders must have the same color as
-     the border of this element */
-  background-image:
-    linear-gradient(to left,
-                    var(--timeline-border-color) 0,
-                    var(--timeline-border-color) 1px,
-                    transparent 1px,
-                    transparent 2px);
-  border: 1px solid var(--timeline-border-color);
-  /* Border-right is already handled by the gradient */
-  border-width: 1px 0 1px 1px;
-
-  /* The background color is set independently */
-  background-color: var(--timeline-background-color);
 }
 
-.animation-timeline .animation .iterations.infinite::before,
-.animation-timeline .animation .iterations.infinite::after {
-  content: "";
-  position: absolute;
-  top: 0;
-  right: 0;
-  width: 0;
-  height: 0;
-  border-right: 4px solid var(--theme-body-background);
-  border-top: 4px solid transparent;
-  border-bottom: 4px solid transparent;
+.animation-timeline .animation .summary path {
+  fill: var(--timeline-background-color);
+  stroke: var(--timeline-border-color);
 }
 
-.animation-timeline .animation .iterations.infinite::after {
-  bottom: 0;
-  top: unset;
+.animation-timeline .animation .summary .infinity.copied {
+  opacity: .3;
+}
+
+.animation-timeline .animation .summary path.delay-path.negative,
+.animation-timeline .animation .summary path.enddelay-path.negative {
+  fill: none;
+  stroke: var(--enddelay-hidden-progress-color);
+  stroke-dasharray: 2, 2;
 }
 
 .animation-timeline .animation .name {
   position: absolute;
-  color: var(--theme-selection-color);
+  color: var(--theme-selection-color3);
+  top: 0px;
+  left: 0px;
   height: 100%;
+  width: 100%;
   display: flex;
   align-items: center;
   padding: 0 2px;
   box-sizing: border-box;
   --fast-track-icon-width: 15px;
   z-index: 1;
 }
 
 .animation-timeline .animation .name div {
   /* Flex items don't support text-overflow, so a child div is used */
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
+  background-color: rgba(255, 255, 255, 0.7);
+  max-width: 50%;
 }
 
 .animation-timeline .fast-track .name div {
   width: calc(100% - var(--fast-track-icon-width));
 }
 
 .animation-timeline .fast-track .name::after {
   /* Animations running on the compositor have the fast-track background image*/
@@ -418,64 +409,67 @@ body {
   top: 1px;
   right: 0;
   height: 100%;
   width: var(--fast-track-icon-width);
   z-index: 1;
 }
 
 .animation-timeline .all-properties .name::after {
-  background-color: white;
+  background-color: var(--theme-content-color3);
   clip-path: url(images/animation-fast-track.svg#thunderbolt);
   background-repeat: no-repeat;
   background-position: center;
 }
 
 .animation-timeline .some-properties .name::after {
   background-color: var(--theme-content-color3);
   clip-path: url(images/animation-fast-track.svg#thunderbolt);
   background-repeat: no-repeat;
   background-position: center;
 }
 
 .animation-timeline .animation .delay,
 .animation-timeline .animation .end-delay {
   position: absolute;
-  height: 100%;
-  border: 1px solid var(--timeline-border-color);
-  box-sizing: border-box;
+  border-bottom: 3px solid var(--fill-none-color);
+  bottom: -0.5px;
 }
 
-.animation-timeline .animation .delay {
-  border-width: 1px 0 1px 1px;
-  background-image: repeating-linear-gradient(45deg,
-                                              transparent,
-                                              transparent 1px,
-                                              var(--theme-selection-color) 1px,
-                                              var(--theme-selection-color) 4px);
-  background-color: var(--timeline-border-color);
+.animation-timeline .animation .delay::after,
+.animation-timeline .animation .end-delay::after {
+  content: "";
+  position: absolute;
+  top: -2px;
+  width: 3px;
+  height: 3px;
+  border: 2px solid var(--fill-none-color);
+  background-color: var(--fill-none-color);
+  border-radius: 50%;
 }
 
-.animation-timeline .animation .end-delay {
-  border-width: 1px 1px 1px 0;
-  background-image: repeating-linear-gradient(
-                      -45deg,
-                      transparent,
-                      transparent 3px,
-                      var(--timeline-border-color) 3px,
-                      var(--timeline-border-color) 4px);
+.animation-timeline .animation .negative.delay::after,
+.animation-timeline .animation .positive.end-delay::after {
+  right: -3px;
+}
+
+.animation-timeline .animation .positive.delay::after,
+.animation-timeline .animation .negative.end-delay::after {
+  left: -3px;
 }
 
-.animation-timeline .animation .delay.negative,
-.animation-timeline .animation .end-delay.negative {
-  /* Negative delays are displayed on top of the animation, so they need a
-     right border. Whereas normal delays are displayed just before the
-     animation, so there's already the animation's left border that serves as
-     a separation. */
-  border-width: 1px;
+.animation-timeline .animation .fill.delay,
+.animation-timeline .animation .fill.end-delay {
+  border-color: var(--fill-enable-color);
+}
+
+.animation-timeline .animation .fill.delay::after,
+.animation-timeline .animation .fill.end-delay::after {
+  border-color: var(--fill-enable-color);
+  background-color: var(--fill-enable-color);
 }
 
 /* Animation target node gutter, contains a preview of the dom node */
 
 .animation-target {
   background-color: var(--theme-toolbar-background);
   padding: 0 4px;
   box-sizing: border-box;
--- a/devtools/client/themes/debugger.css
+++ b/devtools/client/themes/debugger.css
@@ -124,20 +124,16 @@
   -moz-image-region: rect(0,16px,16px,0);
 }
 
 #toggle-breakpoints[checked] > image {
   /* This button has a special checked image, don't make it blue */
   filter: none;
 }
 
-#toggle-promise-debugger {
-  /* TODO Bug 1186119: Add a toggle promise debugger image */
-}
-
 #sources .black-boxed {
   color: rgba(128,128,128,0.4);
 }
 
 #sources .selected .black-boxed {
   color: rgba(255,255,255,0.4);
 }
 
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -233,16 +233,40 @@ var AnimationPlayerActor = protocol.Acto
    * Get the animation iterationStart from this player, in ratio.
    * That is offset of starting position of the animation.
    * @return {Number}
    */
   getIterationStart: function () {
     return this.player.effect.getComputedTiming().iterationStart;
   },
 
+  /**
+   * Get the animation easing from this player.
+   * @return {String}
+   */
+  getEasing: function () {
+    return this.player.effect.timing.easing;
+  },
+
+  /**
+   * Get the animation fill mode from this player.
+   * @return {String}
+   */
+  getFill: function () {
+    return this.player.effect.getComputedTiming().fill;
+  },
+
+  /**
+   * Get the animation direction from this player.
+   * @return {String}
+   */
+  getDirection: function () {
+    return this.player.effect.getComputedTiming().direction;
+  },
+
   getPropertiesCompositorStatus: function () {
     let properties = this.player.effect.getProperties();
     return properties.map(prop => {
       return {
         property: prop.property,
         runningOnCompositor: prop.runningOnCompositor,
         warning: prop.warning
       };
@@ -275,16 +299,19 @@ var AnimationPlayerActor = protocol.Acto
       playState: this.player.playState,
       playbackRate: this.player.playbackRate,
       name: this.getName(),
       duration: this.getDuration(),
       delay: this.getDelay(),
       endDelay: this.getEndDelay(),
       iterationCount: this.getIterationCount(),
       iterationStart: this.getIterationStart(),
+      fill: this.getFill(),
+      easing: this.getEasing(),
+      direction: this.getDirection(),
       // animation is hitting the fast path or not. Returns false whenever the
       // animation is paused as it is taken off the compositor then.
       isRunningOnCompositor:
         this.getPropertiesCompositorStatus()
             .some(propState => propState.runningOnCompositor),
       propertyState: this.getPropertiesCompositorStatus(),
       // The document timeline's currentTime is being sent along too. This is
       // not strictly related to the node's animationPlayer, but is useful to
@@ -392,16 +419,30 @@ var AnimationPlayerActor = protocol.Acto
   ready: function () {
     return this.player.ready;
   },
 
   /**
    * Set the current time of the animation player.
    */
   setCurrentTime: function (currentTime) {
+    // The spec is that the progress of animation is changed
+    // if the time of setCurrentTime is during the endDelay.
+    // We should prevent the time
+    // to make the same animation behavior as the original.
+    // Likewise, in case the time is less than 0.
+    const timing = this.player.effect.getComputedTiming();
+    if (timing.delay < 0) {
+      currentTime += timing.delay;
+    }
+    if (currentTime < 0) {
+      currentTime = 0;
+    } else if (currentTime * this.player.playbackRate > timing.endTime) {
+      currentTime = timing.endTime;
+    }
     this.player.currentTime = currentTime * this.player.playbackRate;
   },
 
   /**
    * Set the playback rate of the animation player.
    */
   setPlaybackRate: function (playbackRate) {
     this.player.playbackRate = playbackRate;
--- a/devtools/server/tests/browser/animation.html
+++ b/devtools/server/tests/browser/animation.html
@@ -23,28 +23,31 @@
   .multiple-animations {
     display: inline-block;
 
     width: 50px;
     height: 50px;
     border-radius: 50%;
     background: #eee;
 
-    animation: move 200s infinite, glow 100s 5;
+    animation: move 200s infinite , glow 100s 5;
+    animation-timing-function: ease-out;
+    animation-direction: reverse;
+    animation-fill-mode: both;
   }
 
   .transition {
     display: inline-block;
 
     width: 50px;
     height: 50px;
     border-radius: 50%;
     background: #f06;
 
-    transition: width 500s;
+    transition: width 500s ease-out;
   }
   .transition.get-round {
     width: 200px;
   }
 
   .long-animation {
     display: inline-block;
 
--- a/devtools/server/tests/browser/browser_animation_playerState.js
+++ b/devtools/server/tests/browser/browser_animation_playerState.js
@@ -26,71 +26,98 @@ function* playerHasAnInitialState(walker
   ok("currentTime" in player.initialState, "Player's state has currentTime");
   ok("playState" in player.initialState, "Player's state has playState");
   ok("playbackRate" in player.initialState, "Player's state has playbackRate");
   ok("name" in player.initialState, "Player's state has name");
   ok("duration" in player.initialState, "Player's state has duration");
   ok("delay" in player.initialState, "Player's state has delay");
   ok("iterationCount" in player.initialState,
      "Player's state has iterationCount");
+  ok("fill" in player.initialState, "Player's state has fill");
+  ok("easing" in player.initialState, "Player's state has easing");
+  ok("direction" in player.initialState, "Player's state has direction");
   ok("isRunningOnCompositor" in player.initialState,
      "Player's state has isRunningOnCompositor");
   ok("type" in player.initialState, "Player's state has type");
   ok("documentCurrentTime" in player.initialState,
      "Player's state has documentCurrentTime");
 }
 
 function* playerStateIsCorrect(walker, animations) {
   info("Checking the state of the simple animation");
 
-  let state = yield getAnimationStateForNode(walker, animations,
-                                             ".simple-animation", 0);
+  let player = yield getAnimationPlayerForNode(walker, animations,
+                                               ".simple-animation", 0);
+  let state = yield player.getCurrentState();
   is(state.name, "move", "Name is correct");
   is(state.duration, 200000, "Duration is correct");
   // null = infinite count
   is(state.iterationCount, null, "Iteration count is correct");
+  is(state.fill, "none", "Fill is correct");
+  is(state.easing, "linear", "Easing is correct");
+  is(state.direction, "normal", "Direction is correct");
   is(state.playState, "running", "PlayState is correct");
   is(state.playbackRate, 1, "PlaybackRate is correct");
   is(state.type, "cssanimation", "Type is correct");
 
   info("Checking the state of the transition");
 
-  state = yield getAnimationStateForNode(walker, animations, ".transition", 0);
+  player =
+    yield getAnimationPlayerForNode(walker, animations, ".transition", 0);
+  state = yield player.getCurrentState();
   is(state.name, "width", "Transition name matches transition property");
   is(state.duration, 500000, "Transition duration is correct");
   // transitions run only once
   is(state.iterationCount, 1, "Transition iteration count is correct");
+  is(state.fill, "backwards", "Transition fill is correct");
+  is(state.easing, "linear", "Transition easing is correct");
+  is(state.direction, "normal", "Transition direction is correct");
   is(state.playState, "running", "Transition playState is correct");
   is(state.playbackRate, 1, "Transition playbackRate is correct");
   is(state.type, "csstransition", "Transition type is correct");
+  // chech easing in keyframe
+  let keyframes = yield player.getFrames();
+  is(keyframes.length, 2, "Transition length of keyframe is correct");
+  is(keyframes[0].easing,
+     "ease-out", "Transition kerframes's easing is correct");
 
   info("Checking the state of one of multiple animations on a node");
 
   // Checking the 2nd player
-  state = yield getAnimationStateForNode(walker, animations,
-                                         ".multiple-animations", 1);
+  player = yield getAnimationPlayerForNode(walker, animations,
+                                           ".multiple-animations", 1);
+  state = yield player.getCurrentState();
   is(state.name, "glow", "The 2nd animation's name is correct");
   is(state.duration, 100000, "The 2nd animation's duration is correct");
   is(state.iterationCount, 5, "The 2nd animation's iteration count is correct");
+  is(state.fill, "both", "The 2nd animation's fill is correct");
+  is(state.easing, "linear", "The 2nd animation's easing is correct");
+  is(state.direction, "reverse", "The 2nd animation's direction is correct");
   is(state.playState, "running", "The 2nd animation's playState is correct");
   is(state.playbackRate, 1, "The 2nd animation's playbackRate is correct");
+  // chech easing in keyframe
+  keyframes = yield player.getFrames();
+  is(keyframes.length, 2, "The 2nd animation's length of keyframe is correct");
+  is(keyframes[0].easing,
+     "ease-out", "The 2nd animation's easing of kerframes is correct");
 
   info("Checking the state of an animation with delay");
 
-  state = yield getAnimationStateForNode(walker, animations,
-                                         ".delayed-animation", 0);
+  player = yield getAnimationPlayerForNode(walker, animations,
+                                           ".delayed-animation", 0);
+  state = yield player.getCurrentState();
   is(state.delay, 5000, "The animation delay is correct");
 
   info("Checking the state of an transition with delay");
 
-  state = yield getAnimationStateForNode(walker, animations,
-                                         ".delayed-transition", 0);
+  player = yield getAnimationPlayerForNode(walker, animations,
+                                           ".delayed-transition", 0);
+  state = yield player.getCurrentState();
   is(state.delay, 3000, "The transition delay is correct");
 }
 
-function* getAnimationStateForNode(walker, animations, nodeSelector, index) {
+function* getAnimationPlayerForNode(walker, animations, nodeSelector, index) {
   let node = yield walker.querySelector(walker.rootNode, nodeSelector);
   let players = yield animations.getAnimationPlayersForNode(node);
   let player = players[index];
   yield player.ready();
-  let state = yield player.getCurrentState();
-  return state;
+  return player;
 }
--- a/devtools/shared/fronts/animation.js
+++ b/devtools/shared/fronts/animation.js
@@ -60,16 +60,19 @@ const AnimationPlayerFront = FrontClassW
       playState: this._form.playState,
       playbackRate: this._form.playbackRate,
       name: this._form.name,
       duration: this._form.duration,
       delay: this._form.delay,
       endDelay: this._form.endDelay,
       iterationCount: this._form.iterationCount,
       iterationStart: this._form.iterationStart,
+      easing: this._form.easing,
+      fill: this._form.fill,
+      direction: this._form.direction,
       isRunningOnCompositor: this._form.isRunningOnCompositor,
       propertyState: this._form.propertyState,
       documentCurrentTime: this._form.documentCurrentTime
     };
   },
 
   /**
    * Executed when the AnimationPlayerActor emits a "changed" event. Used to
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -554,21 +554,21 @@ RTCPeerConnection.prototype = {
 
     rtcConfig.iceServers.forEach(server => {
       if (!server.urls) {
         throw new this._win.DOMException(msg + " - missing urls", "InvalidAccessError");
       }
       server.urls.forEach(urlStr => {
         let url = nicerNewURI(urlStr);
         if (url.scheme in { turn:1, turns:1 }) {
-          if (!server.username) {
+          if (server.username == undefined) {
             throw new this._win.DOMException(msg + " - missing username: " + urlStr,
                                              "InvalidAccessError");
           }
-          if (!server.credential) {
+          if (server.credential == undefined) {
             throw new this._win.DOMException(msg + " - missing credential: " + urlStr,
                                              "InvalidAccessError");
           }
           if (server.credentialType != "password") {
             this.logWarning("RTCConfiguration TURN credentialType \""+
                             server.credentialType +
                             "\" is not yet implemented. Treating as password."+
                             " https://bugzil.la/1247616");
--- a/dom/media/tests/mochitest/test_peerConnection_bug825703.html
+++ b/dom/media/tests/mochitest/test_peerConnection_bug825703.html
@@ -61,16 +61,18 @@ runNetworkTest(() => {
   makePC({ iceServers: [{ urls:"" }] }, "SyntaxError");
 
   makePC({ iceServers: [
     { urls:"stun:127.0.0.1" },
     { urls:"stun:localhost", foo:"" },
     { urls: ["stun:127.0.0.1", "stun:localhost"] },
     { urls:"stuns:localhost", foo:"" },
     { urls:"turn:[::1]:3478", username:"p", credential:"p" },
+    { urls:"turn:[::1]:3478", username:"", credential:"" },
+    { urls:"turns:[::1]:3478", username:"", credential:"" },
     { urls:"turn:localhost:3478?transport=udp", username:"p", credential:"p" },
     { urls: ["turn:[::1]:3478", "turn:localhost"], username:"p", credential:"p" },
     { urls:"turns:localhost:3478?transport=udp", username:"p", credential:"p" },
     { url:"stun:localhost", foo:"" },
     { url:"turn:localhost", username:"p", credential:"p" }
   ]});
 
   makePC({ iceServers: [{ urls: ["stun:127.0.0.1", ""] }] }, "SyntaxError");
--- a/gfx/cairo/cairo/src/moz.build
+++ b/gfx/cairo/cairo/src/moz.build
@@ -217,16 +217,17 @@ if CONFIG['MOZ_TREE_FREETYPE']:
 if CONFIG['GNU_CC'] or CONFIG['CLANG_CL']:
     CFLAGS += [
         '-Wno-enum-compare',
         '-Wno-int-to-pointer-cast',
         '-Wno-sign-compare',
         '-Wno-type-limits',
         '-Wno-missing-field-initializers',
         '-Wno-conversion',
+        '-Wno-unused-but-set-variable',
     ]
 if CONFIG['CLANG_CXX'] or CONFIG['CLANG_CL']:
     CFLAGS += [
         '-Wno-incompatible-pointer-types',
         '-Wno-tautological-compare',
         '-Wno-tautological-constant-out-of-range-compare',
         '-Wno-error=uninitialized',
     ]
--- a/layout/reftests/w3c-css/submitted/masking/mask-image-6.html
+++ b/layout/reftests/w3c-css/submitted/masking/mask-image-6.html
@@ -12,16 +12,17 @@
       div {
         width: 100px;
         height: 100px;
       }
       span {
         font-size: 100px;
         line-height: 100px;
         mask-image: url(support/transparent-100x50-blue-100x50.png);
+        mask-position: center;
         mask-repeat: repeat;
       }
 
     </style>
   </head>
   <body>
     <div>
       <span>A B</span>
--- a/layout/reftests/w3c-css/submitted/masking/mask-repeat-1-ref.html
+++ b/layout/reftests/w3c-css/submitted/masking/mask-repeat-1-ref.html
@@ -29,19 +29,17 @@
 
       #repeat-y {
         position: absolute;
         width: 50px; height: 100%;
       }
     </style>
   </head>
   <body>
-    <div class="outer">
-      <div class="color" id="default"></div>
-    </div>
+    <div class="outer color"></div>
     <div class="outer">
       <div class="color" id="default"></div>
     </div>
     <div class="outer color"></div>
     <div class="outer">
       <div class="color" id="repeat-x"></div>
     </div>
     <div class="outer">
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -12241,30 +12241,22 @@ CSSParserImpl::ParseImageLayersItem(
   RefPtr<nsCSSValue::Array> positionXArr = nsCSSValue::Array::Create(2);
   RefPtr<nsCSSValue::Array> positionYArr = nsCSSValue::Array::Create(2);
   aState.mPositionX->mValue.SetArrayValue(positionXArr, eCSSUnit_Array);
   aState.mPositionY->mValue.SetArrayValue(positionYArr, eCSSUnit_Array);
 
   if (eCSSProperty_mask == aTable[nsStyleImageLayers::shorthand]) {
     aState.mOrigin->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ORIGIN_BORDER,
                                        eCSSUnit_Enumerated);
-    aState.mRepeat->mXValue.SetIntValue(NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT,
-                                        eCSSUnit_Enumerated);
-
-    positionXArr->Item(1).SetPercentValue(0.5f);
-    positionYArr->Item(1).SetPercentValue(0.5f);
   } else {
     aState.mOrigin->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ORIGIN_PADDING,
                                        eCSSUnit_Enumerated);
-    aState.mRepeat->mXValue.SetIntValue(NS_STYLE_IMAGELAYER_REPEAT_REPEAT,
-                                        eCSSUnit_Enumerated);
-
-    positionXArr->Item(1).SetPercentValue(0.0f);
-    positionYArr->Item(1).SetPercentValue(0.0f);
-  }
+  }
+  positionXArr->Item(1).SetPercentValue(0.0f);
+  positionYArr->Item(1).SetPercentValue(0.0f);
 
   aState.mSize->mXValue.SetAutoValue();
   aState.mSize->mYValue.SetAutoValue();
   aState.mComposite->mValue.SetIntValue(NS_STYLE_MASK_COMPOSITE_ADD,
                                         eCSSUnit_Enumerated);
   aState.mMode->mValue.SetIntValue(NS_STYLE_MASK_MODE_MATCH_SOURCE,
                                    eCSSUnit_Enumerated);
   bool haveColor = false,
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -6137,17 +6137,17 @@ nsComputedDOMStyle::DoGetMask()
   // a longhand.
   if (svg->mMask.mImageCount > 1 ||
       firstLayer.mClip != NS_STYLE_IMAGELAYER_CLIP_BORDER ||
       firstLayer.mOrigin != NS_STYLE_IMAGELAYER_ORIGIN_BORDER ||
       firstLayer.mComposite != NS_STYLE_MASK_COMPOSITE_ADD ||
       firstLayer.mMaskMode != NS_STYLE_MASK_MODE_MATCH_SOURCE ||
       !nsStyleImageLayers::IsInitialPositionForLayerType(
         firstLayer.mPosition, nsStyleImageLayers::LayerType::Mask) ||
-      !firstLayer.mRepeat.IsInitialValue(nsStyleImageLayers::LayerType::Mask) ||
+      !firstLayer.mRepeat.IsInitialValue() ||
       !firstLayer.mSize.IsInitialValue() ||
       !(firstLayer.mImage.GetType() == eStyleImageType_Null ||
         firstLayer.mImage.GetType() == eStyleImageType_Image)) {
     return nullptr;
   }
 
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
 
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -7254,17 +7254,17 @@ nsRuleNode::ComputeBackgroundData(void* 
                     parentBG->mImage.mLayers,
                     &nsStyleImageLayers::Layer::mImage,
                     initialImage, parentBG->mImage.mImageCount,
                     bg->mImage.mImageCount,
                     maxItemCount, rebuild, conditions);
 
   // background-repeat: enum, inherit, initial [pair list]
   nsStyleImageLayers::Repeat initialRepeat;
-  initialRepeat.SetInitialValues(nsStyleImageLayers::LayerType::Background);
+  initialRepeat.SetInitialValues();
   SetImageLayerPairList(aContext, *aRuleData->ValueForBackgroundRepeat(),
                         bg->mImage.mLayers,
                         parentBG->mImage.mLayers,
                         &nsStyleImageLayers::Layer::mRepeat,
                         initialRepeat, parentBG->mImage.mRepeatCount,
                         bg->mImage.mRepeatCount, maxItemCount, rebuild,
                         conditions);
 
@@ -7303,19 +7303,17 @@ nsRuleNode::ComputeBackgroundData(void* 
                     &nsStyleImageLayers::Layer::mOrigin,
                     uint8_t(NS_STYLE_IMAGELAYER_ORIGIN_PADDING),
                     parentBG->mImage.mOriginCount,
                     bg->mImage.mOriginCount, maxItemCount, rebuild,
                     conditions);
 
   // background-position-x/y: enum, length, percent (flags), inherit [list]
   Position::Coord initialPositionCoord;
-  initialPositionCoord.mPercent =
-    nsStyleImageLayers::GetInitialPositionForLayerType(
-      nsStyleImageLayers::LayerType::Background);
+  initialPositionCoord.mPercent = 0.0f;
   initialPositionCoord.mLength = 0;
   initialPositionCoord.mHasPercent = true;
 
   SetImageLayerPositionCoordList(
                     aContext, *aRuleData->ValueForBackgroundPositionX(),
                     bg->mImage.mLayers,
                     parentBG->mImage.mLayers,
                     &Position::mXPosition,
@@ -9955,17 +9953,17 @@ nsRuleNode::ComputeSVGResetData(void* aS
                     &nsStyleImageLayers::Layer::mSourceURI,
                     RefPtr<css::URLValueData>(),
                     parentSVGReset->mMask.mImageCount,
                     svgReset->mMask.mImageCount,
                     maxItemCount, rebuild, conditions);
 
   // mask-repeat: enum, inherit, initial [pair list]
   nsStyleImageLayers::Repeat initialRepeat;
-  initialRepeat.SetInitialValues(nsStyleImageLayers::LayerType::Mask);
+  initialRepeat.SetInitialValues();
   SetImageLayerPairList(aContext, *aRuleData->ValueForMaskRepeat(),
                         svgReset->mMask.mLayers,
                         parentSVGReset->mMask.mLayers,
                         &nsStyleImageLayers::Layer::mRepeat,
                         initialRepeat, parentSVGReset->mMask.mRepeatCount,
                         svgReset->mMask.mRepeatCount, maxItemCount, rebuild,
                         conditions);
 
@@ -9986,19 +9984,17 @@ nsRuleNode::ComputeSVGResetData(void* aS
                     &nsStyleImageLayers::Layer::mOrigin,
                     uint8_t(NS_STYLE_IMAGELAYER_ORIGIN_BORDER),
                     parentSVGReset->mMask.mOriginCount,
                     svgReset->mMask.mOriginCount, maxItemCount, rebuild,
                     conditions);
 
   // mask-position-x/y: enum, length, percent (flags), inherit [list]
   Position::Coord initialPositionCoord;
-  initialPositionCoord.mPercent =
-    nsStyleImageLayers::GetInitialPositionForLayerType(
-      nsStyleImageLayers::LayerType::Mask);
+  initialPositionCoord.mPercent = 0.0f;
   initialPositionCoord.mLength = 0;
   initialPositionCoord.mHasPercent = true;
 
   SetImageLayerPositionCoordList(
                     aContext, *aRuleData->ValueForMaskPositionX(),
                     svgReset->mMask.mLayers,
                     parentSVGReset->mMask.mLayers,
                     &Position::mXPosition,
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2620,21 +2620,20 @@ nsStyleImageLayers::HasLayerWithImage() 
   }
 
   return false;
 }
 
 bool
 nsStyleImageLayers::IsInitialPositionForLayerType(Position aPosition, LayerType aType)
 {
-  float intialValue = nsStyleImageLayers::GetInitialPositionForLayerType(aType);
-  if (aPosition.mXPosition.mPercent == intialValue &&
+  if (aPosition.mXPosition.mPercent == 0.0f &&
       aPosition.mXPosition.mLength == 0 &&
       aPosition.mXPosition.mHasPercent &&
-      aPosition.mYPosition.mPercent == intialValue &&
+      aPosition.mYPosition.mPercent == 0.0f &&
       aPosition.mYPosition.mLength == 0 &&
       aPosition.mYPosition.mHasPercent) {
     return true;
   }
 
   return false;
 }
 
@@ -2760,43 +2759,16 @@ nsStyleImageLayers::Size::operator==(con
              "bad mHeightType for aOther");
 
   return mWidthType == aOther.mWidthType &&
          mHeightType == aOther.mHeightType &&
          (mWidthType != eLengthPercentage || mWidth == aOther.mWidth) &&
          (mHeightType != eLengthPercentage || mHeight == aOther.mHeight);
 }
 
-bool
-nsStyleImageLayers::Repeat::IsInitialValue(LayerType aType) const
-{
-  if (aType == LayerType::Background) {
-    return mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT &&
-           mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
-  } else {
-    MOZ_ASSERT(aType == LayerType::Mask);
-    return mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT &&
-           mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
-  }
-}
-
-void
-nsStyleImageLayers::Repeat::SetInitialValues(LayerType aType)
-{
-  if (aType == LayerType::Background) {
-    mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
-    mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
-  } else {
-    MOZ_ASSERT(aType == LayerType::Mask);
-
-    mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
-    mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
-  }
-}
-
 nsStyleImageLayers::Layer::Layer()
   : mClip(NS_STYLE_IMAGELAYER_CLIP_BORDER)
   , mAttachment(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL)
   , mBlendMode(NS_STYLE_BLEND_NORMAL)
   , mComposite(NS_STYLE_MASK_COMPOSITE_ADD)
   , mMaskMode(NS_STYLE_MASK_MODE_MATCH_SOURCE)
 {
   mImage.SetNull();
@@ -2805,21 +2777,19 @@ nsStyleImageLayers::Layer::Layer()
 
 nsStyleImageLayers::Layer::~Layer()
 {
 }
 
 void
 nsStyleImageLayers::Layer::Initialize(nsStyleImageLayers::LayerType aType)
 {
-  mRepeat.SetInitialValues(aType);
-
-  float initialPositionValue =
-    nsStyleImageLayers::GetInitialPositionForLayerType(aType);
-  mPosition.SetInitialPercentValues(initialPositionValue);
+  mRepeat.SetInitialValues();
+
+  mPosition.SetInitialPercentValues(0.0f);
 
   if (aType == LayerType::Background) {
     mOrigin = NS_STYLE_IMAGELAYER_ORIGIN_PADDING;
   } else {
     MOZ_ASSERT(aType == LayerType::Mask, "unsupported layer type.");
     mOrigin = NS_STYLE_IMAGELAYER_ORIGIN_BORDER;
   }
 }
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -665,20 +665,16 @@ struct nsStyleImageLayers {
   explicit nsStyleImageLayers(LayerType aType);
   nsStyleImageLayers(const nsStyleImageLayers &aSource);
   ~nsStyleImageLayers() {
     MOZ_COUNT_DTOR(nsStyleImageLayers);
   }
 
   static bool IsInitialPositionForLayerType(mozilla::Position aPosition, LayerType aType);
 
-  static float GetInitialPositionForLayerType(LayerType aType) {
-    return (aType == LayerType::Background) ? 0.0f : 0.5f;
-  }
-
   struct Size;
   friend struct Size;
   struct Size {
     struct Dimension : public nsStyleCoord::CalcValue {
       nscoord ResolveLengthPercentage(nscoord aAvailable) const {
         double d = double(mPercent) * double(aAvailable) + double(mLength);
         if (d < 0.0) {
           return 0;
@@ -740,25 +736,31 @@ struct nsStyleImageLayers {
   struct Repeat;
   friend struct Repeat;
   struct Repeat {
     uint8_t mXRepeat, mYRepeat;
 
     // Initialize nothing
     Repeat() {}
 
-    bool IsInitialValue(LayerType aType) const;
+    bool IsInitialValue() const {
+      return mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT &&
+             mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+    }
 
     bool DependsOnPositioningAreaSize() const {
       return mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_SPACE ||
              mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_SPACE;
     }
 
     // Initialize to initial values
-    void SetInitialValues(LayerType aType);
+    void SetInitialValues() {
+      mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+      mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+    }
 
     bool operator==(const Repeat& aOther) const {
       return mXRepeat == aOther.mXRepeat &&
              mYRepeat == aOther.mYRepeat;
     }
     bool operator!=(const Repeat& aOther) const {
       return !(*this == aOther);
     }
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -7016,78 +7016,78 @@ function SupportsMaskShorthand() {
 
 if (SupportsMaskShorthand()) {
   gCSSProperties["mask"] = {
     domProp: "mask",
     inherited: false,
     type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
     /* FIXME: All mask-border-* should be added when we implement them. */
     subproperties: ["mask-clip", "mask-image", "mask-mode", "mask-origin", "mask-position-x", "mask-position-y", "mask-repeat", "mask-size" , "mask-composite"],
-    initial_values: [ "match-source", "none", "no-repeat", "add", "50% 50%", "center center", "50% 50% / auto", "center / auto", "center center / auto", "50% 50% / auto auto",
-      "center none", "center center none", "none center", "none center center", "none 50% 50%", "center / auto none",
-      "center center / auto auto none",
-      "match-source none no-repeat add center center", "center center no-repeat none add", "none no-repeat add center center / auto", "center center / auto no-repeat none add match-source", "none no-repeat add 50% 50% / auto auto match-source",
+    initial_values: [ "match-source", "none", "repeat", "add", "0% 0%", "top left", "0% 0% / auto", "top left / auto", "left top / auto", "0% 0% / auto auto",
+      "top left none", "left top none", "none left top", "none top left", "none 0% 0%", "top left / auto none", "left top / auto none",
+      "top left / auto auto none",
+      "match-source none repeat add top left", "top left repeat none add", "none repeat add top left / auto", "top left / auto repeat none add match-source", "none repeat add 0% 0% / auto auto match-source",
       "border-box", "border-box border-box" ],
     other_values: [
-      "none alpha no-repeat add center",
+      "none alpha repeat add left top",
       "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==)",
-      "no-repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==') alpha center add",
+      "no-repeat url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAKElEQVR42u3NQQ0AAAgEoNP+nTWFDzcoQE1udQQCgUAgEAgEAsGTYAGjxAE/G/Q2tQAAAABJRU5ErkJggg==') alpha left top add",
       "repeat-x",
       "repeat-y",
-      "repeat",
-      "none repeat-y alpha add 50% 50%",
+      "no-repeat",
+      "none repeat-y alpha add 0% 0%",
       "subtract",
-      "50% center subtract alpha no-repeat none",
+      "0% top subtract alpha repeat none",
       "top",
       "left",
-      "0% 0%",
-      "top left",
-      "center / 100px",
-      "center / contain",
-      "center / cover",
+      "50% 50%",
+      "center",
+      "top / 100px",
+      "left / contain",
+      "left / cover",
       "10px / 10%",
       "10em / calc(20px)",
-      "center center / 100px 100px",
-      "center center / 100px auto",
-      "center center / 100px 10%",
-      "center center / 100px calc(20px)",
-      "bottom right add none alpha no-repeat",
-      "0% alpha",
-      "alpha 0%",
-      "0%",
+      "top left / 100px 100px",
+      "top left / 100px auto",
+      "top left / 100px 10%",
+      "top left / 100px calc(20px)",
+      "bottom right add none alpha repeat",
+      "50% alpha",
+      "alpha 50%",
+      "50%",
       "url(#mymask)",
-      "-moz-radial-gradient(10% bottom, #ffffff, black) add repeat",
-      "-moz-linear-gradient(10px 10px -45deg, red, blue) no-repeat",
-      "-moz-linear-gradient(10px 10px -0.125turn, red, blue) no-repeat",
-      "-moz-repeating-radial-gradient(10% bottom, #ffffff, black) add repeat",
-      "-moz-repeating-linear-gradient(10px 10px -45deg, red, blue) no-repeat",
+      "-moz-radial-gradient(10% bottom, #ffffff, black) add no-repeat",
+      "-moz-linear-gradient(10px 10px -45deg, red, blue) repeat",
+      "-moz-linear-gradient(10px 10px -0.125turn, red, blue) repeat",
+      "-moz-repeating-radial-gradient(10% bottom, #ffffff, black) add no-repeat",
+      "-moz-repeating-linear-gradient(10px 10px -45deg, red, blue) repeat",
       "-moz-element(#test) alpha",
       /* multiple mask-image */
       "url(404.png), url(404.png)",
       "repeat-x, subtract, none",
-      "50% top url(404.png), url(404.png) 50% top",
+      "0% top url(404.png), url(404.png) 50% top",
       "subtract repeat-y top left url(404.png), repeat-x alpha",
       "url(404.png), -moz-linear-gradient(20px 20px -45deg, blue, green), -moz-element(#a) alpha",
       "top left / contain, bottom right / cover",
       /* test cases with clip+origin in the shorthand */
       "url(404.png) alpha padding-box",
       "url(404.png) border-box alpha",
       "content-box url(404.png)",
       "url(404.png) alpha padding-box padding-box",
       "url(404.png) alpha padding-box border-box",
       "content-box border-box url(404.png)",
     ],
     invalid_values: [
       /* mixes with keywords have to be in correct order */
       "50% left", "top 50%",
       /* no quirks mode colors */
-      "-moz-radial-gradient(10% bottom, ffffff, black) add repeat",
+      "-moz-radial-gradient(10% bottom, ffffff, black) add no-repeat",
       /* no quirks mode lengths */
-      "-moz-linear-gradient(10 10px -45deg, red, blue) no-repeat",
-      "-moz-linear-gradient(10px 10 -45deg, red, blue) no-repeat",
+      "-moz-linear-gradient(10 10px -45deg, red, blue) repeat",
+      "-moz-linear-gradient(10px 10 -45deg, red, blue) repeat",
       "linear-gradient(red -99, yellow, green, blue 120%)",
       /* bug 258080: don't accept background-position separated */
       "left url(404.png) top", "top url(404.png) left",
       "alpha padding-box url(404.png) border-box",
       "alpha padding-box url(404.png) padding-box",
       "-moz-element(#a rubbish)",
       "left top / match-source"
     ]
@@ -7142,19 +7142,18 @@ if (SupportsMaskShorthand()) {
     initial_values: [ "border-box" ],
     other_values: [ "padding-box", "content-box", "border-box, padding-box", "padding-box, padding-box, padding-box", "border-box, border-box" ],
     invalid_values: [ "margin-box", "padding-box padding-box" ]
   };
   gCSSProperties["mask-position"] = {
     domProp: "maskPosition",
     inherited: false,
     type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
-    initial_values: [ "left 50% top 50%", "left 50% center", "center", "center center", "50% 50%", "50% center", "center 50%" ],
-    other_values: [ "top", "left", "right", "bottom", "center bottom", "bottom center", "center right", "right center", "center top", "top center", "center left", "left center", "right bottom", "bottom right", "0%", "top left, top left", "top left, top right", "top right, top left", "left top, 0% 0%", "10% 20%, 30%, 40%", "top left, bottom right", "right bottom, left top", "0%", "0px", "30px", "0%, 10%, 20%, 30%", "top, top, top, top, top",
-      "top 0% left 0%", "top 0% left", "top left", "left top", "0% 0%", "0% top", "left 0%",
+    initial_values: [ "top 0% left 0%", "top 0% left", "top left", "left top", "0% 0%", "0% top", "left 0%" ],
+    other_values: [ "top", "left", "right", "bottom", "center", "center bottom", "bottom center", "center right", "right center", "center top", "top center", "center left", "left center", "right bottom", "bottom right", "50%", "top left, top left", "top left, top right", "top right, top left", "left top, 0% 0%", "10% 20%, 30%, 40%", "top left, bottom right", "right bottom, left top", "0%", "0px", "30px", "0%, 10%, 20%, 30%", "top, top, top, top, top",
       "calc(20px)",
       "calc(20px) 10px",
       "10px calc(20px)",
       "calc(20px) 25%",
       "25% calc(20px)",
       "calc(20px) calc(20px)",
       "calc(20px + 1em) calc(20px / 2)",
       "calc(20px + 50%) calc(50% - 10px)",
@@ -7186,18 +7185,18 @@ if (SupportsMaskShorthand()) {
                       "top bottom", "left 10% right",
                       "top 20px bottom 20px", "left left",
                       "0px calc(0px + rubbish)"],
   };
   gCSSProperties["mask-position-x"] = {
     domProp: "maskPositionX",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
-    initial_values: [ "center", "50%" ],
-    other_values: [ "right", "left", "0%", "left, left", "left, right", "right, left", "left, 0%", "10%, 20%, 40%", "0px", "30px", "0%, 10%, 20%, 30%", "left, left, left, left, left",
+    initial_values: [ "left", "0%" ],
+    other_values: [ "right", "center", "50%", "center, center", "center, right", "right, center", "center, 50%", "10%, 20%, 40%", "1px", "30px", "50%, 10%, 20%, 30%", "center, center, center, center, center",
       "calc(20px)",
       "calc(20px + 1em)",
       "calc(20px / 2)",
       "calc(20px + 50%)",
       "calc(50% - 10px)",
       "calc(-20px)",
       "calc(-50%)",
       "calc(-20%)",
@@ -7212,18 +7211,18 @@ if (SupportsMaskShorthand()) {
                       "bottom 20px", "top 10%", "bottom 3em",
                       "top", "bottom", "top, top", "top, bottom", "bottom, top", "top, 0%", "top, top, top, top, top",
                       "calc(0px + rubbish)", "center 0%"],
   };
   gCSSProperties["mask-position-y"] = {
     domProp: "maskPositionY",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
-    initial_values: [ "center", "50%" ],
-    other_values: [ "bottom", "top", "0%", "top, top", "top, bottom", "bottom, top", "top, 0%", "10%, 20%, 40%", "0px", "30px", "0%, 10%, 20%, 30%", "top, top, top, top, top",
+    initial_values: [ "top", "0%" ],
+    other_values: [ "bottom", "center", "50%", "center, center", "center, bottom", "bottom, center", "center, 0%", "10%, 20%, 40%", "1px", "30px", "50%, 10%, 20%, 30%", "center, center, center, center, center",
       "calc(20px)",
       "calc(20px + 1em)",
       "calc(20px / 2)",
       "calc(20px + 50%)",
       "calc(50% - 10px)",
       "calc(-20px)",
       "calc(-50%)",
       "calc(-20%)",
@@ -7238,25 +7237,26 @@ if (SupportsMaskShorthand()) {
                       "right 20px", "left 10%", "right 3em",
                       "left", "right", "left, left", "left, right", "right, left", "left, 0%", "left, left, left, left, left",
                       "calc(0px + rubbish)", "center 0%"],
   };
   gCSSProperties["mask-repeat"] = {
     domProp: "maskRepeat",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
-    initial_values: [ "no-repeat", "no-repeat no-repeat" ],
-    other_values: [ "repeat-x", "repeat-y", "repeat",
+    initial_values: [ "repeat", "repeat repeat" ],
+    other_values: [ "repeat-x", "repeat-y", "no-repeat",
       "repeat-x, repeat-x",
-      "no-repeat, repeat",
-      "repeat-y, repeat, repeat-y",
-      "no-repeat, no-repeat, no-repeat",
+      "repeat, no-repeat",
+      "repeat-y, no-repeat, repeat-y",
+      "repeat, repeat, repeat",
+      "repeat no-repeat",
       "no-repeat repeat",
+      "no-repeat no-repeat",
       "repeat no-repeat",
-      "repeat repeat",
       "no-repeat no-repeat, no-repeat no-repeat",
     ],
     invalid_values: [ "repeat repeat repeat",
                       "repeat-x repeat-y",
                       "repeat repeat-x",
                       "repeat repeat-y",
                       "repeat-x repeat",
                       "repeat-y repeat" ]
--- a/layout/style/test/test_computed_style.html
+++ b/layout/style/test/test_computed_style.html
@@ -272,24 +272,24 @@ var noframe_container = document.getElem
     // any mask-composite value other than "add".
     "mask-composite": [
       "subtract", "intersect", "exclude"
     ],
     // any mask-mode value other than "match-source".
     "mask-mode": [
       "alpha", "luminance"
     ],
-    // any mask-position value other then "50%" "50% 50%" "center"
+    // any mask-position value other then "0%" "top" "left"
     // "center center".
     "mask-position": [
-      "left", "right", "top", "bottom", "0%", "100%"
+      "0%", "center", "right", "bottom", "50%", "100%"
     ],
-    // any mask-repeat value other then "no-repeat" "no-repeat no-repeat".
+    // any mask-repeat value other then "repeat" "repeat repeat".
     "mask-repeat": [
-      "repeat-x", "repeat-y", "repeat", "space", "round"
+      "repeat-x", "repeat-y", "no-repeat", "space", "round"
     ],
     // any mask-size value other then "auto" "auto auto".
     "mask-size": [
       "10px", "100%", "cover", "contain", "auto 5px"
     ],
   };
 
   // "masks" object contains initial mask longhand values.
@@ -305,20 +305,20 @@ var noframe_container = document.getElem
     ],
     "mask-composite": [
       "add"
     ],
     "mask-mode": [
       "match-source"
     ],
     "mask-position": [
-      "50%", "50% 50%", "center"
+      "0% 0%", "left top"
     ],
     "mask-repeat": [
-      "no-repeat", "no-repeat no-repeat"
+      "repeat", "repeat repeat"
     ],
     "mask-size": [
       "auto", "auto auto"
     ],
   };
 
   var p = document.createElement("p");
   var cs = getComputedStyle(p, "");
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -875,16 +875,23 @@ nsMenuPopupFrame::ShowPopup(bool aIsCont
   InvalidateFrameSubtree();
 
   if (mPopupState == ePopupShowing || mPopupState == ePopupPositioning) {
     mPopupState = ePopupOpening;
     mIsOpenChanged = true;
 
     // Clear mouse capture when a popup is opened.
     if (mPopupType == ePopupTypeMenu) {
+      EventStateManager* activeESM =
+        static_cast<EventStateManager*>(
+          EventStateManager::GetActiveEventStateManager());
+      if (activeESM) {
+        EventStateManager::ClearGlobalActiveContent(activeESM);
+      }
+
       nsIPresShell::SetCapturingContent(nullptr, 0);
     }
 
     nsMenuFrame* menuFrame = do_QueryFrame(GetParent());
     if (menuFrame) {
       nsWeakFrame weakFrame(this);
       menuFrame->PopupOpened();
       if (!weakFrame.IsAlive())
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -238,21 +238,16 @@ nsresult NrIceStunServer::ToNicerStunStr
 
 nsresult NrIceTurnServer::ToNicerTurnStruct(nr_ice_turn_server *server) const {
   memset(server, 0, sizeof(nr_ice_turn_server));
 
   nsresult rv = ToNicerStunStruct(&server->turn_server);
   if (NS_FAILED(rv))
     return rv;
 
-  if (username_.empty())
-    return NS_ERROR_INVALID_ARG;
-  if (password_.empty())
-    return NS_ERROR_INVALID_ARG;
-
   if (!(server->username=r_strdup(username_.c_str())))
     return NS_ERROR_OUT_OF_MEMORY;
 
   // TODO(ekr@rtfm.com): handle non-ASCII passwords somehow?
   // STUN requires they be SASLpreped, but we don't know if
   // they are at this point.
 
   // C++03 23.2.4, Paragraph 1 stipulates that the elements
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
@@ -667,26 +667,30 @@ static int nr_ice_get_default_local_addr
   {
     int r,_status;
     nr_transport_addr default_addr;
     int i;
 
     if ((r=nr_ice_get_default_address(ctx, ip_version, &default_addr)))
         ABORT(r);
 
-    for(i=0; i<addr_ct; ++i) {
+    for (i=0; i < addr_ct; ++i) {
       if (!nr_transport_addr_cmp(&default_addr, &addrs[i].addr,
                                  NR_TRANSPORT_ADDR_CMP_MODE_ADDR)) {
         if ((r=nr_local_addr_copy(addrp, &addrs[i])))
           ABORT(r);
         break;
       }
     }
-    if (i==addr_ct)
-      ABORT(R_NOT_FOUND);
+
+    if (i == addr_ct) {
+      if ((r=nr_transport_addr_copy(&addrp->addr, &default_addr)))
+        ABORT(r);
+      strlcpy(addrp->addr.ifname, "default route", sizeof(addrp->addr.ifname));
+    }
 
     _status=0;
   abort:
     return(_status);
   }
 
 static int nr_ice_get_local_addresses(nr_ice_ctx *ctx)
   {
@@ -695,47 +699,50 @@ static int nr_ice_get_local_addresses(nr
     nr_local_addr *addrs = 0;
     int i,addr_ct;
     nr_local_addr default_addrs[2];
     int default_addr_ct = 0;
 
     if (!ctx->local_addrs) {
       /* First, gather all the local addresses we have */
       if((r=nr_stun_find_local_addresses(local_addrs,MAXADDRS,&addr_ct))) {
-        r_log(LOG_ICE,LOG_ERR,"ICE(%s): unable to find local addresses",ctx->label);
-        ABORT(r);
+        r_log(LOG_ICE,LOG_ERR,"ICE(%s): unable to gather local addresses, trying default route",ctx->label);
       }
 
-      if (ctx->force_net_interface[0]) {
+      if (ctx->force_net_interface[0] && addr_ct) {
         /* Limit us to only addresses on a single interface */
         int force_addr_ct = 0;
         for(i=0;i<addr_ct;i++){
           if (!strcmp(local_addrs[i].addr.ifname, ctx->force_net_interface)) {
             // copy it down in the array, if needed
             if (i != force_addr_ct) {
               if (r=nr_local_addr_copy(&local_addrs[force_addr_ct], &local_addrs[i])) {
                 ABORT(r);
               }
             }
             force_addr_ct++;
           }
         }
         addr_ct = force_addr_ct;
       }
 
-      if (ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS) {
+      if ((!addr_ct) || (ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS)) {
         /* Get just the default IPv4 and IPv6 addrs */
         if(!nr_ice_get_default_local_address(ctx, NR_IPV4, local_addrs, addr_ct,
                                              &default_addrs[default_addr_ct])) {
           ++default_addr_ct;
         }
         if(!nr_ice_get_default_local_address(ctx, NR_IPV6, local_addrs, addr_ct,
                                              &default_addrs[default_addr_ct])) {
           ++default_addr_ct;
         }
+        if (!default_addr_ct) {
+          r_log(LOG_ICE,LOG_ERR,"ICE(%s): failed to find default addresses",ctx->label);
+          ABORT(R_FAILED);
+        }
         addrs = default_addrs;
         addr_ct = default_addr_ct;
       }
       else {
         addrs = local_addrs;
       }
 
       /* Sort interfaces by preference */
--- a/media/mtransport/third_party/nICEr/src/net/local_addr.c
+++ b/media/mtransport/third_party/nICEr/src/net/local_addr.c
@@ -51,10 +51,11 @@ int nr_local_addr_fmt_info_string(nr_loc
 
     const char *type = (addr_type & NR_INTERFACE_TYPE_WIRED) ? "wired" :
                        (addr_type & NR_INTERFACE_TYPE_WIFI) ? "wifi" :
                        (addr_type & NR_INTERFACE_TYPE_MOBILE) ? "mobile" :
                        "unknown";
 
     snprintf(buf, len, "%s%s, estimated speed: %d kbps",
              vpn, type, addr->interface.estimated_speed);
+    buf[len - 1] = '\0';
     return (0);
   }
--- a/media/mtransport/third_party/nICEr/src/stun/addrs.c
+++ b/media/mtransport/third_party/nICEr/src/stun/addrs.c
@@ -144,51 +144,52 @@ abort:
       if (my_fn) free(my_fn);
     }
     return(_status);
 }
 
 static int
 stun_get_win32_addrs(nr_local_addr addrs[], int maxaddrs, int *count)
 {
-    int r,_status;
+    int r, _status;
     PIP_ADAPTER_ADDRESSES AdapterAddresses = NULL, tmpAddress = NULL;
-    ULONG buflen;
+    // recomended per https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915(v=vs.85).aspx
+    static const ULONG initialBufLen = 15000;
+    ULONG buflen = initialBufLen;
     char bin_hashed_ifname[NR_MD5_HASH_LENGTH];
     char hex_hashed_ifname[MAXIFNAME];
     int n = 0;
 
     *count = 0;
 
     if (maxaddrs <= 0)
-      ABORT(R_INTERNAL);
+      ABORT(R_BAD_ARGS);
 
-    /* Call GetAdaptersAddresses() twice.  First, just to get the buf length */
+    /* According to MSDN (see above) we have try GetAdapterAddresses() multiple times */
+    for (n = 0; n < 5; n++) {
+      AdapterAddresses = (PIP_ADAPTER_ADDRESSES) RMALLOC(buflen);
+      if (AdapterAddresses == NULL) {
+        r_log(NR_LOG_STUN, LOG_ERR, "Error allocating buf for GetAdaptersAddresses()");
+        ABORT(R_NO_MEMORY);
+      }
 
-    buflen = 0;
+      r = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, NULL, AdapterAddresses, &buflen);
+      if (r == NO_ERROR) {
+        break;
+      }
+      r_log(NR_LOG_STUN, LOG_ERR, "GetAdaptersAddresses() returned error (%d)", r);
+      RFREE(AdapterAddresses);
+    }
 
-    r = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, AdapterAddresses, &buflen);
-    if (r != ERROR_BUFFER_OVERFLOW) {
-      r_log(NR_LOG_STUN, LOG_ERR, "Error getting buf len from GetAdaptersAddresses()");
+    if (n >= 5) {
+      r_log(NR_LOG_STUN, LOG_ERR, "5 failures calling GetAdaptersAddresses()");
       ABORT(R_INTERNAL);
     }
 
-    AdapterAddresses = (PIP_ADAPTER_ADDRESSES) RMALLOC(buflen);
-    if (AdapterAddresses == NULL) {
-      r_log(NR_LOG_STUN, LOG_ERR, "Error allocating buf for GetAdaptersAddresses()");
-      ABORT(R_NO_MEMORY);
-    }
-
-    /* for real, this time */
-
-    r = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, AdapterAddresses, &buflen);
-    if (r != NO_ERROR) {
-      r_log(NR_LOG_STUN, LOG_ERR, "Error getting addresses from GetAdaptersAddresses()");
-      ABORT(R_INTERNAL);
-    }
+    n = 0;
 
     /* Loop through the adapters */
 
     for (tmpAddress = AdapterAddresses; tmpAddress != NULL; tmpAddress = tmpAddress->Next) {
 
       if (tmpAddress->OperStatus != IfOperStatusUp)
         continue;
 
@@ -253,17 +254,20 @@ nr_stun_is_duplicate_addr(nr_local_addr 
 
 static int
 stun_getifaddrs(nr_local_addr addrs[], int maxaddrs, int *count)
 {
   int r,_status;
   struct ifaddrs* if_addrs_head=NULL;
   struct ifaddrs* if_addr;
 
-  *count=0;
+  *count = 0;
+
+  if (maxaddrs <= 0)
+    ABORT(R_BAD_ARGS);
 
   if (getifaddrs(&if_addrs_head) == -1) {
     r_log(NR_LOG_STUN, LOG_ERR, "getifaddrs error e = %d", errno);
     ABORT(R_INTERNAL);
   }
 
   if_addr = if_addrs_head;
 
@@ -390,16 +394,17 @@ nr_stun_remove_duplicate_addrs(nr_local_
             if ((r=nr_local_addr_copy(&tmp[n], &addrs[i])))
                 ABORT(r);
             ++n;
         }
     }
 
     *count = n;
 
+    memset(addrs, 0, *count * sizeof(*addrs));
     /* copy temporary array into passed in/out array */
     for (i = 0; i < *count; ++i) {
         if ((r=nr_local_addr_copy(&addrs[i], &tmp[i])))
             ABORT(r);
     }
 
     _status = 0;
   abort:
@@ -407,30 +412,32 @@ nr_stun_remove_duplicate_addrs(nr_local_
     return _status;
 }
 
 #ifndef USE_PLATFORM_NR_STUN_GET_ADDRS
 
 int
 nr_stun_get_addrs(nr_local_addr addrs[], int maxaddrs, int drop_loopback, int drop_link_local, int *count)
 {
-    int _status=0;
+    int r,_status=0;
     int i;
     char typestr[100];
 
 #ifdef WIN32
     _status = stun_get_win32_addrs(addrs, maxaddrs, count);
 #else
     _status = stun_getifaddrs(addrs, maxaddrs, count);
 #endif
 
-    nr_stun_remove_duplicate_addrs(addrs, drop_loopback, drop_link_local, count);
+    if ((r=nr_stun_remove_duplicate_addrs(addrs, drop_loopback, drop_link_local, count)))
+      ABORT(r);
 
     for (i = 0; i < *count; ++i) {
-    nr_local_addr_fmt_info_string(addrs+i,typestr,sizeof(typestr));
-        r_log(NR_LOG_STUN, LOG_DEBUG, "Address %d: %s on %s, type: %s\n",
+      nr_local_addr_fmt_info_string(addrs+i,typestr,sizeof(typestr));
+      r_log(NR_LOG_STUN, LOG_DEBUG, "Address %d: %s on %s, type: %s\n",
             i,addrs[i].addr.as_string,addrs[i].addr.ifname,typestr);
     }
 
+abort:
     return _status;
 }
 
 #endif
--- a/media/mtransport/third_party/nICEr/src/stun/stun_util.c
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_util.c
@@ -114,22 +114,22 @@ nr_stun_xor_mapped_address(UINT4 magicCo
   abort:
     return _status;
 }
 
 int
 nr_stun_find_local_addresses(nr_local_addr addrs[], int maxaddrs, int *count)
 {
     int r,_status;
-    NR_registry *children = 0;
+    //NR_registry *children = 0;
+
+    *count = 0;
 
     if ((r=NR_reg_get_child_count(NR_STUN_REG_PREF_ADDRESS_PRFX, (unsigned int*)count)))
-        if (r == R_NOT_FOUND)
-            *count = 0;
-        else
+        if (r != R_NOT_FOUND)
             ABORT(r);
 
     if (*count == 0) {
         char allow_loopback;
         char allow_link_local;
 
         if ((r=NR_reg_get_char(NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, &allow_loopback))) {
             if (r == R_NOT_FOUND)
@@ -177,17 +177,17 @@ nr_stun_find_local_addresses(nr_local_ad
         }
     }
 #endif
 
   done:
 
      _status=0;
  abort:
-     RFREE(children);
+     //RFREE(children);
      return _status;
 }
 
 int
 nr_stun_different_transaction(UCHAR *msg, int len, nr_stun_message *req)
 {
     int _status;
     nr_stun_message_header header;
--- a/python/mozboot/mozboot/archlinux.py
+++ b/python/mozboot/mozboot/archlinux.py
@@ -163,18 +163,18 @@ class ArchlinuxBootstrapper(BaseBootstra
 
         self.run_as_root(command)
 
     def pacman_update(self):
         command = ['pacman', '-S', '--refresh']
 
         self.run_as_root(command)
 
-    def run(self, command):
-        subprocess.check_call(command, stdin=sys.stdin)
+    def run(self, command, env=None):
+        subprocess.check_call(command, stdin=sys.stdin, env=env)
 
     def download(self, uri):
         command = ['curl', '-L', '-O', uri]
         self.run(command)
 
     def unpack(self, path, name, ext):
         if ext == 'gz':
             compression = '-z'
@@ -184,18 +184,20 @@ class ArchlinuxBootstrapper(BaseBootstra
             compression == 'x'
 
         name = os.path.join(path, name) + '.tar.' + ext
         command = ['tar', '-x', compression, '-f', name, '-C', path]
         self.run(command)
 
     def makepkg(self, name):
         command = ['makepkg', '-s']
-        self.run(command)
-        pack = glob.glob(name + '*.tar.xz')[0]
+        makepkg_env = os.environ.copy()
+        makepkg_env['PKGEXT'] = '.pkg.tar.xz'
+        self.run(command, env=makepkg_env)
+        pack = glob.glob(name + '*.pkg.tar.xz')[0]
         command = ['pacman', '-U']
         if self.no_interactive:
             command.append('--noconfirm')
         command.append(pack)
         self.run_as_root(command)
 
     def aur_install(self, *packages):
         path = tempfile.mkdtemp()
--- a/python/mozbuild/mozbuild/backend/common.py
+++ b/python/mozbuild/mozbuild/backend/common.py
@@ -1,14 +1,15 @@
 # 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/.
 
 from __future__ import absolute_import, unicode_literals
 
+import cPickle as pickle
 import itertools
 import json
 import os
 
 import mozpack.path as mozpath
 
 from mozbuild.backend.base import BuildBackend
 
@@ -365,29 +366,28 @@ class CommonBackend(BuildBackend):
         self._write_unified_files(unified_source_mapping, ipdl_dir, poison_windows_h=False)
         self._handle_ipdl_sources(ipdl_dir, sorted_ipdl_sources, unified_source_mapping)
 
         for config in self._configs:
             self.backend_input_files.add(config.source)
 
         # Write out a machine-readable file describing every test.
         topobjdir = self.environment.topobjdir
-        with self._write_file(mozpath.join(topobjdir, 'all-tests.json')) as fh:
-            json.dump(self._test_manager.tests_by_path, fh)
+        with self._write_file(mozpath.join(topobjdir, 'all-tests.pkl'), mode='rb') as fh:
+            pickle.dump(dict(self._test_manager.tests_by_path), fh, protocol=2)
 
-        with self._write_file(mozpath.join(topobjdir, 'test-defaults.json')) as fh:
-            json.dump(self._test_manager.manifest_defaults, fh)
+        with self._write_file(mozpath.join(topobjdir, 'test-defaults.pkl'), mode='rb') as fh:
+            pickle.dump(self._test_manager.manifest_defaults, fh, protocol=2)
 
-        path = mozpath.join(self.environment.topobjdir, 'test-installs.json')
-        with self._write_file(path) as fh:
-            json.dump({k: v for k, v in self._test_manager.installs_by_path.items()
-                       if k in self._test_manager.deferred_installs},
-                      fh,
-                      sort_keys=True,
-                      indent=4)
+        path = mozpath.join(self.environment.topobjdir, 'test-installs.pkl')
+        with self._write_file(path, mode='rb') as fh:
+            pickle.dump({k: v for k, v in self._test_manager.installs_by_path.items()
+                         if k in self._test_manager.deferred_installs},
+                        fh,
+                        protocol=2)
 
         # Write out a machine-readable file describing binaries.
         with self._write_file(mozpath.join(topobjdir, 'binaries.json')) as fh:
             d = {
                 'shared_libraries': [s.to_dict() for s in self._binaries.shared_libraries],
                 'programs': [p.to_dict() for p in self._binaries.programs],
             }
             json.dump(d, fh, sort_keys=True, indent=4)
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -1,14 +1,15 @@
 # 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/.
 
 from __future__ import unicode_literals
 
+import cPickle as pickle
 import json
 import os
 import unittest
 
 from mozpack.manifests import (
     InstallManifest,
 )
 from mozunit import main
@@ -519,21 +520,21 @@ class TestRecursiveMakeBackend(BackendTe
         lines = [l.strip() for l in open(x_master, 'rt').readlines()]
         self.assertEqual(lines, [
             '; THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT MODIFY BY HAND.',
             '',
             '[include:dir1/xpcshell.ini]',
             '[include:xpcshell.ini]',
         ])
 
-        all_tests_path = mozpath.join(env.topobjdir, 'all-tests.json')
+        all_tests_path = mozpath.join(env.topobjdir, 'all-tests.pkl')
         self.assertTrue(os.path.exists(all_tests_path))
 
-        with open(all_tests_path, 'rt') as fh:
-            o = json.load(fh)
+        with open(all_tests_path, 'rb') as fh:
+            o = pickle.load(fh)
 
             self.assertIn('xpcshell.js', o)
             self.assertIn('dir1/test_bar.js', o)
 
             self.assertEqual(len(o['xpcshell.js']), 1)
 
     def test_test_manifest_pattern_matches_recorded(self):
         """Pattern matches in test manifests' support-files should be recorded."""
@@ -545,39 +546,39 @@ class TestRecursiveMakeBackend(BackendTe
         # done.
         entries = [e for e in m._dests.keys() if '**' in e]
         self.assertEqual(len(entries), 1)
         self.assertIn('support/**', entries[0])
 
     def test_test_manifest_deffered_installs_written(self):
         """Shared support files are written to their own data file by the backend."""
         env = self._consume('test-manifest-shared-support', RecursiveMakeBackend)
-        all_tests_path = mozpath.join(env.topobjdir, 'all-tests.json')
+        all_tests_path = mozpath.join(env.topobjdir, 'all-tests.pkl')
         self.assertTrue(os.path.exists(all_tests_path))
-        test_installs_path = mozpath.join(env.topobjdir, 'test-installs.json')
+        test_installs_path = mozpath.join(env.topobjdir, 'test-installs.pkl')
 
         with open(test_installs_path, 'r') as fh:
-            test_installs = json.load(fh)
+            test_installs = pickle.load(fh)
 
         self.assertEqual(set(test_installs.keys()),
                          set(['child/test_sub.js',
                               'child/data/**',
                               'child/another-file.sjs']))
         for key in test_installs.keys():
             self.assertIn(key, test_installs)
 
         test_files_manifest = mozpath.join(env.topobjdir,
                                            '_build_manifests',
                                            'install',
                                            '_test_files')
 
         # First, read the generated for ini manifest contents.
         m = InstallManifest(path=test_files_manifest)
 
-        # Then, synthesize one from the test-installs.json file. This should
+        # Then, synthesize one from the test-installs.pkl file. This should
         # allow us to re-create a subset of the above.
         synthesized_manifest = InstallManifest()
         for item, installs in test_installs.items():
             for install_info in installs:
                 if len(install_info) == 3:
                     synthesized_manifest.add_pattern_symlink(*install_info)
                 if len(install_info) == 2:
                     synthesized_manifest.add_symlink(*install_info)
@@ -845,21 +846,21 @@ class TestRecursiveMakeBackend(BackendTe
             # Destination and install manifest are relative to topobjdir.
             stem = '%s/android_eclipse/%s' % (env.topobjdir, project_name)
             self.assertIn(command_template % (stem, stem), lines)
 
     def test_install_manifests_package_tests(self):
         """Ensure test suites honor package_tests=False."""
         env = self._consume('test-manifests-package-tests', RecursiveMakeBackend)
 
-        all_tests_path = mozpath.join(env.topobjdir, 'all-tests.json')
+        all_tests_path = mozpath.join(env.topobjdir, 'all-tests.pkl')
         self.assertTrue(os.path.exists(all_tests_path))
 
-        with open(all_tests_path, 'rt') as fh:
-            o = json.load(fh)
+        with open(all_tests_path, 'rb') as fh:
+            o = pickle.load(fh)
             self.assertIn('mochitest.js', o)
             self.assertIn('not_packaged.java', o)
 
         man_dir = mozpath.join(env.topobjdir, '_build_manifests', 'install')
         self.assertTrue(os.path.isdir(man_dir))
 
         full = mozpath.join(man_dir, '_test_files')
         self.assertTrue(os.path.exists(full))
--- a/python/mozbuild/mozbuild/test/test_preprocessor.py
+++ b/python/mozbuild/mozbuild/test/test_preprocessor.py
@@ -614,23 +614,23 @@ class TestPreprocessor(unittest.TestCase
                               '//@line 6 "CWD/f.js"\n'
                               'fin\n').replace('CWD/',
                                                os.getcwd() + os.path.sep))
 
     def test_include_missing_file(self):
         with MockedOpen({'f': '#include foo\n'}):
             with self.assertRaises(Preprocessor.Error) as e:
                 self.pp.do_include('f')
-                self.assertEqual(e.key, 'FILE_NOT_FOUND')
+            self.assertEqual(e.exception.key, 'FILE_NOT_FOUND')
 
     def test_include_undefined_variable(self):
         with MockedOpen({'f': '#filter substitution\n#include @foo@\n'}):
             with self.assertRaises(Preprocessor.Error) as e:
                 self.pp.do_include('f')
-                self.assertEqual(e.key, 'UNDEFINED_VAR')
+            self.assertEqual(e.exception.key, 'UNDEFINED_VAR')
 
     def test_include_literal_at(self):
         files = {
             '@foo@': '#define foo foobarbaz\n',
             'f': '#include @foo@\n#filter substitution\n@foo@\n',
         }
 
         with MockedOpen(files):
--- a/python/mozbuild/mozbuild/test/test_testing.py
+++ b/python/mozbuild/mozbuild/test/test_testing.py
@@ -1,14 +1,15 @@
 # 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/.
 
 from __future__ import unicode_literals
 
+import cPickle as pickle
 import os
 import shutil
 import tempfile
 import unittest
 
 import mozpack.path as mozpath
 
 from mozfile import NamedTemporaryFile
@@ -16,18 +17,17 @@ from mozunit import main
 
 from mozbuild.base import MozbuildObject
 from mozbuild.testing import (
     TestMetadata,
     TestResolver,
 )
 
 
-ALL_TESTS_JSON = b'''
-{
+ALL_TESTS = {
     "accessible/tests/mochitest/actions/test_anchors.html": [
         {
             "dir_relpath": "accessible/tests/mochitest/actions",
             "expected": "pass",
             "file_relpath": "accessible/tests/mochitest/actions/test_anchors.html",
             "flavor": "a11y",
             "here": "/Users/gps/src/firefox/accessible/tests/mochitest/actions",
             "manifest": "/Users/gps/src/firefox/accessible/tests/mochitest/actions/a11y.ini",
@@ -149,41 +149,41 @@ ALL_TESTS_JSON = b'''
             "manifest": "/home/chris/m-c/devtools/client/markupview/test/browser.ini",
             "name": "browser_markupview_copy_image_data.js",
             "path": "/home/chris/m-c/obj-dbg/_tests/testing/mochitest/browser/devtools/client/markupview/test/browser_markupview_copy_image_data.js",
             "relpath": "devtools/client/markupview/test/browser_markupview_copy_image_data.js",
             "subsuite": "devtools",
             "tags": "devtools"
         }
    ]
-}'''.strip()
+}
 
-TEST_DEFAULTS = b'''{
-    "/Users/gps/src/firefox/toolkit/mozapps/update/test/unit/xpcshell_updater.ini": {"support-files": "\\ndata/**\\nxpcshell_updater.ini"}
-}'''
+TEST_DEFAULTS = {
+    "/Users/gps/src/firefox/toolkit/mozapps/update/test/unit/xpcshell_updater.ini": {"support-files": "\ndata/**\nxpcshell_updater.ini"}
+}
 
 
 class Base(unittest.TestCase):
     def setUp(self):
         self._temp_files = []
 
     def tearDown(self):
         for f in self._temp_files:
             del f
 
         self._temp_files = []
 
     def _get_test_metadata(self):
-        all_tests = NamedTemporaryFile()
-        all_tests.write(ALL_TESTS_JSON)
+        all_tests = NamedTemporaryFile(mode='wb')
+        pickle.dump(ALL_TESTS, all_tests)
         all_tests.flush()
         self._temp_files.append(all_tests)
 
-        test_defaults = NamedTemporaryFile()
-        test_defaults.write(TEST_DEFAULTS)
+        test_defaults = NamedTemporaryFile(mode='wb')
+        pickle.dump(TEST_DEFAULTS, test_defaults)
         test_defaults.flush()
         self._temp_files.append(test_defaults)
 
         return TestMetadata(all_tests.name, test_defaults=test_defaults.name)
 
 
 class TestTestMetadata(Base):
     def test_load(self):
@@ -246,20 +246,20 @@ class TestTestResolver(Base):
 
         for d in self._temp_dirs:
             shutil.rmtree(d)
 
     def _get_resolver(self):
         topobjdir = tempfile.mkdtemp()
         self._temp_dirs.append(topobjdir)
 
-        with open(os.path.join(topobjdir, 'all-tests.json'), 'wt') as fh:
-            fh.write(ALL_TESTS_JSON)
-        with open(os.path.join(topobjdir, 'test-defaults.json'), 'wt') as fh:
-            fh.write(TEST_DEFAULTS)
+        with open(os.path.join(topobjdir, 'all-tests.pkl'), 'wb') as fh:
+            pickle.dump(ALL_TESTS, fh)
+        with open(os.path.join(topobjdir, 'test-defaults.pkl'), 'wb') as fh:
+            pickle.dump(TEST_DEFAULTS, fh)
 
         o = MozbuildObject(self.FAKE_TOPSRCDIR, None, None, topobjdir=topobjdir)
 
         # Monkey patch the test resolver to avoid tests failing to find make
         # due to our fake topscrdir.
         TestResolver._run_make = lambda *a, **b: None
 
         return o._spawn(TestResolver)
--- a/python/mozbuild/mozbuild/testing.py
+++ b/python/mozbuild/mozbuild/testing.py
@@ -1,15 +1,15 @@
 # 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/.
 
 from __future__ import absolute_import, unicode_literals
 
-import json
+import cPickle as pickle
 import os
 import sys
 
 import mozpack.path as mozpath
 
 from mozpack.copier import FileCopier
 from mozpack.manifests import InstallManifest
 
@@ -51,22 +51,22 @@ class TestMetadata(object):
     configuration.
     """
 
     def __init__(self, all_tests, test_defaults=None):
         self._tests_by_path = OrderedDefaultDict(list)
         self._tests_by_flavor = defaultdict(set)
         self._test_dirs = set()
 
-        with open(all_tests, 'rt') as fh:
-            test_data = json.load(fh)
+        with open(all_tests, 'rb') as fh:
+            test_data = pickle.load(fh)
         defaults = None
         if test_defaults:
-            with open(test_defaults, 'rt') as fh:
-                defaults = json.load(fh)
+            with open(test_defaults, 'rb') as fh:
+                defaults = pickle.load(fh)
         for path, tests in test_data.items():
             for metadata in tests:
                 if defaults:
                     manifest = metadata['manifest']
                     manifest_defaults = defaults.get(manifest)
                     if manifest_defaults:
                         metadata = manifestparser.combine_fields(manifest_defaults,
                                                                  metadata)
@@ -173,24 +173,24 @@ class TestMetadata(object):
 class TestResolver(MozbuildObject):
     """Helper to resolve tests from the current environment to test files."""
 
     def __init__(self, *args, **kwargs):
         MozbuildObject.__init__(self, *args, **kwargs)
 
         # If installing tests is going to result in re-generating the build
         # backend, we need to do this here, so that the updated contents of
-        # all-tests.json make it to the set of tests to run.
+        # all-tests.pkl make it to the set of tests to run.
         self._run_make(target='run-tests-deps', pass_thru=True,
                        print_directory=False)
 
         self._tests = TestMetadata(os.path.join(self.topobjdir,
-                                                'all-tests.json'),
+                                                'all-tests.pkl'),
                                    test_defaults=os.path.join(self.topobjdir,
-                                                              'test-defaults.json'))
+                                                              'test-defaults.pkl'))
 
         self._test_rewrites = {
             'a11y': os.path.join(self.topobjdir, '_tests', 'testing',
                 'mochitest', 'a11y'),
             'browser-chrome': os.path.join(self.topobjdir, '_tests', 'testing',
                 'mochitest', 'browser'),
             'jetpack-package': os.path.join(self.topobjdir, '_tests', 'testing',
                 'mochitest', 'jetpack-package'),
@@ -412,19 +412,19 @@ class SupportFilesConverter(object):
                     info.installs.append((full, mozpath.normpath(dest_path)))
         return info
 
 def _resolve_installs(paths, topobjdir, manifest):
     """Using the given paths as keys, find any unresolved installs noted
     by the build backend corresponding to those keys, and add them
     to the given manifest.
     """
-    filename = os.path.join(topobjdir, 'test-installs.json')
-    with open(filename, 'r') as fh:
-        resolved_installs = json.load(fh)
+    filename = os.path.join(topobjdir, 'test-installs.pkl')
+    with open(filename, 'rb') as fh:
+        resolved_installs = pickle.load(fh)
 
     for path in paths:
         path = path[2:]
         if path not in resolved_installs:
             raise Exception('A cross-directory support file path noted in a '
                 'test manifest does not appear in any other manifest.\n "%s" '
                 'must appear in another test manifest to specify an install '
                 'for "!/%s".' % (path, path))
--- a/services/sync/modules/addonsreconciler.js
+++ b/services/sync/modules/addonsreconciler.js
@@ -440,16 +440,17 @@ AddonsReconciler.prototype = {
       this._addons[id] = record;
       this._log.debug("Adding change because add-on not present locally: " +
                       id);
       this._addChange(now, CHANGE_INSTALLED, record);
       return;
     }
 
     let record = this._addons[id];
+    record.isSyncable = addon.isSyncable;
 
     if (!record.installed) {
       // It is possible the record is marked as uninstalled because an
       // uninstall is pending.
       if (!(addon.pendingOperations & AddonManager.PENDING_UNINSTALL)) {
         record.installed = true;
         record.modified = now;
       }
--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -9,16 +9,17 @@
 
 #include "mozilla/AbstractThread.h"
 #include "mozilla/IndexSequence.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Tuple.h"
+#include "mozilla/TypeTraits.h"
 
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 
 extern LazyLogModule gMozPromiseLog;
 
@@ -963,23 +964,42 @@ public:
     return NS_OK;
   }
 
 private:
   RefPtr<typename PromiseType::Private> mProxyPromise;
   nsAutoPtr<MethodCall<PromiseType, ThisType, ArgTypes...>> mMethodCall;
 };
 
+constexpr bool Any()
+{
+  return false;
+}
+
+template <typename T1>
+constexpr bool Any(T1 a)
+{
+  return static_cast<bool>(a);
+}
+
+template <typename T1, typename... Ts>
+constexpr bool Any(T1 a, Ts... aOthers)
+{
+  return a || Any(aOthers...);
+}
+
 } // namespace detail
 
 template<typename PromiseType, typename ThisType, typename ...ArgTypes, typename ...ActualArgTypes>
 static RefPtr<PromiseType>
 InvokeAsync(AbstractThread* aTarget, ThisType* aThisVal, const char* aCallerName,
             RefPtr<PromiseType>(ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs)
 {
+  static_assert(!detail::Any(IsReference<ArgTypes>::value...),
+                "Cannot pass reference types through InvokeAsync, see bug 1313497 if you require it");
   typedef detail::MethodCall<PromiseType, ThisType, ArgTypes...> MethodCallType;
   typedef detail::ProxyRunnable<PromiseType, ThisType, ArgTypes...> ProxyRunnableType;
 
   MethodCallType* methodCall = new MethodCallType(aMethod, aThisVal, Forward<ActualArgTypes>(aArgs)...);
   RefPtr<typename PromiseType::Private> p = new (typename PromiseType::Private)(aCallerName);
   RefPtr<ProxyRunnableType> r = new ProxyRunnableType(p, methodCall);
   MOZ_ASSERT(aTarget->IsDispatchReliable());
   aTarget->Dispatch(r.forget());