Merge mozilla-central to inbound. a=merge CLOSED TREE
authorCsoregi Natalia <ncsoregi@mozilla.com>
Tue, 10 Apr 2018 13:02:17 +0300
changeset 468655 7e2492c45ab41d26f131d64ebc9a3e10fae65e9f
parent 468654 db2973e7667c86f991162206506e64fdcd2d18d4 (current diff)
parent 468627 a8061a09cd7064a8783ca9e67979d77fb52e001e (diff)
child 468656 8a735eb98349815f732f712f4a67bcdbd0024b4b
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
devtools/client/preferences/devtools.js
devtools/client/webide/webide-prefs.js
devtools/startup/devtools-startup-prefs.js
testing/talos/talos/tests/tabswitch/bootstrap.js
testing/talos/talos/tests/tabswitch/chrome.manifest
testing/talos/talos/tests/tabswitch/install.rdf
--- a/.eslintignore
+++ b/.eslintignore
@@ -148,17 +148,18 @@ devtools/client/commandline/**
 # also being an imported repository.
 devtools/client/debugger/**
 
 # Ignore devtools imported repositories
 devtools/client/debugger/new/**
 
 # Ignore devtools preferences files
 devtools/client/preferences/**
-devtools/startup/devtools-startup-prefs.js
+devtools/shared/preferences/**
+devtools/startup/preferences/devtools-startup.js
 
 # Ignore devtools third-party libs
 devtools/shared/jsbeautify/*
 devtools/shared/acorn/*
 devtools/shared/gcli/source/*
 devtools/shared/node-properties/*
 devtools/shared/pretty-fast/*
 devtools/shared/sourcemap/*
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -415,27 +415,27 @@
 @RESPATH@/browser/chrome/icons/default/default64.png
 @RESPATH@/browser/chrome/icons/default/default128.png
 #endif
 @RESPATH@/browser/features/*
 
 ; [Webide Files]
 @RESPATH@/browser/chrome/webide@JAREXT@
 @RESPATH@/browser/chrome/webide.manifest
-@RESPATH@/browser/@PREF_DIR@/webide-prefs.js
+@RESPATH@/browser/@PREF_DIR@/webide.js
 
 ; [DevTools Startup Files]
 @RESPATH@/browser/chrome/devtools-startup@JAREXT@
 @RESPATH@/browser/chrome/devtools-startup.manifest
-@RESPATH@/browser/@PREF_DIR@/devtools-startup-prefs.js
+@RESPATH@/browser/@PREF_DIR@/devtools-startup.js
 
 ; DevTools
 @RESPATH@/browser/chrome/devtools@JAREXT@
 @RESPATH@/browser/chrome/devtools.manifest
-@RESPATH@/browser/@PREF_DIR@/devtools.js
+@RESPATH@/browser/@PREF_DIR@/devtools-client.js
 @RESPATH@/browser/@PREF_DIR@/debugger.js
 
 ; shell icons
 #ifdef XP_UNIX
 #ifndef XP_MACOSX
 #ifdef MOZ_UPDATER
 ; updater icon
 @RESPATH@/icons/updater.png
--- a/devtools/client/performance/test/helpers/prefs.js
+++ b/devtools/client/performance/test/helpers/prefs.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const Services = require("Services");
 const { Preferences } = require("resource://gre/modules/Preferences.jsm");
 
 // Prefs to revert to default once tests finish. Keep these in sync with
-// all the preferences defined in devtools/client/preferences/devtools.js.
+// all the preferences defined in devtools/client/preferences/devtools-client.js.
 exports.MEMORY_SAMPLE_PROB_PREF = "devtools.performance.memory.sample-probability";
 exports.MEMORY_MAX_LOG_LEN_PREF = "devtools.performance.memory.max-log-length";
 exports.PROFILER_BUFFER_SIZE_PREF = "devtools.performance.profiler.buffer-size";
 exports.PROFILER_SAMPLE_RATE_PREF = "devtools.performance.profiler.sample-frequency-khz";
 
 exports.UI_EXPERIMENTAL_PREF = "devtools.performance.ui.experimental";
 exports.UI_INVERT_CALL_TREE_PREF = "devtools.performance.ui.invert-call-tree";
 exports.UI_INVERT_FLAME_PREF = "devtools.performance.ui.invert-flame-graph";
rename from devtools/client/preferences/devtools.js
rename to devtools/client/preferences/devtools-client.js
--- a/devtools/client/preferences/moz.build
+++ b/devtools/client/preferences/moz.build
@@ -1,13 +1,13 @@
 # -*- 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/.
 
 JS_PREFERENCE_PP_FILES += [
-    'devtools.js'
+    'devtools-client.js'
 ]
 
 JS_PREFERENCE_FILES += [
     'debugger.js',
 ]
--- a/devtools/client/themes/animation.css
+++ b/devtools/client/themes/animation.css
@@ -196,16 +196,29 @@ select.playback-rate-selector.devtools-b
 .animation-target {
   align-items: center;
   display: flex;
   height: 100%;
   padding-left: 4px;
   width: var(--sidebar-width);
 }
 
+/* Reps component */
+.animation-target .objectBox {
+  display: flex;
+  width: 100%;
+}
+
+.animation-target .objectBox .attrName {
+  min-width: 0;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
 /* Summary Graph */
 .animation-summary-graph {
   cursor: pointer;
   height: 100%;
   padding-top: 5px;
   position: relative;
   width: calc(100% - var(--sidebar-width) - var(--graph-right-offset));
 }
@@ -331,16 +344,18 @@ select.playback-rate-selector.devtools-b
 }
 
 .animation-detail-header {
   display: flex;
 }
 
 .animation-detail-title {
   flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
   white-space: nowrap;
 }
 
 .animation-detail-close-button::before {
   background-image: url(chrome://devtools/skin/images/close.svg);
 }
 
 /* Animated Property List Container */
--- a/devtools/client/webide/moz.build
+++ b/devtools/client/webide/moz.build
@@ -2,24 +2,21 @@
 # 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/.
 
 DIRS += [
     'content',
     'modules',
+    'preferences',
     'themes',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
     'test/browser.ini'
 ]
 MOCHITEST_CHROME_MANIFESTS += [
     'test/chrome.ini'
 ]
 
-JS_PREFERENCE_FILES += [
-    'webide-prefs.js',
-]
-
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Developer Tools: WebIDE')
new file mode 100644
--- /dev/null
+++ b/devtools/client/webide/preferences/moz.build
@@ -0,0 +1,9 @@
+# -*- 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/.
+
+JS_PREFERENCE_FILES += [
+    'webide.js',
+]
rename from devtools/client/webide/webide-prefs.js
rename to devtools/client/webide/preferences/webide.js
--- a/devtools/docs/preferences.md
+++ b/devtools/docs/preferences.md
@@ -65,24 +65,27 @@ preference cannot be found and you didn'
 value does not match the preference type!
 
 These APIs are very similar for each preference type.
 
 ## Create a new preference
 
 To create a new preference, it should be assigned a default value. Default preferences are
 defined in preferences files such as:
-- devtools/client/preferences/devtools.js
+- devtools/client/preferences/devtools-client.js
 - devtools/client/preferences/debugger.js
-- devtools/startup/devtools-startup-prefs.js
+- devtools/shared/preferences/devtools-shared.js
+- devtools/startup/preferences/devtools-startup.js
 
-Most new preferences should go in devtools/client/preferences/devtools.js. Debugger
-specific preferences should go in devtools/client/preferences/debugger.js. Finally if a
-preference needs to be available very early during the Firefox startup sequence, it should
-go in devtools/startup/devtools-startup-prefs.js.
+Most new preferences should go in devtools/client/preferences/devtools-client.js. Debugger
+specific preferences should go in devtools/client/preferences/debugger.js. If a preference
+should be available even when the client for DevTools is not shipped (for instance on
+Fennec) the preference should go to devtools/shared/preferences/devtools-shared.js.
+Finally if a preference needs to be available very early during the Firefox startup
+sequence, it should go in devtools/startup/preferences/devtools-startup.js.
 
 ### Projects using Launchpad
 
 At the time of writing this doc, projects using Launchpad have to duplicate the default
 definition of a preference.
 * debugger.html: update [src/utils/prefs.js](https://github.com/devtools-html/debugger.html/blob/master/src/utils/prefs.js)
 * netmonitor: update [index.js](http://searchfox.org/mozilla-central/source/devtools/client/netmonitor/index.js)
 * webconsole: update [local-dev/index.js](http://searchfox.org/mozilla-central/source/devtools/client/webconsole/local-dev/index.js)
--- a/devtools/moz.build
+++ b/devtools/moz.build
@@ -9,20 +9,20 @@ if CONFIG['MOZ_DEVTOOLS'] and CONFIG['MO
 
 if CONFIG['MOZ_DEVTOOLS'] == 'all':
     DIRS += [
         'client',
     ]
 
 # `platform` contains all native components
 DIRS += [
+    'platform',
     'server',
     'shared',
     'startup',
-    'platform',
 ]
 
 # /browser uses DIST_SUBDIR.  We opt-in to this treatment when building
 # DevTools for the browser to keep the root omni.ja slim for use by external XUL
 # apps.  Mulet also uses this since it includes /browser.
 if CONFIG['MOZ_BUILD_APP'] == 'browser':
     DIST_SUBDIR = 'browser'
     export('DIST_SUBDIR')
new file mode 100644
--- /dev/null
+++ b/devtools/shared/preferences/README.md
@@ -0,0 +1,4 @@
+# How is devtools-shared loaded?
+
+This preference file is included via modules/libpref/greprefs.js which guarantees it will
+be loaded on all distributions, including xpcshell binary.
new file mode 100644
--- /dev/null
+++ b/devtools/shared/preferences/devtools-shared.js
@@ -0,0 +1,82 @@
+/* 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/. */
+
+// Tells if DevTools have been explicitely enabled by the user.
+// This pref allows to disable all features related to DevTools
+// for users that never use them.
+// Until bug 1361080 lands, we always consider them enabled.
+pref("devtools.enabled", true);
+
+// Enable deprecation warnings.
+pref("devtools.errorconsole.deprecation_warnings", true);
+
+#ifdef NIGHTLY_BUILD
+// Don't show the Browser Toolbox prompt on local builds / nightly
+pref("devtools.debugger.prompt-connection", false, sticky);
+#else
+pref("devtools.debugger.prompt-connection", true, sticky);
+#endif
+
+#ifdef MOZILLA_OFFICIAL
+// Disable debugging chrome
+pref("devtools.chrome.enabled", false, sticky);
+// Disable remote debugging connections
+pref("devtools.debugger.remote-enabled", false, sticky);
+#else
+// In local builds, enable the browser toolbox by default
+pref("devtools.chrome.enabled", true, sticky);
+pref("devtools.debugger.remote-enabled", true, sticky);
+#endif
+
+// Disable remote debugging protocol logging
+pref("devtools.debugger.log", false);
+pref("devtools.debugger.log.verbose", false);
+
+pref("devtools.debugger.remote-port", 6000);
+pref("devtools.debugger.remote-websocket", false);
+// Force debugger server binding on the loopback interface
+pref("devtools.debugger.force-local", true);
+// Block tools from seeing / interacting with certified apps
+pref("devtools.debugger.forbid-certified-apps", true);
+
+// Limit for intercepted response bodies (1 MB)
+// Possible values:
+// 0 => the response body has no limit
+// n => represents max number of bytes stored
+pref("devtools.netmonitor.responseBodyLimit", 1048576);
+
+// DevTools default color unit
+pref("devtools.defaultColorUnit", "authored");
+
+// Used for devtools debugging
+pref("devtools.dump.emit", false);
+
+// Disable device discovery logging
+pref("devtools.discovery.log", false);
+// Whether to scan for DevTools devices via WiFi
+pref("devtools.remote.wifi.scan", true);
+// Client must complete TLS handshake within this window (ms)
+pref("devtools.remote.tls-handshake-timeout", 10000);
+
+// URL of the remote JSON catalog used for device simulation
+pref("devtools.devices.url", "https://code.cdn.mozilla.net/devices/devices.json");
+
+// Display the introductory text
+pref("devtools.gcli.hideIntro", false);
+
+// How eager are we to show help: never=1, sometimes=2, always=3
+pref("devtools.gcli.eagerHelper", 2);
+
+// Alias to the script URLs for inject command.
+pref("devtools.gcli.jquerySrc", "https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js");
+pref("devtools.gcli.lodashSrc", "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.6.1/lodash.min.js");
+pref("devtools.gcli.underscoreSrc", "https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js");
+
+// Set imgur upload client ID
+pref("devtools.gcli.imgurClientID", '0df414e888d7240');
+// Imgur's upload URL
+pref("devtools.gcli.imgurUploadURL", "https://api.imgur.com/3/image");
+
+// GCLI commands directory
+pref("devtools.commands.dir", "");
--- a/devtools/startup/moz.build
+++ b/devtools/startup/moz.build
@@ -1,18 +1,18 @@
 # -*- 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/.
 
 JAR_MANIFESTS += ['jar.mn']
 
-JS_PREFERENCE_PP_FILES += [
-    'devtools-startup-prefs.js',
+DIRS += [
+    'preferences',
 ]
 
 # Register the startup components only for 'all' builds.
 if CONFIG['MOZ_DEVTOOLS'] == 'all':
     EXTRA_COMPONENTS += [
         'aboutdebugging-registration.js',
         'aboutdebugging.manifest',
         'aboutdevtoolstoolbox-registration.js',
rename from devtools/startup/devtools-startup-prefs.js
rename to devtools/startup/preferences/devtools-startup.js
new file mode 100644
--- /dev/null
+++ b/devtools/startup/preferences/moz.build
@@ -0,0 +1,9 @@
+# -*- 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/.
+
+JS_PREFERENCE_PP_FILES += [
+    'devtools-startup.js'
+]
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -1021,19 +1021,18 @@ Animation::WillComposeStyle()
   MOZ_ASSERT(mEffect);
 
   KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
   if (keyframeEffect) {
     keyframeEffect->WillComposeStyle();
   }
 }
 
-template<typename ComposeAnimationResult>
 void
-Animation::ComposeStyle(ComposeAnimationResult&& aComposeResult,
+Animation::ComposeStyle(RawServoAnimationValueMap& aComposeResult,
                         const nsCSSPropertyIDSet& aPropertiesToSkip)
 {
   if (!mEffect) {
     return;
   }
 
   // In order to prevent flicker, there are a few cases where we want to use
   // a different time for rendering that would otherwise be returned by
@@ -1083,18 +1082,17 @@ Animation::ComposeStyle(ComposeAnimation
       if (!timeToUse.IsNull()) {
         mHoldTime = CurrentTimeFromTimelineTime(
           timeToUse.Value(), mStartTime.Value(), mPlaybackRate);
       }
     }
 
     KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
     if (keyframeEffect) {
-      keyframeEffect->ComposeStyle(Forward<ComposeAnimationResult>(aComposeResult),
-                                   aPropertiesToSkip);
+      keyframeEffect->ComposeStyle(aComposeResult, aPropertiesToSkip);
     }
   }
 
   MOZ_ASSERT(pending == Pending(),
              "Pending state should not change during the course of compositing");
 }
 
 void
@@ -1653,16 +1651,10 @@ bool
 Animation::IsRunningOnCompositor() const
 {
   return mEffect &&
          mEffect->AsKeyframeEffect() &&
          mEffect->AsKeyframeEffect()->IsRunningOnCompositor();
 }
 
 
-template
-void
-Animation::ComposeStyle<RawServoAnimationValueMap&>(
-  RawServoAnimationValueMap& aAnimationValues,
-  const nsCSSPropertyIDSet& aPropertiesToSkip);
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -368,18 +368,17 @@ public:
   void WillComposeStyle();
 
   /**
    * Updates |aComposeResult| with the animation values of this animation's
    * effect, if any.
    * Any properties contained in |aPropertiesToSkip| will not be added or
    * updated in |aComposeResult|.
    */
-  template<typename ComposeAnimationResult>
-  void ComposeStyle(ComposeAnimationResult&& aComposeResult,
+  void ComposeStyle(RawServoAnimationValueMap& aComposeResult,
                     const nsCSSPropertyIDSet& aPropertiesToSkip);
 
   void NotifyEffectTimingUpdated();
   void NotifyGeometricAnimationsStartingThisFrame();
 
   /**
    * Used by subclasses to synchronously queue a cancel event in situations
    * where the Animation may have been cancelled.
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -374,34 +374,33 @@ EffectCompositor::ClearRestyleRequestsFo
              pseudoType == CSSPseudoElementType::after) {
     Element* parentElement = aElement->GetParentElement();
     MOZ_ASSERT(parentElement);
     PseudoElementHashEntry::KeyType key = { parentElement, pseudoType };
     elementsToRestyle.Remove(key);
   }
 }
 
-template<typename StyleType>
 void
-EffectCompositor::UpdateEffectProperties(StyleType* aStyleType,
+EffectCompositor::UpdateEffectProperties(const ComputedStyle* aStyle,
                                          Element* aElement,
                                          CSSPseudoElementType aPseudoType)
 {
   EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
   if (!effectSet) {
     return;
   }
 
   // Style context (Gecko) or computed values (Stylo) change might cause CSS
   // cascade level, e.g removing !important, so we should update the cascading
   // result.
   effectSet->MarkCascadeNeedsUpdate();
 
   for (KeyframeEffectReadOnly* effect : *effectSet) {
-    effect->UpdateProperties(aStyleType);
+    effect->UpdateProperties(aStyle);
   }
 }
 
 
 namespace {
   class EffectCompositeOrderComparator {
   public:
     bool Equals(const KeyframeEffectReadOnly* a,
@@ -984,16 +983,9 @@ EffectCompositor::PreTraverse(dom::Eleme
 
     elementSet.Remove(key);
     found = true;
   }
   return found;
 }
 
 
-template
-void
-EffectCompositor::UpdateEffectProperties(
-  const ComputedStyle* aComputedStyle,
-  Element* aElement,
-  CSSPseudoElementType aPseudoType);
-
 } // namespace mozilla
--- a/dom/animation/EffectCompositor.h
+++ b/dom/animation/EffectCompositor.h
@@ -113,18 +113,17 @@ public:
   // Clear all pending restyle requests for the given (pseudo-) element (and its
   // ::before and ::after elements if the given element is not pseudo).
   void ClearRestyleRequestsFor(dom::Element* aElement);
 
   // Called when computed style on the specified (pseudo-) element might
   // have changed so that any context-sensitive values stored within
   // animation effects (e.g. em-based endpoints used in keyframe effects)
   // can be re-resolved to computed values.
-  template<typename StyleType>
-  void UpdateEffectProperties(StyleType* aStyleType,
+  void UpdateEffectProperties(const ComputedStyle* aStyle,
                               dom::Element* aElement,
                               CSSPseudoElementType aPseudoType);
 
 
   // Get animation rule for stylo. This is an equivalent of GetAnimationRule
   // and will be called from servo side.
   // The animation rule is stored in |RawServoAnimationValueMapBorrowed|.
   // We need to be careful while doing any modification because it may cause
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -206,17 +206,18 @@ KeyframeEffectReadOnly::SetKeyframes(
 
   mKeyframes = Move(aKeyframes);
   KeyframeUtils::DistributeKeyframes(mKeyframes);
 
   if (mAnimation && mAnimation->IsRelevant()) {
     nsNodeUtils::AnimationChanged(mAnimation);
   }
 
-  // We need to call UpdateProperties() if the StyleType is not nullptr.
+  // We need to call UpdateProperties() unless the target element doesn't have
+  // style (e.g. the target element is not associated with any document).
   if (aStyle) {
     UpdateProperties(aStyle);
     MaybeUpdateFrameForCompositor();
   }
 }
 
 const AnimationProperty*
 KeyframeEffectReadOnly::GetEffectiveAnimationOfProperty(
@@ -277,25 +278,16 @@ SpecifiedKeyframeArraysAreEqual(const ns
 }
 #endif
 
 void
 KeyframeEffectReadOnly::UpdateProperties(const ComputedStyle* aStyle)
 {
   MOZ_ASSERT(aStyle);
 
-  // Skip updating properties when we are composing style.
-  // FIXME: Bug 1324966. Drop this check once we have a function to get
-  // ComputedStyle without resolving animating style.
-  MOZ_DIAGNOSTIC_ASSERT(!mIsComposingStyle,
-                        "Should not be called while processing ComposeStyle()");
-  if (mIsComposingStyle) {
-    return;
-  }
-
   nsTArray<AnimationProperty> properties = BuildProperties(aStyle);
 
   // We need to update base styles even if any properties are not changed at all
   // since base styles might have been changed due to parent style changes, etc.
   EnsureBaseStyles(aStyle, properties);
 
   if (mProperties == properties) {
     return;
@@ -415,31 +407,21 @@ KeyframeEffectReadOnly::ComposeStyleRule
                          &mBaseStyleValuesForServo,
                          aProperty.mProperty,
                          &aSegment,
                          &aProperty.mSegments.LastElement(),
                          &aComputedTiming,
                          mEffectOptions.mIterationComposite);
 }
 
-template<typename ComposeAnimationResult>
 void
 KeyframeEffectReadOnly::ComposeStyle(
-  ComposeAnimationResult&& aComposeResult,
+  RawServoAnimationValueMap& aComposeResult,
   const nsCSSPropertyIDSet& aPropertiesToSkip)
 {
-  MOZ_DIAGNOSTIC_ASSERT(!mIsComposingStyle,
-                        "Should not be called recursively");
-  if (mIsComposingStyle) {
-    return;
-  }
-
-  AutoRestore<bool> isComposingStyle(mIsComposingStyle);
-  mIsComposingStyle = true;
-
   ComputedTiming computedTiming = GetComputedTiming();
 
   // If the progress is null, we don't have fill data for the current
   // time so we shouldn't animate.
   if (computedTiming.mProgress.IsNull()) {
     return;
   }
 
@@ -471,20 +453,17 @@ KeyframeEffectReadOnly::ComposeStyle(
       MOZ_ASSERT(segment->mFromKey == (segment-1)->mToKey, "incorrect keys");
     }
     MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys");
     MOZ_ASSERT(segment >= prop.mSegments.Elements() &&
                size_t(segment - prop.mSegments.Elements()) <
                  prop.mSegments.Length(),
                "out of array bounds");
 
-    ComposeStyleRule(Forward<ComposeAnimationResult>(aComposeResult),
-                     prop,
-                     *segment,
-                     computedTiming);
+    ComposeStyleRule(aComposeResult, prop, *segment, computedTiming);
   }
 
   // If the animation produces a transform change hint that affects the overflow
   // region, we need to record the current time to unthrottle the animation
   // periodically when the animation is being throttled because it's scrolled
   // out of view.
   if (HasTransformThatMightAffectOverflow()) {
     nsPresContext* presContext =
@@ -681,19 +660,18 @@ KeyframeEffectReadOnly::ConstructKeyfram
   // Copy aSource's keyframes and animation properties.
   // Note: We don't call SetKeyframes directly, which might revise the
   //       computed offsets and rebuild the animation properties.
   effect->mKeyframes = aSource.mKeyframes;
   effect->mProperties = aSource.mProperties;
   return effect.forget();
 }
 
-template<typename StyleType>
 nsTArray<AnimationProperty>
-KeyframeEffectReadOnly::BuildProperties(StyleType* aStyle)
+KeyframeEffectReadOnly::BuildProperties(const ComputedStyle* aStyle)
 {
 
   MOZ_ASSERT(aStyle);
 
   nsTArray<AnimationProperty> result;
   // If mTarget is null, return an empty property array.
   if (!mTarget) {
     return result;
@@ -1694,16 +1672,10 @@ KeyframeEffectReadOnly::UpdateEffectSet(
     effectSet->SetMayHaveTransformAnimation();
     if (frame) {
       frame->SetMayHaveTransformAnimation();
     }
   }
 }
 
 
-template
-void
-KeyframeEffectReadOnly::ComposeStyle<RawServoAnimationValueMap&>(
-  RawServoAnimationValueMap& aAnimationValues,
-  const nsCSSPropertyIDSet& aPropertiesToSkip);
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -41,17 +41,16 @@ namespace mozilla {
 
 class AnimValuesStyleRule;
 enum class CSSPseudoElementType : uint8_t;
 class ErrorResult;
 struct AnimationRule;
 struct TimingParams;
 class EffectSet;
 class ComputedStyle;
-class GeckoComputedStyle;
 
 namespace dom {
 class ElementOrCSSPseudoElement;
 class GlobalObject;
 class OwningElementOrCSSPseudoElement;
 class UnrestrictedDoubleOrKeyframeAnimationOptions;
 class UnrestrictedDoubleOrKeyframeEffectOptions;
 enum class IterationCompositeOperation : uint8_t;
@@ -199,18 +198,17 @@ public:
   // We need to update this outside ComposeStyle() because we should avoid
   // mutating any state in ComposeStyle() since it might be called during
   // parallel traversal.
   void WillComposeStyle();
 
   // Updates |aComposeResult| with the animation values produced by this
   // AnimationEffect for the current time except any properties contained
   // in |aPropertiesToSkip|.
-  template<typename ComposeAnimationResult>
-  void ComposeStyle(ComposeAnimationResult&& aRestultContainer,
+  void ComposeStyle(RawServoAnimationValueMap& aComposeResult,
                     const nsCSSPropertyIDSet& aPropertiesToSkip);
 
 
   // Returns true if at least one property is being animated on compositor.
   bool IsRunningOnCompositor() const;
   void SetIsRunningOnCompositor(nsCSSPropertyID aProperty, bool aIsRunning);
   void ResetIsRunningOnCompositor();
 
@@ -291,18 +289,17 @@ protected:
   static already_AddRefed<KeyframeEffectType>
   ConstructKeyframeEffect(const GlobalObject& aGlobal,
                           KeyframeEffectReadOnly& aSource,
                           ErrorResult& aRv);
 
   // Build properties by recalculating from |mKeyframes| using |aComputedStyle|
   // to resolve specified values. This function also applies paced spacing if
   // needed.
-  template<typename StyleType>
-  nsTArray<AnimationProperty> BuildProperties(StyleType* aStyle);
+  nsTArray<AnimationProperty> BuildProperties(const ComputedStyle* aStyle);
 
   // This effect is registered with its target element so long as:
   //
   // (a) It has a target element, and
   // (b) It is "relevant" (i.e. yet to finish but not idle, or finished but
   //     filling forwards)
   //
   // As a result, we need to make sure this gets called whenever anything
@@ -417,17 +414,14 @@ private:
   // This function is used for updating scroll bars or notifying intersection
   // observers reflected by the transform.
   bool HasTransformThatMightAffectOverflow() const
   {
     return mCumulativeChangeHint & (nsChangeHint_UpdatePostTransformOverflow |
                                     nsChangeHint_AddOrRemoveTransform |
                                     nsChangeHint_UpdateTransformLayer);
   }
-
-  // FIXME: This flag will be removed in bug 1324966.
-  bool mIsComposingStyle = false;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_KeyframeEffectReadOnly_h
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -476,22 +476,21 @@ KeyframeUtils::DistributeKeyframes(nsTAr
     keyframeB->mComputedOffset = keyframeB->mOffset.valueOr(1.0);
 
     // Fill computed offsets in (keyframe A, keyframe B).
     DistributeRange(Range<Keyframe>(keyframeA, keyframeB + 1));
     keyframeA = keyframeB;
   }
 }
 
-template<typename StyleType>
 /* static */ nsTArray<AnimationProperty>
 KeyframeUtils::GetAnimationPropertiesFromKeyframes(
   const nsTArray<Keyframe>& aKeyframes,
   dom::Element* aElement,
-  StyleType* aStyle,
+  const ComputedStyle* aStyle,
   dom::CompositeOperation aEffectComposite)
 {
   nsTArray<AnimationProperty> result;
 
   const nsTArray<ComputedKeyframeValues> computedValues =
     GetComputedKeyframeValues(aKeyframes, aElement, aStyle);
   if (computedValues.IsEmpty()) {
     // In rare cases GetComputedKeyframeValues might fail and return an empty
@@ -1487,17 +1486,9 @@ DistributeRange(const Range<Keyframe>& a
   const double diffOffset = aRange[n].mComputedOffset - startOffset;
   for (auto iter = rangeToAdjust.begin(); iter != rangeToAdjust.end(); ++iter) {
     size_t index = iter - aRange.begin();
     iter->mComputedOffset = startOffset + double(index) / n * diffOffset;
   }
 }
 
 
-template
-nsTArray<AnimationProperty>
-KeyframeUtils::GetAnimationPropertiesFromKeyframes(
-  const nsTArray<Keyframe>& aKeyframes,
-  dom::Element* aElement,
-  const ComputedStyle* aStyle,
-  dom::CompositeOperation aEffectComposite);
-
 } // namespace mozilla
--- a/dom/animation/KeyframeUtils.h
+++ b/dom/animation/KeyframeUtils.h
@@ -76,29 +76,27 @@ public:
   /**
    * Converts an array of Keyframe objects into an array of AnimationProperty
    * objects. This involves creating an array of computed values for each
    * longhand property and determining the offset and timing function to use
    * for each value.
    *
    * @param aKeyframes The input keyframes.
    * @param aElement The context element.
-   * @param aStyleType The |ComputedStyle| or |GeckoComputedStyle| to use
-   *   when computing values.
+   * @param aStyle The computed style values.
    * @param aEffectComposite The composite operation specified on the effect.
    *   For any keyframes in |aKeyframes| that do not specify a composite
    *   operation, this value will be used.
    * @return The set of animation properties. If an error occurs, the returned
    *   array will be empty.
    */
-  template<typename StyleType>
   static nsTArray<AnimationProperty> GetAnimationPropertiesFromKeyframes(
     const nsTArray<Keyframe>& aKeyframes,
     dom::Element* aElement,
-    StyleType* aStyleType,
+    const ComputedStyle* aStyle,
     dom::CompositeOperation aEffectComposite);
 
   /**
    * Check if the property or, for shorthands, one or more of
    * its subproperties, is animatable.
    *
    * @param aProperty The property to check.
    * @param aBackend  The style backend, Servo or Gecko, that should determine
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -430,67 +430,46 @@ HangMonitorChild::RecvEndStartingDebugge
 
 mozilla::ipc::IPCResult
 HangMonitorChild::RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch)
 {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
   {
     MonitorAutoLock lock(mMonitor);
-    // If we lose our race, and the main thread has already painted,
-    // the NotifyActivity call below would result in an indefinite
-    // hang, since it wouldn't have a matching NotifyWait()
-    if (mForcePaintEpoch >= aLayerObserverEpoch) {
-      return IPC_OK();
-    }
     MaybeStartForcePaint();
     mForcePaint = true;
     mForcePaintTab = aTabId;
     mForcePaintEpoch = aLayerObserverEpoch;
   }
 
   JS_RequestInterruptCallback(mContext);
 
   return IPC_OK();
 }
 
 void
 HangMonitorChild::MaybeStartForcePaint()
 {
+  // See Bug 1449662. The body of this function other than assertions
+  // has been temporarily removed to diagnose a tab switch spinner
+  // problem.
   if (!NS_IsMainThread()) {
     mMonitor.AssertCurrentThreadOwns();
   }
-
-  if (!mBHRMonitorActive.exchange(true)) {
-    mForcePaintMonitor->NotifyActivity();
-  }
 }
 
 void
 HangMonitorChild::ClearForcePaint(uint64_t aLayerObserverEpoch)
 {
+  // See Bug 1449662. The body of this function other than assertions
+  // has been temporarily removed to diagnose a tab switch spinner
+  // problem.
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
-
-  {
-    MonitorAutoLock lock(mMonitor);
-    // Set the epoch, so that if the forcepaint loses its race, it
-    // knows it and can exit appropriately. However, ensure we don't
-    // overwrite an even newer mForcePaintEpoch which could have
-    // come in in a ForcePaint notification while we were painting.
-    if (aLayerObserverEpoch > mForcePaintEpoch) {
-      mForcePaintEpoch = aLayerObserverEpoch;
-    }
-    mForcePaintMonitor->NotifyWait();
-
-    // ClearForcePaint must be called on the main thread, and the
-    // hang monitor thread only sets this with mMonitor held, so there
-    // should be no risk of missing NotifyActivity calls here.
-    mBHRMonitorActive = false;
-  }
 }
 
 void
 HangMonitorChild::Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint)
 {
   MOZ_RELEASE_ASSERT(IsOnThread());
 
   MOZ_ASSERT(!sInstance);
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -308,17 +308,17 @@ BasicCompositor::CreateRenderTargetForWi
   } else {
     IntRect windowRect = rect;
     // Adjust bounds rect to account for new origin at (0, 0).
     if (windowRect.Size() != mDrawTarget->GetSize()) {
       windowRect.ExpandToEnclose(IntPoint(0, 0));
     }
     rt = new BasicCompositingRenderTarget(mDrawTarget, windowRect);
     if (!aClearRect.IsEmpty()) {
-      IntRect clearRect = aRect.ToUnknownRect();
+      IntRect clearRect = aClearRect.ToUnknownRect();
       mDrawTarget->ClearRect(Rect(clearRect - rt->GetOrigin()));
     }
   }
 
   return rt.forget();
 }
 
 already_AddRefed<DataTextureSource>
@@ -1033,34 +1033,28 @@ BasicCompositor::TryToEndRemoteDrawing(b
                              [self]() { self->TryToEndRemoteDrawing(); });
     MessageLoop::current()->PostDelayedTask(runnable.forget(), retryMs);
     return;
   }
 
   if (mRenderTarget->mDrawTarget != mDrawTarget) {
     // Note: Most platforms require us to buffer drawing to the widget surface.
     // That's why we don't draw to mDrawTarget directly.
-    RefPtr<SourceSurface> source;
-    if (mRenderTarget->mDrawTarget != mDrawTarget) {
-      source = mWidget->EndBackBufferDrawing();
-    } else {
-      source = mRenderTarget->mDrawTarget->Snapshot();
-    }
-    RefPtr<DrawTarget> dest(mTarget ? mTarget : mDrawTarget);
+    RefPtr<SourceSurface> source = mWidget->EndBackBufferDrawing();
 
     nsIntPoint offset = mTarget ? mTargetBounds.TopLeft() : nsIntPoint();
 
     // The source DrawTarget is clipped to the invalidation region, so we have
     // to copy the individual rectangles in the region or else we'll draw blank
     // pixels.
     for (auto iter = mInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
       const LayoutDeviceIntRect& r = iter.Get();
-      dest->CopySurface(source,
-                        IntRect(r.X(), r.Y(), r.Width(), r.Height()) - mRenderTarget->GetOrigin(),
-                        IntPoint(r.X(), r.Y()) - offset);
+      mDrawTarget->CopySurface(source,
+                               r.ToUnknownRect() - mRenderTarget->GetOrigin(),
+                               r.TopLeft().ToUnknownPoint() - offset);
     }
   }
 
   if (aForceToEnd || !mTarget) {
     mWidget->EndRemoteDrawingInRegion(mDrawTarget, mInvalidRegion);
   }
 
   mDrawTarget = nullptr;
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -531,21 +531,21 @@ struct DIGroup
         InvalidateRect(data->mRect);
         iter.Remove();
         delete data;
       } else {
         data->mUsed = false;
       }
     }
 
-    // Round the bounds in a way that matches the existing fallback code
-    LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(mGroupBounds, aGrouper->mAppUnitsPerDevPixel);
-    bounds = LayoutDeviceRect(RoundedToInt(bounds));
-
-    IntSize size = mGroupBounds.Size().ScaleToNearestPixels(mScale.width, mScale.height, aGrouper->mAppUnitsPerDevPixel);
+    // Round the bounds out to leave space for unsnapped content
+    LayoutDeviceToLayerScale2D scale(mScale.width, mScale.height);
+    LayerIntRect layerBounds = LayerIntRect::FromUnknownRect(mGroupBounds.ScaleToOutsidePixels(mScale.width, mScale.height, aGrouper->mAppUnitsPerDevPixel));
+    IntSize dtSize = layerBounds.Size().ToUnknownSize();
+    LayoutDeviceRect bounds = layerBounds / scale;
 
     if (mInvalidRect.IsEmpty()) {
       GP("Not repainting group because it's empty\n");
       GP("End EndGroup\n");
       if (mKey) {
         PushImage(aBuilder, bounds);
       }
       return;
@@ -561,17 +561,17 @@ struct DIGroup
             wr::FontKey key = aWrManager->WrBridge()->GetFontKeyForUnscaledFont(unscaled);
             aStream.write((const char*)&key, sizeof(key));
           }
         });
 
     RefPtr<gfx::DrawTarget> dummyDt =
       gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, gfx::IntSize(1, 1), format);
 
-    RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt, size);
+    RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateRecordingDrawTarget(recorder, dummyDt, dtSize);
     // Setup the gfxContext
     RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
     GP("ctx-offset %f %f\n", bounds.x, bounds.y);
     context->SetMatrix(Matrix::Scaling(mScale.width, mScale.height).PreTranslate(-bounds.x, -bounds.y));
 
     GP("mInvalidRect: %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y, mInvalidRect.width, mInvalidRect.height);
 
     bool empty = aStartItem == aEndItem;
@@ -585,42 +585,42 @@ struct DIGroup
 
     PaintItemRange(aGrouper, aStartItem, aEndItem, context, recorder);
 
     if (aStartItem->Frame()->PresContext()->GetPaintFlashing()) {
       context->SetMatrix(Matrix());
       float r = float(rand()) / RAND_MAX;
       float g = float(rand()) / RAND_MAX;
       float b = float(rand()) / RAND_MAX;
-      dt->FillRect(gfx::Rect(0, 0, size.width, size.height), gfx::ColorPattern(gfx::Color(r, g, b, 0.5)));
-      dt->FlushItem(IntRect(IntPoint(0, 0), size));
+      dt->FillRect(gfx::Rect(0, 0, dtSize.width, dtSize.height), gfx::ColorPattern(gfx::Color(r, g, b, 0.5)));
+      dt->FlushItem(IntRect(IntPoint(0, 0), dtSize));
     }
 
     // XXX: set this correctly perhaps using aItem->GetOpaqueRegion(aDisplayListBuilder, &snapped).Contains(paintBounds);?
     bool isOpaque = false;
 
     bool hasItems = recorder->Finish();
     GP("%d Finish\n", hasItems);
     Range<uint8_t> bytes((uint8_t*)recorder->mOutputStream.mData, recorder->mOutputStream.mLength);
     if (!mKey) {
       if (!hasItems) // we don't want to send a new image that doesn't have any items in it
         return;
       wr::ImageKey key = aWrManager->WrBridge()->GetNextImageKey();
       GP("No previous key making new one %d\n", key.mHandle);
-      wr::ImageDescriptor descriptor(size, 0, dt->GetFormat(), isOpaque);
+      wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), isOpaque);
       MOZ_RELEASE_ASSERT(bytes.length() > sizeof(size_t));
       if (!aResources.AddBlobImage(key, descriptor, bytes)) {
         return;
       }
       mKey = Some(key);
     } else {
-      wr::ImageDescriptor descriptor(size, 0, dt->GetFormat(), isOpaque);
+      wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), isOpaque);
       auto bottomRight = mInvalidRect.BottomRight();
-      GP("check invalid %d %d - %d %d\n", bottomRight.x, bottomRight.y, size.width, size.height);
-      MOZ_RELEASE_ASSERT(bottomRight.x <= size.width && bottomRight.y <= size.height);
+      GP("check invalid %d %d - %d %d\n", bottomRight.x, bottomRight.y, dtSize.width, dtSize.height);
+      MOZ_RELEASE_ASSERT(bottomRight.x <= dtSize.width && bottomRight.y <= dtSize.height);
       GP("Update Blob %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y, mInvalidRect.width, mInvalidRect.height);
       if (!aResources.UpdateBlobImage(mKey.value(), descriptor, bytes, ViewAs<ImagePixel>(mInvalidRect))) {
         return;
       }
     }
     mInvalidRect.SetEmpty();
     PushImage(aBuilder, bounds);
     GP("End EndGroup\n\n");
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -2619,57 +2619,66 @@ already_AddRefed<LayerManager> nsDisplay
         }
       } else {
         if (!layerManager->BeginTransaction()) {
           return nullptr;
         }
       }
     }
 
-    // Windowed plugins are not supported with WebRender enabled.
-    // But PluginGeometry needs to be updated to show plugin.
-    // Windowed plugins are going to be removed by Bug 1296400.
-    nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
-    if (rootPresContext && XRE_IsContentProcess()) {
-      if (aBuilder->WillComputePluginGeometry()) {
-        rootPresContext->ComputePluginGeometryUpdates(aBuilder->RootReferenceFrame(), aBuilder, this);
+    bool prevIsCompositingCheap =
+      aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap());
+    MaybeSetupTransactionIdAllocator(layerManager, presContext);
+
+    bool sent = false;
+    if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) {
+      sent = layerManager->EndEmptyTransaction();
+    }
+
+    if (!sent) {
+      // Windowed plugins are not supported with WebRender enabled.
+      // But PluginGeometry needs to be updated to show plugin.
+      // Windowed plugins are going to be removed by Bug 1296400.
+      nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
+      if (rootPresContext && XRE_IsContentProcess()) {
+        if (aBuilder->WillComputePluginGeometry()) {
+          rootPresContext->ComputePluginGeometryUpdates(aBuilder->RootReferenceFrame(), aBuilder, this);
+        }
+        // This must be called even if PluginGeometryUpdates were not computed.
+        rootPresContext->CollectPluginGeometryUpdates(layerManager);
       }
-      // This must be called even if PluginGeometryUpdates were not computed.
-      rootPresContext->CollectPluginGeometryUpdates(layerManager);
-    }
-
-    WebRenderLayerManager* wrManager = static_cast<WebRenderLayerManager*>(layerManager.get());
-
-    nsIDocShell* docShell = presContext->GetDocShell();
-    nsTArray<wr::WrFilterOp> wrFilters;
-    gfx::Matrix5x4* colorMatrix = nsDocShell::Cast(docShell)->GetColorMatrix();
-    if (colorMatrix) {
-      wr::WrFilterOp gs = {
-        wr::WrFilterOpType::ColorMatrix
-      };
-      MOZ_ASSERT(sizeof(gs.matrix) == sizeof(colorMatrix->components));
-      memcpy(&(gs.matrix), colorMatrix->components, sizeof(gs.matrix));
-      wrFilters.AppendElement(gs);
-    }
-
-    MaybeSetupTransactionIdAllocator(layerManager, presContext);
-    bool temp = aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap());
-    wrManager->EndTransactionWithoutLayer(this, aBuilder, wrFilters);
+
+      WebRenderLayerManager* wrManager = static_cast<WebRenderLayerManager*>(layerManager.get());
+
+      nsIDocShell* docShell = presContext->GetDocShell();
+      nsTArray<wr::WrFilterOp> wrFilters;
+      gfx::Matrix5x4* colorMatrix = nsDocShell::Cast(docShell)->GetColorMatrix();
+      if (colorMatrix) {
+        wr::WrFilterOp gs = {
+          wr::WrFilterOpType::ColorMatrix
+        };
+        MOZ_ASSERT(sizeof(gs.matrix) == sizeof(colorMatrix->components));
+        memcpy(&(gs.matrix), colorMatrix->components, sizeof(gs.matrix));
+        wrFilters.AppendElement(gs);
+      }
+
+      wrManager->EndTransactionWithoutLayer(this, aBuilder, wrFilters);
+    }
 
     // For layers-free mode, we check the invalidation state bits in the EndTransaction.
     // So we clear the invalidation state bits after EndTransaction.
     if (widgetTransaction ||
         // SVG-as-an-image docs don't paint as part of the retained layer tree,
         // but they still need the invalidation state bits cleared in order for
         // invalidation for CSS/SMIL animation to work properly.
         (document && document->IsBeingUsedAsImage())) {
       frame->ClearInvalidationStateBits();
     }
 
-    aBuilder->SetIsCompositingCheap(temp);
+    aBuilder->SetIsCompositingCheap(prevIsCompositingCheap);
     if (document && widgetTransaction) {
       TriggerPendingAnimations(document, layerManager->GetAnimationReadyTime());
     }
 
     if (presContext->RefreshDriver()->HasScheduleFlush()) {
       presContext->NotifyInvalidation(layerManager->GetLastTransactionId(), frame->GetRect());
     }
 
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -630,18 +630,18 @@ Gecko_UpdateAnimations(RawGeckoElementBo
   if (!aComputedData) {
     return;
   }
 
   if (aTasks & UpdateAnimationsTasks::CSSTransitions) {
     MOZ_ASSERT(aOldComputedData);
     presContext->TransitionManager()->
       UpdateTransitions(const_cast<dom::Element*>(aElement), pseudoType,
-                        aOldComputedData,
-                        aComputedData);
+                        *aOldComputedData,
+                        *aComputedData);
   }
 
   if (aTasks & UpdateAnimationsTasks::EffectProperties) {
     presContext->EffectCompositor()->UpdateEffectProperties(
       aComputedData, const_cast<dom::Element*>(aElement), pseudoType);
   }
 
   if (aTasks & UpdateAnimationsTasks::CascadeResults) {
--- a/layout/style/StyleAnimationValue.h
+++ b/layout/style/StyleAnimationValue.h
@@ -23,17 +23,16 @@
 #include "nsStyleTransformMatrix.h"
 
 class nsIFrame;
 class gfx3DMatrix;
 
 namespace mozilla {
 
 class ComputedStyle;
-class GeckoComputedStyle;
 
 namespace css {
 class StyleRule;
 } // namespace css
 
 namespace dom {
 class Element;
 } // namespace dom
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -413,24 +413,23 @@ public:
     keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Standard);
   }
 
 private:
   const ComputedStyle* mComputedStyle;
 };
 
 
-template<class BuilderType>
 static void
 UpdateOldAnimationPropertiesWithNew(
     CSSAnimation& aOld,
     TimingParams& aNewTiming,
     nsTArray<Keyframe>&& aNewKeyframes,
     bool aNewIsStylePaused,
-    BuilderType& aBuilder)
+    ServoCSSAnimationBuilder& aBuilder)
 {
   bool animationChanged = false;
 
   // Update the old from the new so we can keep the original object
   // identity (and any expando properties attached to it).
   if (aOld.GetEffect()) {
     dom::AnimationEffectReadOnly* oldEffect = aOld.GetEffect();
     animationChanged = oldEffect->SpecifiedTiming() != aNewTiming;
@@ -467,23 +466,22 @@ UpdateOldAnimationPropertiesWithNew(
   if (animationChanged && aOld.IsRelevant()) {
     nsNodeUtils::AnimationChanged(&aOld);
   }
 }
 
 // Returns a new animation set up with given StyleAnimation.
 // Or returns an existing animation matching StyleAnimation's name updated
 // with the new StyleAnimation.
-template<class BuilderType>
 static already_AddRefed<CSSAnimation>
 BuildAnimation(nsPresContext* aPresContext,
                const NonOwningAnimationTarget& aTarget,
                const nsStyleDisplay& aStyleDisplay,
                uint32_t animIdx,
-               BuilderType& aBuilder,
+               ServoCSSAnimationBuilder& aBuilder,
                nsAnimationManager::CSSAnimationCollection* aCollection)
 {
   MOZ_ASSERT(aPresContext);
 
   nsAtom* animationName = aStyleDisplay.GetAnimationName(animIdx);
   nsTArray<Keyframe> keyframes;
   if (!aBuilder.BuildKeyframes(aPresContext,
                                animationName,
@@ -550,22 +548,21 @@ BuildAnimation(nsPresContext* aPresConte
   }
 
   aBuilder.NotifyNewOrRemovedAnimation(*animation);
 
   return animation.forget();
 }
 
 
-template<class BuilderType>
 static nsAnimationManager::OwningCSSAnimationPtrArray
 BuildAnimations(nsPresContext* aPresContext,
                 const NonOwningAnimationTarget& aTarget,
                 const nsStyleDisplay& aStyleDisplay,
-                BuilderType& aBuilder,
+                ServoCSSAnimationBuilder& aBuilder,
                 nsAnimationManager::CSSAnimationCollection* aCollection,
                 nsTHashtable<nsRefPtrHashKey<nsAtom>>& aReferencedAnimations)
 {
   nsAnimationManager::OwningCSSAnimationPtrArray result;
 
   for (size_t animIdx = aStyleDisplay.mAnimationNameCount; animIdx-- != 0;) {
     nsAtom* name = aStyleDisplay.GetAnimationName(animIdx);
     // CSS Animations whose animation-name does not match a @keyframes rule do
@@ -623,22 +620,21 @@ nsAnimationManager::UpdateAnimations(
   }
 
   NonOwningAnimationTarget target(aElement, aPseudoType);
   ServoCSSAnimationBuilder builder(aComputedStyle);
 
   DoUpdateAnimations(target, *disp, builder);
 }
 
-template<class BuilderType>
 void
 nsAnimationManager::DoUpdateAnimations(
   const NonOwningAnimationTarget& aTarget,
   const nsStyleDisplay& aStyleDisplay,
-  BuilderType& aBuilder)
+  ServoCSSAnimationBuilder& aBuilder)
 {
   // Everything that causes our animation data to change triggers a
   // style change, which in turn triggers a non-animation restyle.
   // Likewise, when we initially construct frames, we're not in a
   // style change, but also not in an animation restyle.
 
   CSSAnimationCollection* collection =
     CSSAnimationCollection::GetAnimationCollection(aTarget.mElement,
--- a/layout/style/nsAnimationManager.h
+++ b/layout/style/nsAnimationManager.h
@@ -14,29 +14,28 @@
 #include "mozilla/Keyframe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/TimeStamp.h"
 #include "nsISupportsImpl.h"
 
 class nsIGlobalObject;
 class ServoComputedData;
 struct nsStyleDisplay;
+class ServoCSSAnimationBuilder;
 
 namespace mozilla {
 class ComputedStyle;
 namespace css {
 class Declaration;
 } /* namespace css */
 namespace dom {
 class KeyframeEffectReadOnly;
 class Promise;
 } /* namespace dom */
 
-class GeckoComputedStyle;
-class ComputedStyle;
 enum class CSSPseudoElementType : uint8_t;
 struct NonOwningAnimationTarget;
 
 namespace dom {
 
 class CSSAnimation final : public Animation
 {
 public:
@@ -345,16 +344,15 @@ private:
   // This includes all animation names referenced regardless of whether a
   // corresponding `@keyframes` rule is available.
   //
   // It may contain names which are no longer referenced, but it should always
   // contain names which are currently referenced, so that it is usable for
   // style invalidation.
   nsTHashtable<nsRefPtrHashKey<nsAtom>> mMaybeReferencedAnimations;
 
-  template<class BuilderType>
   void DoUpdateAnimations(
     const mozilla::NonOwningAnimationTarget& aTarget,
     const nsStyleDisplay& aStyleDisplay,
-    BuilderType& aBuilder);
+    ServoCSSAnimationBuilder& aBuilder);
 };
 
 #endif /* !defined(nsAnimationManager_h_) */
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -406,62 +406,61 @@ CSSTransition::SetEffectFromStyle(dom::A
   }
 }
 
 ////////////////////////// nsTransitionManager ////////////////////////////
 
 
 static inline bool
 ExtractNonDiscreteComputedValue(nsCSSPropertyID aProperty,
-                                const ComputedStyle* aComputedStyle,
+                                const ComputedStyle& aComputedStyle,
                                 AnimationValue& aAnimationValue)
 {
   if (Servo_Property_IsDiscreteAnimatable(aProperty) &&
       aProperty != eCSSProperty_visibility) {
     return false;
   }
 
   aAnimationValue.mServo =
-    Servo_ComputedValues_ExtractAnimationValue(aComputedStyle,
+    Servo_ComputedValues_ExtractAnimationValue(&aComputedStyle,
                                                aProperty).Consume();
   return !!aAnimationValue.mServo;
 }
 
 
 bool
 nsTransitionManager::UpdateTransitions(
   dom::Element *aElement,
   CSSPseudoElementType aPseudoType,
-  const ComputedStyle* aOldStyle,
-  const ComputedStyle* aNewStyle)
+  const ComputedStyle& aOldStyle,
+  const ComputedStyle& aNewStyle)
 {
   if (!mPresContext->IsDynamic()) {
     // For print or print preview, ignore transitions.
     return false;
   }
 
   CSSTransitionCollection* collection =
     CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
   const nsStyleDisplay* disp =
-      aNewStyle->ComputedData()->GetStyleDisplay();
+      aNewStyle.ComputedData()->GetStyleDisplay();
   return DoUpdateTransitions(*disp,
                              aElement, aPseudoType,
                              collection,
                              aOldStyle, aNewStyle);
 }
 
-template<typename StyleType>
 bool
 nsTransitionManager::DoUpdateTransitions(
   const nsStyleDisplay& aDisp,
   dom::Element* aElement,
   CSSPseudoElementType aPseudoType,
   CSSTransitionCollection*& aElementTransitions,
-  StyleType aOldStyle,
-  StyleType aNewStyle)
+  const ComputedStyle& aOldStyle,
+  const ComputedStyle& aNewStyle)
 {
   MOZ_ASSERT(!aElementTransitions ||
              aElementTransitions->mElement == aElement, "Element mismatch");
 
   // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
   // I'll consider only the transitions from the number of items in
   // 'transition-property' on down, and later ones will override earlier
   // ones (tracked using |whichStarted|).
@@ -628,27 +627,26 @@ GetTransitionKeyframes(nsCSSPropertyID a
 }
 
 static bool
 IsTransitionable(nsCSSPropertyID aProperty)
 {
   return Servo_Property_IsTransitionable(aProperty);
 }
 
-template<typename StyleType>
 void
 nsTransitionManager::ConsiderInitiatingTransition(
   nsCSSPropertyID aProperty,
   const nsStyleDisplay& aStyleDisplay,
   uint32_t transitionIdx,
   dom::Element* aElement,
   CSSPseudoElementType aPseudoType,
   CSSTransitionCollection*& aElementTransitions,
-  StyleType aOldStyle,
-  StyleType aNewStyle,
+  const ComputedStyle& aOldStyle,
+  const ComputedStyle& aNewStyle,
   bool* aStartedAny,
   nsCSSPropertyIDSet* aWhichStarted)
 {
   // IsShorthand itself will assert if aProperty is not a property.
   MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
              "property out of range");
   NS_ASSERTION(!aElementTransitions ||
                aElementTransitions->mElement == aElement, "Element mismatch");
@@ -818,17 +816,17 @@ nsTransitionManager::ConsiderInitiatingT
   KeyframeEffectParams effectOptions;
   RefPtr<ElementPropertyTransition> pt =
     new ElementPropertyTransition(aElement->OwnerDoc(), target, timing,
                                   startForReversingTest, reversePortion,
                                   effectOptions);
 
   pt->SetKeyframes(GetTransitionKeyframes(aProperty,
                                           Move(startValue), Move(endValue), tf),
-                   aNewStyle);
+                   &aNewStyle);
 
   RefPtr<CSSTransition> animation =
     new CSSTransition(mPresContext->Document()->GetScopeObject());
   animation->SetOwningElement(OwningElementRef(*aElement, aPseudoType));
   animation->SetTimelineNoUpdate(timeline);
   animation->SetCreationSequence(
     mPresContext->RestyleManager()->GetAnimationGeneration());
   animation->SetEffectFromStyle(pt);
--- a/layout/style/nsTransitionManager.h
+++ b/layout/style/nsTransitionManager.h
@@ -18,20 +18,18 @@
 
 class nsIGlobalObject;
 class nsPresContext;
 class nsCSSPropertyIDSet;
 
 namespace mozilla {
 class ComputedStyle;
 enum class CSSPseudoElementType : uint8_t;
-class GeckoComputedStyle;
 struct Keyframe;
 struct StyleTransition;
-class ComputedStyle;
 } // namespace mozilla
 
 /*****************************************************************************
  * Per-Element data                                                          *
  *****************************************************************************/
 
 namespace mozilla {
 
@@ -317,46 +315,44 @@ public:
 
 
   /**
    * Update transitions for stylo.
    */
   bool UpdateTransitions(
     mozilla::dom::Element *aElement,
     mozilla::CSSPseudoElementType aPseudoType,
-    const mozilla::ComputedStyle* aOldStyle,
-    const mozilla::ComputedStyle* aNewStyle);
+    const mozilla::ComputedStyle& aOldStyle,
+    const mozilla::ComputedStyle& aNewStyle);
 
 
 protected:
   virtual ~nsTransitionManager() {}
 
   typedef nsTArray<RefPtr<mozilla::dom::CSSTransition>>
     OwningCSSTransitionPtrArray;
 
   // Update transitions. This will start new transitions,
   // replace existing transitions, and stop existing transitions
   // as needed. aDisp and aElement must be non-null.
   // aElementTransitions is the collection of current transitions, and it
   // could be a nullptr if we don't have any transitions.
-  template<typename StyleType> bool
-  DoUpdateTransitions(const nsStyleDisplay& aDisp,
-                      mozilla::dom::Element* aElement,
-                      mozilla::CSSPseudoElementType aPseudoType,
-                      CSSTransitionCollection*& aElementTransitions,
-                      StyleType aOldStyle,
-                      StyleType aNewStyle);
+  bool DoUpdateTransitions(const nsStyleDisplay& aDisp,
+                           mozilla::dom::Element* aElement,
+                           mozilla::CSSPseudoElementType aPseudoType,
+                           CSSTransitionCollection*& aElementTransitions,
+                           const mozilla::ComputedStyle& aOldStyle,
+                           const mozilla::ComputedStyle& aNewStyle);
 
-  template<typename StyleType> void
-  ConsiderInitiatingTransition(nsCSSPropertyID aProperty,
-                               const nsStyleDisplay& aStyleDisplay,
-                               uint32_t transitionIdx,
-                               mozilla::dom::Element* aElement,
-                               mozilla::CSSPseudoElementType aPseudoType,
-                               CSSTransitionCollection*& aElementTransitions,
-                               StyleType aOldStyle,
-                               StyleType aNewStyle,
-                               bool* aStartedAny,
-                               nsCSSPropertyIDSet* aWhichStarted);
+  void ConsiderInitiatingTransition(nsCSSPropertyID aProperty,
+                                    const nsStyleDisplay& aStyleDisplay,
+                                    uint32_t transitionIdx,
+                                    mozilla::dom::Element* aElement,
+                                    mozilla::CSSPseudoElementType aPseudoType,
+                                    CSSTransitionCollection*& aElementTransitions,
+                                    const mozilla::ComputedStyle& aOldStyle,
+                                    const mozilla::ComputedStyle& aNewStyle,
+                                    bool* aStartedAny,
+                                    nsCSSPropertyIDSet* aWhichStarted);
 
 };
 
 #endif /* !defined(nsTransitionManager_h_) */
--- a/modules/libpref/greprefs.js
+++ b/modules/libpref/greprefs.js
@@ -1,10 +1,11 @@
 #include ../../security/manager/ssl/security-prefs.js
 #include init/all.js
+#include ../../devtools/shared/preferences/devtools-shared.js
 #ifdef MOZ_DATA_REPORTING
 #include ../../toolkit/components/telemetry/datareporting-prefs.js
 #endif
 #ifdef MOZ_SERVICES_HEALTHREPORT
 #if MOZ_WIDGET_TOOLKIT != android
 #include ../../toolkit/components/telemetry/healthreport-prefs.js
 #endif
 #endif
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1072,102 +1072,26 @@ pref("toolkit.asyncshutdown.crash_timeou
 #else
 // MOZ_ASAN builds can be considerably slower. Extending the grace period
 // of both asyncshutdown and the terminator.
 pref("toolkit.asyncshutdown.crash_timeout", 180000); // 3 minutes
 #endif // MOZ_ASAN
 // Extra logging for AsyncShutdown barriers and phases
 pref("toolkit.asyncshutdown.log", false);
 
-// Tells if DevTools have been explicitely enabled by the user.
-// This pref allows to disable all features related to DevTools
-// for users that never use them.
-// Until bug 1361080 lands, we always consider them enabled.
-pref("devtools.enabled", true);
-
-// Enable deprecation warnings.
-pref("devtools.errorconsole.deprecation_warnings", true);
-
-#ifdef NIGHTLY_BUILD
-// Don't show the Browser Toolbox prompt on local builds / nightly
-pref("devtools.debugger.prompt-connection", false, sticky);
-#else
-pref("devtools.debugger.prompt-connection", true, sticky);
-#endif
-
 #ifdef MOZILLA_OFFICIAL
-// Disable debugging chrome
-pref("devtools.chrome.enabled", false, sticky);
-// Disable remote debugging connections
-pref("devtools.debugger.remote-enabled", false, sticky);
 // enable JS dump() function.
 pref("browser.dom.window.dump.enabled", false, sticky);
 #else
-// In local builds, enable the browser toolbox by default
-pref("devtools.chrome.enabled", true, sticky);
-pref("devtools.debugger.remote-enabled", true, sticky);
 pref("browser.dom.window.dump.enabled", true, sticky);
 #endif
 
-
-// Disable remote debugging protocol logging
-pref("devtools.debugger.log", false);
-pref("devtools.debugger.log.verbose", false);
-
-pref("devtools.debugger.remote-port", 6000);
-pref("devtools.debugger.remote-websocket", false);
-// Force debugger server binding on the loopback interface
-pref("devtools.debugger.force-local", true);
-// Block tools from seeing / interacting with certified apps
-pref("devtools.debugger.forbid-certified-apps", true);
-
-// Limit for intercepted response bodies (1 MB)
-// Possible values:
-// 0 => the response body has no limit
-// n => represents max number of bytes stored
-pref("devtools.netmonitor.responseBodyLimit", 1048576);
-
-// DevTools default color unit
-pref("devtools.defaultColorUnit", "authored");
-
-// Used for devtools debugging
-pref("devtools.dump.emit", false);
-
 // Controls whether EventEmitter module throws dump message on each emit
 pref("toolkit.dump.emit", false);
 
-// Disable device discovery logging
-pref("devtools.discovery.log", false);
-// Whether to scan for DevTools devices via WiFi
-pref("devtools.remote.wifi.scan", true);
-// Client must complete TLS handshake within this window (ms)
-pref("devtools.remote.tls-handshake-timeout", 10000);
-
-// URL of the remote JSON catalog used for device simulation
-pref("devtools.devices.url", "https://code.cdn.mozilla.net/devices/devices.json");
-
-// Display the introductory text
-pref("devtools.gcli.hideIntro", false);
-
-// How eager are we to show help: never=1, sometimes=2, always=3
-pref("devtools.gcli.eagerHelper", 2);
-
-// Alias to the script URLs for inject command.
-pref("devtools.gcli.jquerySrc", "https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js");
-pref("devtools.gcli.lodashSrc", "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.6.1/lodash.min.js");
-pref("devtools.gcli.underscoreSrc", "https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js");
-
-// Set imgur upload client ID
-pref("devtools.gcli.imgurClientID", '0df414e888d7240');
-// Imgur's upload URL
-pref("devtools.gcli.imgurUploadURL", "https://api.imgur.com/3/image");
-
-// GCLI commands directory
-pref("devtools.commands.dir", "");
-
 // view source
 pref("view_source.syntax_highlight", true);
 pref("view_source.wrap_long_lines", false);
 pref("view_source.editor.path", "");
 // allows to add further arguments to the editor; use the %LINE% placeholder
 // for jumping to a specific line (e.g. "/line:%LINE%" or "--goto %LINE%")
 pref("view_source.editor.args", "");
 
--- a/python/mozbuild/mozbuild/backend/tup.py
+++ b/python/mozbuild/mozbuild/backend/tup.py
@@ -1,15 +1,16 @@
 # 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 os
+import json
 import sys
 
 import mozpack.path as mozpath
 from mozbuild.base import MozbuildObject
 from mozbuild.backend.base import PartialBackend, HybridBackend
 from mozbuild.backend.recursivemake import RecursiveMakeBackend
 from mozbuild.shellutil import quote as shell_quote
 from mozbuild.util import OrderedDefaultDict
@@ -199,16 +200,19 @@ class TupOnly(CommonBackend, PartialBack
         # will be built before any rules that list this as an input.
         self._installed_idls = '$(MOZ_OBJ_ROOT)/<installed-idls>'
         self._installed_files = '$(MOZ_OBJ_ROOT)/<installed-files>'
         # The preprocessor including source-repo.h and buildid.h creates
         # dependencies that aren't specified by moz.build and cause errors
         # in Tup. Express these as a group dependency.
         self._early_generated_files = '$(MOZ_OBJ_ROOT)/<early-generated-files>'
 
+        self._built_in_addons = set()
+        self._built_in_addons_file = 'dist/bin/browser/chrome/browser/content/browser/built_in_addons.json'
+
         # application.ini.h is a special case since we need to process
         # the FINAL_TARGET_PP_FILES for application.ini before running
         # the GENERATED_FILES script, and tup doesn't handle the rules
         # out of order. Similarly, dependentlibs.list uses libxul as
         # an input, so must be written after the rule for libxul.
         self._delayed_files = (
             'application.ini.h',
             'dependentlibs.list',
@@ -440,16 +444,21 @@ class TupOnly(CommonBackend, PartialBack
 
         # The approach here is similar to fastermake.py, but we
         # simply write out the resulting files here.
         for target, entries in self._manifest_entries.iteritems():
             with self._write_file(mozpath.join(self.environment.topobjdir,
                                                target)) as fh:
                 fh.write(''.join('%s\n' % e for e in sorted(entries)))
 
+        if self._built_in_addons:
+            with self._write_file(mozpath.join(self.environment.topobjdir,
+                                               self._built_in_addons_file)) as fh:
+                json.dump({'system': sorted(list(self._built_in_addons))}, fh)
+
         for objdir, backend_file in sorted(self._backend_files.items()):
             backend_file.gen_sources_rules([self._installed_files])
             for condition, gen_method in ((backend_file.shared_lib, self._gen_shared_library),
                                           (backend_file.static_lib and backend_file.static_lib.no_expand_lib,
                                            self._gen_static_library),
                                           (backend_file.program, self._gen_program)):
                 if condition:
                     backend_file.export_shell()
@@ -543,16 +552,22 @@ class TupOnly(CommonBackend, PartialBack
     def _process_defines(self, backend_file, obj, host=False):
         defines = list(obj.get_defines())
         if defines:
             if host:
                 backend_file.host_defines = defines
             else:
                 backend_file.defines = defines
 
+    def _add_features(self, target, path):
+        path_parts = mozpath.split(path)
+        if all([target == 'dist/bin/browser', path_parts[0] == 'features',
+                len(path_parts) > 1]):
+            self._built_in_addons.add(path_parts[1])
+
     def _process_final_target_files(self, obj):
         target = obj.install_target
         if not isinstance(obj, ObjdirFiles):
             path = mozpath.basedir(target, (
                 'dist/bin',
                 'dist/xpi-stage',
                 '_tests',
                 'dist/include',
@@ -562,16 +577,17 @@ class TupOnly(CommonBackend, PartialBack
                 raise Exception("Cannot install to " + target)
 
         if target.startswith('_tests'):
             # TODO: TEST_HARNESS_FILES present a few challenges for the tup
             # backend (bug 1372381).
             return
 
         for path, files in obj.files.walk():
+            self._add_features(target, path)
             for f in files:
                 if not isinstance(f, ObjDirPath):
                     backend_file = self._get_backend_file(mozpath.join(target, path))
                     if '*' in f:
                         if f.startswith('/') or isinstance(f, AbsolutePath):
                             basepath, wild = os.path.split(f.full_path)
                             if '*' in basepath:
                                 raise Exception("Wildcards are only supported in the filename part of "
@@ -613,16 +629,17 @@ class TupOnly(CommonBackend, PartialBack
                             gen_backend_file.delayed_installed_files.append((f.full_path, output))
                         else:
                             gen_backend_file.symlink_rule(f.full_path, output=output,
                                                           output_group=self._installed_files)
 
 
     def _process_final_target_pp_files(self, obj, backend_file):
         for i, (path, files) in enumerate(obj.files.walk()):
+            self._add_features(obj.install_target, path)
             for f in files:
                 self._preprocess(backend_file, f.full_path,
                                  destdir=mozpath.join(self.environment.topobjdir, obj.install_target, path),
                                  target=f.target_basename)
 
     def _process_computed_flags(self, obj, backend_file):
         for var, flags in obj.get_flags():
             backend_file.local_flags[var] = flags
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -200,17 +200,17 @@ dependencies = [
 [[package]]
 name = "blurdroid"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "blurmac"
 version = "0.1.0"
-source = "git+https://github.com/servo/devices#1069d67cbacb28b77a3d5dd7f211171c05f32c62"
+source = "git+https://github.com/servo/devices#eeb6eaddb79c019bb5c85558b3410b836da57a57"
 dependencies = [
  "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "blurmock"
 version = "0.1.2"
@@ -683,17 +683,17 @@ name = "deny_public_fields_tests"
 version = "0.0.1"
 dependencies = [
  "deny_public_fields 0.0.1",
 ]
 
 [[package]]
 name = "device"
 version = "0.0.1"
-source = "git+https://github.com/servo/devices#1069d67cbacb28b77a3d5dd7f211171c05f32c62"
+source = "git+https://github.com/servo/devices#eeb6eaddb79c019bb5c85558b3410b836da57a57"
 dependencies = [
  "blurdroid 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "blurmac 0.1.0 (git+https://github.com/servo/devices)",
  "blurmock 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "blurz 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1105,17 +1105,17 @@ dependencies = [
  "gl_generator 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
  "objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "shared_library 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "wayland-client 0.12.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "winit 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winit 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "x11-dl 2.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "glx"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -2732,17 +2732,17 @@ dependencies = [
  "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "osmesa-src 17.3.1-devel (git+https://github.com/servo/osmesa-src)",
  "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "sig 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "tinyfiledialogs 3.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "winit 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winit 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "winres 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "x11 2.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo-egl"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3659,17 +3659,17 @@ name = "wincolor"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "winit"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "android_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "cocoa 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -4077,17 +4077,17 @@ dependencies = [
 "checksum webrender_api 0.57.0 (git+https://github.com/servo/webrender)" = "<none>"
 "checksum which 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4be6cfa54dab45266e98b5d7be2f8ce959ddd49abd141a05d52dce4b07f803bb"
 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
 "checksum winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b09fb3b6f248ea4cd42c9a65113a847d612e17505d6ebd1f7357ad68a8bf8693"
 "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
 "checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc"
 "checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668"
 "checksum wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb06499a3a4d44302791052df005d5232b927ed1a9658146d842165c4de7767"
-"checksum winit 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "396f0350e661940359e3c8c7d58ff847f67997943e2c80ecac374c5aa8287f63"
+"checksum winit 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f1a29847ed6928d6cbabe6b2d5b11dd0ce63380af53a8dcd41775d27d104d285"
 "checksum winres 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "27d9192d6356d7efe8405dec6c5506b67543cf64b6049968f39f4c4623b4f25d"
 "checksum ws 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "89c48c53bf9dee34411a08993c10b879c36e105d609b46e25673befe3a5c1320"
 "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
 "checksum x11 2.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e5c4ac579b5d324dc4add02312b5d0e3e0218521e2d5779d526ac39ee4bb171"
 "checksum x11-clipboard 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2e7374c7699210cca7084ca61d57e09640fc744d1391808cb9ae2fe4ca9bd1df"
 "checksum x11-dl 2.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "29e78a65a3239e5511ffe2c832edb9224982ebf67bcaabc218ef1b07d8494b3e"
 "checksum xcb 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de"
 "checksum xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a66b7c2281ebde13cf4391d70d4c7e5946c3c25e72a7b859ca8f677dcd0b0c61"
--- a/servo/components/canvas_traits/webgl.rs
+++ b/servo/components/canvas_traits/webgl.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use euclid::Size2D;
-use nonzero::NonZero;
+use nonzero::NonZeroU32;
 use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use serde_bytes::ByteBuf;
 use std::fmt;
 use webrender_api::{DocumentId, ImageKey, PipelineId};
 
 /// Sender type used in WebGLCommands.
 pub use ::webgl_channel::WebGLSender;
 /// Receiver type used in WebGLCommands.
@@ -275,23 +275,23 @@ pub enum WebGLCommand {
     DeleteVertexArray(WebGLVertexArrayId),
     BindVertexArray(Option<WebGLVertexArrayId>),
     AliasedPointSizeRange(WebGLSender<(f32, f32)>),
 }
 
 macro_rules! define_resource_id_struct {
     ($name:ident) => {
         #[derive(Clone, Copy, Eq, Hash, PartialEq)]
-        pub struct $name(NonZero<u32>);
+        pub struct $name(NonZeroU32);
 
         impl $name {
             #[allow(unsafe_code)]
             #[inline]
             pub unsafe fn new(id: u32) -> Self {
-                $name(NonZero::new_unchecked(id))
+                $name(NonZeroU32::new_unchecked(id))
             }
 
             #[inline]
             pub fn get(self) -> u32 {
                 self.0.get()
             }
         }
 
--- a/servo/components/compositing/compositor.rs
+++ b/servo/components/compositing/compositor.rs
@@ -9,17 +9,17 @@ use compositor_thread::{InitialComposito
 use euclid::{TypedPoint2D, TypedVector2D, TypedScale};
 use gfx_traits::Epoch;
 use gleam::gl;
 use image::{DynamicImage, ImageFormat, RgbImage};
 use ipc_channel::ipc::{self, IpcSharedMemory};
 use libc::c_void;
 use msg::constellation_msg::{PipelineId, PipelineIndex, PipelineNamespaceId};
 use net_traits::image::base::{Image, PixelFormat};
-use nonzero::NonZero;
+use nonzero::NonZeroU32;
 use profile_traits::time::{self, ProfilerCategory, profile};
 use script_traits::{AnimationState, AnimationTickType, ConstellationMsg, LayoutControlMsg};
 use script_traits::{MouseButton, MouseEventType, ScrollState, TouchEventType, TouchId};
 use script_traits::{UntrustedNodeAddress, WindowSizeData, WindowSizeType};
 use script_traits::CompositorEvent::{MouseMoveEvent, MouseButtonEvent, TouchEvent};
 use servo_config::opts;
 use servo_geometry::{DeviceIndependentPixel, DeviceUintLength};
 use std::collections::HashMap;
@@ -59,17 +59,17 @@ const MIN_ZOOM: f32 = 0.1;
 trait ConvertPipelineIdFromWebRender {
     fn from_webrender(&self) -> PipelineId;
 }
 
 impl ConvertPipelineIdFromWebRender for webrender_api::PipelineId {
     fn from_webrender(&self) -> PipelineId {
         PipelineId {
             namespace_id: PipelineNamespaceId(self.0),
-            index: PipelineIndex(NonZero::new(self.1).expect("Webrender pipeline zero?")),
+            index: PipelineIndex(NonZeroU32::new(self.1).expect("Webrender pipeline zero?")),
         }
     }
 }
 
 /// Holds the state when running reftests that determines when it is
 /// safe to save the output image.
 #[derive(Clone, Copy, Debug, PartialEq)]
 enum ReadyState {
--- a/servo/components/dom_struct/lib.rs
+++ b/servo/components/dom_struct/lib.rs
@@ -2,26 +2,25 @@
  * 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/. */
 
 #![feature(proc_macro)]
 
 extern crate proc_macro;
 
 use proc_macro::{TokenStream, quote};
-use std::iter;
 
 #[proc_macro_attribute]
 pub fn dom_struct(args: TokenStream, input: TokenStream) -> TokenStream {
     if !args.is_empty() {
         panic!("#[dom_struct] takes no arguments");
     }
     let attributes = quote! {
         #[derive(DenyPublicFields, DomObject, JSTraceable, MallocSizeOf)]
         #[must_root]
         #[repr(C)]
     };
 
     // Work around https://github.com/rust-lang/rust/issues/46489
-    let attributes = attributes.to_string().parse().unwrap();
+    let attributes: TokenStream = attributes.to_string().parse().unwrap();
 
-    iter::once(attributes).chain(iter::once(input)).collect()
+    attributes.into_iter().chain(input.into_iter()).collect()
 }
--- a/servo/components/msg/constellation_msg.rs
+++ b/servo/components/msg/constellation_msg.rs
@@ -1,16 +1,16 @@
 /* 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/. */
 
 //! The high-level interface from script to constellation. Using this abstract interface helps
 //! reduce coupling between these two components.
 
-use nonzero::NonZero;
+use nonzero::NonZeroU32;
 use std::cell::Cell;
 use std::fmt;
 use webrender_api;
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
 pub enum KeyState {
     Pressed,
     Released,
@@ -190,19 +190,19 @@ impl PipelineNamespace {
             assert!(tls.get().is_none());
             tls.set(Some(PipelineNamespace {
                 id: namespace_id,
                 index: 0,
             }));
         });
     }
 
-    fn next_index(&mut self) -> NonZero<u32> {
+    fn next_index(&mut self) -> NonZeroU32 {
         self.index += 1;
-        NonZero::new(self.index).expect("pipeline id index wrapped!")
+        NonZeroU32::new(self.index).expect("pipeline id index wrapped!")
     }
 
     fn next_pipeline_id(&mut self) -> PipelineId {
         PipelineId {
             namespace_id: self.id,
             index: PipelineIndex(self.next_index()),
         }
     }
@@ -216,17 +216,17 @@ impl PipelineNamespace {
 }
 
 thread_local!(pub static PIPELINE_NAMESPACE: Cell<Option<PipelineNamespace>> = Cell::new(None));
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize)]
 pub struct PipelineNamespaceId(pub u32);
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
-pub struct PipelineIndex(pub NonZero<u32>);
+pub struct PipelineIndex(pub NonZeroU32);
 malloc_size_of_is_0!(PipelineIndex);
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize)]
 pub struct PipelineId {
     pub namespace_id: PipelineNamespaceId,
     pub index: PipelineIndex
 }
 
@@ -247,17 +247,17 @@ impl PipelineId {
     }
 
     #[allow(unsafe_code)]
     pub fn from_webrender(pipeline: webrender_api::PipelineId) -> PipelineId {
         let webrender_api::PipelineId(namespace_id, index) = pipeline;
         unsafe {
             PipelineId {
                 namespace_id: PipelineNamespaceId(namespace_id),
-                index: PipelineIndex(NonZero::new_unchecked(index)),
+                index: PipelineIndex(NonZeroU32::new_unchecked(index)),
             }
         }
     }
 
     pub fn root_scroll_node(&self) -> webrender_api::ClipId {
         webrender_api::ClipId::root_scroll_node(self.to_webrender())
     }
 
@@ -274,17 +274,17 @@ impl fmt::Display for PipelineId {
     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
         let PipelineNamespaceId(namespace_id) = self.namespace_id;
         let PipelineIndex(index) = self.index;
         write!(fmt, "({},{})", namespace_id, index.get())
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
-pub struct BrowsingContextIndex(pub NonZero<u32>);
+pub struct BrowsingContextIndex(pub NonZeroU32);
 malloc_size_of_is_0!(BrowsingContextIndex);
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd, Serialize)]
 pub struct BrowsingContextId {
     pub namespace_id: PipelineNamespaceId,
     pub index: BrowsingContextIndex,
 }
 
@@ -350,18 +350,18 @@ impl PartialEq<BrowsingContextId> for To
         self.0.eq(rhs)
     }
 }
 
 // We provide ids just for unit testing.
 pub const TEST_NAMESPACE: PipelineNamespaceId = PipelineNamespaceId(1234);
 #[allow(unsafe_code)]
 #[cfg(feature = "unstable")]
-pub const TEST_PIPELINE_INDEX: PipelineIndex = unsafe { PipelineIndex(NonZero::new_unchecked(5678)) };
+pub const TEST_PIPELINE_INDEX: PipelineIndex = unsafe { PipelineIndex(NonZeroU32::new_unchecked(5678)) };
 #[cfg(feature = "unstable")]
 pub const TEST_PIPELINE_ID: PipelineId = PipelineId { namespace_id: TEST_NAMESPACE, index: TEST_PIPELINE_INDEX };
 #[allow(unsafe_code)]
 #[cfg(feature = "unstable")]
 pub const TEST_BROWSING_CONTEXT_INDEX: BrowsingContextIndex =
-    unsafe { BrowsingContextIndex(NonZero::new_unchecked(8765)) };
+    unsafe { BrowsingContextIndex(NonZeroU32::new_unchecked(8765)) };
 #[cfg(feature = "unstable")]
 pub const TEST_BROWSING_CONTEXT_ID: BrowsingContextId =
     BrowsingContextId { namespace_id: TEST_NAMESPACE, index: TEST_BROWSING_CONTEXT_INDEX };
--- a/servo/components/nonzero/lib.rs
+++ b/servo/components/nonzero/lib.rs
@@ -2,140 +2,111 @@
  * 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/. */
 
 //! `NonZero*` types that are either `core::nonzero::NonZero<_>`
 //! or some stable types with an equivalent API (but no memory layout optimization).
 
 #![cfg_attr(feature = "unstable", feature(nonzero))]
 #![cfg_attr(feature = "unstable", feature(const_fn))]
-#![cfg_attr(feature = "unstable", feature(const_nonzero_new))]
 
-#[cfg_attr(not(feature = "unstable"), macro_use)]
 extern crate serde;
 
-pub use imp::*;
-
-#[cfg(feature = "unstable")]
-mod imp {
-    extern crate core;
-    use self::core::nonzero::NonZero as CoreNonZero;
-    use serde::{Serialize, Serializer, Deserialize, Deserializer};
-
-    pub use self::core::nonzero::Zeroable;
-
-    #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
-    pub struct NonZero<T: Zeroable>(CoreNonZero<T>);
-
-    impl<T: Zeroable> NonZero<T> {
-        #[inline]
-        pub const unsafe fn new_unchecked(x: T) -> Self {
-            NonZero(CoreNonZero::new_unchecked(x))
-        }
-
-        #[inline]
-        pub fn new(x: T) -> Option<Self> {
-            CoreNonZero::new(x).map(NonZero)
-        }
+use std::fmt;
 
-        #[inline]
-        pub fn get(self) -> T {
-            self.0.get()
-        }
-    }
-
-    // Not using derive because of the additional Clone bound required by the inner impl
-
-    impl<T> Serialize for NonZero<T>
-    where
-        T: Serialize + Zeroable + Clone,
-    {
-        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-        where
-            S: Serializer,
-        {
-            self.0.serialize(serializer)
-        }
-    }
-
-    impl<'de, T> Deserialize<'de> for NonZero<T>
-    where
-        T: Deserialize<'de> + Zeroable,
-    {
-        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-        where
-            D: Deserializer<'de>,
-        {
-            CoreNonZero::deserialize(deserializer).map(NonZero)
-        }
+macro_rules! impl_nonzero_fmt {
+    ( ( $( $Trait: ident ),+ ) for $Ty: ident ) => {
+        $(
+            impl fmt::$Trait for $Ty {
+                #[inline]
+                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                    self.get().fmt(f)
+                }
+            }
+        )+
     }
 }
 
-#[cfg(not(feature = "unstable"))]
-mod imp {
-    #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
-    pub struct NonZero<T: Zeroable>(T);
+macro_rules! nonzero_integers {
+    ( $( $Ty: ident($Int: ty); )+ ) => {
+        $(
+            #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
+            pub struct $Ty(
+                #[cfg(feature = "unstable")] std::num::$Ty,
+                #[cfg(not(feature = "unstable"))] $Int,
+            );
 
-    impl<T: Zeroable> NonZero<T> {
-        #[inline]
-        pub unsafe fn new_unchecked(x: T) -> Self {
-            NonZero(x)
-        }
-
-        #[inline]
-        pub fn new(x: T) -> Option<Self> {
-            if x.is_zero() {
-                None
-            } else {
-                Some(NonZero(x))
-            }
-        }
+            impl $Ty {
+                #[cfg(feature = "unstable")]
+                #[inline]
+                pub const unsafe fn new_unchecked(n: $Int) -> Self {
+                    $Ty(std::num::$Ty::new_unchecked(n))
+                }
 
-        #[inline]
-        pub fn get(self) -> T {
-            self.0
-        }
-    }
+                #[cfg(not(feature = "unstable"))]
+                #[inline]
+                pub unsafe fn new_unchecked(n: $Int) -> Self {
+                    $Ty(n)
+                }
 
-    /// Unsafe trait to indicate what types are usable with the NonZero struct
-    pub unsafe trait Zeroable {
-        /// Whether this value is zero
-        fn is_zero(&self) -> bool;
-    }
+                #[cfg(feature = "unstable")]
+                #[inline]
+                pub fn new(n: $Int) -> Option<Self> {
+                    std::num::$Ty::new(n).map($Ty)
+                }
 
-    macro_rules! impl_zeroable_for_pointer_types {
-        ( $( $Ptr: ty )+ ) => {
-            $(
-                /// For fat pointers to be considered "zero", only the "data" part needs to be null.
-                unsafe impl<T: ?Sized> Zeroable for $Ptr {
-                    #[inline]
-                    fn is_zero(&self) -> bool {
-                        // Cast because `is_null` is only available on thin pointers
-                        (*self as *mut u8).is_null()
+                #[cfg(not(feature = "unstable"))]
+                #[inline]
+                pub fn new(n: $Int) -> Option<Self> {
+                    if n != 0 {
+                        Some($Ty(n))
+                    } else {
+                        None
                     }
                 }
-            )+
-        }
-    }
+
+                #[cfg(feature = "unstable")]
+                #[inline]
+                pub fn get(self) -> $Int {
+                    self.0.get()
+                }
+
+                #[cfg(not(feature = "unstable"))]
+                #[inline]
+                pub fn get(self) -> $Int {
+                    self.0
+                }
+            }
 
-    macro_rules! impl_zeroable_for_integer_types {
-        ( $( $Int: ty )+ ) => {
-            $(
-                unsafe impl Zeroable for $Int {
-                    #[inline]
-                    fn is_zero(&self) -> bool {
-                        *self == 0
+            impl_nonzero_fmt! {
+                (Debug, Display, Binary, Octal, LowerHex, UpperHex) for $Ty
+            }
+
+            impl serde::Serialize for $Ty {
+                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+                    where S: serde::Serializer
+                {
+                    self.get().serialize(serializer)
+                }
+            }
+
+            impl<'de> serde::Deserialize<'de> for $Ty {
+                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+                    where D: serde::Deserializer<'de>
+                {
+                    let value = <$Int>::deserialize(deserializer)?;
+                    match <$Ty>::new(value) {
+                        Some(nonzero) => Ok(nonzero),
+                        None => Err(serde::de::Error::custom("expected a non-zero value")),
                     }
                 }
-            )+
-        }
-    }
-
-    impl_zeroable_for_pointer_types! {
-        *const T
-        *mut T
-    }
-
-    impl_zeroable_for_integer_types! {
-        usize u8 u16 u32 u64
-        isize i8 i16 i32 i64
+            }
+        )+
     }
 }
+
+nonzero_integers! {
+    NonZeroU8(u8);
+    NonZeroU16(u16);
+    NonZeroU32(u32);
+    NonZeroU64(u64);
+    NonZeroUsize(usize);
+}
--- a/servo/components/remutex/lib.rs
+++ b/servo/components/remutex/lib.rs
@@ -9,36 +9,36 @@
 
 //! It provides the same interface as https://github.com/rust-lang/rust/blob/master/src/libstd/sys/common/remutex.rs
 //! so if those types are ever exported, we should be able to replace this implemtation.
 
 extern crate nonzero;
 #[macro_use] extern crate lazy_static;
 #[macro_use] extern crate log;
 
-use nonzero::NonZero;
+use nonzero::NonZeroUsize;
 use std::cell::{Cell, UnsafeCell};
 use std::ops::Deref;
 use std::sync::{LockResult, Mutex, MutexGuard, PoisonError, TryLockError, TryLockResult};
 use std::sync::atomic::{AtomicUsize, Ordering};
 
 /// A type for thread ids.
 
 // TODO: can we use the thread-id crate for this?
 
 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
-pub struct ThreadId(NonZero<usize>);
+pub struct ThreadId(NonZeroUsize);
 
 lazy_static!{ static ref THREAD_COUNT: AtomicUsize = AtomicUsize::new(1); }
 
 impl ThreadId {
     #[allow(unsafe_code)]
     fn new() -> ThreadId {
         let number = THREAD_COUNT.fetch_add(1, Ordering::SeqCst);
-        ThreadId(NonZero::new(number).unwrap())
+        ThreadId(NonZeroUsize::new(number).unwrap())
     }
     pub fn current() -> ThreadId {
         THREAD_ID.with(|tls| tls.clone())
     }
 }
 
 thread_local!{ static THREAD_ID: ThreadId = ThreadId::new() }
 
@@ -52,23 +52,23 @@ impl AtomicOptThreadId {
     }
     pub fn store(&self, value: Option<ThreadId>, ordering: Ordering) {
         let number = value.map(|id| id.0.get()).unwrap_or(0);
         self.0.store(number, ordering);
     }
     #[allow(unsafe_code)]
     pub fn load(&self, ordering: Ordering) -> Option<ThreadId> {
         let number = self.0.load(ordering);
-        NonZero::new(number).map(ThreadId)
+        NonZeroUsize::new(number).map(ThreadId)
     }
     #[allow(unsafe_code)]
     pub fn swap(&self, value: Option<ThreadId>, ordering: Ordering) -> Option<ThreadId> {
         let number = value.map(|id| id.0.get()).unwrap_or(0);
         let number = self.0.swap(number, ordering);
-        NonZero::new(number).map(ThreadId)
+        NonZeroUsize::new(number).map(ThreadId)
     }
 }
 
 /// A type for hand-over-hand mutexes.
 ///
 /// These support `lock` and `unlock` functions. `lock` blocks waiting to become the
 /// mutex owner. `unlock` can only be called by the lock owner, and panics otherwise.
 /// They have the same happens-before and poisoning semantics as `Mutex`.
--- a/servo/components/script/dom/bindings/str.rs
+++ b/servo/components/script/dom/bindings/str.rs
@@ -203,17 +203,17 @@ impl DOMString {
             None => {
                 self.0.clear();
                 return;
             }
         };
         let first_non_whitespace = self.0.find(|ref c| !char::is_ascii_whitespace(c)).unwrap();
 
         self.0.truncate(last_non_whitespace);
-        let _ = self.0.splice(0..first_non_whitespace, "");
+        let _ = self.0.replace_range(0..first_non_whitespace, "");
     }
 
     /// Validates this `DOMString` is a time string according to
     /// <https://html.spec.whatwg.org/multipage/#valid-time-string>.
     pub fn is_valid_time_string(&self) -> bool {
         enum State {
             HourHigh,
             HourLow09,
--- a/servo/components/script/lib.rs
+++ b/servo/components/script/lib.rs
@@ -1,21 +1,18 @@
 /* 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/. */
 
 #![cfg_attr(feature = "unstable", feature(core_intrinsics))]
 #![cfg_attr(feature = "unstable", feature(on_unimplemented))]
-#![feature(ascii_ctype)]
-#![feature(conservative_impl_trait)]
 #![feature(const_fn)]
 #![feature(mpsc_select)]
 #![feature(plugin)]
 #![feature(proc_macro)]
-#![feature(splice)]
 #![feature(string_retain)]
 
 #![deny(unsafe_code)]
 #![allow(non_snake_case)]
 
 #![doc = "The script crate contains all matters DOM."]
 
 #![plugin(script_plugins)]
--- a/servo/rust-toolchain
+++ b/servo/rust-toolchain
@@ -1,1 +1,1 @@
-nightly-2018-01-27
+nightly-2018-04-08
rename from testing/talos/talos/tests/tabswitch/bootstrap.js
rename to testing/talos/talos/tests/tabswitch/api.js
--- a/testing/talos/talos/tests/tabswitch/bootstrap.js
+++ b/testing/talos/talos/tests/tabswitch/api.js
@@ -1,58 +1,19 @@
-
 // -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
 
-/* globals APP_SHUTDOWN */
+/* globals ExtensionAPI */
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/Promise.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://gre/modules/Task.jsm");
-ChromeUtils.import("resource://gre/modules/Timer.jsm");
 ChromeUtils.import("resource://gre/modules/RemotePageManager.jsm");
 
-let aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
-                           .getService(Ci.nsIAboutNewTabService);
-
-var aboutBlankTab = null;
 let context = {};
 let TalosParentProfiler;
 
-var windowListener = {
-  onOpenWindow(aWindow) {
-    // Ensure we don't get tiles which contact the network
-    aboutNewTabService.newTabURL = "about:blank";
-
-    // Wait for the window to finish loading
-    let window = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
-    let cb = function() {
-      window.removeEventListener("load", cb);
-      loadIntoWindow(window);
-    };
-    window.addEventListener("load", cb);
-  },
-
-  onCloseWindow(aWindow) {
-    aboutNewTabService.resetNewTabURL();
-  },
-};
-
-function promiseOneEvent(target, eventName, capture) {
-  let deferred = Promise.defer();
-  target.addEventListener(eventName, function handler(event) {
-    deferred.resolve();
-  }, {capture, once: true});
-  return deferred.promise;
-}
-
-function executeSoon(callback) {
-  Services.tm.dispatchToMainThread(callback);
-}
-
 /**
  * Returns a Promise that resolves when browser-delayed-startup-finished
  * fires for a given window
  *
  * @param win
  *        The window that we're waiting for the notification for.
  * @returns Promise
  */
@@ -111,143 +72,38 @@ function loadTabs(gBrowser, urls) {
       },
     };
 
     gBrowser.addTabsProgressListener(listener);
   });
 }
 
 /**
- * Loads the utility content script for the tps for out-of-process
- * browsers into a browser. This should not be used for in-process
- * browsers.
- *
- * The utility script will send a "TPS:ContentSawPaint" message
- * through the browser's message manager when it sees that its
- * content has been presented to the user.
- *
- * @param browser (<xul:browser>)
- *        The remote browser to load the script in.
- * @returns Promise
- *        Resolves once the script has been loaded and executed in
- *        the remote browser.
- */
-function loadTPSContentScript(browser) {
-  if (!browser.isRemoteBrowser) {
-    throw new Error("loadTPSContentScript expects a remote browser.");
-  }
-  return new Promise((resolve) => {
-    // Here's our utility script. We'll serialize this and send it down
-    // to run in the content process for this browser.
-    let script = function() {
-      ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-      /**
-       * In order to account for the fact that a MozAfterPaint might fire
-       * for a composite that's unrelated to this tab's content being
-       * painted, we'll get the last used layer transaction ID for
-       * this content's refresh driver, and make sure that the MozAfterPaint
-       * that we react to has a greater transaction id.
-       *
-       * Note also that this comment needs to stay inside this comment
-       * block. No // comments allowed when serializing JS to content
-       * scripts this way.
-       */
-      let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDOMWindowUtils);
-      let lastTransactionId = cwu.lastTransactionId;
-      Services.profiler.AddMarker("Content waiting for id > " + lastTransactionId);
-      addEventListener("MozAfterPaint", function onPaint(event) {
-        Services.profiler.AddMarker("Content saw transaction id: " + event.transactionId);
-        if (event.transactionId > lastTransactionId) {
-          Services.profiler.AddMarker("Content saw correct MozAfterPaint");
-          let time = Math.floor(content.performance.timing.navigationStart + content.performance.now());
-          sendAsyncMessage("TPS:ContentSawPaint", { time });
-          removeEventListener("MozAfterPaint", onPaint);
-        }
-      });
-
-      sendAsyncMessage("TPS:ContentReady");
-    };
-
-    let mm = browser.messageManager;
-    mm.loadFrameScript("data:,(" + script.toString() + ")();", true);
-    mm.addMessageListener("TPS:ContentReady", function onReady() {
-      mm.removeMessageListener("TPS:ContentReady", onReady);
-      resolve();
-    });
-  });
-}
-
-/**
  * For some <xul:tab> in a browser window, have that window switch
  * to that tab. Returns a Promise that resolves ones the tab content
  * has been presented to the user.
  */
-function switchToTab(tab) {
+async function switchToTab(tab) {
   let browser = tab.linkedBrowser;
   let gBrowser = tab.ownerGlobal.gBrowser;
-  let window = tab.ownerGlobal;
 
-  // Single-process tab switching works quite differently from
-  // multi-process tab switching. In the single-process case, tab
-  // switching is synchronous, whereas in the multi-process case,
-  // it is not. The following two tab switching mechanisms encapsulate
-  // those two differences.
-
-  if (browser.isRemoteBrowser) {
-    return Task.spawn(function* () {
-      // The multi-process case requires that we load our utility script
-      // inside the content, since it's the content that will hear a MozAfterPaint
-      // once the content is presented to the user.
-      yield loadTPSContentScript(browser);
-      let start = Math.floor(window.performance.timing.navigationStart + window.performance.now());
-
-      // We need to wait for the TabSwitchDone event to make sure
-      // that the async tab switcher has shut itself down.
-      let switchDone = waitForTabSwitchDone(browser);
-      // Set up our promise that will wait for the content to be
-      // presented.
-      let finishPromise = waitForContentPresented(browser);
-      // Finally, do the tab switch.
-      gBrowser.selectedTab = tab;
+  let start = Cu.now();
 
-      yield switchDone;
-      let finish = yield finishPromise;
-      return finish - start;
-    });
-  }
-
-  return Task.spawn(function* () {
-    let win = browser.ownerGlobal;
-    let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIDOMWindowUtils);
-
-    let start = Math.floor(window.performance.timing.navigationStart + window.performance.now());
+  // We need to wait for the TabSwitchDone event to make sure
+  // that the async tab switcher has shut itself down.
+  let switchDone = waitForTabSwitchDone(browser);
+  // Set up our promise that will wait for the content to be
+  // presented.
+  let finishPromise = waitForContentPresented(browser);
+  // Finally, do the tab switch.
+  gBrowser.selectedTab = tab;
 
-    // There is no async tab switcher for the single-process case,
-    // but tabbrowser.xml will still fire this once the updateCurrentBrowser
-    // method runs.
-    let switchDone = waitForTabSwitchDone(browser);
-    // Do our tab switch
-    gBrowser.selectedTab = tab;
-    // Because the above tab switch is synchronous, we know that the
-    // we want a MozAfterPaint with a greater layer transaction id than
-    // what is currently the "last transaction id" for the window.
-    let lastTransactionId = winUtils.lastTransactionId;
-
-    yield switchDone;
-
-    // Now we'll wait for content to be presented. Because
-    // this is the single-process case, we pass the last transaction
-    // id that we got so that we don't get any intermediate MozAfterPaint's
-    // that might fire before web content is shown.
-    let finish = yield waitForContentPresented(browser, lastTransactionId);
-    return finish - start;
-  });
+  await switchDone;
+  let finish = await finishPromise;
+  return finish - start;
 }
 
 /**
  * For some <xul:browser>, find the <xul:tabbrowser> associated with it,
  * and wait until that tabbrowser has finished a tab switch. This function
  * assumes a tab switch has started, or is about to start.
  *
  * @param browser (<xul:browser>)
@@ -266,58 +122,29 @@ function waitForTabSwitchDone(browser) {
 }
 
 /**
  * For some <xul:browser>, returns a Promise that resolves once its
  * content has been presented to the user.
  *
  * @param browser (<xul:browser>)
  *        The browser we expect to be presented.
- * @param lastTransactionId (int, optional)
- *        In the single-process case, we need to know the last layer
- *        transaction id that was used before the switch started. That
- *        way, when the MozAfterPaint fires, we can be sure that its
- *        transaction id is greater than the one that was last used,
- *        so we know that the content has definitely been presented.
- *
- *        This argument is ignored in the multi-process browser case.
  *
  * @returns Promise
  *        Resolves once the content has been presented. Resolves to
  *        the system time that the presentation occurred at, in
  *        milliseconds since midnight 01 January, 1970 UTC.
  */
-function waitForContentPresented(browser, lastTransactionId) {
-  // We treat multi-process browsers differently here - we expect the
-  // utility script we loaded to inform us once content has been presented.
-  if (browser.isRemoteBrowser) {
-    return new Promise((resolve) => {
-      let mm = browser.messageManager;
-      mm.addMessageListener("TPS:ContentSawPaint", function onContentPaint(msg) {
-        mm.removeMessageListener("TPS:ContentSawPaint", onContentPaint);
-        resolve(msg.data.time);
-      });
-    });
-  }
-
-  // Wait for the next MozAfterPaint for this browser's window that has
-  // a greater transaction id than lastTransactionId.
+function waitForContentPresented(browser) {
   return new Promise((resolve) => {
-    let win = browser.ownerGlobal;
-    win.addEventListener("MozAfterPaint", function onPaint(event) {
-      if (ChromeUtils.getClassName(event) === "NotifyPaintEvent") {
-        TalosParentProfiler.mark("Content saw transaction id: " + event.transactionId);
-        if (event.transactionId > lastTransactionId) {
-          win.removeEventListener("MozAfterPaint", onPaint);
-          TalosParentProfiler.mark("Content saw MozAfterPaint");
-          let time = Math.floor(win.performance.timing.navigationStart + win.performance.now());
-          resolve(time);
-        }
-      }
-    });
+    browser.addEventListener("MozLayerTreeReady", function onLayersReady(event) {
+      let now = Cu.now();
+      TalosParentProfiler.mark("MozLayerTreeReady seen by tps");
+      resolve(now);
+    }, { once: true });
   });
 }
 
 /**
  * Given some browser, do a garbage collect in the parent, and then
  * a garbage collection in the content process that the browser is
  * running in.
  *
@@ -353,164 +180,120 @@ function forceGC(win, browser) {
  * loading, switch through each tab, recording their tab switch times. Finally,
  * report the results.
  *
  * @param window
  *        A host window. Primarily, we just use this for the OpenBrowserWindow
  *        function defined in that window.
  * @returns Promise
  */
-function test(window) {
+async function test(window) {
+  if (!window.gMultiProcessBrowser) {
+    dump("** The TPS Talos test does not support running in non-e10s mode " +
+         "anymore! Bailing out!\n");
+    return;
+  }
+
   Services.scriptloader.loadSubScript("chrome://talos-powers-content/content/TalosParentProfiler.js", context);
   TalosParentProfiler = context.TalosParentProfiler;
 
-  return Task.spawn(function* () {
-    let testURLs = [];
+  let testURLs = [];
 
-    let win = window.OpenBrowserWindow();
-    try {
-      let prefFile = Services.prefs.getCharPref("addon.test.tabswitch.urlfile");
-      if (prefFile) {
-        testURLs = handleFile(win, prefFile);
-      }
-    } catch (ex) { /* error condition handled below */ }
-    if (!testURLs || testURLs.length == 0) {
-      dump("no tabs to test, 'addon.test.tabswitch.urlfile' pref isn't set to page set path\n");
-      return;
+  let win = window.OpenBrowserWindow();
+  try {
+    let prefFile = Services.prefs.getCharPref("addon.test.tabswitch.urlfile");
+    if (prefFile) {
+      testURLs = handleFile(win, prefFile);
     }
+  } catch (ex) { /* error condition handled below */ }
+  if (!testURLs || testURLs.length == 0) {
+    dump("no tabs to test, 'addon.test.tabswitch.urlfile' pref isn't set to page set path\n");
+    return;
+  }
+
+  await waitForDelayedStartup(win);
 
-    yield waitForDelayedStartup(win);
+  let gBrowser = win.gBrowser;
 
-    let gBrowser = win.gBrowser;
+  // We don't want to catch scrolling the tabstrip in our tests
+  gBrowser.tabContainer.style.opacity = "0";
 
-    // We don't want to catch scrolling the tabstrip in our tests
-    gBrowser.tabContainer.style.visibility = "hidden";
+  let initialTab = gBrowser.selectedTab;
+  await loadTabs(gBrowser, testURLs);
 
-    let initialTab = gBrowser.selectedTab;
-    yield loadTabs(gBrowser, testURLs);
+  // We'll switch back to about:blank after each tab switch
+  // in an attempt to put the graphics layer into a "steady"
+  // state before switching to the next tab.
+  initialTab.linkedBrowser.loadURI("about:blank", null, null);
 
-    // We'll switch back to about:blank after each tab switch
-    // in an attempt to put the graphics layer into a "steady"
-    // state before switching to the next tab.
-    initialTab.linkedBrowser.loadURI("about:blank", null, null);
-
-    let tabs = gBrowser.getTabsToTheEndFrom(initialTab);
-    let times = [];
+  let tabs = gBrowser.getTabsToTheEndFrom(initialTab);
+  let times = [];
 
-    for (let tab of tabs) {
-      // Let's do an initial run to warm up any paint related caches
-      // (like glyph caches for text). In the next loop we will start with
-      // a GC before each switch so we don't need here.
-      // Note: in case of multiple content processes, closing all the tabs
-      // would close the related content processes, and even if we kept them
-      // alive it would be unlikely that the same pages end up in the same
-      // content processes, so we cannot do this at the manifest level.
-      yield switchToTab(tab);
-      yield switchToTab(initialTab);
-    }
+  for (let tab of tabs) {
+    // Let's do an initial run to warm up any paint related caches
+    // (like glyph caches for text). In the next loop we will start with
+    // a GC before each switch so we don't need here.
+    // Note: in case of multiple content processes, closing all the tabs
+    // would close the related content processes, and even if we kept them
+    // alive it would be unlikely that the same pages end up in the same
+    // content processes, so we cannot do this at the manifest level.
+    await switchToTab(tab);
+    await switchToTab(initialTab);
+  }
 
-    for (let tab of tabs) {
-      gBrowser.moveTabTo(tab, 1);
-      yield forceGC(win, tab.linkedBrowser);
-      TalosParentProfiler.resume("start: " + tab.linkedBrowser.currentURI.spec);
-      let time = yield switchToTab(tab);
-      TalosParentProfiler.pause("finish: " + tab.linkedBrowser.currentURI.spec);
-      dump(`${tab.linkedBrowser.currentURI.spec}: ${time}ms\n`);
-      times.push(time);
-      yield switchToTab(initialTab);
-    }
+  for (let tab of tabs) {
+    gBrowser.moveTabTo(tab, 1);
+    await forceGC(win, tab.linkedBrowser);
+    TalosParentProfiler.resume("start: " + tab.linkedBrowser.currentURI.spec);
+    let time = await switchToTab(tab);
+    TalosParentProfiler.pause("finish: " + tab.linkedBrowser.currentURI.spec);
+    dump(`${tab.linkedBrowser.currentURI.spec}: ${time}ms\n`);
+    times.push(time);
+    await switchToTab(initialTab);
+  }
 
-    let output = "<!DOCTYPE html>" +
-                 '<html lang="en">' +
-                 "<head><title>Tab Switch Results</title></head>" +
-                 "<body><h1>Tab switch times</h1>" +
-                 "<table>";
-    let time = 0;
-    for (let i in times) {
-      time += times[i];
-      output += "<tr><td>" + testURLs[i] + "</td><td>" + times[i] + "ms</td></tr>";
-    }
-    output += "</table></body></html>";
-    dump("total tab switch time:" + time + "\n");
+  let output = "<!DOCTYPE html>" +
+               '<html lang="en">' +
+               "<head><title>Tab Switch Results</title></head>" +
+               "<body><h1>Tab switch times</h1>" +
+               "<table>";
+  let time = 0;
+  for (let i in times) {
+    time += times[i];
+    output += "<tr><td>" + testURLs[i] + "</td><td>" + times[i] + "ms</td></tr>";
+  }
+  output += "</table></body></html>";
+  dump("total tab switch time:" + time + "\n");
 
-    let resultsTab = win.gBrowser.loadOneTab(
-      "data:text/html;charset=utf-8," + encodeURIComponent(output), {
-      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
-    });
+  let resultsTab = win.gBrowser.loadOneTab(
+    "data:text/html;charset=utf-8," + encodeURIComponent(output), {
+    triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+  });
 
-    win.gBrowser.selectedTab = resultsTab;
+  win.gBrowser.selectedTab = resultsTab;
 
-    remotePage.sendAsyncMessage("tabswitch-test-results", {
-      times,
-      urls: testURLs,
-    });
+  remotePage.sendAsyncMessage("tabswitch-test-results", {
+    times,
+    urls: testURLs,
+  });
 
-    TalosParentProfiler.afterProfileGathered().then(() => {
-      win.close();
-    });
+  TalosParentProfiler.afterProfileGathered().then(() => {
+    win.close();
   });
 }
 
-function unloadFromWindow(window) {
-  if (!window)
-    return;
-  let toolsMenu = window.document.getElementById("menu_ToolsPopup");
-  if (!toolsMenu)
-    return;
-  toolsMenu.removeChild(window.document.getElementById("start_test_item"));
-}
-
-function loadIntoWindow(window) {
-  if (!window)
-    return;
-  let item = window.document.createElement("menuitem");
-  item.setAttribute("label", "Start test");
-  item.id = "start_test_item";
-  window.tab_switch_test = test;
-  item.setAttribute("oncommand", "tab_switch_test(window)");
-  let toolsMenu = window.document.getElementById("menu_ToolsPopup");
-  if (!toolsMenu)
-    return;
-  toolsMenu.appendChild(item);
-}
-
-function install(aData, aReason) {}
-function uninstall(aData, aReason) {}
-
-function shutdown(aData, aReason) {
-  // When the application is shutting down we normally don't have to clean
-  // up any UI changes made
-  if (aReason == APP_SHUTDOWN) {
-    return;
-  }
-
-  Services.wm.removeListener(windowListener);
-
-  // Unload from any existing windows
-  let list = Services.wm.getEnumerator("navigator:browser");
-  while (list.hasMoreElements()) {
-    let window = list.getNext().QueryInterface(Ci.nsIDOMWindow);
-    unloadFromWindow(window);
-  }
-  Services.obs.removeObserver(observer, "tabswitch-urlfile");
-
-  remotePage.destroy();
-}
-
 function handleFile(win, file) {
-
   let localFile = Cc["@mozilla.org/file/local;1"]
     .createInstance(Ci.nsIFile);
   localFile.initWithPath(file);
   let localURI = Services.io.newFileURI(localFile);
   let req = new win.XMLHttpRequest();
   req.open("get", localURI.spec, false);
   req.send(null);
 
-
   let testURLs = [];
   let server = Services.prefs.getCharPref("addon.test.tabswitch.webserver");
   let maxurls = Services.prefs.getIntPref("addon.test.tabswitch.maxurls");
   let parent = server + "/tests/";
   let lines = req.responseText.split('<a href=\"');
   testURLs = [];
   if (maxurls && maxurls > 0) {
     lines.splice(maxurls, lines.length);
@@ -519,39 +302,36 @@ function handleFile(win, file) {
     if (a.split('\"')[0] != "") {
       testURLs.push(parent + "tp5n/" + a.split('\"')[0]);
     }
   });
 
   return testURLs;
 }
 
-var observer = {
-  observe(aSubject, aTopic, aData) {
-    if (aTopic == "tabswitch-urlfile") {
-      handleFile(aSubject, aData);
-    }
-  }
-};
-
 var remotePage;
 
-function startup(aData, aReason) {
-  // Load into any existing windows
-  let list = Services.wm.getEnumerator("navigator:browser");
-  let window;
-  while (list.hasMoreElements()) {
-    window = list.getNext().QueryInterface(Ci.nsIDOMWindow);
-    loadIntoWindow(window);
-  }
+this.tps = class extends ExtensionAPI {
+  getAPI(context) {
+    return {
+      tps: {
+        setup({ frameScriptPath }) {
+          const AboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
+                                       .getService(Ci.nsIAboutNewTabService);
+          AboutNewTabService.newTabURL = "about:blank";
 
-  // Load into any new windows
-  Services.wm.addListener(windowListener);
-
-  Services.obs.addObserver(observer, "tabswitch-urlfile");
+          const frameScriptURL = context.extension.baseURI.resolve(frameScriptPath);
+          Services.ppmm.loadFrameScript(frameScriptURL, true);
+          remotePage = new RemotePages("about:tabswitch");
+          remotePage.addMessageListener("tabswitch-do-test", function doTest(msg) {
+            test(msg.target.browser.ownerGlobal);
+          });
 
-  Services.ppmm.loadProcessScript("chrome://tabswitch/content/tabswitch-content-process.js", true);
-
-  remotePage = new RemotePages("about:tabswitch");
-  remotePage.addMessageListener("tabswitch-do-test", function doTest(msg) {
-    test(msg.target.browser.ownerGlobal);
-  });
-}
+          return () => {
+            Services.ppmm.sendAsyncMessage("TPS:Teardown");
+            remotePage.destroy();
+            AboutNewTabService.resetNewTabURL();
+          };
+        }
+      }
+    };
+  }
+};
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/tests/tabswitch/background.js
@@ -0,0 +1,13 @@
+/* globals browser */
+
+/**
+ * The TPS test is a Pageloader test, meaning that the tps.manifest file
+ * tells Talos to load a particular page. The loading of that page signals
+ * the start of the test. It's also where results need to go, as the
+ * Talos gunk augments the loaded page with a special tpRecordTime
+ * function that is used to report results.
+ */
+
+let frameScriptPath = "content/tabswitch-content-process.js";
+
+browser.tps.setup({ frameScriptPath });
deleted file mode 100644
--- a/testing/talos/talos/tests/tabswitch/chrome.manifest
+++ /dev/null
@@ -1,1 +0,0 @@
-content tabswitch content/
\ No newline at end of file
--- a/testing/talos/talos/tests/tabswitch/content/tabswitch-content-process.js
+++ b/testing/talos/talos/tests/tabswitch/content/tabswitch-content-process.js
@@ -1,30 +1,64 @@
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-const CHROME_URI = "chrome://tabswitch/content/test.html";
-
-class TabSwitchAboutModule {
-  constructor() {
-    this.QueryInterface = XPCOMUtils.generateQI([Ci.nsIAboutModule]);
-  }
+const WEBEXTENSION_ID = "tabswitch-talos@mozilla.org";
+const ABOUT_PAGE_NAME = "tabswitch";
+const Registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+const UUID = "0f459ab4-b4ba-4741-ac89-ee47dea07adb";
+const ABOUT_PATH_PATH = "content/test.html";
 
-  newChannel(aURI, aLoadInfo) {
-    let uri = Services.io.newURI(CHROME_URI);
-    let chan = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
-    chan.originalURI = aURI;
-    return chan;
-  }
+XPCOMUtils.defineLazyGetter(
+  this, "processScript",
+  () => Cc["@mozilla.org/webextensions/extension-process-script;1"]
+          .getService().wrappedJSObject);
+
+const TPSProcessScript = {
+  init() {
+    let extensionChild = processScript.getExtensionChild(WEBEXTENSION_ID);
+    let aboutPageURI = extensionChild.baseURI.resolve(ABOUT_PATH_PATH);
 
-  getURIFlags(aURI) {
-    return Ci.nsIAboutModule.ALLOW_SCRIPT |
-           Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD;
-  }
-}
+    class TabSwitchAboutModule {
+      constructor() {
+        this.QueryInterface = XPCOMUtils.generateQI([Ci.nsIAboutModule]);
+      }
+      newChannel(aURI, aLoadInfo) {
+        let uri = Services.io.newURI(aboutPageURI);
+        let chan = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
+        chan.originalURI = aURI;
+        return chan;
+      }
+      getURIFlags(aURI) {
+        return Ci.nsIAboutModule.ALLOW_SCRIPT |
+               Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD;
+      }
+    }
+
+    let factory = XPCOMUtils._getFactory(TabSwitchAboutModule);
+    this._factory = factory;
 
-let factory = XPCOMUtils._getFactory(TabSwitchAboutModule);
-let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
-let UUIDGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+    Registrar.registerFactory(
+      Components.ID(UUID), "",
+      `@mozilla.org/network/protocol/about;1?what=${ABOUT_PAGE_NAME}`,
+      factory);
+
+    this._hasSetup = true;
+  },
+
+  teardown() {
+    if (!this._hasSetup) {
+      return;
+    }
 
-registrar.registerFactory(UUIDGenerator.generateUUID(), "",
-                          "@mozilla.org/network/protocol/about;1?what=tabswitch",
-                          factory);
+    Registrar.unregisterFactory(Components.ID(UUID), this._factory);
+    this._hasSetup = false;
+    this._factory = null;
+  },
+
+  receiveMessage(msg) {
+    if (msg.name == "TPS:Teardown") {
+      this.teardown();
+    }
+  }
+};
+
+TPSProcessScript.init();
--- a/testing/talos/talos/tests/tabswitch/content/test.html
+++ b/testing/talos/talos/tests/tabswitch/content/test.html
@@ -1,18 +1,17 @@
 <html>
   <head>
     <script>
       function do_test(override) {
         if (override || document.location.hash.indexOf("#auto") == 0) {
           sendAsyncMessage("tabswitch-do-test");
           addMessageListener("tabswitch-test-results", function onMessage(msg) {
             let data = msg.data;
-            content.tpRecordTime(data.times.join(","), 0, data.urls.join(","));
-            sendAsyncMessage("tabswitch-test-results-reported");
+            tpRecordTime(data.times.join(","), 0, data.urls.join(","));
           });
         }
       }
     </script>
   </head>
   <body onload="do_test(false)">
     Hello Talos!
   </body>
deleted file mode 100644
--- a/testing/talos/talos/tests/tabswitch/install.rdf
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0"?>
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>tab-switch-test@lassey.us</em:id>
-    <em:type>2</em:type>
-    <em:name>Tab Switch Test</em:name>
-    <em:version>1.0.7</em:version>
-    <em:bootstrap>true</em:bootstrap>
-    <em:description>Measures the performance of switching tabs</em:description>
-    <em:creator>Brad Lassey</em:creator>
-    <em:multiprocessCompatible>true</em:multiprocessCompatible>
-    <!-- Desktop -->
-    <em:targetApplication>
-      <Description>
-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>38.0</em:minVersion>
-        <em:maxVersion>*</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-  </Description>
-</RDF>
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/tests/tabswitch/manifest.json
@@ -0,0 +1,24 @@
+{
+  "applications": {
+    "gecko": {
+      "id": "tabswitch-talos@mozilla.org"
+    }
+  },
+  "manifest_version": 2,
+  "name": "TPS (tabswitch) Talos Test",
+  "version": "0.1",
+  "permissions": [],
+  "background": {
+    "scripts": ["background.js"]
+  },
+  "experiment_apis": {
+    "tps": {
+      "schema": "schema.json",
+      "parent": {
+        "scopes": ["addon_parent"],
+        "script": "api.js",
+        "paths": [["tps"]]
+      }
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/tests/tabswitch/schema.json
@@ -0,0 +1,23 @@
+[
+  {
+    "namespace": "tps",
+    "description": "Special powers for the TPS Talos test",
+    "functions": [
+      {
+        "name": "setup",
+        "type": "function",
+        "description": "Prepares the TPS test to be run by the Talos framework.",
+        "parameters": [{
+          "type": "object",
+          "name": "setupArgs",
+          "properties": {
+            "frameScriptPath": {
+              "type": "string",
+              "description": "Relative path for the frame script to load for the test in the initial tab."
+            }
+          }
+        }]
+      }
+    ]
+  }
+]
\ No newline at end of file
--- a/toolkit/components/extensions/parent/ext-contextualIdentities.js
+++ b/toolkit/components/extensions/parent/ext-contextualIdentities.js
@@ -181,20 +181,22 @@ this.contextualIdentities = class extend
             throw new ExtensionError(`Invalid contextual identity: ${cookieStoreId}`);
           }
 
           if (details.name !== null) {
             identity.name = details.name;
           }
 
           if (details.color !== null) {
+            getContainerColor(details.color);
             identity.color = details.color;
           }
 
           if (details.icon !== null) {
+            getContainerIcon(details.icon);
             identity.icon = details.icon;
           }
 
           if (!ContextualIdentityService.update(identity.userContextId,
                                                 identity.name, identity.icon,
                                                 identity.color)) {
             throw new ExtensionError(`Contextual identity failed to update: ${cookieStoreId}`);
           }
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
@@ -195,16 +195,28 @@ add_task(async function test_contextualI
     browser.test.assertEq(5, cis.length, "we should still have have 5 containers");
 
     ci = await browser.contextualIdentities.get(ci.cookieStoreId);
     browser.test.assertTrue(!!ci, "We have an identity");
     browser.test.assertEq("foobar", ci.name, "identity.name is correct");
     browser.test.assertEq("red", ci.color, "identity.color is correct");
     browser.test.assertEq("gift", ci.icon, "identity.icon is correct");
 
+    browser.test.assertRejects(
+      browser.contextualIdentities.update(ci.cookieStoreId, {name: "foobar", color: "red", icon: "firefox"}),
+      "Invalid icon firefox for container",
+      "Create container called with an invalid icon"
+    );
+
+    browser.test.assertRejects(
+      browser.contextualIdentities.update(ci.cookieStoreId, {name: "foobar", color: "firefox-orange", icon: "gift"}),
+      "Invalid color name firefox-orange for container",
+      "Create container called with an invalid color"
+    );
+
     cis = await browser.contextualIdentities.query({});
     browser.test.assertEq(5, cis.length, "now we have 5 identities");
 
     ci = await browser.contextualIdentities.update(ci.cookieStoreId, {name: "barfoo", color: "blue", icon: "cart"});
     browser.test.assertTrue(!!ci, "We have an identity");
     browser.test.assertEq("barfoo", ci.name, "identity.name is correct");
     browser.test.assertEq("blue", ci.color, "identity.color is correct");
     browser.test.assertEq("cart", ci.icon, "identity.icon is correct");
--- a/toolkit/modules/NewTabUtils.jsm
+++ b/toolkit/modules/NewTabUtils.jsm
@@ -948,16 +948,17 @@ var ActivityStreamProvider = {
     /* Extract relevant parts needed to show this card as a highlight:
      * url, preview image, title, description, and the unique item_id
      * necessary for Pocket to identify the item
      */
     let items = Object.values(data.list)
                   // status "0" means not archived or deleted
                   .filter(item => item.status === "0")
                   .map(item => ({
+                    date_added: item.time_added * 1000,
                     description: item.excerpt,
                     preview_image_url: item.image && item.image.src,
                     title: item.resolved_title,
                     url: item.resolved_url,
                     pocket_id: item.item_id,
                     open_url: item.open_url
                   }));
 
@@ -981,33 +982,34 @@ var ActivityStreamProvider = {
 
     const sqlQuery = `
       SELECT
         b.guid AS bookmarkGuid,
         description,
         h.guid,
         preview_image_url,
         b.title,
+        b.dateAdded / 1000 AS date_added,
         url
       FROM moz_bookmarks b
       JOIN moz_bookmarks p
         ON p.id = b.parent
       JOIN moz_places h
         ON h.id = b.fk
       WHERE b.dateAdded >= :dateAddedThreshold
         AND b.title NOTNULL
         AND b.type = :bookmarkType
         AND p.parent <> :tagsFolderId
         ${this._commonPlacesWhere}
       ORDER BY b.dateAdded DESC
       LIMIT :limit
     `;
 
     return this._processHighlights(await this.executePlacesQuery(sqlQuery, {
-      columns: this._highlightsColumns,
+      columns: [...this._highlightsColumns, "date_added"],
       params: this._getCommonParams(options, {
         dateAddedThreshold: (Date.now() - options.bookmarkSecondsAgo * 1000) * 1000
       })
     }), options, "bookmark");
   },
 
   /**
    * Get total count of all bookmarks.
--- a/toolkit/modules/tests/xpcshell/test_NewTabUtils.js
+++ b/toolkit/modules/tests/xpcshell/test_NewTabUtils.js
@@ -433,18 +433,20 @@ add_task(async function getHighlightsWit
   for (let placeInfo of bookmarks) {
     await PlacesTestUtils.addVisits(placeInfo.url);
   }
 
   links = await provider.getHighlights();
   Assert.equal(links.length, 2, "adding visits to bookmarks yields more links");
   Assert.equal(links[0].url, bookmarks[1].url, "first bookmark is younger bookmark");
   Assert.equal(links[0].type, "bookmark", "first bookmark is bookmark");
+  Assert.ok(links[0].date_added, "got a date_added for the bookmark");
   Assert.equal(links[1].url, bookmarks[0].url, "second bookmark is older bookmark");
   Assert.equal(links[1].type, "bookmark", "second bookmark is bookmark");
+  Assert.ok(links[1].date_added, "got a date_added for the bookmark");
 
   // Add metadata to history
   await addMetadata(testURI);
 
   links = await provider.getHighlights();
   Assert.equal(links.length, 3, "adding metadata yield more links");
   Assert.equal(links[0].url, bookmarks[1].url, "still have younger bookmark");
   Assert.equal(links[1].url, bookmarks[0].url, "still have older bookmark");
@@ -490,16 +492,17 @@ add_task(async function getHighlightsWit
     title: "now a bookmark",
     url: testURI
   });
 
   links = await provider.getHighlights();
   Assert.equal(links.length, 3, "a visited bookmark doesn't appear as bookmark and history");
   Assert.equal(links[0].url, testURI, "history is now the first, i.e., most recent, bookmark");
   Assert.equal(links[0].type, "bookmark", "was history now bookmark");
+  Assert.ok(links[0].date_added, "got a date_added for the now bookmark");
   Assert.equal(links[1].url, bookmarks[1].url, "still have younger bookmark now second");
   Assert.equal(links[2].url, bookmarks[0].url, "still have older bookmark now third");
 
   // Test the `withFavicons` option.
   await PlacesTestUtils.addFavicons(new Map([[testURI, image1x1]]));
   links = await provider.getHighlights({ withFavicons: true });
   Assert.equal(links.length, 3, "We're not expecting a change in links");
   Assert.equal(links[0].favicon, image1x1, "Link 1 should contain a favicon");
@@ -517,16 +520,17 @@ add_task(async function getHighlightsWit
       description: "desc",
       preview_image_url: "foo.com/img.png",
       url: "https://mozilla1.com/"
   };
 
   const fakeResponse = {
     list: {
       "123": {
+        time_added: "123",
         image: {src: "foo.com/img.png"},
         excerpt: "A description for foo",
         resolved_title: "A title for foo",
         resolved_url: "http://www.foo.com",
         item_id: "123",
         open_url: "getpocket.com/itemID",
         status: "0"
       },
@@ -560,26 +564,28 @@ add_task(async function getHighlightsWit
   let currentLink = links[1];
   Assert.equal(currentLink.url, pocketItem.resolved_url, "Correct Pocket item");
   Assert.equal(currentLink.type, "pocket", "Attached the correct type");
   Assert.equal(currentLink.preview_image_url, pocketItem.image.src, "Correct preview image was added");
   Assert.equal(currentLink.title, pocketItem.resolved_title, "Correct title was added");
   Assert.equal(currentLink.description, pocketItem.excerpt, "Correct description was added");
   Assert.equal(currentLink.pocket_id, pocketItem.item_id, "item_id was preserved");
   Assert.equal(currentLink.open_url, pocketItem.open_url, "open_url was preserved");
+  Assert.equal(currentLink.date_added, pocketItem.time_added * 1000, "date_added was added to pocket item");
 
   NewTabUtils.activityStreamLinks._savedPocketStories = null;
 });
 
 add_task(async function getHighlightsWithPocketCached() {
   await setUpActivityStreamTest();
 
   let fakeResponse = {
     list: {
       "123": {
+        time_added: "123",
         image: {src: "foo.com/img.png"},
         excerpt: "A description for foo",
         resolved_title: "A title for foo",
         resolved_url: "http://www.foo.com",
         item_id: "123",
         open_url: "getpocket.com/itemID",
         status: "0"
       },
@@ -594,16 +600,17 @@ add_task(async function getHighlightsWit
   let provider = NewTabUtils.activityStreamLinks;
 
   let links = await provider.getHighlights();
   Assert.equal(links.length, 1, "Sanity check that we got 1 link back for highlights");
   Assert.equal(links[0].url, fakeResponse.list["123"].resolved_url, "Sanity check that it was the pocket story");
 
   // Update what the response would be
   fakeResponse.list["789"] = {
+    time_added: "123",
     image: {src: "bar.com/img.png"},
     excerpt: "A description for bar",
     resolved_title: "A title for bar",
     resolved_url: "http://www.bar.com",
     item_id: "789",
     open_url: "getpocket.com/itemID",
     status: "0"
   };
--- a/tools/fuzzing/libfuzzer/FuzzerIOPosix.cpp
+++ b/tools/fuzzing/libfuzzer/FuzzerIOPosix.cpp
@@ -49,17 +49,17 @@ size_t FileSize(const std::string &Path)
 void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
                              Vector<std::string> *V, bool TopDir) {
   auto E = GetEpoch(Dir);
   if (Epoch)
     if (E && *Epoch >= E) return;
 
   DIR *D = opendir(Dir.c_str());
   if (!D) {
-    Printf("No such directory: %s; exiting\n", Dir.c_str());
+    Printf("%s: %s; exiting\n", strerror(errno), Dir.c_str());
     exit(1);
   }
   while (auto E = readdir(D)) {
     std::string Path = DirPlusFile(Dir, E->d_name);
     if (E->d_type == DT_REG || E->d_type == DT_LNK ||
         (E->d_type == DT_UNKNOWN && IsFile(Path)))
       V->push_back(Path);
     else if ((E->d_type == DT_DIR ||
--- a/tools/fuzzing/libfuzzer/clone_libfuzzer.sh
+++ b/tools/fuzzing/libfuzzer/clone_libfuzzer.sh
@@ -1,11 +1,11 @@
 #!/bin/sh
 
 mkdir tmp/
 git clone --no-checkout --depth 1 https://chromium.googlesource.com/chromium/llvm-project/compiler-rt/lib/fuzzer tmp/
-(cd tmp && git reset --hard 2c1f00d30409ff8662e62a6480718726dad77586)
+(cd tmp && git reset --hard c2b235ee789fd452ba37c57957cc280fb37f9c52)
 
 # Copy only source code and includes
 cp tmp/*.cpp tmp/*.h tmp/*.def .
 
 # Remove the temporary directory
 rm -Rf tmp/
--- a/widget/cocoa/RectTextureImage.h
+++ b/widget/cocoa/RectTextureImage.h
@@ -59,17 +59,17 @@ public:
 
   void Draw(mozilla::layers::GLManager* aManager,
             const LayoutDeviceIntPoint& aLocation,
             const gfx::Matrix4x4& aTransform = gfx::Matrix4x4());
 
 
 protected:
   void DeleteTexture();
-  void BindIOSurfaceToTexture(gl::GLContext* aGL);
+  bool BindIOSurfaceToTexture(gl::GLContext* aGL);
 
   RefPtr<MacIOSurface> mIOSurface;
   gl::GLContext* mGLContext;
   LayoutDeviceIntRegion mUpdateRegion;
   LayoutDeviceIntSize mBufferSize;
   GLuint mTexture;
   bool mInUpdate;
 };
--- a/widget/cocoa/RectTextureImage.mm
+++ b/widget/cocoa/RectTextureImage.mm
@@ -103,17 +103,20 @@ RectTextureImage::UpdateFromCGContext(co
 
 void
 RectTextureImage::Draw(layers::GLManager* aManager,
                        const LayoutDeviceIntPoint& aLocation,
                        const gfx::Matrix4x4& aTransform)
 {
   gl::GLContext* gl = aManager->gl();
 
-  BindIOSurfaceToTexture(gl);
+  bool bound = BindIOSurfaceToTexture(gl);
+  if (!bound) {
+    return;
+  }
 
   layers::ShaderProgramOGL* program =
     aManager->GetProgram(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
                          gfx::SurfaceFormat::R8G8B8A8);
 
   gl->fActiveTexture(LOCAL_GL_TEXTURE0);
   gl::ScopedBindTexture texture(gl, mTexture, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
 
@@ -136,19 +139,25 @@ RectTextureImage::DeleteTexture()
   if (mTexture) {
     MOZ_ASSERT(mGLContext);
     mGLContext->MakeCurrent();
     mGLContext->fDeleteTextures(1, &mTexture);
     mTexture = 0;
   }
 }
 
-void
+bool
 RectTextureImage::BindIOSurfaceToTexture(gl::GLContext* aGL)
 {
+  if (!mIOSurface) {
+    // If our size is zero or MacIOSurface::CreateIOSurface failed for some
+    // other reason, there's nothing we can bind.
+    return false;
+  }
+
   if (!mTexture) {
     MOZ_ASSERT(aGL);
     aGL->fGenTextures(1, &mTexture);
     aGL->fActiveTexture(LOCAL_GL_TEXTURE0);
     gl::ScopedBindTexture texture(aGL, mTexture, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
     aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
                         LOCAL_GL_TEXTURE_MIN_FILTER,
                         LOCAL_GL_LINEAR);
@@ -162,12 +171,14 @@ RectTextureImage::BindIOSurfaceToTexture
                         LOCAL_GL_TEXTURE_WRAP_S,
                         LOCAL_GL_CLAMP_TO_EDGE);
 
     mIOSurface->CGLTexImageIOSurface2D(aGL,
                                        gl::GLContextCGL::Cast(aGL)->GetCGLContext(),
                                        0);
     mGLContext = aGL;
   }
+
+  return true;
 }
 
 } // namespace widget
 } // namespace mozilla
--- a/widget/cocoa/nsChildView.h
+++ b/widget/cocoa/nsChildView.h
@@ -330,16 +330,23 @@ public:
 
   virtual void            Enable(bool aState) override;
   virtual bool            IsEnabled() const override;
   virtual nsresult        SetFocus(bool aRaise) override;
   virtual LayoutDeviceIntRect GetBounds() override;
   virtual LayoutDeviceIntRect GetClientBounds() override;
   virtual LayoutDeviceIntRect GetScreenBounds() override;
 
+  // Refresh mBounds with up-to-date values from [mView frame].
+  // Only called if this nsChildView is the popup content view of a popup window.
+  // For popup windows, the nsIWidget interface to Gecko is provided by
+  // nsCocoaWindow, not by nsChildView. So nsCocoaWindow manages resize requests
+  // from Gecko, fires resize events, and resizes the native NSWindow and NSView.
+  void UpdateBoundsFromView();
+
   // Returns the "backing scale factor" of the view's window, which is the
   // ratio of pixels in the window's backing store to Cocoa points. Prior to
   // HiDPI support in OS X 10.7, this was always 1.0, but in HiDPI mode it
   // will be 2.0 (and might potentially other values as screen resolutions
   // evolve). This gives the relationship between what Gecko calls "device
   // pixels" and the Cocoa "points" coordinate system.
   CGFloat                 BackingScaleFactor() const;
 
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -2753,16 +2753,22 @@ nsChildView::TrackScrollEventAsSwipe(con
 }
 
 void
 nsChildView::SwipeFinished()
 {
   mSwipeTracker = nullptr;
 }
 
+void
+nsChildView::UpdateBoundsFromView()
+{
+  mBounds = CocoaPointsToDevPixels([mView frame]);
+}
+
 already_AddRefed<gfx::DrawTarget>
 nsChildView::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion,
                                         BufferMode* aBufferMode)
 {
   // should have created the GLPresenter in InitCompositor.
   MOZ_ASSERT(mGLPresenter);
   if (!mGLPresenter) {
     mGLPresenter = GLPresenter::CreateForWindow(this);
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -549,20 +549,21 @@ nsCocoaWindow::CreatePopupContentView(co
 
   nsIWidget* thisAsWidget = static_cast<nsIWidget*>(this);
   nsresult rv = mPopupContentView->Create(thisAsWidget, nullptr, aRect,
                                           aInitData);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  ChildView* newContentView = (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET);
-  [newContentView setFrame:NSZeroRect];
-  [newContentView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
-  [[mWindow contentView] addSubview:newContentView];
+  NSView* contentView = [mWindow contentView];
+  ChildView* childView = (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET);
+  [childView setFrame:[contentView bounds]];
+  [childView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+  [contentView addSubview:childView];
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 void nsCocoaWindow::Destroy()
 {
@@ -1700,16 +1701,20 @@ void
 nsCocoaWindow::UpdateBounds()
 {
   NSRect frame = NSZeroRect;
   if (mWindow) {
     frame = [mWindow frame];
   }
   mBounds =
     nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
+
+  if (mPopupContentView) {
+    mPopupContentView->UpdateBoundsFromView();
+  }
 }
 
 LayoutDeviceIntRect
 nsCocoaWindow::GetScreenBounds()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
 #ifdef DEBUG