Merge mozilla-central to mozilla-inbound r=merge a=merge on a CLOSED TREE
authorAndreea Pavel <apavel@mozilla.com>
Sat, 11 Nov 2017 12:03:02 +0200
changeset 444679 fa1adc80866df83946da2991273861af06ebf98b
parent 444678 a04bb99cdd474fa37ace46ecc67df9264dbefcb0 (current diff)
parent 444663 57eb0baf17ce8678767abfbf17bdb0acfd8909ad (diff)
child 444680 8e69fae378b7bd766824f43d5c5935f745821730
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.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 mozilla-inbound r=merge a=merge on a CLOSED TREE
toolkit/mozapps/extensions/DeferredSave.jsm
toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js
--- a/browser/base/content/test/performance/browser.ini
+++ b/browser/base/content/test/performance/browser.ini
@@ -1,10 +1,13 @@
 [DEFAULT]
 prefs =
+  # Skip migration work in BG__migrateUI for browser_startup.js since it isn't
+  # representative of common startup.
+  browser.migration.version=9999999
   browser.startup.record=true
 support-files =
   head.js
 [browser_appmenu_reflows.js]
 skip-if = asan || debug # Bug 1382809, bug 1369959
 [browser_favicon_load.js]
 [browser_startup.js]
 [browser_startup_content.js]
--- a/browser/components/migration/tests/marionette/test_refresh_firefox.py
+++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py
@@ -39,24 +39,32 @@ class TestFirefoxRefresh(MarionetteTestC
             arguments[0],
             arguments[1],
             "username",
             "password"
           );
           Services.logins.addLogin(myLogin)
         """, script_args=(self._username, self._password))
 
-    def createBookmark(self):
+    def createBookmarkInMenu(self):
         self.marionette.execute_script("""
           let url = arguments[0];
           let title = arguments[1];
           PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarks.bookmarksMenuFolder,
             makeURI(url), 0, title);
         """, script_args=(self._bookmarkURL, self._bookmarkText))
 
+    def createBookmarksOnToolbar(self):
+        self.marionette.execute_script("""
+          for (let i = 1; i <= 5; i++) {
+            PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
+              makeURI(`about:rights?p=${i}`), 0, `Bookmark ${i}`);
+          }
+        """)
+
     def createHistory(self):
         error = self.runAsyncCode("""
           // Copied from PlacesTestUtils, which isn't available in Marionette tests.
           let didReturn;
           PlacesUtils.asyncHistory.updatePlaces(
             [{title: arguments[1], uri: makeURI(arguments[0]), visits: [{
                 transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
                 visitDate: (Date.now() - 5000) * 1000,
@@ -195,24 +203,32 @@ class TestFirefoxRefresh(MarionetteTestC
         self.assertEqual(loginInfo[0]['password'], self._password)
 
         loginCount = self.marionette.execute_script("""
           return Services.logins.getAllLogins().length;
         """)
         # Note that we expect 2 logins - one from us, one from sync.
         self.assertEqual(loginCount, 2, "No other logins are present")
 
-    def checkBookmark(self):
+    def checkBookmarkInMenu(self):
         titleInBookmarks = self.marionette.execute_script("""
           let url = arguments[0];
           let bookmarkIds = PlacesUtils.bookmarks.getBookmarkIdsForURI(makeURI(url), {}, {});
           return bookmarkIds.length == 1 ? PlacesUtils.bookmarks.getItemTitle(bookmarkIds[0]) : "";
         """, script_args=(self._bookmarkURL,))
         self.assertEqual(titleInBookmarks, self._bookmarkText)
 
+    def checkBookmarkToolbarVisibility(self):
+        toolbarVisible = self.marionette.execute_script("""
+          const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
+          let xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
+          return xulStore.getValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed")
+        """)
+        self.assertEqual(toolbarVisible, "false")
+
     def checkHistory(self):
         historyResult = self.runAsyncCode("""
           PlacesUtils.history.fetch(arguments[0]).then(pageInfo => {
             if (!pageInfo) {
               marionetteScriptFinished("No visits found");
             } else {
               marionetteScriptFinished(pageInfo);
             }
@@ -373,28 +389,30 @@ class TestFirefoxRefresh(MarionetteTestC
         self.assertEqual(result["accountData"]["keyFetchToken"], "top-secret");
         if hasMigrated:
           # This test doesn't actually configure sync itself, so the username
           # pref only exists after migration.
           self.assertEqual(result["prefUsername"], "test@test.com");
 
     def checkProfile(self, hasMigrated=False):
         self.checkPassword()
-        self.checkBookmark()
+        self.checkBookmarkInMenu()
         self.checkHistory()
         self.checkFormHistory()
         self.checkFormAutofill()
         self.checkCookie()
         self.checkSync(hasMigrated);
         if hasMigrated:
+            self.checkBookmarkToolbarVisibility()
             self.checkSession()
 
     def createProfileData(self):
         self.savePassword()
-        self.createBookmark()
+        self.createBookmarkInMenu()
+        self.createBookmarksOnToolbar()
         self.createHistory()
         self.createFormHistory()
         self.createFormAutofill()
         self.createCookie()
         self.createSession()
         self.createSync()
 
     def setUpScriptData(self):
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1738,27 +1738,65 @@ BrowserGlue.prototype = {
     let clickCallback = (subject, topic, data) => {
       if (topic != "alertclickcallback")
         return;
       this._openPreferences("sync", { origin: "doorhanger" });
     };
     this.AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
   },
 
+  /**
+   * Uncollapses PersonalToolbar if its collapsed status is not
+   * persisted, and user customized it or changed default bookmarks.
+   *
+   * If the user does not have a persisted value for the toolbar's
+   * "collapsed" attribute, try to determine whether it's customized.
+   */
+  _maybeToggleBookmarkToolbarVisibility() {
+    const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
+    const NUM_TOOLBAR_BOOKMARKS_TO_UNHIDE = 3;
+    let xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
+
+    if (!xulStore.hasValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed")) {
+      // We consider the toolbar customized if it has more than NUM_TOOLBAR_BOOKMARKS_TO_UNHIDE
+      // children, or if it has a persisted currentset value.
+      let toolbarIsCustomized = xulStore.hasValue(BROWSER_DOCURL, "PersonalToolbar", "currentset");
+      let getToolbarFolderCount = () => {
+        let toolbarFolder = PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root;
+        let toolbarChildCount = toolbarFolder.childCount;
+        toolbarFolder.containerOpen = false;
+        return toolbarChildCount;
+      };
+
+      if (toolbarIsCustomized || getToolbarFolderCount() > NUM_TOOLBAR_BOOKMARKS_TO_UNHIDE) {
+        xulStore.setValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed", "false");
+      }
+    }
+  },
+
   // eslint-disable-next-line complexity
   _migrateUI: function BG__migrateUI() {
     const UI_VERSION = 58;
     const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
 
     let currentUIVersion;
     if (Services.prefs.prefHasUserValue("browser.migration.version")) {
       currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
     } else {
       // This is a new profile, nothing to migrate.
       Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
+
+      try {
+        // New profiles may have existing bookmarks (imported from another browser or
+        // copied into the profile) and we want to show the bookmark toolbar for them
+        // in some cases.
+        this._maybeToggleBookmarkToolbarVisibility();
+      } catch (ex) {
+        Cu.reportError(ex);
+      }
       return;
     }
 
     if (currentUIVersion >= UI_VERSION)
       return;
 
     let xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
 
--- a/browser/components/tests/browser/browser.ini
+++ b/browser/components/tests/browser/browser.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 
 [browser_bug538331.js]
 skip-if = !updater
 reason = test depends on update channel
 [browser_contentpermissionprompt.js]
+[browser_default_bookmark_toolbar_visibility.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/tests/browser/browser_default_bookmark_toolbar_visibility.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test _maybeToggleBookmarkToolbarVisibility() code running for new profiles.
+ * Ensure that the bookmarks toolbar is hidden in a default configuration.
+ * If new default bookmarks are added to the toolbar then the threshold of > 3
+ * in NUM_TOOLBAR_BOOKMARKS_TO_UNHIDE may need to be adjusted there.
+ */
+add_task(async function test_default_bookmark_toolbar_visibility() {
+  const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
+  let xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
+
+  is(xulStore.getValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed"), "",
+     "Check that @collapsed isn't persisted");
+  ok(document.getElementById("PersonalToolbar").collapsed,
+     "The bookmarks toolbar should be collapsed by default");
+});
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -350,17 +350,25 @@ flashActivate.message=Do you want to all
 flashActivate.outdated.message=Do you want to allow an outdated version of Adobe Flash to run on this site? An outdated version can affect browser performance and security.
 flashActivate.remember=Remember this decision
 flashActivate.noAllow=Don’t Allow
 flashActivate.allow=Allow
 flashActivate.noAllow.accesskey=D
 flashActivate.allow.accesskey=A
 
 # in-page UI
-PluginClickToActivate=Activate %S.
+# LOCALIZATION NOTE (PluginClickToActivate2): Two changes were done to the
+# previous version of the string. The first is that we changed the wording from
+# "Activate" to "Run", because it's shorter and feels less technical in English.
+# Feel free to keep using the previous wording in your language if it's already
+# the best one.
+# The second change is that we removed the period at the end of the phrase, because
+# it's not natural in our UI, and the underline was removed from this, so it doesn't
+# look like a link anymore. We suggest that everyone removes that period too.
+PluginClickToActivate2=Run %S
 PluginVulnerableUpdatable=This plugin is vulnerable and should be updated.
 PluginVulnerableNoUpdate=This plugin has security vulnerabilities.
 
 # infobar UI
 pluginContinueBlocking.label=Continue Blocking
 pluginContinueBlocking.accesskey=B
 # LOCALIZATION NOTE (pluginActivateTrigger): Use the unicode ellipsis char, \u2026,
 # or use "..." if \u2026 doesn't suit traditions in your locale.
--- a/browser/locales/l10n-changesets.json
+++ b/browser/locales/l10n-changesets.json
@@ -969,16 +969,26 @@
             "linux", 
             "linux64", 
             "macosx64", 
             "win32", 
             "win64"
         ], 
         "revision": "default"
     }, 
+    "wo": {
+        "platforms": [
+            "linux", 
+            "linux64", 
+            "macosx64", 
+            "win32", 
+            "win64"
+        ], 
+        "revision": "default"
+    }, 
     "xh": {
         "platforms": [
             "linux", 
             "linux64", 
             "macosx64", 
             "win32", 
             "win64"
         ], 
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -570,17 +570,17 @@ PluginContent.prototype = {
         this.addLinkClickCallback(updateLink, "forwardCallback",
                                   "openPluginUpdatePage", pluginTag);
         /* FALLTHRU */
 
       case "PluginVulnerableNoUpdate":
       case "PluginClickToPlay":
         this._handleClickToPlayEvent(plugin);
         let pluginName = this._getPluginInfo(plugin).pluginName;
-        let messageString = gNavigatorBundle.formatStringFromName("PluginClickToActivate", [pluginName], 1);
+        let messageString = gNavigatorBundle.formatStringFromName("PluginClickToActivate2", [pluginName], 1);
         let overlayText = this.getPluginUI(plugin, "clickToPlay");
         overlayText.textContent = messageString;
         if (eventType == "PluginVulnerableUpdatable" ||
             eventType == "PluginVulnerableNoUpdate") {
           let vulnerabilityString = gNavigatorBundle.GetStringFromName(eventType);
           let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus");
           vulnerabilityText.textContent = vulnerabilityString;
         }
--- a/build/autoconf/arch.m4
+++ b/build/autoconf/arch.m4
@@ -26,17 +26,17 @@ MOZ_ARG_WITH_STRING(arch,
     MOZ_ARCH=$withval)
 
 if test -z "$MOZ_ARCH"; then
     dnl Defaults
     case "${CPU_ARCH}-${OS_TARGET}" in
     arm-Android)
         MOZ_THUMB=yes
         MOZ_ARCH=armv7-a
-        MOZ_FPU=vfp
+        MOZ_FPU=vfpv3-d16
         MOZ_FLOAT_ABI=softfp
         MOZ_ALIGN=no
         ;;
     arm-Darwin)
         MOZ_ARCH=toolchain-default
         ;;
     esac
 fi
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -593,16 +593,20 @@
   border: 1px solid var(--toolbar-border);
 
   font: var(--highlighter-font-family);
   font-size: var(--highlighter-font-size);
 }
 
 /* Shapes highlighter */
 
+:-moz-native-anonymous .shapes-root {
+  pointer-events: auto;
+}
+
 :-moz-native-anonymous .shapes-shape-container {
   position: absolute;
   overflow: visible;
 }
 
 :-moz-native-anonymous .shapes-polygon,
 :-moz-native-anonymous .shapes-ellipse,
 :-moz-native-anonymous .shapes-rect,
--- a/devtools/server/actors/highlighters/shapes.js
+++ b/devtools/server/actors/highlighters/shapes.js
@@ -209,16 +209,45 @@ class ShapesHighlighter extends AutoRefr
     return {
       top: dims.top / zoom,
       left: dims.left / zoom,
       width: dims.width / zoom,
       height: dims.height / zoom
     };
   }
 
+  /**
+   * Changes the appearance of the mouse cursor on the highlighter.
+   *
+   * Because we can't attach event handlers to individual elements in the
+   * highlighter, we determine if the mouse is hovering over a point by seeing if
+   * it's within 5 pixels of it. This creates a square hitbox that doesn't match
+   * perfectly with the circular markers. So if we were to use the :hover
+   * pseudo-class to apply changes to the mouse cursor, the cursor change would not
+   * always accurately reflect whether you can interact with the point. This is
+   * also the reason we have the hidden marker-hover element instead of using CSS
+   * to fill in the marker.
+   *
+   * In addition, the cursor CSS property is applied to .shapes-root because if
+   * it were attached to .shapes-marker, the cursor change no longer applies if
+   * you are for example resizing the shape and your mouse goes off the point.
+   * Also, if you are dragging a polygon point, the marker plays catch up to your
+   * mouse position, resulting in an undesirable visual effect where the cursor
+   * rapidly flickers between "grab" and "auto".
+   *
+   * @param {String} cursorType the name of the cursor to display
+   */
+  setCursor(cursorType) {
+    let container = this.getElement("root");
+    let style = container.getAttribute("style");
+    // remove existing cursor definitions in the style
+    style = style.replace(/cursor:.*?;/g, "");
+    container.setAttribute("style", `${style}cursor:${cursorType};`);
+  }
+
   handleEvent(event, id) {
     // No event handling if the highlighter is hidden
     if (this.areShapesHidden()) {
       return;
     }
 
     let { target, type, pageX, pageY } = event;
 
@@ -260,16 +289,17 @@ class ShapesHighlighter extends AutoRefr
           this._handleInsetClick(pageX, pageY);
         }
         event.stopPropagation();
         event.preventDefault();
         break;
       case "mouseup":
         if (this[_dragging]) {
           this[_dragging] = null;
+          this._handleMarkerHover(this.hoveredPoint);
         }
         break;
       case "mousemove":
         if (!this[_dragging]) {
           this._handleMouseMoveNotDragging(pageX, pageY);
           return;
         }
         event.stopPropagation();
@@ -669,16 +699,17 @@ class ShapesHighlighter extends AutoRefr
     let unitX = getUnit(x);
     let unitY = getUnit(y);
     let valueX = (isUnitless(x)) ? xComputed : parseFloat(x);
     let valueY = (isUnitless(y)) ? yComputed : parseFloat(y);
 
     let ratioX = (valueX / xComputed) || 1;
     let ratioY = (valueY / yComputed) || 1;
 
+    this.setCursor("grabbing");
     this[_dragging] = { point, unitX, unitY, valueX, valueY,
                         ratioX, ratioY, x: pageX, y: pageY };
   }
 
   /**
    * Set the inline style of the polygon, replacing the given point with the given x/y
    * coords.
    * @param {Number} pageX the new x coordinate of the point
@@ -747,16 +778,17 @@ class ShapesHighlighter extends AutoRefr
   _handleCircleClick(pageX, pageY) {
     let { width, height } = this.zoomAdjustedDimensions;
     let { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
     let point = this.getCirclePointAt(percentX, percentY);
     if (!point) {
       return;
     }
 
+    this.setCursor("grabbing");
     if (point === "center") {
       let { cx, cy } = this.coordUnits;
       let cxComputed = this.coordinates.cx / 100 * width;
       let cyComputed = this.coordinates.cy / 100 * height;
       let unitX = getUnit(cx);
       let unitY = getUnit(cy);
       let valueX = (isUnitless(cx)) ? cxComputed : parseFloat(cx);
       let valueY = (isUnitless(cy)) ? cyComputed : parseFloat(cy);
@@ -828,16 +860,17 @@ class ShapesHighlighter extends AutoRefr
   _handleEllipseClick(pageX, pageY) {
     let { width, height } = this.zoomAdjustedDimensions;
     let { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
     let point = this.getEllipsePointAt(percentX, percentY);
     if (!point) {
       return;
     }
 
+    this.setCursor("grabbing");
     if (point === "center") {
       let { cx, cy } = this.coordUnits;
       let cxComputed = this.coordinates.cx / 100 * width;
       let cyComputed = this.coordinates.cy / 100 * height;
       let unitX = getUnit(cx);
       let unitY = getUnit(cy);
       let valueX = (isUnitless(cx)) ? cxComputed : parseFloat(cx);
       let valueY = (isUnitless(cy)) ? cyComputed : parseFloat(cy);
@@ -927,16 +960,17 @@ class ShapesHighlighter extends AutoRefr
   _handleInsetClick(pageX, pageY) {
     let { width, height } = this.zoomAdjustedDimensions;
     let { percentX, percentY } = this.convertPageCoordsToPercent(pageX, pageY);
     let point = this.getInsetPointAt(percentX, percentY);
     if (!point) {
       return;
     }
 
+    this.setCursor("grabbing");
     let value = this.coordUnits[point];
     let size = (point === "left" || point === "right") ? width : height;
     let computedValue = this.coordinates[point] / 100 * size;
     let unit = getUnit(value);
     value = (isUnitless(value)) ? computedValue : parseFloat(value);
     let ratio = (value / computedValue) || 1;
     let origValue = (point === "left" || point === "right") ? pageX : pageY;
 
@@ -1016,71 +1050,82 @@ class ShapesHighlighter extends AutoRefr
       this.hoveredPoint = point ? point : null;
       if (this.hoveredPoint !== oldHoveredPoint) {
         this._emitHoverEvent(this.hoveredPoint);
       }
       this._handleMarkerHover(point);
     }
   }
 
+  /**
+   * Change the appearance of the given marker when the mouse hovers over it.
+   * @param {String|Number} point if the shape is a polygon, the integer index of the
+   *        point being hovered. Otherwise, a string identifying the point being hovered.
+   *        Integers < 0 and falsey values excluding 0 indicate no point is being hovered.
+   */
   _handleMarkerHover(point) {
     // Hide hover marker for now, will be shown if point is a valid hover target
     this.getElement("marker-hover").setAttribute("hidden", true);
-    if (point === null || point === undefined) {
+    // Catch all falsey values except when point === 0, as that's a valid point
+    if (!point && point !== 0) {
+      this.setCursor("auto");
       return;
     }
+    let hoverCursor = (this[_dragging]) ? "grabbing" : "grab";
 
     if (this.transformMode) {
-      if (!point) {
-        return;
-      }
       let { minX, minY, maxX, maxY } = this.boundingBox;
       let centerX = (minX + maxX) / 2;
       let centerY = (minY + maxY) / 2;
 
       const points = [
-        { pointName: "translate", x: centerX, y: centerY },
-        { pointName: "scale-se", x: maxX, y: maxY },
-        { pointName: "scale-ne", x: maxX, y: minY },
-        { pointName: "scale-sw", x: minX, y: maxY },
-        { pointName: "scale-nw", x: minX, y: minY },
+        { pointName: "translate", x: centerX, y: centerY, cursor: "move" },
+        { pointName: "scale-se", x: maxX, y: maxY, cursor: "nwse-resize" },
+        { pointName: "scale-ne", x: maxX, y: minY, cursor: "nesw-resize" },
+        { pointName: "scale-sw", x: minX, y: maxY, cursor: "nesw-resize" },
+        { pointName: "scale-nw", x: minX, y: minY, cursor: "nwse-resize" },
       ];
 
-      for (let { pointName, x, y } of points) {
+      for (let { pointName, x, y, cursor } of points) {
         if (point === pointName) {
           this._drawHoverMarker([[x, y]]);
+          this.setCursor(cursor);
         }
       }
     } else if (this.shapeType === "polygon") {
       if (point === -1) {
+        this.setCursor("auto");
         return;
       }
+      this.setCursor(hoverCursor);
       this._drawHoverMarker([this.coordinates[point]]);
     } else if (this.shapeType === "circle") {
+      this.setCursor(hoverCursor);
+
       let { cx, cy, rx } = this.coordinates;
       if (point === "radius") {
         this._drawHoverMarker([[cx + rx, cy]]);
       } else if (point === "center") {
         this._drawHoverMarker([[cx, cy]]);
       }
     } else if (this.shapeType === "ellipse") {
+      this.setCursor(hoverCursor);
+
       if (point === "center") {
         let { cx, cy } = this.coordinates;
         this._drawHoverMarker([[cx, cy]]);
       } else if (point === "rx") {
         let { cx, cy, rx } = this.coordinates;
         this._drawHoverMarker([[cx + rx, cy]]);
       } else if (point === "ry") {
         let { cx, cy, ry } = this.coordinates;
         this._drawHoverMarker([[cx, cy + ry]]);
       }
     } else if (this.shapeType === "inset") {
-      if (!point) {
-        return;
-      }
+      this.setCursor(hoverCursor);
 
       let { top, right, bottom, left } = this.coordinates;
       let centerX = (left + (100 - right)) / 2;
       let centerY = (top + (100 - bottom)) / 2;
       let points = point.split(",");
       let coords = points.map(side => {
         if (side === "top") {
           return [centerX, top];
@@ -1798,22 +1843,22 @@ class ShapesHighlighter extends AutoRefr
     } else if (this.shapeType === "circle") {
       this._updateCircleShape(width, height, zoom);
     } else if (this.shapeType === "ellipse") {
       this._updateEllipseShape(width, height, zoom);
     } else if (this.shapeType === "inset") {
       this._updateInsetShape(width, height, zoom);
     }
 
-    this._handleMarkerHover(this.hoveredPoint);
-
     let { width: winWidth, height: winHeight } = this._winDimensions;
     root.removeAttribute("hidden");
     root.setAttribute("style",
-      `position:absolute; width:${winWidth}px;height:${winHeight}px; overflow:hidden`);
+      `position:absolute; width:${winWidth}px;height:${winHeight}px; overflow:hidden;`);
+
+    this._handleMarkerHover(this.hoveredPoint);
 
     setIgnoreLayoutChanges(false, this.highlighterEnv.window.document.documentElement);
 
     return true;
   }
 
   /**
    * Update the SVGs for transform mode to fit the new shape.
--- a/dom/animation/test/style/file_composite.html
+++ b/dom/animation/test/style/file_composite.html
@@ -28,96 +28,101 @@ function waitForPaintsFlushed() {
 }
 
 promise_test(t => {
   // Without this, the first test case fails on Android.
   return waitForDocumentLoad();
 }, 'Ensure document has been loaded');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'transform: translateX(100px)' });
+    div.animate({ transform: ['translateX(0px)', 'translateX(200px)'],
+                  composite: 'accumulate' },
+                100 * MS_PER_SEC);
 
-  var div = addDiv(t, { style: 'transform: translateX(100px)' });
-  div.animate({ transform: ['translateX(0px)', 'translateX(200px)'],
-                composite: 'accumulate' },
-              100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
       'Transform value at 50%');
   });
 }, 'Accumulate onto the base value');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t);
+    div.animate({ transform: ['translateX(100px)', 'translateX(200px)'],
+                  composite: 'replace' },
+                100 * MS_PER_SEC);
+    div.animate({ transform: ['translateX(0px)', 'translateX(100px)'],
+                  composite: 'accumulate' },
+                100 * MS_PER_SEC);
 
-  var div = addDiv(t);
-  div.animate({ transform: ['translateX(100px)', 'translateX(200px)'],
-                composite: 'replace' },
-              100 * MS_PER_SEC);
-  div.animate({ transform: ['translateX(0px)', 'translateX(100px)'],
-                composite: 'accumulate' },
-              100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
       'Transform value at 50%');
   });
 }, 'Accumulate onto an underlying animation value');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'transform: translateX(100px)' });
+    div.animate([{ transform: 'translateX(100px)', composite: 'accumulate' },
+                 { transform: 'translateX(300px)', composite: 'replace' }],
+                100 * MS_PER_SEC);
 
-  var div = addDiv(t, { style: 'transform: translateX(100px)' });
-  div.animate([{ transform: 'translateX(100px)', composite: 'accumulate' },
-               { transform: 'translateX(300px)', composite: 'replace' }],
-              100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
       'Transform value at 50s');
   });
 }, 'Composite when mixing accumulate and replace');
 
 promise_test(t => {
-  useTestRefreshMode(t);
-
-  var div = addDiv(t, { style: 'transform: translateX(100px)' });
-  div.animate([{ transform: 'translateX(100px)', composite: 'replace' },
-               { transform: 'translateX(300px)' }],
-              { duration: 100 * MS_PER_SEC, composite: 'accumulate' });
-
-  return waitForPaintsFlushed().then(() => {
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'transform: translateX(100px)' });
+    div.animate([{ transform: 'translateX(100px)', composite: 'replace' },
+                 { transform: 'translateX(300px)' }],
+                { duration: 100 * MS_PER_SEC, composite: 'accumulate' });
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
       'Transform value at 50%');
   });
 }, 'Composite specified on a keyframe overrides the composite mode of the ' +
    'effect');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  var anim;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t);
+    div.animate({ transform: [ 'scale(2)', 'scale(2)' ] }, 100 * MS_PER_SEC);
+    anim = div.animate({ transform: [ 'scale(4)', 'scale(4)' ] },
+                       { duration: 100 * MS_PER_SEC, composite: 'add' });
 
-  var div = addDiv(t);
-  div.animate({ transform: [ 'scale(2)', 'scale(2)' ] }, 100 * MS_PER_SEC);
-  var anim = div.animate({ transform: [ 'scale(4)', 'scale(4)' ] },
-                         { duration: 100 * MS_PER_SEC, composite: 'add' });
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(8, 0, 0, 8, 0, 0)',
       'The additive scale value should be scale(8)'); // scale(2) scale(4)
 
     anim.effect.composite = 'accumulate';
     return waitForPaintsFlushed();
   }).then(() => {
--- a/dom/animation/test/style/file_missing-keyframe-on-compositor.html
+++ b/dom/animation/test/style/file_missing-keyframe-on-compositor.html
@@ -30,132 +30,137 @@ function waitForPaintsFlushed() {
 // Note that promise tests run in sequence so this ensures the document is
 // loaded before any of the other tests run.
 promise_test(t => {
   // Without this, the first test case fails on Android.
   return waitForDocumentLoad();
 }, 'Ensure document has been loaded');
 
 promise_test(t => {
-  useTestRefreshMode(t);
-
-  var div = addDiv(t, { style: 'opacity: 0.1' });
-  div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'opacity: 0.1' });
+    div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
+    return waitForPaintsFlushed();
+  }).then(() => {
     var opacity =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
     assert_equals(opacity, '0.1',
                   'The initial opacity value should be the base value');
   });
 }, 'Initial opacity value for animation with no no keyframe at offset 0');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'opacity: 0.1' });
+    div.animate({ opacity: [ 0.5, 1 ] }, 100 * MS_PER_SEC);
+    div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
 
-  var div = addDiv(t, { style: 'opacity: 0.1' });
-  div.animate({ opacity: [ 0.5, 1 ] }, 100 * MS_PER_SEC);
-  div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     var opacity =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
     assert_equals(opacity, '0.5',
                   'The initial opacity value should be the value of ' +
                   'lower-priority animation value');
   });
 }, 'Initial opacity value for animation with no keyframe at offset 0 when ' +
    'there is a lower-priority animation');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'opacity: 0.1; transition: opacity 100s linear' });
+    getComputedStyle(div).opacity;
 
-  var div =
-    addDiv(t, { style: 'opacity: 0.1; transition: opacity 100s linear' });
-  getComputedStyle(div).opacity;
+    div.style.opacity = '0.5';
+    div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
 
-  div.style.opacity = '0.5';
-  div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     var opacity =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
     assert_equals(opacity, '0.1',
                   'The initial opacity value should be the initial value of ' +
                   'the transition');
   });
 }, 'Initial opacity value for animation with no keyframe at offset 0 when ' +
    'there is a transition on the same property');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'opacity: 0' });
+    div.animate([{ offset: 0, opacity: 1 }], 100 * MS_PER_SEC);
 
-  var div = addDiv(t, { style: 'opacity: 0' });
-  div.animate([{ offset: 0, opacity: 1 }], 100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var opacity =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
     assert_equals(opacity, '0.5',
                   'Opacity value at 50% should be composed onto the base ' +
                   'value');
   });
 }, 'Opacity value for animation with no keyframe at offset 1 at 50% ');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'opacity: 0' });
+    div.animate({ opacity: [ 0.5, 0.5 ] }, 100 * MS_PER_SEC);
+    div.animate([{ offset: 0, opacity: 1 }], 100 * MS_PER_SEC);
 
-  var div = addDiv(t, { style: 'opacity: 0' });
-  div.animate({ opacity: [ 0.5, 0.5 ] }, 100 * MS_PER_SEC);
-  div.animate([{ offset: 0, opacity: 1 }], 100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var opacity =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
     assert_equals(opacity, '0.75', // (0.5 + 1) * 0.5
                   'Opacity value at 50% should be composed onto the value ' +
                   'of middle of lower-priority animation');
   });
 }, 'Opacity value for animation with no keyframe at offset 1 at 50% when ' +
    'there is a lower-priority animation');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'opacity: 0; transition: opacity 100s linear' });
+    getComputedStyle(div).opacity;
 
-  var div =
-    addDiv(t, { style: 'opacity: 0; transition: opacity 100s linear' });
-  getComputedStyle(div).opacity;
+    div.style.opacity = '0.5';
+    div.animate([{ offset: 0, opacity: 1 }], 100 * MS_PER_SEC);
 
-  div.style.opacity = '0.5';
-  div.animate([{ offset: 0, opacity: 1 }], 100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var opacity =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
     assert_equals(opacity, '0.625', // ((0 + 0.5) * 0.5 + 1) * 0.5
                   'Opacity value at 50% should be composed onto the value ' +
                   'of middle of transition');
   });
 }, 'Opacity value for animation with no keyframe at offset 1 at 50% when ' +
    'there is a transition on the same property');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  var lowerAnimation;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t);
+    lowerAnimation = div.animate({ opacity: [ 0.5, 1 ] }, 100 * MS_PER_SEC);
+    var higherAnimation = div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
 
-  var div = addDiv(t);
-  var lowerAnimation = div.animate({ opacity: [ 0.5, 1 ] }, 100 * MS_PER_SEC);
-  var higherAnimation = div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     lowerAnimation.pause();
     return waitForPaintsFlushed();
   }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var opacity =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
     // The underlying value is the value that is staying at 0ms of the
@@ -164,23 +169,25 @@ promise_test(t => {
     assert_equals(opacity, '0.75',
                   'Composed opacity value should be composed onto the value ' +
                   'of lower-priority paused animation');
   });
 }, 'Opacity value for animation with no keyframe at offset 0 at 50% when ' +
    'composed onto a paused underlying animation');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  var lowerAnimation;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t);
+    lowerAnimation = div.animate({ opacity: [ 0.5, 1 ] }, 100 * MS_PER_SEC);
+    var higherAnimation = div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
 
-  var div = addDiv(t);
-  var lowerAnimation = div.animate({ opacity: [ 0.5, 1 ] }, 100 * MS_PER_SEC);
-  var higherAnimation = div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     lowerAnimation.playbackRate = 0;
     return waitForPaintsFlushed();
   }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var opacity =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
     // The underlying value is the value that is staying at 0ms of the
@@ -189,23 +196,25 @@ promise_test(t => {
     assert_equals(opacity, '0.75',
                   'Composed opacity value should be composed onto the value ' +
                   'of lower-priority zero playback rate animation');
   });
 }, 'Opacity value for animation with no keyframe at offset 0 at 50% when ' +
    'composed onto a zero playback rate underlying animation');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  var lowerAnimation;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t);
+    lowerAnimation = div.animate({ opacity: [ 1, 0.5 ] }, 100 * MS_PER_SEC);
+    var higherAnimation = div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
 
-  var div = addDiv(t);
-  var lowerAnimation = div.animate({ opacity: [ 1, 0.5 ] }, 100 * MS_PER_SEC);
-  var higherAnimation = div.animate({ opacity: 1 }, 100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     lowerAnimation.effect.timing.duration = 0;
     lowerAnimation.effect.timing.fill = 'forwards';
     return waitForPaintsFlushed();
   }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var opacity =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'opacity');
@@ -215,139 +224,145 @@ promise_test(t => {
     assert_equals(opacity, '0.75',
                   'Composed opacity value should be composed onto the value ' +
                   'of lower-priority zero active duration animation');
   });
 }, 'Opacity value for animation with no keyframe at offset 0 at 50% when ' +
    'composed onto a zero active duration underlying animation');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'transform: translateX(100px)' });
+    div.animate({ transform: 'translateX(200px)' }, 100 * MS_PER_SEC);
 
-  var div = addDiv(t, { style: 'transform: translateX(100px)' });
-  div.animate({ transform: 'translateX(200px)' }, 100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 100, 0)',
       'The initial transform value should be the base value');
   });
 }, 'Initial transform value for animation with no keyframe at offset 0');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'transform: translateX(100px)' });
+    div.animate({ transform: [ 'translateX(200px)', 'translateX(300px)' ] },
+                100 * MS_PER_SEC);
+    div.animate({ transform: 'translateX(400px)' }, 100 * MS_PER_SEC);
 
-  var div = addDiv(t, { style: 'transform: translateX(100px)' });
-  div.animate({ transform: [ 'translateX(200px)', 'translateX(300px)' ] },
-              100 * MS_PER_SEC);
-  div.animate({ transform: 'translateX(400px)' }, 100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
       'The initial transform value should be lower-priority animation value');
   });
 }, 'Initial transform value for animation with no keyframe at offset 0 when ' +
    'there is a lower-priority animation');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'transform: translateX(100px);' +
+                             'transition: transform 100s linear' });
+    getComputedStyle(div).transform;
 
-  var div =
-    addDiv(t, { style: 'transform: translateX(100px);' +
-                       'transition: transform 100s linear' });
-  getComputedStyle(div).transform;
+    div.style.transform = 'translateX(200px)';
+    div.animate({ transform: 'translateX(400px)' }, 100 * MS_PER_SEC);
 
-  div.style.transform = 'translateX(200px)';
-  div.animate({ transform: 'translateX(400px)' }, 100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 100, 0)',
       'The initial transform value should be the initial value of the ' +
       'transition');
   });
 }, 'Initial transform value for animation with no keyframe at offset 0 when ' +
    'there is a transition');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'transform: translateX(100px)' });
+    div.animate([{ offset: 0, transform: 'translateX(200pX)' }],
+                100 * MS_PER_SEC);
 
-  var div = addDiv(t, { style: 'transform: translateX(100px)' });
-  div.animate([{ offset: 0, transform: 'translateX(200pX)' }],
-              100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 150, 0)',
       'Transform value at 50% should be the base value');
   });
 }, 'Transform value for animation with no keyframe at offset 1 at 50%');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'transform: translateX(100px)' });
+    div.animate({ transform: [ 'translateX(200px)', 'translateX(200px)' ] },
+                100 * MS_PER_SEC);
+    div.animate([{ offset: 0, transform: 'translateX(300px)' }],
+                100 * MS_PER_SEC);
 
-  var div = addDiv(t, { style: 'transform: translateX(100px)' });
-  div.animate({ transform: [ 'translateX(200px)', 'translateX(200px)' ] },
-              100 * MS_PER_SEC);
-  div.animate([{ offset: 0, transform: 'translateX(300px)' }],
-              100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
       'The final transform value should be the base value');
   });
 }, 'Transform value for animation with no keyframe at offset 1 at 50% when ' +
    'there is a lower-priority animation');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'transform: translateX(100px);' +
+                             'transition: transform 100s linear' });
+    getComputedStyle(div).transform;
 
-  var div =
-    addDiv(t, { style: 'transform: translateX(100px);' +
-                       'transition: transform 100s linear' });
-  getComputedStyle(div).transform;
+    div.style.transform = 'translateX(200px)';
+    div.animate([{ offset: 0, transform: 'translateX(300px)' }],
+                100 * MS_PER_SEC);
 
-  div.style.transform = 'translateX(200px)';
-  div.animate([{ offset: 0, transform: 'translateX(300px)' }],
-              100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
                                                    // (150px + 300px) * 0.5
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 225, 0)',
       'The final transform value should be the final value of the transition');
   });
 }, 'Transform value for animation with no keyframe at offset 1 at 50% when ' +
    'there is a transition');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  var lowerAnimation;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t);
+    lowerAnimation =
+      div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
+                  100 * MS_PER_SEC);
+    var higherAnimation = div.animate({ transform: 'translateX(300px)' },
+                                      100 * MS_PER_SEC);
 
-  var div = addDiv(t);
-  var lowerAnimation =
-    div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
-                100 * MS_PER_SEC);
-  var higherAnimation = div.animate({ transform: 'translateX(300px)' },
-                                    100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     lowerAnimation.pause();
     return waitForPaintsFlushed();
   }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     // The underlying value is the value that is staying at 0ms of the
@@ -356,26 +371,28 @@ promise_test(t => {
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
       'Composed transform value should be composed onto the value of ' +
       'lower-priority paused animation');
   });
 }, 'Transform value for animation with no keyframe at offset 0 at 50% when ' +
    'composed onto a paused underlying animation');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  var lowerAnimation;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t);
+    lowerAnimation =
+      div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
+                  100 * MS_PER_SEC);
+    var higherAnimation = div.animate({ transform: 'translateX(300px)' },
+                                      100 * MS_PER_SEC);
 
-  var div = addDiv(t);
-  var lowerAnimation =
-    div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
-                100 * MS_PER_SEC);
-  var higherAnimation = div.animate({ transform: 'translateX(300px)' },
-                                    100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     lowerAnimation.playbackRate = 0;
     return waitForPaintsFlushed();
   }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     // The underlying value is the value that is staying at 0ms of the
@@ -384,27 +401,28 @@ promise_test(t => {
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 200, 0)',
       'Composed transform value should be composed onto the value of ' +
       'lower-priority zero playback rate animation');
   });
 }, 'Transform value for animation with no keyframe at offset 0 at 50% when ' +
    'composed onto a zero playback rate underlying animation');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t);
+    var lowerAnimation =
+      div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
+                  { duration: 10 * MS_PER_SEC,
+                    fill: 'forwards' });
+    var higherAnimation = div.animate({ transform: 'translateX(300px)' },
+                                      100 * MS_PER_SEC);
 
-  var div = addDiv(t);
-  var lowerAnimation =
-    div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
-                { duration: 10 * MS_PER_SEC,
-                  fill: 'forwards' });
-  var higherAnimation = div.animate({ transform: 'translateX(300px)' },
-                                    100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     // We need to wait for a paint so that we can send the state of the lower
     // animation that is actually finished at this point.
     return waitForPaintsFlushed();
   }).then(() => {
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
@@ -412,28 +430,29 @@ promise_test(t => {
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
       'Composed transform value should be composed onto the value of ' +
       'lower-priority animation with fill:forwards');
   });
 }, 'Transform value for animation with no keyframe at offset 0 at 50% when ' +
    'composed onto a underlying animation with fill:forwards');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t);
+    var lowerAnimation =
+      div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
+                  { duration: 10 * MS_PER_SEC,
+                    endDelay: -5 * MS_PER_SEC,
+                    fill: 'forwards' });
+    var higherAnimation = div.animate({ transform: 'translateX(300px)' },
+                                      100 * MS_PER_SEC);
 
-  var div = addDiv(t);
-  var lowerAnimation =
-    div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
-                { duration: 10 * MS_PER_SEC,
-                  endDelay: -5 * MS_PER_SEC,
-                  fill: 'forwards' });
-  var higherAnimation = div.animate({ transform: 'translateX(300px)' },
-                                    100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     // We need to wait for a paint just like the above test.
     return waitForPaintsFlushed();
   }).then(() => {
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     // (150px + 300px) * (50 * MS_PER_SEC / 100 * MS_PER_SEC) = 225px.
@@ -441,95 +460,99 @@ promise_test(t => {
       'Composed transform value should be composed onto the value of ' +
       'lower-priority animation with fill:forwards and negative endDelay');
   });
 }, 'Transform value for animation with no keyframe at offset 0 at 50% when ' +
    'composed onto a underlying animation with fill:forwards and negative ' +
    'endDelay');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t);
+    var lowerAnimation =
+      div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
+                  { duration: 10 * MS_PER_SEC,
+                    endDelay: 100 * MS_PER_SEC,
+                    fill: 'forwards' });
+    var higherAnimation = div.animate({ transform: 'translateX(300px)' },
+                                      100 * MS_PER_SEC);
 
-  var div = addDiv(t);
-  var lowerAnimation =
-    div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
-                { duration: 10 * MS_PER_SEC,
-                  endDelay: 100 * MS_PER_SEC,
-                  fill: 'forwards' });
-  var higherAnimation = div.animate({ transform: 'translateX(300px)' },
-                                    100 * MS_PER_SEC);
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     // (200px + 300px) * 0.5
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 250, 0)',
       'Composed transform value should be composed onto the value of ' +
       'lower-priority animation with fill:forwards during positive endDelay');
   });
 }, 'Transform value for animation with no keyframe at offset 0 at 50% when ' +
    'composed onto a underlying animation with fill:forwards during positive ' +
    'endDelay');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'transform: translateX(100px)' });
+    div.animate({ transform: 'translateX(200px)' },
+                { duration: 100 * MS_PER_SEC, delay: 50 * MS_PER_SEC });
 
-  var div = addDiv(t, { style: 'transform: translateX(100px)' });
-  div.animate({ transform: 'translateX(200px)' },
-              { duration: 100 * MS_PER_SEC, delay: 50 * MS_PER_SEC });
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(100 * MS_PER_SEC);
 
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 150, 0)',
       'Transform value for animation with positive delay should be composed ' +
       'onto the base style');
   });
 }, 'Transform value for animation with no keyframe at offset 0 and with ' +
    'positive delay');
 
 promise_test(t => {
-  useTestRefreshMode(t);
-
-  var div = addDiv(t, { style: 'transform: translateX(100px)' });
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t, { style: 'transform: translateX(100px)' });
 
-  div.animate([{ offset: 0, transform: 'translateX(200px)'}],
-              { duration: 100 * MS_PER_SEC,
-                iterationStart: 1,
-                iterationComposite: 'accumulate' });
+    div.animate([{ offset: 0, transform: 'translateX(200px)'}],
+                { duration: 100 * MS_PER_SEC,
+                  iterationStart: 1,
+                  iterationComposite: 'accumulate' });
 
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 300, 0)',
       'Transform value for animation with no keyframe at offset 1 and its ' +
       'iterationComposite is accumulate');
   });
 }, 'Transform value for animation with no keyframe at offset 1 and its ' +
    'iterationComposite is accumulate');
 
 promise_test(t => {
-  useTestRefreshMode(t);
+  var div;
+  return useTestRefreshMode(t).then(() => {
+    div = addDiv(t);
+    var lowerAnimation =
+      div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
+                  100 * MS_PER_SEC);
+    var higherAnimation = div.animate({ transform: 'translateX(300px)' },
+                                      100 * MS_PER_SEC);
 
-  var div = addDiv(t);
-  var lowerAnimation =
-    div.animate({ transform: [ 'translateX(100px)', 'translateX(200px)' ] },
-                100 * MS_PER_SEC);
-  var higherAnimation = div.animate({ transform: 'translateX(300px)' },
-                                    100 * MS_PER_SEC);
+    lowerAnimation.timeline = null;
+    // Set current time at 50% duration.
+    lowerAnimation.currentTime = 50 * MS_PER_SEC;
 
-  lowerAnimation.timeline = null;
-  // Set current time at 50% duration.
-  lowerAnimation.currentTime = 50 * MS_PER_SEC;
-
-  return waitForPaintsFlushed().then(() => {
+    return waitForPaintsFlushed();
+  }).then(() => {
     SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(50 * MS_PER_SEC);
 
     var transform =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
     // (150px + 300px) * (50 * MS_PER_SEC / 100 * MS_PER_SEC) = 225px.
     assert_matrix_equals(transform, 'matrix(1, 0, 0, 1, 225, 0)',
       'Composed transform value should be composed onto the value of ' +
       'lower-priority animation without timeline');
--- a/dom/animation/test/testcommon.js
+++ b/dom/animation/test/testcommon.js
@@ -301,19 +301,34 @@ function waitForDocumentLoad() {
     }
   });
 }
 
 /*
  * Enters test refresh mode, and restores the mode when |t| finishes.
  */
 function useTestRefreshMode(t) {
-  SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0);
-  t.add_cleanup(() => {
-    SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+  function ensureNoSuppressedPaints() {
+    return new Promise(resolve => {
+      function checkSuppressedPaints() {
+        if (!SpecialPowers.DOMWindowUtils.paintingSuppressed) {
+          resolve();
+        } else {
+          window.requestAnimationFrame(checkSuppressedPaints);
+        }
+      }
+      checkSuppressedPaints();
+    });
+  }
+
+  return ensureNoSuppressedPaints().then(() => {
+    SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0);
+    t.add_cleanup(() => {
+      SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+    });
   });
 }
 
 /**
  * Returns true if off-main-thread animations.
  */
 function isOMTAEnabled() {
   const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
--- a/dom/base/DOMParser.cpp
+++ b/dom/base/DOMParser.cpp
@@ -352,18 +352,18 @@ DOMParser::Init(nsIPrincipal* principal,
         rv = mPrincipal->GetURI(getter_AddRefs(mDocumentURI));
         NS_ENSURE_SUCCESS(rv, rv);
       }
     }
   }
 
   mBaseURI = baseURI;
 
-  NS_POSTCONDITION(mPrincipal, "Must have principal");
-  NS_POSTCONDITION(mDocumentURI, "Must have document URI");
+  MOZ_ASSERT(mPrincipal, "Must have principal");
+  MOZ_ASSERT(mDocumentURI, "Must have document URI");
   return NS_OK;
 }
 
 /*static */already_AddRefed<DOMParser>
 DOMParser::Constructor(const GlobalObject& aOwner,
                        nsIPrincipal* aPrincipal, nsIURI* aDocumentURI,
                        nsIURI* aBaseURI, ErrorResult& rv)
 {
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1883,20 +1883,20 @@ Element::BindToTree(nsIDocument* aDocume
       }
       MOZ_ASSERT(parent);
     }
   }
 
   // XXXbz script execution during binding can trigger some of these
   // postcondition asserts....  But we do want that, since things will
   // generally be quite broken when that happens.
-  NS_POSTCONDITION(aDocument == GetUncomposedDoc(), "Bound to wrong document");
-  NS_POSTCONDITION(aParent == GetParent(), "Bound to wrong parent");
-  NS_POSTCONDITION(aBindingParent == GetBindingParent(),
-                   "Bound to wrong binding parent");
+  MOZ_ASSERT(aDocument == GetUncomposedDoc(), "Bound to wrong document");
+  MOZ_ASSERT(aParent == GetParent(), "Bound to wrong parent");
+  MOZ_ASSERT(aBindingParent == GetBindingParent(),
+             "Bound to wrong binding parent");
 
   return NS_OK;
 }
 
 RemoveFromBindingManagerRunnable::RemoveFromBindingManagerRunnable(
   nsBindingManager* aManager,
   nsIContent* aContent,
   nsIDocument* aDoc)
@@ -2218,17 +2218,17 @@ Element::GetMappedAttributes() const
   return mAttrsAndChildren.GetMapped();
 }
 
 nsresult
 Element::SetInlineStyleDeclaration(DeclarationBlock* aDeclaration,
                                    const nsAString* aSerialized,
                                    bool aNotify)
 {
-  NS_NOTYETIMPLEMENTED("Element::SetInlineStyleDeclaration");
+  MOZ_ASSERT_UNREACHABLE("Element::SetInlineStyleDeclaration");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP_(bool)
 Element::IsAttributeMapped(const nsAtom* aAttribute) const
 {
   return false;
 }
--- a/dom/base/nsGenericDOMDataNode.cpp
+++ b/dom/base/nsGenericDOMDataNode.cpp
@@ -565,20 +565,20 @@ nsGenericDOMDataNode::BindToTree(nsIDocu
 
   nsNodeUtils::ParentChainChanged(this);
   if (!hadParent && IsRootOfNativeAnonymousSubtree()) {
     nsNodeUtils::NativeAnonymousChildListChange(this, false);
   }
 
   UpdateEditableState(false);
 
-  NS_POSTCONDITION(aDocument == GetUncomposedDoc(), "Bound to wrong document");
-  NS_POSTCONDITION(aParent == GetParent(), "Bound to wrong parent");
-  NS_POSTCONDITION(aBindingParent == GetBindingParent(),
-                   "Bound to wrong binding parent");
+  MOZ_ASSERT(aDocument == GetUncomposedDoc(), "Bound to wrong document");
+  MOZ_ASSERT(aParent == GetParent(), "Bound to wrong parent");
+  MOZ_ASSERT(aBindingParent == GetBindingParent(),
+             "Bound to wrong binding parent");
 
   return NS_OK;
 }
 
 void
 nsGenericDOMDataNode::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   // Unset frame flags; if we need them again later, they'll get set again.
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -1279,18 +1279,18 @@ nsINode::RemoveEventListener(const nsASt
   return NS_OK;
 }
 
 NS_IMPL_REMOVE_SYSTEM_EVENT_LISTENER(nsINode)
 
 nsresult
 nsINode::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
-  // This is only here so that we can use the NS_DECL_NSIDOMTARGET macro
-  NS_ABORT();
+  MOZ_ASSERT_UNREACHABLE("GetEventTargetParent is only here so that we can "
+                         "use the NS_DECL_NSIDOMTARGET macro");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 void
 nsINode::GetBoxQuads(const BoxQuadOptions& aOptions,
                      nsTArray<RefPtr<DOMQuad> >& aResult,
                      CallerType aCallerType,
                      mozilla::ErrorResult& aRv)
--- a/dom/base/nsNameSpaceManager.cpp
+++ b/dom/base/nsNameSpaceManager.cpp
@@ -105,17 +105,17 @@ nsNameSpaceManager::RegisterNameSpace(co
     aNameSpaceID = mURIArray.Length();
 
     rv = AddNameSpace(atom.forget(), aNameSpaceID);
     if (NS_FAILED(rv)) {
       aNameSpaceID = kNameSpaceID_Unknown;
     }
   }
 
-  NS_POSTCONDITION(aNameSpaceID >= -1, "Bogus namespace ID");
+  MOZ_ASSERT(aNameSpaceID >= -1, "Bogus namespace ID");
 
   return rv;
 }
 
 nsresult
 nsNameSpaceManager::GetNameSpaceURI(int32_t aNameSpaceID, nsAString& aURI)
 {
   NS_PRECONDITION(aNameSpaceID >= 0, "Bogus namespace ID");
@@ -154,21 +154,21 @@ nsNameSpaceManager::GetNameSpaceID(nsAto
   }
 
   int32_t nameSpaceID;
   if (!aInChromeDoc
       && (mMathMLDisabled || mSVGDisabled)
       && mDisabledURIToIDTable.Get(aURI, &nameSpaceID)
       && ((mMathMLDisabled && kNameSpaceID_disabled_MathML == nameSpaceID) ||
       (mSVGDisabled && kNameSpaceID_disabled_SVG == nameSpaceID))) {
-    NS_POSTCONDITION(nameSpaceID >= 0, "Bogus namespace ID");
+    MOZ_ASSERT(nameSpaceID >= 0, "Bogus namespace ID");
     return nameSpaceID;
   }
   if (mURIToIDTable.Get(aURI, &nameSpaceID)) {
-    NS_POSTCONDITION(nameSpaceID >= 0, "Bogus namespace ID");
+    MOZ_ASSERT(nameSpaceID >= 0, "Bogus namespace ID");
     return nameSpaceID;
   }
 
   return kNameSpaceID_Unknown;
 }
 
 nsresult
 NS_NewElement(Element** aResult,
--- a/dom/base/nsNodeInfoManager.cpp
+++ b/dom/base/nsNodeInfoManager.cpp
@@ -454,17 +454,17 @@ nsNodeInfoManager::RemoveNodeInfo(NodeIn
     mRecentlyUsedNodeInfos[index] = nullptr;
   }
 
 #ifdef DEBUG
   bool ret =
 #endif
   PL_HashTableRemove(mNodeInfoHash, &aNodeInfo->mInner);
 
-  NS_POSTCONDITION(ret, "Can't find mozilla::dom::NodeInfo to remove!!!");
+  MOZ_ASSERT(ret, "Can't find mozilla::dom::NodeInfo to remove!!!");
 }
 
 bool
 nsNodeInfoManager::InternalSVGEnabled()
 {
   // If the svg.disabled pref. is true, convert all SVG nodes into
   // disabled SVG nodes by swapping the namespace.
   nsNameSpaceManager* nsmgr = nsNameSpaceManager::GetInstance();
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -5365,19 +5365,17 @@ CanvasRenderingContext2D::DrawDirectlyTo
   AdjustedTarget tempTarget(this, aBounds->IsEmpty() ? nullptr: aBounds);
   if (!tempTarget) {
     return;
   }
 
   // Get any existing transforms on the context, including transformations used
   // for context shadow.
   Matrix matrix = tempTarget->GetTransform();
-  gfxMatrix contextMatrix;
-  contextMatrix = gfxMatrix(matrix._11, matrix._12, matrix._21,
-                            matrix._22, matrix._31, matrix._32);
+  gfxMatrix contextMatrix = ThebesMatrix(matrix);
   gfxSize contextScale(contextMatrix.ScaleFactors(true));
 
   // Scale the dest rect to include the context scale.
   aDest.Scale(contextScale.width, contextScale.height);
 
   // Scale the image size to the dest rect, and adjust the source rect to match.
   gfxSize scale(aDest.width / aSrc.width, aDest.height / aSrc.height);
   IntSize scaledImageSize = IntSize::Ceil(aImgSize.width * scale.width,
@@ -5388,20 +5386,20 @@ CanvasRenderingContext2D::DrawDirectlyTo
   // the matrix even though this is a temp gfxContext.
   AutoRestoreTransform autoRestoreTransform(mTarget);
 
   RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempTarget);
   if (!context) {
     gfxDevCrash(LogReason::InvalidContext) << "Canvas context problem";
     return;
   }
-  context->SetMatrix(contextMatrix.
-                       PreScale(1.0 / contextScale.width,
-                                1.0 / contextScale.height).
-                       PreTranslate(aDest.x - aSrc.x, aDest.y - aSrc.y));
+  context->SetMatrixDouble(contextMatrix.
+                           PreScale(1.0 / contextScale.width,
+                                    1.0 / contextScale.height).
+                           PreTranslate(aDest.x - aSrc.x, aDest.y - aSrc.y));
 
   // FLAG_CLAMP is added for increased performance, since we never tile here.
   uint32_t modifiedFlags = aImage.mDrawingFlags | imgIContainer::FLAG_CLAMP;
 
   CSSIntSize sz(scaledImageSize.width, scaledImageSize.height); // XXX hmm is scaledImageSize really in CSS pixels?
   SVGImageContext svgContext(Some(sz));
 
   auto result = aImage.mImgContainer->
@@ -5598,35 +5596,34 @@ CanvasRenderingContext2D::DrawWindow(nsG
     }
   }
   if (op == CompositionOp::OP_OVER &&
       (!mBufferProvider || mBufferProvider->GetType() != LayersBackend::LAYERS_CLIENT))
   {
     thebes = gfxContext::CreateOrNull(mTarget);
     MOZ_ASSERT(thebes); // already checked the draw target above
                         // (in SupportsAzureContentForDrawTarget)
-    thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21,
-                                matrix._22, matrix._31, matrix._32));
+    thebes->SetMatrix(matrix);
   } else {
     IntSize dtSize = IntSize::Ceil(sw, sh);
     if (!Factory::AllowedSurfaceSize(dtSize)) {
       aError.Throw(NS_ERROR_FAILURE);
       return;
     }
     drawDT =
       gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(dtSize,
                                                                    SurfaceFormat::B8G8R8A8);
     if (!drawDT || !drawDT->IsValid()) {
       aError.Throw(NS_ERROR_FAILURE);
       return;
     }
 
     thebes = gfxContext::CreateOrNull(drawDT);
     MOZ_ASSERT(thebes); // alrady checked the draw target above
-    thebes->SetMatrix(gfxMatrix::Scaling(matrix._11, matrix._22));
+    thebes->SetMatrix(Matrix::Scaling(matrix._11, matrix._22));
   }
 
   nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
 
   Unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes);
   // If this canvas was contained in the drawn window, the pre-transaction callback
   // may have returned its DT. If so, we must reacquire it here.
   EnsureTarget(discardContent ? &drawRect : nullptr);
--- a/dom/canvas/DocumentRendererChild.cpp
+++ b/dom/canvas/DocumentRendererChild.cpp
@@ -90,15 +90,15 @@ DocumentRendererChild::RenderDocument(ns
                                          4 * renderSize.width,
                                          SurfaceFormat::B8G8R8A8);
     if (!dt || !dt->IsValid()) {
         gfxWarning() << "DocumentRendererChild::RenderDocument failed to Factory::CreateDrawTargetForData";
         return false;
     }
     RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
     MOZ_ASSERT(ctx); // already checked the draw target above
-    ctx->SetMatrix(mozilla::gfx::ThebesMatrix(transform));
+    ctx->SetMatrix(transform);
 
     nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
     shell->RenderDocument(documentRect, renderFlags, bgColor, ctx);
 
     return true;
 }
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -1289,20 +1289,21 @@ HTMLFormElement::AddElement(nsGenericHTM
            (!mFirstSubmitInElements && !mFirstSubmitNotInElements)) &&
           (*firstSubmitSlot == mDefaultSubmitElement ||
            CompareFormControlPosition(aChild,
                                       mDefaultSubmitElement, this) < 0)) {
         mDefaultSubmitElement = aChild;
       }
       *firstSubmitSlot = aChild;
     }
-    NS_POSTCONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
-                     mDefaultSubmitElement == mFirstSubmitNotInElements ||
-                     !mDefaultSubmitElement,
-                     "What happened here?");
+
+    MOZ_ASSERT(mDefaultSubmitElement == mFirstSubmitInElements ||
+               mDefaultSubmitElement == mFirstSubmitNotInElements ||
+               !mDefaultSubmitElement,
+               "What happened here?");
 
     // Notify that the state of the previous default submit element has changed
     // if the element which is the default submit element has changed.  The new
     // default submit element is responsible for its own state update.
     if (oldDefaultSubmit && oldDefaultSubmit != mDefaultSubmitElement) {
       oldDefaultSubmit->UpdateState(aNotify);
     }
   }
@@ -1425,19 +1426,19 @@ HTMLFormElement::HandleDefaultSubmitRemo
                  "How did that happen?");
     // Have both; use the earlier one
     mDefaultSubmitElement =
       CompareFormControlPosition(mFirstSubmitInElements,
                                  mFirstSubmitNotInElements, this) < 0 ?
       mFirstSubmitInElements : mFirstSubmitNotInElements;
   }
 
-  NS_POSTCONDITION(mDefaultSubmitElement == mFirstSubmitInElements ||
-                   mDefaultSubmitElement == mFirstSubmitNotInElements,
-                   "What happened here?");
+  MOZ_ASSERT(mDefaultSubmitElement == mFirstSubmitInElements ||
+             mDefaultSubmitElement == mFirstSubmitNotInElements,
+             "What happened here?");
 
   // Notify about change if needed.
   if (mDefaultSubmitElement) {
     mDefaultSubmitElement->UpdateState(true);
   }
 }
 
 nsresult
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -6932,17 +6932,17 @@ HTMLInputElement::GetValueMode() const
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
     case NS_FORM_INPUT_COLOR:
     case NS_FORM_INPUT_MONTH:
     case NS_FORM_INPUT_WEEK:
     case NS_FORM_INPUT_DATETIME_LOCAL:
       return VALUE_MODE_VALUE;
     default:
-      NS_NOTYETIMPLEMENTED("Unexpected input type in GetValueMode()");
+      MOZ_ASSERT_UNREACHABLE("Unexpected input type in GetValueMode()");
       return VALUE_MODE_VALUE;
 #else // DEBUG
     default:
       return VALUE_MODE_VALUE;
 #endif // DEBUG
   }
 }
 
@@ -6980,17 +6980,17 @@ HTMLInputElement::DoesReadOnlyApply() co
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
     case NS_FORM_INPUT_MONTH:
     case NS_FORM_INPUT_WEEK:
     case NS_FORM_INPUT_DATETIME_LOCAL:
       return true;
     default:
-      NS_NOTYETIMPLEMENTED("Unexpected input type in DoesReadOnlyApply()");
+      MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesReadOnlyApply()");
       return true;
 #else // DEBUG
     default:
       return true;
 #endif // DEBUG
   }
 }
 
@@ -7020,17 +7020,17 @@ HTMLInputElement::DoesRequiredApply() co
     case NS_FORM_INPUT_NUMBER:
     case NS_FORM_INPUT_DATE:
     case NS_FORM_INPUT_TIME:
     case NS_FORM_INPUT_MONTH:
     case NS_FORM_INPUT_WEEK:
     case NS_FORM_INPUT_DATETIME_LOCAL:
       return true;
     default:
-      NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
+      MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
       return true;
 #else // DEBUG
     default:
       return true;
 #endif // DEBUG
   }
 }
 
@@ -7070,17 +7070,17 @@ HTMLInputElement::DoesMinMaxApply() cons
     case NS_FORM_INPUT_PASSWORD:
     case NS_FORM_INPUT_SEARCH:
     case NS_FORM_INPUT_TEL:
     case NS_FORM_INPUT_EMAIL:
     case NS_FORM_INPUT_URL:
     case NS_FORM_INPUT_COLOR:
       return false;
     default:
-      NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
+      MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
       return false;
 #else // DEBUG
     default:
       return false;
 #endif // DEBUG
   }
 }
 
@@ -7110,17 +7110,17 @@ HTMLInputElement::DoesAutocompleteApply(
     case NS_FORM_INPUT_SUBMIT:
     case NS_FORM_INPUT_IMAGE:
     case NS_FORM_INPUT_BUTTON:
     case NS_FORM_INPUT_RADIO:
     case NS_FORM_INPUT_CHECKBOX:
     case NS_FORM_INPUT_FILE:
       return false;
     default:
-      NS_NOTYETIMPLEMENTED("Unexpected input type in DoesAutocompleteApply()");
+      MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesAutocompleteApply()");
       return false;
 #else // DEBUG
     default:
       return false;
 #endif // DEBUG
   }
 }
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2379,18 +2379,17 @@ ContentParent::InitInternal(ProcessPrior
 #ifdef MOZ_CONTENT_SANDBOX
   bool shouldSandbox = true;
   MaybeFileDesc brokerFd = void_t();
   // XXX: Checking the pref here makes it possible to enable/disable sandboxing
   // during an active session. Currently the pref is only used for testing
   // purpose. If the decision is made to permanently rely on the pref, this
   // should be changed so that it is required to restart firefox for the change
   // of value to take effect.
-  shouldSandbox = (GetEffectiveContentSandboxLevel() > 0) &&
-    !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX");
+  shouldSandbox = IsContentSandboxEnabled();
 
 #ifdef XP_LINUX
   if (shouldSandbox) {
     MOZ_ASSERT(!mSandboxBroker);
     bool isFileProcess = mRemoteType.EqualsLiteral(FILE_REMOTE_TYPE);
     UniquePtr<SandboxBroker::Policy> policy =
       sSandboxBrokerPolicyFactory->GetContentPolicy(Pid(), isFileProcess);
     if (policy) {
--- a/dom/ipc/ContentProcess.cpp
+++ b/dom/ipc/ContentProcess.cpp
@@ -23,24 +23,16 @@
 #endif
 
 using mozilla::ipc::IOThreadChild;
 
 namespace mozilla {
 namespace dom {
 
 #if defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX)
-static bool
-IsSandboxTempDirRequired()
-{
-  // On Windows, a sandbox-writable temp directory is only used
-  // when sandbox pref level >= 1.
-  return GetEffectiveContentSandboxLevel() >= 1;
-}
-
 static void
 SetTmpEnvironmentVariable(nsIFile* aValue)
 {
   // Save the TMP environment variable so that is is picked up by GetTempPath().
   // Note that we specifically write to the TMP variable, as that is the first
   // variable that is checked by GetTempPath() to determine its output.
   nsAutoString fullTmpPath;
   nsresult rv = aValue->GetPath(fullTmpPath);
@@ -50,23 +42,16 @@ SetTmpEnvironmentVariable(nsIFile* aValu
   Unused << NS_WARN_IF(!SetEnvironmentVariableW(L"TMP", fullTmpPath.get()));
   // We also set TEMP in case there is naughty third-party code that is
   // referencing the environment variable directly.
   Unused << NS_WARN_IF(!SetEnvironmentVariableW(L"TEMP", fullTmpPath.get()));
 }
 #endif
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
-static bool
-IsSandboxTempDirRequired()
-{
-  // On OSX, use the sandbox-writable temp when the pref level >= 1.
-  return (GetEffectiveContentSandboxLevel() >= 1);
-}
-
 static void
 SetTmpEnvironmentVariable(nsIFile* aValue)
 {
   nsAutoCString fullTmpPath;
   nsresult rv = aValue->GetNativePath(fullTmpPath);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
@@ -76,17 +61,19 @@ SetTmpEnvironmentVariable(nsIFile* aValu
 
 #if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
 static void
 SetUpSandboxEnvironment()
 {
   MOZ_ASSERT(nsDirectoryService::gService,
     "SetUpSandboxEnvironment relies on nsDirectoryService being initialized");
 
-  if (!IsSandboxTempDirRequired()) {
+  // On macOS and Windows, a sandbox-writable temp directory is used whenever
+  // the sandbox is enabled.
+  if (!IsContentSandboxEnabled()) {
     return;
   }
 
   nsCOMPtr<nsIFile> sandboxedContentTemp;
   nsresult rv =
     nsDirectoryService::gService->Get(NS_APP_CONTENT_PROCESS_TEMP_DIR,
                                       NS_GET_IID(nsIFile),
                                       getter_AddRefs(sandboxedContentTemp));
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -3477,17 +3477,17 @@ TabChild::RecvSetWidgetNativeData(const 
 }
 
 mozilla::plugins::PPluginWidgetChild*
 TabChild::AllocPPluginWidgetChild()
 {
 #ifdef XP_WIN
   return new mozilla::plugins::PluginWidgetChild();
 #else
-  MOZ_ASSERT_UNREACHABLE();
+  MOZ_ASSERT_UNREACHABLE("AllocPPluginWidgetChild only supports Windows");
   return nullptr;
 #endif
 }
 
 bool
 TabChild::DeallocPPluginWidgetChild(mozilla::plugins::PPluginWidgetChild* aActor)
 {
   delete aActor;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -3134,17 +3134,17 @@ TabParent::RecvRemoteIsReadyToHandleInpu
 }
 
 mozilla::plugins::PPluginWidgetParent*
 TabParent::AllocPPluginWidgetParent()
 {
 #ifdef XP_WIN
   return new mozilla::plugins::PluginWidgetParent();
 #else
-  MOZ_ASSERT_UNREACHABLE();
+  MOZ_ASSERT_UNREACHABLE("AllocPPluginWidgetParent only supports Windows");
   return nullptr;
 #endif
 }
 
 bool
 TabParent::DeallocPPluginWidgetParent(mozilla::plugins::PPluginWidgetParent* aActor)
 {
   delete aActor;
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -59,76 +59,91 @@ static nsTHashtable<nsRefPtrHashKey<Medi
  *
  * It is a singleton reporter and the single class object lives as long as at
  * least one Recorder is registered. In MediaRecorder, the reporter is unregistered
  * when it is destroyed.
  */
 class MediaRecorderReporter final : public nsIMemoryReporter
 {
 public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  MediaRecorderReporter() {};
-  static MediaRecorderReporter* UniqueInstance();
-  void InitMemoryReporter();
-
   static void AddMediaRecorder(MediaRecorder *aRecorder)
   {
-    GetRecorders().AppendElement(aRecorder);
+    if (!sUniqueInstance) {
+      sUniqueInstance = MakeAndAddRef<MediaRecorderReporter>();
+      RegisterWeakAsyncMemoryReporter(sUniqueInstance);
+    }
+    sUniqueInstance->mRecorders.AppendElement(aRecorder);
   }
 
   static void RemoveMediaRecorder(MediaRecorder *aRecorder)
   {
-    RecordersArray& recorders = GetRecorders();
-    recorders.RemoveElement(aRecorder);
-    if (recorders.IsEmpty()) {
+    if (!sUniqueInstance) {
+      return;
+    }
+
+    sUniqueInstance->mRecorders.RemoveElement(aRecorder);
+    if (sUniqueInstance->mRecorders.IsEmpty()) {
+      UnregisterWeakMemoryReporter(sUniqueInstance);
       sUniqueInstance = nullptr;
     }
   }
 
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  MediaRecorderReporter() = default;
+
   NS_IMETHOD
   CollectReports(nsIHandleReportCallback* aHandleReport,
                  nsISupports* aData, bool aAnonymize) override
   {
-    RecordersArray& recorders = GetRecorders();
     nsTArray<RefPtr<MediaRecorder::SizeOfPromise>> promises;
-    for (const RefPtr<MediaRecorder>& recorder: recorders) {
+    for (const RefPtr<MediaRecorder>& recorder: mRecorders) {
       promises.AppendElement(recorder->SizeOfExcludingThis(MallocSizeOf));
     }
 
     nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport;
     nsCOMPtr<nsISupports> data = aData;
     MediaRecorder::SizeOfPromise::All(GetCurrentThreadSerialEventTarget(), promises)
       ->Then(GetCurrentThreadSerialEventTarget(), __func__,
           [handleReport, data](const nsTArray<size_t>& sizes) {
+            nsCOMPtr<nsIMemoryReporterManager> manager =
+              do_GetService("@mozilla.org/memory-reporter-manager;1");
+            if (!manager) {
+              return;
+            }
+
             size_t sum = 0;
             for (const size_t& size : sizes) {
               sum += size;
             }
 
             handleReport->Callback(
               EmptyCString(), NS_LITERAL_CSTRING("explicit/media/recorder"),
               KIND_HEAP, UNITS_BYTES, sum,
               NS_LITERAL_CSTRING("Memory used by media recorder."),
               data);
+
+            manager->EndReport();
           },
           [](size_t) { MOZ_CRASH("Unexpected reject"); });
 
     return NS_OK;
   }
 
 private:
   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
-  virtual ~MediaRecorderReporter();
+
+  virtual ~MediaRecorderReporter()
+  {
+    MOZ_ASSERT(mRecorders.IsEmpty(), "All recorders must have been removed");
+  }
+
   static StaticRefPtr<MediaRecorderReporter> sUniqueInstance;
-  typedef nsTArray<MediaRecorder*> RecordersArray;
-  static RecordersArray& GetRecorders()
-  {
-    return UniqueInstance()->mRecorders;
-  }
-  RecordersArray mRecorders;
+
+  nsTArray<RefPtr<MediaRecorder>> mRecorders;
 };
 NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter);
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRecorder)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRecorder,
                                                   DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStream)
@@ -1745,29 +1760,10 @@ MediaRecorder::SizeOfExcludingThis(mozil
       MOZ_CRASH("Unexpected reject");
     });
 
   return promise;
 }
 
 StaticRefPtr<MediaRecorderReporter> MediaRecorderReporter::sUniqueInstance;
 
-MediaRecorderReporter* MediaRecorderReporter::UniqueInstance()
-{
-  if (!sUniqueInstance) {
-    sUniqueInstance = new MediaRecorderReporter();
-    sUniqueInstance->InitMemoryReporter();
-  }
-  return sUniqueInstance;
- }
-
-void MediaRecorderReporter::InitMemoryReporter()
-{
-  RegisterWeakAsyncMemoryReporter(this);
-}
-
-MediaRecorderReporter::~MediaRecorderReporter()
-{
-  UnregisterWeakMemoryReporter(this);
-}
-
 } // namespace dom
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/test/crashtests/1411322.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1411322: Shutdown after getting memory reports from MediaRecorder</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<audio id="audio"></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+let recorder = new MediaRecorder(audio.mozCaptureStream());
+recorder.start();
+SpecialPowers.getMemoryReports();
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/test/crashtests/crashtests.list
+++ b/dom/media/test/crashtests/crashtests.list
@@ -91,16 +91,17 @@ load 1270303.html
 load 1304948.html
 load 1319486.html
 load 1368490.html
 load 1291702.html
 load 1378826.html
 load 1384248.html
 load 1389304.html
 load 1393272.webm
+load 1411322.html
 load disconnect-wrong-destination.html
 load analyser-channels-1.html
 load audiocontext-double-suspend.html
 load buffer-source-duration-1.html
 load buffer-source-ended-1.html
 load buffer-source-resampling-start-1.html
 load buffer-source-slow-resampling-1.html
 load doppler-1.html
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -2639,18 +2639,18 @@ void nsPluginInstanceOwner::Paint(gfxCon
 
   bool transparent;
   mInstance->IsTransparent(&transparent);
   if (!transparent)
     rendererFlags |= Renderer::DRAW_IS_OPAQUE;
 
   // Renderer::Draw() draws a rectangle with top-left at the aContext origin.
   gfxContextAutoSaveRestore autoSR(aContext);
-  aContext->SetMatrix(
-    aContext->CurrentMatrix().PreTranslate(pluginRect.TopLeft()));
+  aContext->SetMatrixDouble(
+    aContext->CurrentMatrixDouble().PreTranslate(pluginRect.TopLeft()));
 
   Renderer renderer(window, this, pluginSize, pluginDirtyRect);
 
   Display* dpy = mozilla::DefaultXDisplay();
   Screen* screen = DefaultScreenOfDisplay(dpy);
   Visual* visual = DefaultVisualOfScreen(screen);
 
   renderer.Draw(aContext, nsIntSize(window->width, window->height),
--- a/dom/xul/templates/nsXULTemplateQueryProcessorRDF.cpp
+++ b/dom/xul/templates/nsXULTemplateQueryProcessorRDF.cpp
@@ -788,17 +788,17 @@ nsXULTemplateQueryProcessorRDF::OnMove(n
                                        nsIRDFResource* aNewSource,
                                        nsIRDFResource* aProperty,
                                        nsIRDFNode* aTarget)
 {
     // Ignore updates if we're batching
     if (mUpdateBatchNest)
         return NS_OK;
 
-    NS_NOTYETIMPLEMENTED("write me");
+    MOZ_ASSERT_UNREACHABLE("write me");
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 
 NS_IMETHODIMP
 nsXULTemplateQueryProcessorRDF::OnBeginUpdateBatch(nsIRDFDataSource* aDataSource)
 {
     mUpdateBatchNest++;
--- a/gfx/gl/GLContextProviderGLX.cpp
+++ b/gfx/gl/GLContextProviderGLX.cpp
@@ -469,17 +469,17 @@ GLXLibrary::AfterGLXCall() const
             char buffer[2048];
             XGetErrorText(DefaultXDisplay(), sErrorEvent.mError.error_code, buffer, sizeof(buffer));
             printf_stderr("X ERROR: %s (%i) - Request: %i.%i, Serial: %lu",
                           buffer,
                           sErrorEvent.mError.error_code,
                           sErrorEvent.mError.request_code,
                           sErrorEvent.mError.minor_code,
                           sErrorEvent.mError.serial);
-            NS_ABORT();
+            MOZ_ASSERT_UNREACHABLE("AfterGLXCall sErrorEvent");
         }
         XSetErrorHandler(sOldErrorHandler);
     }
 }
 
 already_AddRefed<GLContextGLX>
 GLContextGLX::CreateGLContext(CreateContextFlags flags, const SurfaceCaps& caps,
                               bool isOffscreen, Display* display, GLXDrawable drawable,
@@ -1082,9 +1082,8 @@ GLContextProviderGLX::GetGlobalContext()
 
 /*static*/ void
 GLContextProviderGLX::Shutdown()
 {
 }
 
 } /* namespace gl */
 } /* namespace mozilla */
-
--- a/gfx/layers/SyncObject.cpp
+++ b/gfx/layers/SyncObject.cpp
@@ -36,15 +36,15 @@ SyncObjectClient::CreateSyncObjectClient
 {
   if (!aHandle) {
     return nullptr;
   }
 
 #ifdef XP_WIN
   return MakeAndAddRef<SyncObjectD3D11Client>(aHandle, aDevice);
 #else
-  MOZ_ASSERT_UNREACHABLE();
+  MOZ_ASSERT_UNREACHABLE("CreateSyncObjectClient only supports Windows");
   return nullptr;
 #endif
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/basic/BasicDisplayItemLayer.cpp
+++ b/gfx/layers/basic/BasicDisplayItemLayer.cpp
@@ -55,17 +55,17 @@ public:
   {
     if (IsHidden() || !mItem || !mBuilder) {
       return;
     }
 
     AutoRestoreTransform autoRestoreTransform(aDT);
     Matrix transform = aDT->GetTransform();
     RefPtr<gfxContext> context = gfxContext::CreateOrNull(aDT, aDeviceOffset);
-    context->SetMatrix(ThebesMatrix(transform));
+    context->SetMatrix(transform);
 
     mItem->Paint(mBuilder, context);
   }
 
 protected:
   BasicLayerManager* BasicManager()
   {
     return static_cast<BasicLayerManager*>(mManager);
--- a/gfx/layers/basic/BasicLayerManager.cpp
+++ b/gfx/layers/basic/BasicLayerManager.cpp
@@ -62,18 +62,18 @@ using namespace mozilla::gfx;
  */
 static bool
 ClipToContain(gfxContext* aContext, const IntRect& aRect)
 {
   gfxRect userRect(aRect.x, aRect.y, aRect.Width(), aRect.Height());
   gfxRect deviceRect = aContext->UserToDevice(userRect);
   deviceRect.RoundOut();
 
-  gfxMatrix currentMatrix = aContext->CurrentMatrix();
-  aContext->SetMatrix(gfxMatrix());
+  Matrix currentMatrix = aContext->CurrentMatrix();
+  aContext->SetMatrix(Matrix());
   aContext->NewPath();
   aContext->Rectangle(deviceRect);
   aContext->Clip();
   aContext->SetMatrix(currentMatrix);
 
   return aContext->DeviceToUser(deviceRect).IsEqualInterior(userRect);
 }
 
@@ -125,17 +125,17 @@ BasicLayerManager::PushGroupForLayer(gfx
   Matrix maskTransform;
   RefPtr<SourceSurface> maskSurf = GetMaskForLayer(aLayer, &maskTransform);
 
   if (maskSurf) {
     // The returned transform will transform the mask to device space on the
     // destination. Since the User->Device space transform will be applied
     // to the mask by PopGroupAndBlend we need to adjust the transform to
     // transform the mask to user space.
-    Matrix currentTransform = ToMatrix(aGroupResult.mFinalTarget->CurrentMatrix());
+    Matrix currentTransform = aGroupResult.mFinalTarget->CurrentMatrix();
     currentTransform.Invert();
     maskTransform = maskTransform * currentTransform;
   }
 
   if (aLayer->CanUseOpaqueSurface() &&
       ((didCompleteClip && aRegion.GetNumRects() == 1) ||
        !aContext->CurrentMatrix().HasNonIntegerTranslation())) {
     // If the layer is opaque in its visible region we can push a gfxContentType::COLOR
@@ -244,17 +244,17 @@ public:
     // Will return an identity matrix for 3d transforms.
     return mLayer->GetEffectiveTransformForBuffer().CanDraw2D(&mTransform);
   }
 
   // Applies the effective transform if it's 2D. If it's a 3D transform then
   // it applies an identity.
   void Apply2DTransform()
   {
-    mTarget->SetMatrix(ThebesMatrix(mTransform));
+    mTarget->SetMatrix(mTransform);
   }
 
   // Set the opaque rect to match the bounds of the visible region.
   void AnnotateOpaqueRect()
   {
     const nsIntRegion visibleRegion = mLayer->GetLocalVisibleRegion().ToUnknownRegion();
     const IntRect& bounds = visibleRegion.GetBounds();
 
@@ -586,17 +586,17 @@ BasicLayerManager::EndTransactionInterna
     // Need to do this before we call ApplyDoubleBuffering,
     // which depends on correct effective transforms
     if (mTarget) {
       mSnapEffectiveTransforms =
         !mTarget->GetDrawTarget()->GetUserData(&sDisablePixelSnapping);
     } else {
       mSnapEffectiveTransforms = true;
     }
-    mRoot->ComputeEffectiveTransforms(mTarget ? Matrix4x4::From2D(ToMatrix(mTarget->CurrentMatrix())) : Matrix4x4());
+    mRoot->ComputeEffectiveTransforms(mTarget ? Matrix4x4::From2D(mTarget->CurrentMatrix()) : Matrix4x4());
 
     ToData(mRoot)->Validate(aCallback, aCallbackData, nullptr);
     if (mRoot->GetMaskLayer()) {
       ToData(mRoot->GetMaskLayer())->Validate(aCallback, aCallbackData, nullptr);
     }
   }
 
   if (mTarget && mRoot &&
@@ -783,19 +783,19 @@ InstallLayerClipPreserves3D(gfxContext* 
   Matrix4x4 transform3d =
     parent && parent->Extend3DContext() ?
     parent->GetEffectiveTransform() :
     Matrix4x4();
   Matrix transform;
   if (!transform3d.CanDraw2D(&transform)) {
     gfxDevCrash(LogReason::CannotDraw3D) << "GFX: We should not have a 3D transform that CanDraw2D() is false!";
   }
-  gfxMatrix oldTransform = aTarget->CurrentMatrix();
-  transform *= ToMatrix(oldTransform);
-  aTarget->SetMatrix(ThebesMatrix(transform));
+  Matrix oldTransform = aTarget->CurrentMatrix();
+  transform *= oldTransform;
+  aTarget->SetMatrix(transform);
 
   aTarget->NewPath();
   aTarget->SnappedRectangle(gfxRect(clipRect->x, clipRect->y,
                                     clipRect->Width(), clipRect->Height()));
   aTarget->Clip();
 
   aTarget->SetMatrix(oldTransform);
 }
--- a/gfx/layers/basic/BasicLayersImpl.cpp
+++ b/gfx/layers/basic/BasicLayersImpl.cpp
@@ -59,17 +59,17 @@ GetMaskForLayer(Layer* aLayer, Matrix* a
   return nullptr;
 }
 
 void
 PaintWithMask(gfxContext* aContext, float aOpacity, Layer* aMaskLayer)
 {
   AutoMoz2DMaskData mask;
   if (GetMaskData(aMaskLayer, Point(), &mask)) {
-    aContext->SetMatrix(ThebesMatrix(mask.GetTransform()));
+    aContext->SetMatrix(mask.GetTransform());
     aContext->Mask(mask.GetSurface(), aOpacity);
     return;
   }
 
   // if there is no mask, just paint normally
   aContext->Paint(aOpacity);
 }
 
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -986,17 +986,17 @@ void ClientMultiTiledLayerBuffer::Update
         gfxDevCrash(LogReason::InvalidContext) << "Invalid tiled draw target";
         return;
       }
       drawTarget->SetTransform(Matrix());
 
       RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(drawTarget);
       MOZ_ASSERT(ctx); // already checked the draw target above
       ctx->SetMatrix(
-        ctx->CurrentMatrix().PreScale(mResolution, mResolution).PreTranslate(ThebesPoint(-mTilingOrigin)));
+        ctx->CurrentMatrix().PreScale(mResolution, mResolution).PreTranslate(-mTilingOrigin));
 
       mCallback(&mPaintedLayer, ctx, paintRegion, dirtyRegion,
                 DrawRegionClip::DRAW, nsIntRegion(), mCallbackData);
       mMoz2DTiles.clear();
       // Reset:
       mTilingOrigin = IntPoint(std::numeric_limits<int32_t>::max(),
                                std::numeric_limits<int32_t>::max());
     }
--- a/gfx/src/nsDeviceContext.cpp
+++ b/gfx/src/nsDeviceContext.cpp
@@ -421,17 +421,17 @@ nsDeviceContext::CreateRenderingContextC
       transform.PreTranslate(gfxPoint(0, size.width));
       gfxMatrix rotate(0, -1,
                        1,  0,
                        0,  0);
       transform = rotate * transform;
     }
     transform.PreScale(mPrintingScale, mPrintingScale);
 
-    pContext->SetMatrix(transform);
+    pContext->SetMatrixDouble(transform);
     return pContext.forget();
 }
 
 nsresult
 nsDeviceContext::GetDepth(uint32_t& aDepth)
 {
     nsCOMPtr<nsIScreen> screen;
     FindScreen(getter_AddRefs(screen));
--- a/gfx/thebes/gfxBlur.cpp
+++ b/gfx/thebes/gfxBlur.cpp
@@ -47,17 +47,17 @@ gfxAlphaBoxBlur::Init(gfxContext* aDesti
                    dirtyRect.ptrOr(nullptr), skipRect.ptrOr(nullptr),
                    aUseHardwareAccel);
   if (!dt) {
     return nullptr;
   }
 
   RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
   MOZ_ASSERT(context); // already checked for target above
-  context->SetMatrix(gfxMatrix::Translation(-mBlur.GetRect().TopLeft()));
+  context->SetMatrix(Matrix::Translation(-mBlur.GetRect().TopLeft()));
   return context.forget();
 }
 
 already_AddRefed<DrawTarget>
 gfxAlphaBoxBlur::InitDrawTarget(const DrawTarget* aReferenceDT,
                                 const Rect& aRect,
                                 const IntSize& aSpreadRadius,
                                 const IntSize& aBlurRadius,
@@ -581,17 +581,17 @@ GetBlur(gfxContext* aDestinationCtx,
   // We can get seams using the min size rect when drawing to the destination rect
   // if we have a non-pixel aligned destination transformation. In those cases,
   // fallback to just rendering the destination rect.
   // During printing, we record all the Moz 2d commands and replay them on the parent side
   // with Cairo. Cairo printing uses StretchDIBits to stretch the surface. However,
   // since our source image is only 1px for some parts, we make thousands of calls.
   // Instead just render the blur ourself here as one image and send it over for printing.
   // TODO: May need to change this with the blob renderer in WR since it also records.
-  Matrix destMatrix = ToMatrix(aDestinationCtx->CurrentMatrix());
+  Matrix destMatrix = aDestinationCtx->CurrentMatrix();
   bool useDestRect = !destMatrix.IsRectilinear() || destMatrix.HasNonIntegerTranslation() ||
                      aDestinationCtx->GetDrawTarget()->IsRecording();
   if (useDestRect) {
     minSize = aRectSize;
   }
   aOutMinSize = minSize;
 
   DrawTarget* destDT = aDestinationCtx->GetDrawTarget();
--- a/gfx/thebes/gfxContext.cpp
+++ b/gfx/thebes/gfxContext.cpp
@@ -107,17 +107,17 @@ gfxContext::CreatePreservingTransformOrN
 {
   if (!aTarget || !aTarget->IsValid()) {
     gfxCriticalNote << "Invalid target in gfxContext::CreatePreservingTransformOrNull " << hexa(aTarget);
     return nullptr;
   }
 
   Matrix transform = aTarget->GetTransform();
   RefPtr<gfxContext> result = new gfxContext(aTarget);
-  result->SetMatrix(ThebesMatrix(transform));
+  result->SetMatrix(transform);
   return result.forget();
 }
 
 gfxContext::~gfxContext()
 {
   for (int i = mStateStack.Length() - 1; i >= 0; i--) {
     for (unsigned int c = 0; c < mStateStack[i].pushedClips.Length(); c++) {
       mStateStack[i].drawTarget->PopClip();
@@ -313,26 +313,38 @@ gfxContext::Rectangle(const gfxRect& rec
 void
 gfxContext::Multiply(const gfxMatrix& matrix)
 {
   CURRENTSTATE_CHANGED()
   ChangeTransform(ToMatrix(matrix) * mTransform);
 }
 
 void
-gfxContext::SetMatrix(const gfxMatrix& matrix)
+gfxContext::SetMatrix(const gfx::Matrix& matrix)
 {
   CURRENTSTATE_CHANGED()
-  ChangeTransform(ToMatrix(matrix));
+  ChangeTransform(matrix);
+}
+
+void
+gfxContext::SetMatrixDouble(const gfxMatrix& matrix)
+{
+  SetMatrix(ToMatrix(matrix));
+}
+
+gfx::Matrix
+gfxContext::CurrentMatrix() const
+{
+  return mTransform;
 }
 
 gfxMatrix
-gfxContext::CurrentMatrix() const
+gfxContext::CurrentMatrixDouble() const
 {
-  return ThebesMatrix(mTransform);
+  return ThebesMatrix(CurrentMatrix());
 }
 
 gfxPoint
 gfxContext::DeviceToUser(const gfxPoint& point) const
 {
   return ThebesPoint(mTransform.Inverse().TransformPoint(ToPoint(point)));
 }
 
--- a/gfx/thebes/gfxContext.h
+++ b/gfx/thebes/gfxContext.h
@@ -167,22 +167,24 @@ public:
      * matrix's transformation will take place before the previously set
      * transformations.
      */
     void Multiply(const gfxMatrix& other);
 
     /**
      * Replaces the current transformation matrix with matrix.
      */
-    void SetMatrix(const gfxMatrix& matrix);
+    void SetMatrix(const mozilla::gfx::Matrix& matrix);
+    void SetMatrixDouble(const gfxMatrix& matrix);
 
     /**
      * Returns the current transformation matrix.
      */
-    gfxMatrix CurrentMatrix() const;
+    mozilla::gfx::Matrix CurrentMatrix() const;
+    gfxMatrix CurrentMatrixDouble() const;
 
     /**
      * Converts a point from device to user coordinates using the inverse
      * transformation matrix.
      */
     gfxPoint DeviceToUser(const gfxPoint& point) const;
 
     /**
@@ -623,27 +625,27 @@ public:
     void Restore()
     {
       if (mContext) {
         mContext->SetMatrix(mMatrix);
         mContext = nullptr;
       }
     }
 
-    const gfxMatrix& Matrix()
+    const mozilla::gfx::Matrix& Matrix()
     {
       MOZ_ASSERT(mContext, "mMatrix doesn't contain a useful matrix");
       return mMatrix;
     }
 
     bool HasMatrix() const { return !!mContext; }
 
 private:
     gfxContext *mContext;
-    gfxMatrix   mMatrix;
+    mozilla::gfx::Matrix mMatrix;
 };
 
 
 class DrawTargetAutoDisableSubpixelAntialiasing {
 public:
     typedef mozilla::gfx::DrawTarget DrawTarget;
 
     DrawTargetAutoDisableSubpixelAntialiasing(DrawTarget *aDT, bool aDisable)
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1674,17 +1674,17 @@ private:
                 Pattern *pat;
 
                 RefPtr<gfxPattern> fillPattern;
                 if (mFontParams.contextPaint) {
                   imgDrawingParams imgParams;
                   fillPattern =
                     mFontParams.contextPaint->GetFillPattern(
                                           mRunParams.context->GetDrawTarget(),
-                                          mRunParams.context->CurrentMatrix(),
+                                          mRunParams.context->CurrentMatrixDouble(),
                                           imgParams);
                 }
                 if (!fillPattern) {
                     if (state.pattern) {
                         RefPtr<gfxPattern> statePattern =
                           mRunParams.context->CurrentState().pattern;
                         pat = statePattern->GetPattern(mRunParams.dt,
                                       state.patternTransformChanged ?
@@ -2158,17 +2158,17 @@ gfxFont::Draw(const gfxTextRun *aTextRun
         const Metrics& metrics = GetMetrics(eHorizontal);
         // Get a matrix we can use to draw the (horizontally-shaped) textrun
         // with 90-degree CW rotation.
         const gfxFloat
             rotation = (aOrientation ==
                         gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT)
                        ? -M_PI / 2.0 : M_PI / 2.0;
         gfxMatrix mat =
-            aRunParams.context->CurrentMatrix().
+            aRunParams.context->CurrentMatrixDouble().
             PreTranslate(p).     // translate origin for rotation
             PreRotate(rotation). // turn 90deg CCW (sideways-left) or CW (*-right)
             PreTranslate(-p);    // undo the translation
 
         // If we're drawing rotated horizontal text for an element styled
         // text-orientation:mixed, the dominant baseline will be vertical-
         // centered. So in this case, we need to adjust the position so that
         // the rotated horizontal text (which uses an alphabetic baseline) will
@@ -2177,28 +2177,28 @@ gfxFont::Draw(const gfxTextRun *aTextRun
         // should eventually look for baseline tables[1] in the fonts and use
         // those if available.
         // [1] See http://www.microsoft.com/typography/otspec/base.htm
         if (aTextRun->UseCenterBaseline()) {
             gfxPoint baseAdj(0, (metrics.emAscent - metrics.emDescent) / 2);
             mat.PreTranslate(baseAdj);
         }
 
-        aRunParams.context->SetMatrix(mat);
+        aRunParams.context->SetMatrixDouble(mat);
     }
 
     RefPtr<SVGContextPaint> contextPaint;
     if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) {
         // If no pattern is specified for fill, use the current pattern
         NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0,
                      "no pattern supplied for stroking text");
         RefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern();
         contextPaint =
             new SimpleTextContextPaint(fillPattern, nullptr,
-                                       aRunParams.context->CurrentMatrix());
+                                       aRunParams.context->CurrentMatrixDouble());
         fontParams.contextPaint = contextPaint.get();
     }
 
     // Synthetic-bold strikes are each offset one device pixel in run direction.
     // (these values are only needed if IsSyntheticBold() is true)
     if (IsSyntheticBold()) {
         gfx::Float xscale = CalcXScale(aRunParams.context->GetDrawTarget());
         fontParams.synBoldOnePixelOffset = aRunParams.direction * xscale;
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -433,41 +433,41 @@ PrescaleAndTileDrawable(gfxDrawable* aDr
                         gfxContext* aContext,
                         const ImageRegion& aRegion,
                         Rect aImageRect,
                         const SamplingFilter aSamplingFilter,
                         const SurfaceFormat aFormat,
                         gfxFloat aOpacity,
                         ExtendMode aExtendMode)
 {
-  gfxSize scaleFactor = aContext->CurrentMatrix().ScaleFactors(true);
-  gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleFactor.width, scaleFactor.height);
+  Size scaleFactor = aContext->CurrentMatrix().ScaleFactors(true);
+  Matrix scaleMatrix = Matrix::Scaling(scaleFactor.width, scaleFactor.height);
   const float fuzzFactor = 0.01;
 
   // If we aren't scaling or translating, don't go down this path
   if ((FuzzyEqual(scaleFactor.width, 1.0, fuzzFactor) &&
       FuzzyEqual(scaleFactor.width, 1.0, fuzzFactor)) ||
       aContext->CurrentMatrix().HasNonAxisAlignedTransform()) {
     return false;
   }
 
   gfxRect clipExtents = aContext->GetClipExtents();
 
   // Inflate by one pixel because bilinear filtering will sample at most
   // one pixel beyond the computed image pixel coordinate.
   clipExtents.Inflate(1.0);
 
   gfxRect needed = aRegion.IntersectAndRestrict(clipExtents);
-  Rect scaledNeededRect = ToMatrix(scaleMatrix).TransformBounds(ToRect(needed));
+  Rect scaledNeededRect = scaleMatrix.TransformBounds(ToRect(needed));
   scaledNeededRect.RoundOut();
   if (scaledNeededRect.IsEmpty()) {
     return false;
   }
 
-  Rect scaledImageRect = ToMatrix(scaleMatrix).TransformBounds(aImageRect);
+  Rect scaledImageRect = scaleMatrix.TransformBounds(aImageRect);
   if (!ShouldUseTempSurface(scaledImageRect, scaledNeededRect)) {
     return false;
   }
 
   IntSize scaledImageSize((int32_t)scaledImageRect.width,
                           (int32_t)scaledImageRect.height);
   if (scaledImageSize.width != scaledImageRect.width ||
       scaledImageSize.height != scaledImageRect.height) {
@@ -480,32 +480,32 @@ PrescaleAndTileDrawable(gfxDrawable* aDr
     gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(scaledImageSize, aFormat);
   if (!scaledDT || !scaledDT->IsValid()) {
     return false;
   }
 
   RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(scaledDT);
   MOZ_ASSERT(tmpCtx); // already checked the target above
 
-  scaledDT->SetTransform(ToMatrix(scaleMatrix));
+  scaledDT->SetTransform(scaleMatrix);
   gfxRect gfxImageRect(aImageRect.x, aImageRect.y, aImageRect.width, aImageRect.height);
 
   // Since this is just the scaled image, we don't want to repeat anything yet.
   aDrawable->Draw(tmpCtx, gfxImageRect, ExtendMode::CLAMP, aSamplingFilter, 1.0, gfxMatrix());
 
   RefPtr<SourceSurface> scaledImage = scaledDT->Snapshot();
 
   {
     gfxContextMatrixAutoSaveRestore autoSR(aContext);
-    Matrix withoutScale = ToMatrix(aContext->CurrentMatrix());
+    Matrix withoutScale = aContext->CurrentMatrix();
     DrawTarget* destDrawTarget = aContext->GetDrawTarget();
 
     // The translation still is in scaled units
     withoutScale.PreScale(1.0 / scaleFactor.width, 1.0 / scaleFactor.height);
-    aContext->SetMatrix(ThebesMatrix(withoutScale));
+    aContext->SetMatrix(withoutScale);
 
     DrawOptions drawOptions(aOpacity, aContext->CurrentOp(),
                             aContext->CurrentAntialiasMode());
 
     SurfacePattern scaledImagePattern(scaledImage, aExtendMode,
                                       Matrix(), aSamplingFilter);
     destDrawTarget->FillRect(scaledNeededRect, scaledImagePattern, drawOptions);
   }
--- a/gfx/thebes/gfxWindowsNativeDrawing.cpp
+++ b/gfx/thebes/gfxWindowsNativeDrawing.cpp
@@ -63,17 +63,17 @@ gfxWindowsNativeDrawing::BeginNativeDraw
                     surf = gfxASurface::Wrap(s);
                 }
             }
         }
 
         if (surf && surf->CairoStatus() != 0)
             return nullptr;
 
-        gfxMatrix m = mContext->CurrentMatrix();
+        gfxMatrix m = mContext->CurrentMatrixDouble();
         if (!m.HasNonTranslation())
             mTransformType = TRANSLATION_ONLY;
         else if (m.HasNonAxisAlignedTransform())
             mTransformType = COMPLEX;
         else
             mTransformType = AXIS_ALIGNED_SCALE;
 
         // if this is a native win32 surface, we don't have to
--- a/gfx/thebes/gfxXlibNativeRenderer.cpp
+++ b/gfx/thebes/gfxXlibNativeRenderer.cpp
@@ -462,17 +462,17 @@ CopyXlibSurfaceToImage(cairo_surface_t *
 
     return result.forget();
 }
 
 void
 gfxXlibNativeRenderer::Draw(gfxContext* ctx, IntSize size,
                             uint32_t flags, Screen *screen, Visual *visual)
 {
-    gfxMatrix matrix = ctx->CurrentMatrix();
+    Matrix matrix = ctx->CurrentMatrix();
 
     // We can only draw direct or onto a copied background if pixels align and
     // native drawing is compatible with the current operator.  (The matrix is
     // actually also pixel-exact for flips and right-angle rotations, which
     // would permit copying the background but not drawing direct.)
     bool matrixIsIntegerTranslation = !matrix.HasNonIntegerTranslation();
     bool canDrawOverBackground = matrixIsIntegerTranslation &&
          ctx->CurrentOp() == CompositionOp::OP_OVER;
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -297,20 +297,20 @@ SVGDrawingCallback::operator()(gfxContex
   aContext->NewPath();
   aContext->Rectangle(aFillRect);
   aContext->Clip();
 
   gfxMatrix matrix = aTransform;
   if (!matrix.Invert()) {
     return false;
   }
-  aContext->SetMatrix(
-    aContext->CurrentMatrix().PreMultiply(matrix).
-                              PreScale(double(mSize.width) / mViewportSize.width,
-                                       double(mSize.height) / mViewportSize.height));
+  aContext->SetMatrixDouble(
+    aContext->CurrentMatrixDouble().PreMultiply(matrix).
+                                    PreScale(double(mSize.width) / mViewportSize.width,
+                                             double(mSize.height) / mViewportSize.height));
 
   nsPresContext* presContext = presShell->GetPresContext();
   MOZ_ASSERT(presContext, "pres shell w/out pres context");
 
   nsRect svgRect(0, 0,
                  presContext->DevPixelsToAppUnits(mViewportSize.width),
                  presContext->DevPixelsToAppUnits(mViewportSize.height));
 
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -945,18 +945,17 @@ GeckoChildProcessHost::PerformAsyncLaunc
   bool shouldSandboxCurrentProcess = false;
 
   // XXX: Bug 1124167: We should get rid of the process specific logic for
   // sandboxing in this class at some point. Unfortunately it will take a bit
   // of reorganizing so I don't think this patch is the right time.
   switch (mProcessType) {
     case GeckoProcessType_Content:
 #  if defined(MOZ_CONTENT_SANDBOX)
-      if (mSandboxLevel > 0 &&
-          !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) {
+      if (mSandboxLevel > 0) {
         // For now we treat every failure as fatal in SetSecurityLevelForContentProcess
         // and just crash there right away. Should this change in the future then we
         // should also handle the error here.
         mSandboxBroker.SetSecurityLevelForContentProcess(mSandboxLevel,
                                                          mIsFileContent);
         shouldSandboxCurrentProcess = true;
       }
 #  endif // defined(MOZ_CONTENT_SANDBOX)
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -4680,20 +4680,20 @@ PresShell::RenderDocument(const nsRect& 
   gfxPoint offset(-nsPresContext::AppUnitsToFloatCSSPixels(aRect.x),
                   -nsPresContext::AppUnitsToFloatCSSPixels(aRect.y));
   gfxFloat scale = gfxFloat(devCtx->AppUnitsPerDevPixel())/nsPresContext::AppUnitsPerCSSPixel();
 
   // Since canvas APIs use floats to set up their matrices, we may have some
   // slight rounding errors here.  We use NudgeToIntegers() here to adjust
   // matrix components that are integers up to the accuracy of floats to be
   // those integers.
-  gfxMatrix newTM = aThebesContext->CurrentMatrix().PreTranslate(offset).
-                                                    PreScale(scale, scale).
-                                                    NudgeToIntegers();
-  aThebesContext->SetMatrix(newTM);
+  gfxMatrix newTM = aThebesContext->CurrentMatrixDouble().PreTranslate(offset).
+                                                          PreScale(scale, scale).
+                                                          NudgeToIntegers();
+  aThebesContext->SetMatrixDouble(newTM);
 
   AutoSaveRestoreRenderingState _(this);
 
   bool wouldFlushRetainedLayers = false;
   PaintFrameFlags flags = PaintFrameFlags::PAINT_IGNORE_SUPPRESSION;
   if (aThebesContext->CurrentMatrix().HasNonIntegerTranslation()) {
     flags |= PaintFrameFlags::PAINT_IN_TRANSFORM;
   }
@@ -5083,17 +5083,17 @@ PresShell::PaintRangePaintInfo(const nsT
       builder->LineTo(rect.BottomLeft());
       builder->LineTo(rect.TopLeft());
     }
 
     RefPtr<Path> path = builder->Finish();
     ctx->Clip(path);
   }
 
-  gfxMatrix initialTM = ctx->CurrentMatrix();
+  gfxMatrix initialTM = ctx->CurrentMatrixDouble();
 
   if (resize)
     initialTM.PreScale(scale, scale);
 
   // translate so that points are relative to the surface area
   gfxPoint surfaceOffset =
     nsLayoutUtils::PointToGfxPoint(-aArea.TopLeft(), pc->AppUnitsPerDevPixel());
   initialTM.PreTranslate(surfaceOffset);
@@ -5113,17 +5113,17 @@ PresShell::PaintRangePaintInfo(const nsT
 
   // next, paint each range in the selection
   for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) {
     // the display lists paint relative to the offset from the reference
     // frame, so account for that translation too:
     gfxPoint rootOffset =
       nsLayoutUtils::PointToGfxPoint(rangeInfo->mRootOffset,
                                      pc->AppUnitsPerDevPixel());
-    ctx->SetMatrix(gfxMatrix(initialTM).PreTranslate(rootOffset));
+    ctx->SetMatrixDouble(initialTM.PreTranslate(rootOffset));
     aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y);
     nsRegion visible(aArea);
     RefPtr<LayerManager> layerManager =
         rangeInfo->mList.PaintRoot(&rangeInfo->mBuilder, ctx,
                                    nsDisplayList::PAINT_DEFAULT);
     aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y);
   }
 
@@ -10139,18 +10139,18 @@ void ReflowCountMgr::PaintCount(const ch
       (IndiReflowCounter *)PL_HashTableLookup(mIndiFrameCounts, key);
     if (counter != nullptr && counter->mName.EqualsASCII(aName)) {
       DrawTarget* drawTarget = aRenderingContext->GetDrawTarget();
       int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
 
       aRenderingContext->Save();
       gfxPoint devPixelOffset =
         nsLayoutUtils::PointToGfxPoint(aOffset, appUnitsPerDevPixel);
-      aRenderingContext->SetMatrix(
-        aRenderingContext->CurrentMatrix().PreTranslate(devPixelOffset));
+      aRenderingContext->SetMatrixDouble(
+        aRenderingContext->CurrentMatrixDouble().PreTranslate(devPixelOffset));
 
       // We don't care about the document language or user fonts here;
       // just get a default Latin font.
       nsFont font(eFamily_serif, nsPresContext::CSSPixelsToAppUnits(11));
       nsFontMetrics::Params params;
       params.language = nsGkAtoms::x_western;
       params.textPerf = aPresContext->GetTextPerfMetrics();
       RefPtr<nsFontMetrics> fm =
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -1513,17 +1513,17 @@ nsFrameConstructorState::ProcessFrameIns
           break;
         }
       }
       mFrameManager->InsertFrames(containingBlock, aChildListID,
                                   insertionPoint, aFrameItems);
     }
   }
 
-  NS_POSTCONDITION(aFrameItems.IsEmpty(), "How did that happen?");
+  MOZ_ASSERT(aFrameItems.IsEmpty(), "How did that happen?");
 }
 
 
 nsFrameConstructorSaveState::nsFrameConstructorSaveState()
   : mItems(nullptr),
     mSavedItems(nullptr),
     mChildListID(kPrincipalList),
     mState(nullptr),
@@ -9369,17 +9369,17 @@ nsCSSFrameConstructor::CreateContinuingF
   if (nextInFlow) {
     nextInFlow->SetPrevInFlow(newFrame);
     newFrame->SetNextInFlow(nextInFlow);
   } else if (nextContinuation) {
     nextContinuation->SetPrevContinuation(newFrame);
     newFrame->SetNextContinuation(nextContinuation);
   }
 
-  NS_POSTCONDITION(!newFrame->GetNextSibling(), "unexpected sibling");
+  MOZ_ASSERT(!newFrame->GetNextSibling(), "unexpected sibling");
   return newFrame;
 }
 
 nsresult
 nsCSSFrameConstructor::ReplicateFixedFrames(nsPageContentFrame* aParentFrame)
 {
   // Now deal with fixed-pos things....  They should appear on all pages,
   // so we want to move over the placeholders when processing the child
@@ -13202,32 +13202,32 @@ Iterator::AppendItemsToList(nsCSSFrameCo
   // Swap out undisplayed item arrays, before we nuke the array on our end
   aTargetList.mUndisplayedItems.SwapElements(mList.mUndisplayedItems);
 
   // reset mList
   mList.Reset(aFCtor);
 
   // Point ourselves to aEnd, as advertised
   SetToEnd();
-  NS_POSTCONDITION(*this == aEnd, "How did that happen?");
+  MOZ_ASSERT(*this == aEnd, "How did that happen?");
 }
 
 void
 nsCSSFrameConstructor::FrameConstructionItemList::
 Iterator::InsertItem(FrameConstructionItem* aItem)
 {
   if (IsDone()) {
     mList.mItems.insertBack(aItem);
   } else {
     // Just insert the item before us.  There's no magic here.
     mCurrent->setPrevious(aItem);
   }
   mList.AdjustCountsForItem(aItem, 1);
 
-  NS_POSTCONDITION(aItem->getNext() == mCurrent, "How did that happen?");
+  MOZ_ASSERT(aItem->getNext() == mCurrent, "How did that happen?");
 }
 
 void
 nsCSSFrameConstructor::FrameConstructionItemList::
 Iterator::DeleteItemsTo(nsCSSFrameConstructor* aFCtor, const Iterator& aEnd)
 {
   NS_PRECONDITION(&mList == &aEnd.mList, "End iterator for some other list?");
   NS_PRECONDITION(*this != aEnd, "Shouldn't be at aEnd yet");
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1622,17 +1622,17 @@ nsLayoutUtils::GetChildListNameFor(nsIFr
 #ifdef DEBUG
   // Verify that the frame is actually in that child list or in the
   // corresponding overflow list.
   nsContainerFrame* parent = aChildFrame->GetParent();
   bool found = parent->GetChildList(id).ContainsFrame(aChildFrame);
   if (!found) {
     found = parent->GetChildList(nsIFrame::kOverflowList)
               .ContainsFrame(aChildFrame);
-    NS_POSTCONDITION(found, "not in child list");
+    MOZ_ASSERT(found, "not in child list");
   }
 #endif
 
   return id;
 }
 
 static Element*
 GetPseudo(const nsIContent* aContent, nsAtom* aPseudoProperty)
@@ -3723,18 +3723,18 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
       // Make visibleRegion and aRenderingContext relative to the
       // scrolled frame instead of the root frame.
       nsPoint pos = rootScrollableFrame->GetScrollPosition();
       visibleRegion.MoveBy(-pos);
       if (aRenderingContext) {
         gfxPoint devPixelOffset =
           nsLayoutUtils::PointToGfxPoint(pos,
                                          presContext->AppUnitsPerDevPixel());
-        aRenderingContext->SetMatrix(
-          aRenderingContext->CurrentMatrix().PreTranslate(devPixelOffset));
+        aRenderingContext->SetMatrixDouble(
+          aRenderingContext->CurrentMatrixDouble().PreTranslate(devPixelOffset));
       }
     }
     builder.SetIgnoreScrollFrame(rootScrollFrame);
 
     nsCanvasFrame* canvasFrame =
       do_QueryFrame(rootScrollableFrame->GetScrolledFrame());
     if (canvasFrame) {
       // Use UnionRect here to ensure that areas where the scrollbars
@@ -6669,17 +6669,17 @@ ComputeSnappedImageDrawingParameters(gfx
 
   gfxRect devPixelDest =
     nsLayoutUtils::RectToGfxRect(appUnitDest, aAppUnitsPerDevPixel);
   gfxRect devPixelFill =
     nsLayoutUtils::RectToGfxRect(aFill, aAppUnitsPerDevPixel);
   gfxRect devPixelDirty =
     nsLayoutUtils::RectToGfxRect(aDirty, aAppUnitsPerDevPixel);
 
-  gfxMatrix currentMatrix = aCtx->CurrentMatrix();
+  gfxMatrix currentMatrix = aCtx->CurrentMatrixDouble();
   gfxRect fill = devPixelFill;
   gfxRect dest = devPixelDest;
   bool didSnap;
   // Snap even if we have a scale in the context. But don't snap if
   // we have something that's not translation+scale, or if the scale flips in
   // the X or Y direction, because snapped image drawing can't handle that yet.
   if (!currentMatrix.HasNonAxisAlignedTransform() &&
       currentMatrix._11 > 0.0 && currentMatrix._22 > 0.0 &&
@@ -6896,17 +6896,17 @@ DrawImageInternal(gfxContext&           
 
   if (!params.shouldDraw) {
     return result;
   }
 
   {
     gfxContextMatrixAutoSaveRestore contextMatrixRestorer(&aContext);
 
-    aContext.SetMatrix(params.imageSpaceToDeviceSpace);
+    aContext.SetMatrixDouble(params.imageSpaceToDeviceSpace);
 
     Maybe<SVGImageContext> fallbackContext;
     if (!aSVGContext) {
       // Use the default viewport.
       fallbackContext.emplace(Some(params.svgViewportSize));
     }
 
     result = aImage->Draw(&aContext, params.size, params.region,
--- a/layout/forms/nsListControlFrame.cpp
+++ b/layout/forms/nsListControlFrame.cpp
@@ -2559,11 +2559,11 @@ nsListEventListener::HandleEvent(nsIDOME
     return mFrame->nsListControlFrame::MouseUp(aEvent);
   }
   if (eventType.EqualsLiteral("mousemove")) {
     // I don't think we want to honor defaultPrevented on mousemove
     // in general, and it would only prevent highlighting here.
     return mFrame->nsListControlFrame::MouseMove(aEvent);
   }
 
-  NS_ABORT();
+  MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
   return NS_OK;
 }
--- a/layout/generic/nsContainerFrame.cpp
+++ b/layout/generic/nsContainerFrame.cpp
@@ -1310,17 +1310,17 @@ nsContainerFrame::StealFrame(nsIFrame* a
         removed = frameList->ContinueRemoveFrame(aChild);
         if (frameList->IsEmpty()) {
           DestroyOverflowList();
         }
       }
     }
   }
 
-  NS_POSTCONDITION(removed, "StealFrame: can't find aChild");
+  MOZ_ASSERT(removed, "StealFrame: can't find aChild");
   return removed ? NS_OK : NS_ERROR_UNEXPECTED;
 }
 
 nsFrameList
 nsContainerFrame::StealFramesAfter(nsIFrame* aChild)
 {
   NS_ASSERTION(!aChild ||
                !(aChild->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER),
@@ -1425,17 +1425,17 @@ nsContainerFrame::DeleteNextInFlowChild(
     nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow);
   }
 #endif
 
   // Delete the next-in-flow frame and its descendants. This will also
   // remove it from its next-in-flow/prev-in-flow chain.
   aNextInFlow->Destroy();
 
-  NS_POSTCONDITION(!prevInFlow->GetNextInFlow(), "non null next-in-flow");
+  MOZ_ASSERT(!prevInFlow->GetNextInFlow(), "non null next-in-flow");
 }
 
 /**
  * Set the frames on the overflow list
  */
 void
 nsContainerFrame::SetOverflowFrames(const nsFrameList& aOverflowFrames)
 {
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -6521,17 +6521,18 @@ nsIFrame* nsIFrame::GetTailContinuation(
     frame = frame->GetPrevContinuation();
     NS_ASSERTION(frame, "first continuation can't be overflow container");
   }
   for (nsIFrame* next = frame->GetNextContinuation();
        next && !(next->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER);
        next = frame->GetNextContinuation())  {
     frame = next;
   }
-  NS_POSTCONDITION(frame, "illegal state in continuation chain.");
+
+  MOZ_ASSERT(frame, "illegal state in continuation chain.");
   return frame;
 }
 
 // Associated view object
 void
 nsIFrame::SetView(nsView* aView)
 {
   if (aView) {
--- a/layout/generic/nsFrameList.cpp
+++ b/layout/generic/nsFrameList.cpp
@@ -252,17 +252,17 @@ nsFrameList::ExtractTail(FrameLinkEnumer
     newFirstFrame = mFirstChild;
     newLastFrame = mLastChild;
     Clear();
   }
 
   // Now make sure aLink doesn't point to a frame we no longer have.
   aLink.mFrame = nullptr;
 
-  NS_POSTCONDITION(aLink.AtEnd(), "What's going on here?");
+  MOZ_ASSERT(aLink.AtEnd(), "What's going on here?");
 
   return nsFrameList(newFirstFrame, newLastFrame);
 }
 
 nsIFrame*
 nsFrameList::FrameAt(int32_t aIndex) const
 {
   NS_PRECONDITION(aIndex >= 0, "invalid arg");
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -34,16 +34,17 @@
 #include "nsLayoutUtils.h"
 #include "nsBidiPresUtils.h"
 #include "nsBidiUtils.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
 #include "mozilla/dom/HTMLTextAreaElement.h"
 #include <stdint.h>
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Telemetry.h"
 #include "FrameLayerBuilder.h"
 #include "nsSMILKeySpline.h"
 #include "nsSubDocumentFrame.h"
 #include "nsSVGOuterSVGFrame.h"
@@ -2180,16 +2181,17 @@ ScrollFrameHelper::CompleteAsyncScroll(c
   AutoWeakFrame weakFrame(mOuter);
   ScrollToImpl(mDestination, aRange, aOrigin);
   if (!weakFrame.IsAlive()) {
     return;
   }
   // We are done scrolling, set our destination to wherever we actually ended
   // up scrolling to.
   mDestination = GetScrollPosition();
+  FireScrollEndEvent();
 }
 
 bool
 ScrollFrameHelper::HasPluginFrames()
 {
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   if (XRE_IsContentProcess()) {
     nsPresContext* presContext = mOuter->PresContext();
@@ -4404,16 +4406,27 @@ ScrollFrameHelper::FireScrollPortEvent()
                                                       mVerticalOverflow) ?
     eScrollPortOverflow : eScrollPortUnderflow, nullptr);
   event.mOrient = orient;
   return EventDispatcher::Dispatch(mOuter->GetContent(),
                                    mOuter->PresContext(), &event);
 }
 
 void
+ScrollFrameHelper::FireScrollEndEvent()
+{
+  MOZ_ASSERT(mOuter->GetContent());
+  nsContentUtils::DispatchEventOnlyToChrome(mOuter->GetContent()->OwnerDoc(),
+                                            mOuter->GetContent(),
+                                            NS_LITERAL_STRING("scrollend"),
+                                            true /* aCanBubble */,
+                                            false /* aCancelable */);
+}
+
+void
 ScrollFrameHelper::ReloadChildFrames()
 {
   mScrolledFrame = nullptr;
   mHScrollbarBox = nullptr;
   mVScrollbarBox = nullptr;
   mScrollCornerBox = nullptr;
   mResizerBox = nullptr;
 
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -64,16 +64,17 @@ public:
   // reload our child frame list.
   // We need this if a scrollbar frame is recreated.
   void ReloadChildFrames();
 
   nsresult CreateAnonymousContent(
     nsTArray<nsIAnonymousContentCreator::ContentInfo>& aElements);
   void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, uint32_t aFilter);
   nsresult FireScrollPortEvent();
+  void FireScrollEndEvent();
   void PostOverflowEvent();
   using PostDestroyData = nsIFrame::PostDestroyData;
   void Destroy(PostDestroyData& aPostDestroyData);
 
   void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                         const nsDisplayListSet& aLists);
 
   void AppendScrollPartsTo(nsDisplayListBuilder*   aBuilder,
@@ -395,16 +396,19 @@ public:
   bool ShouldClampScrollPosition() const;
 
   bool IsAlwaysActive() const;
   void MarkRecentlyScrolled();
   void MarkNotRecentlyScrolled();
   nsExpirationState* GetExpirationState() { return &mActivityExpirationState; }
 
   void SetTransformingByAPZ(bool aTransforming) {
+    if (mTransformingByAPZ && !aTransforming) {
+      FireScrollEndEvent();
+    }
     mTransformingByAPZ = aTransforming;
     if (!mozilla::css::TextOverflow::HasClippedOverflow(mOuter)) {
       // If the block has some text-overflow stuff we should kick off a paint
       // because we have special behaviour for it when APZ scrolling is active.
       mOuter->SchedulePaint();
     }
   }
   bool IsTransformingByAPZ() const {
--- a/layout/generic/nsPluginFrame.cpp
+++ b/layout/generic/nsPluginFrame.cpp
@@ -866,17 +866,17 @@ nsPluginFrame::PaintPrintPlugin(nsIFrame
                                 const nsRect& aDirtyRect, nsPoint aPt)
 {
   // Translate the context:
   nsPoint pt = aPt + aFrame->GetContentRectRelativeToSelf().TopLeft();
   gfxPoint devPixelPt =
     nsLayoutUtils::PointToGfxPoint(pt, aFrame->PresContext()->AppUnitsPerDevPixel());
 
   gfxContextMatrixAutoSaveRestore autoSR(aCtx);
-  aCtx->SetMatrix(aCtx->CurrentMatrix().PreTranslate(devPixelPt));
+  aCtx->SetMatrixDouble(aCtx->CurrentMatrixDouble().PreTranslate(devPixelPt));
 
   // FIXME - Bug 385435: Doesn't aDirtyRect need translating too?
 
   static_cast<nsPluginFrame*>(aFrame)->PrintPlugin(*aCtx, aDirtyRect);
 }
 
 /**
  * nsDisplayPluginReadback creates an active ReadbackLayer. The ReadbackLayer
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -601,17 +601,18 @@ ClearAllTextRunReferences(nsTextFrame* a
   bool found = aStartContinuation == aFrame;
   while (aFrame) {
     NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
     if (!aFrame->RemoveTextRun(aTextRun)) {
       break;
     }
     aFrame = aFrame->GetNextContinuation();
   }
-  NS_POSTCONDITION(!found || aStartContinuation, "how did we find null?");
+
+  MOZ_ASSERT(!found || aStartContinuation, "how did we find null?");
   return found;
 }
 
 /**
  * Kill all references to |aTextRun| starting at |aStartContinuation|.
  * It could be referenced by any of its owners, and all their in-flows.
  * If |aStartContinuation| is null then process all userdata frames
  * and their continuations.
@@ -5185,19 +5186,19 @@ nsDisplayText::RenderToContext(gfxContex
   if (f->StyleContext()->IsTextCombined()) {
     float scaleFactor = GetTextCombineScaleFactor(f);
     if (scaleFactor != 1.0f) {
       matrixSR.SetContext(aCtx);
       // Setup matrix to compress text for text-combine-upright if
       // necessary. This is done here because we want selection be
       // compressed at the same time as text.
       gfxPoint pt = nsLayoutUtils::PointToGfxPoint(framePt, A2D);
-      gfxMatrix mat = aCtx->CurrentMatrix()
+      gfxMatrix mat = aCtx->CurrentMatrixDouble()
         .PreTranslate(pt).PreScale(scaleFactor, 1.0).PreTranslate(-pt);
-      aCtx->SetMatrix (mat);
+      aCtx->SetMatrixDouble(mat);
     }
   }
   nsTextFrame::PaintTextParams params(aCtx);
   params.framePt = gfx::Point(framePt.x, framePt.y);
   params.dirtyRect = extraVisible;
 
   if (aBuilder->IsForGenerateGlyphMask()) {
     MOZ_ASSERT(!aBuilder->IsForPaintingSelectionBG());
@@ -7235,20 +7236,20 @@ nsTextFrame::DrawTextRunAndDecorations(R
     // The matrix of the context may have been altered for text-combine-
     // upright. However, we want to draw decoration lines unscaled, thus
     // we need to revert the scaling here.
     gfxContextMatrixAutoSaveRestore scaledRestorer;
     if (StyleContext()->IsTextCombined()) {
       float scaleFactor = GetTextCombineScaleFactor(this);
       if (scaleFactor != 1.0f) {
         scaledRestorer.SetContext(aParams.context);
-        gfxMatrix unscaled = aParams.context->CurrentMatrix();
+        gfxMatrix unscaled = aParams.context->CurrentMatrixDouble();
         gfxPoint pt(x / app, y / app);
         unscaled.PreTranslate(pt).PreScale(1.0f / scaleFactor, 1.0f).PreTranslate(-pt);
-        aParams.context->SetMatrix(unscaled);
+        aParams.context->SetMatrixDouble(unscaled);
       }
     }
 
     typedef gfxFont::Metrics Metrics;
     auto paintDecorationLine = [&](const LineDecoration& dec,
                                    gfxFloat Metrics::* lineSize,
                                    gfxFloat Metrics::* lineOffset) {
       if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
@@ -9093,19 +9094,21 @@ nsTextFrame::SetLength(int32_t aLength, 
         framesToRemove = f;
       }
     } else if (framesToRemove) {
       RemoveEmptyInFlows(framesToRemove, f);
       framesToRemove = nullptr;
     }
     f = next;
   }
-  NS_POSTCONDITION(!framesToRemove || (f && f->mContentOffset == end),
-                   "How did we exit the loop if we null out framesToRemove if "
-                   "!next || next->mContentOffset > end ?");
+
+  MOZ_ASSERT(!framesToRemove || (f && f->mContentOffset == end),
+             "How did we exit the loop if we null out framesToRemove if "
+             "!next || next->mContentOffset > end ?");
+
   if (framesToRemove) {
     // We are guaranteed that we exited the loop with f not null, per the
     // postcondition above
     RemoveEmptyInFlows(framesToRemove, f);
   }
 
 #ifdef DEBUG
   f = this;
--- a/layout/mathml/nsMathMLChar.cpp
+++ b/layout/mathml/nsMathMLChar.cpp
@@ -2020,26 +2020,26 @@ nsMathMLChar::ApplyTransforms(gfxContext
                               int32_t aAppUnitsPerGfxUnit,
                               nsRect &r)
 {
   // apply the transforms
   if (mMirrored) {
     nsPoint pt = r.TopRight();
     gfxPoint devPixelOffset(NSAppUnitsToFloatPixels(pt.x, aAppUnitsPerGfxUnit),
                             NSAppUnitsToFloatPixels(pt.y, aAppUnitsPerGfxUnit));
-    aThebesContext->SetMatrix(
-      aThebesContext->CurrentMatrix().PreTranslate(devPixelOffset).
-                                      PreScale(-mScaleX, mScaleY));
+    aThebesContext->SetMatrixDouble(
+      aThebesContext->CurrentMatrixDouble().PreTranslate(devPixelOffset).
+                                            PreScale(-mScaleX, mScaleY));
   } else {
     nsPoint pt = r.TopLeft();
     gfxPoint devPixelOffset(NSAppUnitsToFloatPixels(pt.x, aAppUnitsPerGfxUnit),
                             NSAppUnitsToFloatPixels(pt.y, aAppUnitsPerGfxUnit));
-    aThebesContext->SetMatrix(
-      aThebesContext->CurrentMatrix().PreTranslate(devPixelOffset).
-                                      PreScale(mScaleX, mScaleY));
+    aThebesContext->SetMatrixDouble(
+      aThebesContext->CurrentMatrixDouble().PreTranslate(devPixelOffset).
+                                            PreScale(mScaleX, mScaleY));
   }
 
   // update the bounding rectangle.
   r.x = r.y = 0;
   r.width /= mScaleX;
   r.height /= mScaleY;
 }
 
--- a/layout/mathml/nsMathMLmactionFrame.cpp
+++ b/layout/mathml/nsMathMLmactionFrame.cpp
@@ -269,17 +269,17 @@ nsMathMLmactionFrame::MouseListener::Han
   }
   else if (eventType.EqualsLiteral("click")) {
     mOwner->MouseClick();
   }
   else if (eventType.EqualsLiteral("mouseout")) {
     mOwner->MouseOut();
   }
   else {
-    NS_ABORT();
+    MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
   }
 
   return NS_OK;
 }
 
 void
 nsMathMLmactionFrame::MouseOver()
 {
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -3679,18 +3679,18 @@ PaintInactiveLayer(nsDisplayListBuilder*
                                       SurfaceFormat::B8G8R8A8);
     if (tempDT) {
       context = gfxContext::CreateOrNull(tempDT);
       if (!context) {
         // Leave this as crash, it's in the debugging code, we want to know
         gfxDevCrash(LogReason::InvalidContext) << "PaintInactive context problem " << gfx::hexa(tempDT);
         return;
       }
-      context->SetMatrix(gfxMatrix::Translation(-itemVisibleRect.x,
-                                                -itemVisibleRect.y));
+      context->SetMatrix(Matrix::Translation(-itemVisibleRect.x,
+                                             -itemVisibleRect.y));
     }
   }
 #endif
   basic->BeginTransaction();
   basic->SetTarget(context);
 
   if (aItem->GetType() == DisplayItemType::TYPE_MASK) {
     static_cast<nsDisplayMask*>(aItem)->PaintAsLayer(aBuilder, aCtx, basic);
@@ -3909,17 +3909,17 @@ ContainerState::SetupMaskLayerForCSSMask
   MaskImageData imageData(surfaceSize, mManager);
   RefPtr<DrawTarget> dt = imageData.CreateDrawTarget();
   if (!dt || !dt->IsValid()) {
     NS_WARNING("Could not create DrawTarget for mask layer.");
     return;
   }
 
   RefPtr<gfxContext> maskCtx = gfxContext::CreateOrNull(dt);
-  maskCtx->SetMatrix(gfxMatrix::Translation(-itemRect.TopLeft()));
+  maskCtx->SetMatrix(Matrix::Translation(-itemRect.TopLeft()));
   maskCtx->Multiply(gfxMatrix::Scaling(mParameters.mXScale, mParameters.mYScale));
 
   bool isPaintFinished = aMaskItem->PaintMask(mBuilder, maskCtx);
 
   RefPtr<ImageContainer> imgContainer =
     imageData.CreateImageAndImageContainer();
   if (!imgContainer) {
     return;
@@ -5884,17 +5884,17 @@ static void DebugPaintItem(DrawTarget& a
     aDrawTarget.CreateSimilarDrawTarget(IntSize::Truncate(bounds.width, bounds.height),
                                         SurfaceFormat::B8G8R8A8);
   RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempDT);
   if (!context) {
     // Leave this as crash, it's in the debugging code, we want to know
     gfxDevCrash(LogReason::InvalidContext) << "DebugPaintItem context problem " << gfx::hexa(tempDT);
     return;
   }
-  context->SetMatrix(gfxMatrix::Translation(-bounds.x, -bounds.y));
+  context->SetMatrix(Matrix::Translation(-bounds.x, -bounds.y));
 
   aItem->Paint(aBuilder, context);
   RefPtr<SourceSurface> surface = tempDT->Snapshot();
   DumpPaintedImage(aItem, surface);
 
   aDrawTarget.DrawSurface(surface, bounds, Rect(Point(0,0), bounds.Size()));
 
   aItem->SetPainted();
@@ -6172,35 +6172,35 @@ FrameLayerBuilder::DrawPaintedLayer(Pain
       aContext->Clip();
 
       DrawForcedBackgroundColor(aDrawTarget, iterRect,
                                 userData->mForcedBackgroundColor);
 
       // Apply the residual transform if it has been enabled, to ensure that
       // snapping when we draw into aContext exactly matches the ideal transform.
       // See above for why this is OK.
-      aContext->SetMatrix(
-        aContext->CurrentMatrix().PreTranslate(aLayer->GetResidualTranslation() - gfxPoint(offset.x, offset.y)).
-                                  PreScale(userData->mXScale, userData->mYScale));
+      aContext->SetMatrixDouble(
+        aContext->CurrentMatrixDouble().PreTranslate(aLayer->GetResidualTranslation() - gfxPoint(offset.x, offset.y)).
+                                        PreScale(userData->mXScale, userData->mYScale));
 
       layerBuilder->PaintItems(entry->mItems, iterRect, aContext,
                                builder, presContext,
                                offset, userData->mXScale, userData->mYScale,
                                entry->mCommonClipCount);
       if (gfxPrefs::GfxLoggingPaintedPixelCountEnabled()) {
         aLayer->Manager()->AddPaintedPixelCount(iterRect.Area());
       }
     }
   } else {
     // Apply the residual transform if it has been enabled, to ensure that
     // snapping when we draw into aContext exactly matches the ideal transform.
     // See above for why this is OK.
-    aContext->SetMatrix(
-      aContext->CurrentMatrix().PreTranslate(aLayer->GetResidualTranslation() - gfxPoint(offset.x, offset.y)).
-                                PreScale(userData->mXScale,userData->mYScale));
+    aContext->SetMatrixDouble(
+      aContext->CurrentMatrixDouble().PreTranslate(aLayer->GetResidualTranslation() - gfxPoint(offset.x, offset.y)).
+                                      PreScale(userData->mXScale,userData->mYScale));
 
     layerBuilder->PaintItems(entry->mItems, aRegionToDraw.GetBounds(), aContext,
                              builder, presContext,
                              offset, userData->mXScale, userData->mYScale,
                              entry->mCommonClipCount);
     if (gfxPrefs::GfxLoggingPaintedPixelCountEnabled()) {
       aLayer->Manager()->AddPaintedPixelCount(
         aRegionToDraw.GetBounds().Area());
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -1680,18 +1680,18 @@ nsCSSRendering::PaintBoxShadowOuter(nsPr
       // We assume that the native theme is going to paint over the shadow.
 
       // Draw the widget shape
       gfxContextMatrixAutoSaveRestore save(shadowContext);
       gfxPoint devPixelOffset =
         nsLayoutUtils::PointToGfxPoint(nsPoint(shadowItem->mXOffset,
                                                shadowItem->mYOffset),
                                        aPresContext->AppUnitsPerDevPixel());
-      shadowContext->SetMatrix(
-        shadowContext->CurrentMatrix().PreTranslate(devPixelOffset));
+      shadowContext->SetMatrixDouble(
+        shadowContext->CurrentMatrixDouble().PreTranslate(devPixelOffset));
 
       nsRect nativeRect = aDirtyRect;
       nativeRect.MoveBy(-nsPoint(shadowItem->mXOffset, shadowItem->mYOffset));
       nativeRect.IntersectRect(frameRect, nativeRect);
       aPresContext->GetTheme()->DrawWidgetBackground(shadowContext, aForFrame,
           styleDisplay->mAppearance, aFrameArea, nativeRect);
 
       blurringArea.DoPaint();
@@ -4474,17 +4474,17 @@ nsContextBoxBlur::Init(const nsRect& aRe
 
   // Convert from app units to device pixels
   gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
 
   gfxRect dirtyRect =
     nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
   dirtyRect.RoundOut();
 
-  gfxMatrix transform = aDestinationCtx->CurrentMatrix();
+  gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble();
   rect = transform.TransformBounds(rect);
 
   mPreTransformed = !transform.IsIdentity();
 
   // Create the temporary surface for blurring
   dirtyRect = transform.TransformBounds(dirtyRect);
   bool useHardwareAccel = !(aFlags & DISABLE_HARDWARE_ACCELERATION_BLUR);
   if (aSkipRect) {
@@ -4511,17 +4511,17 @@ nsContextBoxBlur::DoPaint()
 {
   if (mContext == mDestinationCtx) {
     return;
   }
 
   gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
 
   if (mPreTransformed) {
-    mDestinationCtx->SetMatrix(gfxMatrix());
+    mDestinationCtx->SetMatrix(Matrix());
   }
 
   mAlphaBoxBlur.Paint(mDestinationCtx);
 }
 
 gfxContext*
 nsContextBoxBlur::GetContext()
 {
@@ -4572,22 +4572,22 @@ nsContextBoxBlur::BlurRectangle(gfxConte
   }
 
   gfxFloat scaleX = 1;
   gfxFloat scaleY = 1;
 
   // Do blurs in device space when possible.
   // Chrome/Skia always does the blurs in device space
   // and will sometimes get incorrect results (e.g. rotated blurs)
-  gfxMatrix transform = aDestinationCtx->CurrentMatrix();
+  gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble();
   // XXX: we could probably handle negative scales but for now it's easier just to fallback
   if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && transform._22 > 0.0) {
     scaleX = transform._11;
     scaleY = transform._22;
-    aDestinationCtx->SetMatrix(gfxMatrix());
+    aDestinationCtx->SetMatrix(Matrix());
   } else {
     transform = gfxMatrix();
   }
 
   gfxPoint blurStdDev = ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
 
   gfxRect dirtyRect =
     nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
@@ -4672,23 +4672,23 @@ nsContextBoxBlur::InsetBoxBlur(gfxContex
   GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
                          aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
                          blurRadius, spreadRadius, constrainSpreadRadius);
 
   // The blur and spread radius are scaled already, so scale all
   // input data to the blur. This way, we don't have to scale the min
   // inset blur to the invert of the dest context, then rescale it back
   // when we draw to the destination surface.
-  gfxSize scale = aDestinationCtx->CurrentMatrix().ScaleFactors(true);
-  Matrix transform = ToMatrix(aDestinationCtx->CurrentMatrix());
+  gfx::Size scale = aDestinationCtx->CurrentMatrix().ScaleFactors(true);
+  Matrix transform = aDestinationCtx->CurrentMatrix();
 
   // XXX: we could probably handle negative scales but for now it's easier just to fallback
   if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && transform._22 > 0.0) {
     // If we don't have a rotation, we're pre-transforming all the rects.
-    aDestinationCtx->SetMatrix(gfxMatrix());
+    aDestinationCtx->SetMatrix(Matrix());
   } else {
     // Don't touch anything, we have a rotation.
     transform = Matrix();
   }
 
   Rect transformedDestRect = transform.TransformBounds(aDestinationRect);
   Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect);
   Rect transformedSkipRect = transform.TransformBounds(aSkipRect);
--- a/layout/painting/nsCSSRenderingGradients.cpp
+++ b/layout/painting/nsCSSRenderingGradients.cpp
@@ -927,17 +927,17 @@ nsCSSGradientRenderer::Paint(gfxContext&
   if (!dirty.IntersectRect(aDirtyRect, aFillArea))
     return;
 
   gfxRect areaToFill =
     nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
   gfxRect dirtyAreaToFill = nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel);
   dirtyAreaToFill.RoundOut();
 
-  gfxMatrix ctm = aContext.CurrentMatrix();
+  Matrix ctm = aContext.CurrentMatrix();
   bool isCTMPreservingAxisAlignedRectangles = ctm.PreservesAxisAlignedRectangles();
 
   // xStart/yStart are the top-left corner of the top-left tile.
   nscoord xStart = FindTileStart(dirty.x, aDest.x, aRepeatSize.width);
   nscoord yStart = FindTileStart(dirty.y, aDest.y, aRepeatSize.height);
   nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost();
   nscoord yEnd = forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost();
 
@@ -970,32 +970,32 @@ nsCSSGradientRenderer::Paint(gfxContext&
           continue;
         }
         // Set the context's transform to the transform that maps fillRect to
         // snappedFillRect. The part of the gradient that was going to
         // exactly fill fillRect will fill snappedFillRect instead.
         gfxMatrix transform = gfxUtils::TransformRectToRect(fillRect,
             snappedFillRectTopLeft, snappedFillRectTopRight,
             snappedFillRectBottomRight);
-        aContext.SetMatrix(transform);
+        aContext.SetMatrixDouble(transform);
       }
       aContext.NewPath();
       aContext.Rectangle(fillRect);
 
       gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill);
       gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft();
       Color edgeColor;
       if (mGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR && !isRepeat &&
           RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, mStops,
                                          gradientStart, gradientEnd, &edgeColor)) {
         edgeColor.a *= aOpacity;
         aContext.SetColor(edgeColor);
       } else {
-        aContext.SetMatrix(
-          aContext.CurrentMatrix().Copy().PreTranslate(tileRect.TopLeft()));
+        aContext.SetMatrixDouble(
+          aContext.CurrentMatrixDouble().Copy().PreTranslate(tileRect.TopLeft()));
         aContext.SetPattern(gradientPattern);
       }
       aContext.Fill();
       aContext.SetMatrix(ctm);
     }
   }
 }
 
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -766,17 +766,17 @@ GenerateAndPushTextMask(nsIFrame* aFrame
   gfxContext* sourceCtx = aContext;
   gfxRect bounds =
     nsLayoutUtils::RectToGfxRect(aFillRect,
                                  aFrame->PresContext()->AppUnitsPerDevPixel());
 
   {
     // Paint text selection background into sourceCtx.
     gfxContextMatrixAutoSaveRestore save(sourceCtx);
-    sourceCtx->SetMatrix(sourceCtx->CurrentMatrix().PreTranslate(bounds.TopLeft()));
+    sourceCtx->SetMatrixDouble(sourceCtx->CurrentMatrixDouble().PreTranslate(bounds.TopLeft()));
 
     nsLayoutUtils::PaintFrame(aContext, aFrame,
                               nsRect(nsPoint(0, 0), aFrame->GetSize()),
                               NS_RGB(255, 255, 255),
                               nsDisplayListBuilderMode::PAINTING_SELECTION_BACKGROUND);
   }
 
   // Evaluate required surface size.
@@ -788,20 +788,20 @@ GenerateAndPushTextMask(nsIFrame* aFrame
   RefPtr<DrawTarget> maskDT =
     sourceTarget->CreateSimilarDrawTarget(drawRect.Size(),
                                           SurfaceFormat::A8);
   if (!maskDT || !maskDT->IsValid()) {
     return false;
   }
   RefPtr<gfxContext> maskCtx = gfxContext::CreatePreservingTransformOrNull(maskDT);
   MOZ_ASSERT(maskCtx);
-  gfxMatrix currentMatrix = sourceCtx->CurrentMatrix();
-  maskCtx->SetMatrix(gfxMatrix::Translation(bounds.TopLeft()) *
-                     currentMatrix *
-                     gfxMatrix::Translation(-drawRect.TopLeft()));
+  gfxMatrix currentMatrix = sourceCtx->CurrentMatrixDouble();
+  maskCtx->SetMatrixDouble(gfxMatrix::Translation(bounds.TopLeft()) *
+                           currentMatrix *
+                           gfxMatrix::Translation(-drawRect.TopLeft()));
 
   // Shade text shape into mask A8 surface.
   nsLayoutUtils::PaintFrame(maskCtx, aFrame,
                             nsRect(nsPoint(0, 0), aFrame->GetSize()),
                             NS_RGB(255, 255, 255),
                             nsDisplayListBuilderMode::GENERATE_GLYPH);
 
   // Push the generated mask into aContext, so that the caller can pop and
@@ -9166,17 +9166,17 @@ ComputeMaskGeometry(PaintFramesParams& a
   nsPoint offsetToUserSpace =
       nsLayoutUtils::ComputeOffsetToUserSpace(aParams.builder, aParams.frame);
 
   gfxPoint devPixelOffsetToUserSpace =
     nsLayoutUtils::PointToGfxPoint(offsetToUserSpace,
                                    frame->PresContext()->AppUnitsPerDevPixel());
 
   gfxContextMatrixAutoSaveRestore matSR(&ctx);
-  ctx.SetMatrix(ctx.CurrentMatrix().PreTranslate(devPixelOffsetToUserSpace));
+  ctx.SetMatrixDouble(ctx.CurrentMatrixDouble().PreTranslate(devPixelOffsetToUserSpace));
 
   // Convert boaderArea and dirtyRect to user space.
   int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
   nsRect userSpaceBorderArea = aParams.borderArea - offsetToUserSpace;
   nsRect userSpaceDirtyRect = aParams.dirtyRect - offsetToUserSpace;
 
   // Union all mask layer rectangles in user space.
   gfxRect maskInUserSpace;
--- a/layout/painting/nsImageRenderer.cpp
+++ b/layout/painting/nsImageRenderer.cpp
@@ -693,17 +693,17 @@ nsImageRenderer::DrawableForElement(cons
     int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
     nsRect destRect = aImageRect - aImageRect.TopLeft();
     nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
     IntSize imageSize(roundedOut.width, roundedOut.height);
     RefPtr<gfxDrawable> drawable =
       nsSVGIntegrationUtils::DrawableFromPaintServer(
         mPaintServerFrame, mForFrame, mSize, imageSize,
         aContext.GetDrawTarget(),
-        aContext.CurrentMatrix(),
+        aContext.CurrentMatrixDouble(),
         nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES);
 
     return drawable.forget();
   }
   NS_ASSERTION(mImageElementSurface.GetSourceSurface(), "Surface should be ready.");
   RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
                                 mImageElementSurface.GetSourceSurface().get(),
                                 mImageElementSurface.mSize);
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -208,18 +208,18 @@ SheetLoadData::SheetLoadData(Loader* aLo
   NS_PRECONDITION(mLoader, "Must have a loader!");
   if (mParentData) {
     mSyncLoad = mParentData->mSyncLoad;
     mIsNonDocumentSheet = mParentData->mIsNonDocumentSheet;
     mUseSystemPrincipal = mParentData->mUseSystemPrincipal;
     ++(mParentData->mPendingChildren);
   }
 
-  NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
-                   "Shouldn't use system principal for async loads");
+  MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
+             "Shouldn't use system principal for async loads");
 }
 
 SheetLoadData::SheetLoadData(Loader* aLoader,
                              nsIURI* aURI,
                              StyleSheet* aSheet,
                              bool aSyncLoad,
                              bool aUseSystemPrincipal,
                              const Encoding* aPreloadEncoding,
@@ -243,18 +243,18 @@ SheetLoadData::SheetLoadData(Loader* aLo
   , mSheetAlreadyComplete(false)
   , mOwningElement(nullptr)
   , mObserver(aObserver)
   , mLoaderPrincipal(aLoaderPrincipal)
   , mRequestingNode(aRequestingNode)
   , mPreloadEncoding(aPreloadEncoding)
 {
   NS_PRECONDITION(mLoader, "Must have a loader!");
-  NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
-                   "Shouldn't use system principal for async loads");
+  MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
+             "Shouldn't use system principal for async loads");
 }
 
 SheetLoadData::~SheetLoadData()
 {
   NS_CSS_NS_RELEASE_LIST_MEMBER(SheetLoadData, this, mNext);
 }
 
 NS_IMETHODIMP
--- a/layout/style/nsDOMCSSValueList.cpp
+++ b/layout/style/nsDOMCSSValueList.cpp
@@ -101,17 +101,17 @@ nsDOMCSSValueList::GetCssText(nsString& 
 
 NS_IMETHODIMP
 nsDOMCSSValueList::SetCssText(const nsAString& aCssText)
 {
   if (mReadonly) {
     return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
   }
 
-  NS_NOTYETIMPLEMENTED("Can't SetCssText yet: please write me!");
+  MOZ_ASSERT_UNREACHABLE("Can't SetCssText yet: please write me!");
   return NS_OK;
 }
 
 void
 nsDOMCSSValueList::SetCssText(const nsAString& aText, ErrorResult& aRv)
 {
   aRv = SetCssText(aText);
 }
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -3105,22 +3105,22 @@ nsRuleNode::SetDefaultOnRoot(const nsSty
 
 /**
  * End an nsRuleNode::Compute*Data function for an inherited struct.
  *
  * @param type_ The nsStyle* type this function computes.
  * @param data_ Variable holding the result of this function.
  */
 #define COMPUTE_END_INHERITED(type_, data_)                                   \
-  NS_POSTCONDITION(!conditions.CacheableWithoutDependencies() ||              \
-                   aRuleDetail == eRuleFullReset ||                           \
-                   (aStartStruct && aRuleDetail == eRulePartialReset),        \
-                   "conditions.CacheableWithoutDependencies() must be false " \
-                   "for inherited structs unless all properties have been "   \
-                   "specified with values other than inherit");               \
+  MOZ_ASSERT(!conditions.CacheableWithoutDependencies() ||                    \
+             aRuleDetail == eRuleFullReset ||                                 \
+             (aStartStruct && aRuleDetail == eRulePartialReset),              \
+             "conditions.CacheableWithoutDependencies() must be false "       \
+             "for inherited structs unless all properties have been "         \
+             "specified with values other than inherit");                     \
   if (conditions.CacheableWithoutDependencies()) {                            \
     /* We were fully specified and can therefore be cached right on the */    \
     /* rule node. */                                                          \
     if (!aHighestNode->mStyleData.mInheritedData) {                           \
       aHighestNode->mStyleData.mInheritedData =                               \
         new (mPresContext) nsInheritedStyleData;                              \
     }                                                                         \
     NS_ASSERTION(!aHighestNode->mStyleData.mInheritedData->                   \
@@ -3140,23 +3140,23 @@ nsRuleNode::SetDefaultOnRoot(const nsSty
 
 /**
  * End an nsRuleNode::Compute*Data function for a reset struct.
  *
  * @param type_ The nsStyle* type this function computes.
  * @param data_ Variable holding the result of this function.
  */
 #define COMPUTE_END_RESET(type_, data_)                                       \
-  NS_POSTCONDITION(!conditions.CacheableWithoutDependencies() ||              \
-                   aRuleDetail == eRuleNone ||                                \
-                   aRuleDetail == eRulePartialReset ||                        \
-                   aRuleDetail == eRuleFullReset,                             \
-                   "conditions.CacheableWithoutDependencies() must be false " \
-                   "for reset structs if any properties were specified as "   \
-                   "inherit");                                                \
+  MOZ_ASSERT(!conditions.CacheableWithoutDependencies() ||                    \
+             aRuleDetail == eRuleNone ||                                      \
+             aRuleDetail == eRulePartialReset ||                              \
+             aRuleDetail == eRuleFullReset,                                   \
+             "conditions.CacheableWithoutDependencies() must be false "       \
+             "for reset structs if any properties were specified as "         \
+             "inherit");                                                      \
   if (conditions.CacheableWithoutDependencies()) {                            \
     /* We were fully specified and can therefore be cached right on the */    \
     /* rule node. */                                                          \
     if (!aHighestNode->mStyleData.mResetData) {                               \
       aHighestNode->mStyleData.mResetData =                                   \
         new (mPresContext) nsConditionalResetStyleData;                       \
     }                                                                         \
     NS_ASSERTION(!aHighestNode->mStyleData.mResetData->                       \
@@ -4840,23 +4840,22 @@ nsRuleNode::ComputeTextData(void* aStart
         } else {
           lh = minimumFontSize;
         }
       }
       text->mLineHeight.SetCoordValue(lh);
     }
   }
 
-
   // text-align: enum, string, pair(enum|string), inherit, initial
   // NOTE: string is not implemented yet.
   const nsCSSValue* textAlignValue = aRuleData->ValueForTextAlign();
   text->mTextAlignTrue = false;
   if (eCSSUnit_String == textAlignValue->GetUnit()) {
-    NS_NOTYETIMPLEMENTED("align string");
+    MOZ_ASSERT_UNREACHABLE("align string");
   } else if (eCSSUnit_Enumerated == textAlignValue->GetUnit() &&
              NS_STYLE_TEXT_ALIGN_MOZ_CENTER_OR_INHERIT ==
                textAlignValue->GetIntValue()) {
     conditions.SetUncacheable();
     uint8_t parentAlign = parentText->mTextAlign;
     text->mTextAlign = (NS_STYLE_TEXT_ALIGN_START == parentAlign) ?
       NS_STYLE_TEXT_ALIGN_CENTER : parentAlign;
   } else if (eCSSUnit_Enumerated == textAlignValue->GetUnit() &&
@@ -4888,17 +4887,17 @@ nsRuleNode::ComputeTextData(void* aStart
       text->mTextAlignTrue = true;
       const nsCSSValuePair& textAlignValuePair = textAlignValue->GetPairValue();
       textAlignValue = &textAlignValuePair.mXValue;
       if (eCSSUnit_Enumerated == textAlignValue->GetUnit()) {
         if (textAlignValue->GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
           textAlignValue = &textAlignValuePair.mYValue;
         }
       } else if (eCSSUnit_String == textAlignValue->GetUnit()) {
-        NS_NOTYETIMPLEMENTED("align string");
+        MOZ_ASSERT_UNREACHABLE("align string");
       }
     } else if (eCSSUnit_Inherit == textAlignValue->GetUnit() ||
                eCSSUnit_Unset == textAlignValue->GetUnit()) {
       text->mTextAlignTrue = parentText->mTextAlignTrue;
     }
     SetValue(*textAlignValue, text->mTextAlign, conditions,
              SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
              parentText->mTextAlign,
--- a/layout/style/nsRuleWalker.h
+++ b/layout/style/nsRuleWalker.h
@@ -25,17 +25,17 @@ public:
     mCurrent = aNode;
   }
 
   nsPresContext* PresContext() const { return mRoot->PresContext(); }
 
 protected:
   void DoForward(nsIStyleRule* aRule) {
     mCurrent = mCurrent->Transition(aRule, mLevel, mImportance);
-    NS_POSTCONDITION(mCurrent, "Transition messed up");
+    MOZ_ASSERT(mCurrent, "Transition messed up");
   }
 
 public:
   void Forward(nsIStyleRule* aRule) {
     NS_PRECONDITION(!RefPtr<mozilla::css::Declaration>(do_QueryObject(aRule)),
                     "Calling the wrong Forward() overload");
     DoForward(aRule);
   }
--- a/layout/svg/SVGGeometryFrame.cpp
+++ b/layout/svg/SVGGeometryFrame.cpp
@@ -272,17 +272,17 @@ SVGGeometryFrame::PaintSVG(gfxContext& a
                            imgDrawingParams& aImgParams,
                            const nsIntRect* aDirtyRect)
 {
   if (!StyleVisibility()->IsVisible())
     return;
 
   // Matrix to the geometry's user space:
   gfxMatrix newMatrix =
-    aContext.CurrentMatrix().PreMultiply(aTransform).NudgeToIntegers();
+    aContext.CurrentMatrixDouble().PreMultiply(aTransform).NudgeToIntegers();
   if (newMatrix.IsSingular()) {
     return;
   }
 
   uint32_t paintOrder = StyleSVG()->mPaintOrder;
 
   if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) {
     Render(&aContext, eRenderFill | eRenderStroke, newMatrix, aImgParams);
@@ -762,17 +762,17 @@ SVGGeometryFrame::Render(gfxContext* aCo
     (StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED ||
      StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_CRISPEDGES) ?
     AntialiasMode::NONE : AntialiasMode::SUBPIXEL;
 
   // We wait as late as possible before setting the transform so that we don't
   // set it unnecessarily if we return early (it's an expensive operation for
   // some backends).
   gfxContextMatrixAutoSaveRestore autoRestoreTransform(aContext);
-  aContext->SetMatrix(aNewTransform);
+  aContext->SetMatrixDouble(aNewTransform);
 
   if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
     // We don't complicate this code with GetAsSimplePath since the cost of
     // masking will dwarf Path creation overhead anyway.
     RefPtr<Path> path = element->GetOrBuildPath(*drawTarget, fillRule);
     if (path) {
       ColorPattern white(ToDeviceColor(Color(1.0f, 1.0f, 1.0f, 1.0f)));
       drawTarget->Fill(path, white,
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -2938,17 +2938,17 @@ SVGTextDrawPathCallbacks::HandleTextGeom
 {
   if (IsClipPathChild()) {
     RefPtr<Path> path = mContext.GetPath();
     ColorPattern white(Color(1.f, 1.f, 1.f, 1.f)); // for masking, so no ToDeviceColor
     mContext.GetDrawTarget()->Fill(path, white);
   } else {
     // Normal painting.
     gfxContextMatrixAutoSaveRestore saveMatrix(&mContext);
-    mContext.SetMatrix(mCanvasTM);
+    mContext.SetMatrixDouble(mCanvasTM);
 
     FillAndStrokeGeometry();
   }
 }
 
 void
 SVGTextDrawPathCallbacks::MakeFillPattern(GeneralPattern* aOutPattern,
                                           imgDrawingParams& aImgParams)
@@ -3584,17 +3584,17 @@ SVGTextFrame::PaintSVG(gfxContext& aCont
   DrawTarget& aDrawTarget = *aContext.GetDrawTarget();
   nsIFrame* kid = PrincipalChildList().FirstChild();
   if (!kid) {
     return;
   }
 
   nsPresContext* presContext = PresContext();
 
-  gfxMatrix initialMatrix = aContext.CurrentMatrix();
+  gfxMatrix initialMatrix = aContext.CurrentMatrixDouble();
 
   if (mState & NS_FRAME_IS_NONDISPLAY) {
     // If we are in a canvas DrawWindow call that used the
     // DRAWWINDOW_DO_NOT_FLUSH flag, then we may still have out
     // of date frames.  Just don't paint anything if they are
     // dirty.
     if (presContext->PresShell()->InDrawWindowNotFlushing() &&
         NS_SUBTREE_DIRTY(this)) {
@@ -3645,17 +3645,17 @@ SVGTextFrame::PaintSVG(gfxContext& aCont
   float cssPxPerDevPx = presContext->AppUnitsToFloatCSSPixels(auPerDevPx);
   gfxMatrix canvasTMForChildren = aTransform;
   canvasTMForChildren.PreScale(cssPxPerDevPx, cssPxPerDevPx);
   initialMatrix.PreScale(1 / cssPxPerDevPx, 1 / cssPxPerDevPx);
 
   gfxContextMatrixAutoSaveRestore matSR(&aContext);
   aContext.NewPath();
   aContext.Multiply(canvasTMForChildren);
-  gfxMatrix currentMatrix = aContext.CurrentMatrix();
+  gfxMatrix currentMatrix = aContext.CurrentMatrixDouble();
 
   RefPtr<nsCaret> caret = presContext->PresShell()->GetCaret();
   nsRect caretRect;
   nsIFrame* caretFrame = caret->GetPaintGeometry(&caretRect);
 
   gfxContextAutoSaveRestore ctxSR;
   TextRenderedRunIterator it(this, TextRenderedRunIterator::eVisibleFrames);
   TextRenderedRun run = it.Current();
@@ -3682,17 +3682,17 @@ SVGTextFrame::PaintSVG(gfxContext& aCont
       nsSVGUtils::SetupStrokeGeometry(frame, &aContext, outerContextPaint);
     }
 
     // Set up the transform for painting the text frame for the substring
     // indicated by the run.
     gfxMatrix runTransform =
       run.GetTransformFromUserSpaceForPainting(presContext, item) *
       currentMatrix;
-    aContext.SetMatrix(runTransform);
+    aContext.SetMatrixDouble(runTransform);
 
     if (drawMode != DrawMode(0)) {
       bool paintSVGGlyphs;
       nsTextFrame::PaintTextParams params(&aContext);
       params.framePt = gfx::Point();
       params.dirtyRect = LayoutDevicePixel::
         FromAppUnits(frame->GetVisualOverflowRect(), auPerDevPx);
       params.contextPaint = contextPaint;
--- a/layout/svg/nsFilterInstance.cpp
+++ b/layout/svg/nsFilterInstance.cpp
@@ -66,31 +66,31 @@ nsFilterInstance::PaintFilteredFrame(nsI
                                      nsSVGFilterPaintCallback *aPaintCallback,
                                      const nsRegion *aDirtyArea,
                                      imgDrawingParams& aImgParams)
 {
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
 
   gfxContextMatrixAutoSaveRestore autoSR(aCtx);
-  gfxSize scaleFactors = aCtx->CurrentMatrix().ScaleFactors(true);
+  gfxSize scaleFactors = aCtx->CurrentMatrixDouble().ScaleFactors(true);
   if (scaleFactors.IsEmpty()) {
     return;
   }
 
   gfxMatrix scaleMatrix(scaleFactors.width, 0.0f,
                         0.0f, scaleFactors.height,
                         0.0f, 0.0f);
 
   gfxMatrix reverseScaleMatrix = scaleMatrix;
   DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
   MOZ_ASSERT(invertible);
   // Pull scale vector out of aCtx's transform, put all scale factors, which
   // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits.
-  aCtx->SetMatrix(reverseScaleMatrix * aCtx->CurrentMatrix());
+  aCtx->SetMatrixDouble(reverseScaleMatrix * aCtx->CurrentMatrixDouble());
 
   gfxMatrix scaleMatrixInDevUnits =
     scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
 
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
   nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
                             *metrics, filterChain, /* InputIsTainted */ true,
@@ -409,18 +409,18 @@ nsFilterInstance::BuildSourcePaint(Sourc
   if (!offscreenDT || !offscreenDT->IsValid()) {
     return;
   }
 
   RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
   MOZ_ASSERT(ctx); // already checked the draw target above
   gfxContextAutoSaveRestore saver(ctx);
 
-  ctx->SetMatrix(mPaintTransform *
-                 gfxMatrix::Translation(-neededRect.TopLeft()));
+  ctx->SetMatrixDouble(mPaintTransform *
+                       gfxMatrix::Translation(-neededRect.TopLeft()));
   GeneralPattern pattern;
   if (aSource == &mFillPaint) {
     nsSVGUtils::MakeFillPatternFor(mTargetFrame, ctx, &pattern, aImgParams);
   } else if (aSource == &mStrokePaint) {
     nsSVGUtils::MakeStrokePatternFor(mTargetFrame, ctx, &pattern, aImgParams);
   }
 
   if (pattern.GetPattern()) {
@@ -478,18 +478,18 @@ nsFilterInstance::BuildSourceImage(DrawT
   // space to device space and back again). However, that would make the
   // code more complex while being hard to get right without introducing
   // subtle bugs, and in practice it probably makes no real difference.)
   RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
   MOZ_ASSERT(ctx); // already checked the draw target above
   gfxMatrix devPxToCssPxTM = nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
   DebugOnly<bool> invertible = devPxToCssPxTM.Invert();
   MOZ_ASSERT(invertible);
-  ctx->SetMatrix(devPxToCssPxTM * mPaintTransform *
-                 gfxMatrix::Translation(-neededRect.TopLeft()));
+  ctx->SetMatrixDouble(devPxToCssPxTM * mPaintTransform *
+                       gfxMatrix::Translation(-neededRect.TopLeft()));
 
   mPaintCallback->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty, aImgParams);
 
   mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
   mSourceGraphic.mSurfaceRect = neededRect;
 }
 
 void
--- a/layout/svg/nsSVGClipPathFrame.cpp
+++ b/layout/svg/nsSVGClipPathFrame.cpp
@@ -58,19 +58,19 @@ nsSVGClipPathFrame::ApplyClipPath(gfxCon
     SVGGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild);
     if (pathFrame) {
       SVGGeometryElement* pathElement =
         static_cast<SVGGeometryElement*>(pathFrame->GetContent());
       gfxMatrix toChildsUserSpace = pathElement->
         PrependLocalTransformsTo(GetClipPathTransform(aClippedFrame) * aMatrix,
                                  eUserSpaceToParent);
       gfxMatrix newMatrix =
-        aContext.CurrentMatrix().PreMultiply(toChildsUserSpace).NudgeToIntegers();
+        aContext.CurrentMatrixDouble().PreMultiply(toChildsUserSpace).NudgeToIntegers();
       if (!newMatrix.IsSingular()) {
-        aContext.SetMatrix(newMatrix);
+        aContext.SetMatrixDouble(newMatrix);
         FillRule clipRule =
           nsSVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule);
         clipPath = pathElement->GetOrBuildPath(aDrawTarget, clipRule);
       }
     }
   }
 
   if (clipPath) {
@@ -98,17 +98,17 @@ nsSVGClipPathFrame::CreateClipMask(gfxCo
     referenceDT->CreateSimilarDrawTarget(bounds.Size(), SurfaceFormat::A8);
 
   aOffset = bounds.TopLeft();
 
   return maskDT.forget();
 }
 
 static void
-ComposeExtraMask(DrawTarget* aTarget, const gfxMatrix& aMaskTransfrom,
+ComposeExtraMask(DrawTarget* aTarget,
                  SourceSurface* aExtraMask, const Matrix& aExtraMasksTransform)
 {
   MOZ_ASSERT(aExtraMask);
 
   Matrix origin = aTarget->GetTransform();
   aTarget->SetTransform(aExtraMasksTransform * aTarget->GetTransform());
   aTarget->MaskSurface(ColorPattern(Color(0.0, 0.0, 0.0, 1.0)),
                        aExtraMask,
@@ -173,24 +173,24 @@ nsSVGClipPathFrame::PaintClipMask(gfxCon
 
   if (maskUsage.shouldGenerateClipMaskLayer) {
     aMaskContext.PopGroupAndBlend();
   } else if (maskUsage.shouldApplyClipPath) {
     aMaskContext.PopClip();
   }
 
   // Moz2D transforms in the opposite direction to Thebes
-  gfxMatrix maskTransfrom = aMaskContext.CurrentMatrix();
+  Matrix maskTransfrom = aMaskContext.CurrentMatrix();
   maskTransfrom.Invert();
 
   if (aExtraMask) {
-    ComposeExtraMask(maskDT, maskTransfrom, aExtraMask, aExtraMasksTransform);
+    ComposeExtraMask(maskDT, aExtraMask, aExtraMasksTransform);
   }
 
-  *aMaskTransform = ToMatrix(maskTransfrom);
+  *aMaskTransform = maskTransfrom;
 }
 
 void
 nsSVGClipPathFrame::PaintFrameIntoMask(nsIFrame *aFrame,
                                        nsIFrame* aClippedFrame,
                                        gfxContext& aTarget,
                                        const gfxMatrix& aMatrix)
 {
@@ -266,17 +266,17 @@ nsSVGClipPathFrame::GetClipMask(gfxConte
   }
 
   RefPtr<gfxContext> maskContext = gfxContext::CreateOrNull(maskDT);
   if (!maskContext) {
     gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT);
     return nullptr;
   }
   maskContext->SetMatrix(aReferenceContext.CurrentMatrix() *
-                         gfxMatrix::Translation(-offset));
+                         Matrix::Translation(-offset));
 
   PaintClipMask(*maskContext, aClippedFrame, aMatrix, aMaskTransform,
                 aExtraMask, aExtraMasksTransform);
 
   RefPtr<SourceSurface> surface = maskDT->Snapshot();
   return surface.forget();
 }
 
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -414,17 +414,17 @@ public:
                      const nsIntRect* aDirtyRect,
                      imgDrawingParams& aImgParams) override
   {
     BasicLayerManager* basic = mLayerManager->AsBasicLayerManager();
     RefPtr<gfxContext> oldCtx = basic->GetTarget();
     basic->SetTarget(&aContext);
 
     gfxContextMatrixAutoSaveRestore autoSR(&aContext);
-    aContext.SetMatrix(aContext.CurrentMatrix().PreTranslate(-mUserSpaceToFrameSpaceOffset));
+    aContext.SetMatrixDouble(aContext.CurrentMatrixDouble().PreTranslate(-mUserSpaceToFrameSpaceOffset));
 
     mLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, mBuilder);
     basic->SetTarget(oldCtx);
   }
 
 private:
   nsDisplayListBuilder* mBuilder;
   LayerManager* mLayerManager;
@@ -435,17 +435,17 @@ typedef nsSVGIntegrationUtils::PaintFram
 
 /**
  * Paint css-positioned-mask onto a given target(aMaskDT).
  */
 static void
 PaintMaskSurface(const PaintFramesParams& aParams,
                  DrawTarget* aMaskDT, float aOpacity, nsStyleContext* aSC,
                  const nsTArray<nsSVGMaskFrame*>& aMaskFrames,
-                 const gfxMatrix& aMaskSurfaceMatrix,
+                 const Matrix& aMaskSurfaceMatrix,
                  const nsPoint& aOffsetToUserSpace)
 {
   MOZ_ASSERT(aMaskFrames.Length() > 0);
   MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8);
   MOZ_ASSERT(aOpacity == 1.0 || aMaskFrames.Length() == 1);
 
   const nsStyleSVGReset *svgReset = aSC->StyleSVGReset();
   gfxMatrix cssPxToDevPxMatrix =
@@ -568,18 +568,18 @@ CreateAndPaintMaskSurface(const PaintFra
   // We can paint mask along with opacity only if
   // 1. There is only one mask, or
   // 2. No overlap among masks.
   // Collision detect in #2 is not that trivial, we only accept #1 here.
   paintResult.opacityApplied = (aMaskFrames.Length() == 1);
 
   // Set context's matrix on maskContext, offset by the maskSurfaceRect's
   // position. This makes sure that we combine the masks in device space.
-  gfxMatrix maskSurfaceMatrix =
-    ctx.CurrentMatrix() * gfxMatrix::Translation(-aParams.maskRect.TopLeft());
+  Matrix maskSurfaceMatrix =
+    ctx.CurrentMatrix() * Matrix::Translation(-aParams.maskRect.TopLeft());
 
   PaintMaskSurface(aParams, maskDT,
                    paintResult.opacityApplied ? aOpacity : 1.0,
                    aSC, aMaskFrames, maskSurfaceMatrix,
                    aOffsetToUserSpace);
 
   if (aParams.imgParams.result != DrawResult::SUCCESS) {
     // Now we know the status of mask resource since we used it while painting.
@@ -599,17 +599,17 @@ CreateAndPaintMaskSurface(const PaintFra
     //   masked content as if this mask is an opaque white one(no mask).
     paintResult.transparentBlackMask =
       !(aParams.frame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
 
     MOZ_ASSERT(!paintResult.maskSurface);
     return paintResult;
   }
 
-  paintResult.maskTransform = ToMatrix(maskSurfaceMatrix);
+  paintResult.maskTransform = maskSurfaceMatrix;
   if (!paintResult.maskTransform.Invert()) {
     return paintResult;
   }
 
   paintResult.maskSurface = maskDT->Snapshot();
   return paintResult;
 }
 
@@ -706,18 +706,18 @@ ComputeEffectOffset(nsIFrame* aFrame, co
  * Setup transform matrix of a gfx context by a specific frame. Move the
  * origin of aParams.ctx to the user space of aFrame.
  */
 static EffectOffsets
 MoveContextOriginToUserSpace(nsIFrame* aFrame, const PaintFramesParams& aParams)
 {
   EffectOffsets offset = ComputeEffectOffset(aFrame, aParams);
 
-  aParams.ctx.SetMatrix(
-    aParams.ctx.CurrentMatrix().PreTranslate(offset.offsetToUserSpaceInDevPx));
+  aParams.ctx.SetMatrixDouble(
+    aParams.ctx.CurrentMatrixDouble().PreTranslate(offset.offsetToUserSpaceInDevPx));
 
   return offset;
 }
 
 bool
 nsSVGIntegrationUtils::IsMaskResourceReady(nsIFrame* aFrame)
 {
   nsIFrame* firstFrame =
@@ -847,17 +847,17 @@ nsSVGIntegrationUtils::PaintMask(const P
     Matrix clipMaskTransform;
     gfxMatrix cssPxToDevPxMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
 
     nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame();
     RefPtr<SourceSurface> maskSurface =
       maskUsage.shouldGenerateMaskLayer ? maskTarget->Snapshot() : nullptr;
     clipPathFrame->PaintClipMask(ctx, frame, cssPxToDevPxMatrix,
                                    &clipMaskTransform, maskSurface,
-                                   ToMatrix(ctx.CurrentMatrix()));
+                                   ctx.CurrentMatrix());
   }
 }
 
 void
 nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams)
 {
   MOZ_ASSERT(UsingMaskOrClipPathForFrame(aParams.frame),
              "Should not use this method when no mask or clipPath effect"
--- a/layout/svg/nsSVGMaskFrame.cpp
+++ b/layout/svg/nsSVGMaskFrame.cpp
@@ -94,18 +94,18 @@ nsSVGMaskFrame::GetMaskForMaskedFrame(Ma
     maskDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
                maskSurfaceSize, SurfaceFormat::A8);
   }
 
   if (!maskDT || !maskDT->IsValid()) {
     return nullptr;
   }
 
-  gfxMatrix maskSurfaceMatrix =
-    context->CurrentMatrix() * gfxMatrix::Translation(-maskSurfaceRect.TopLeft());
+  Matrix maskSurfaceMatrix =
+    context->CurrentMatrix() * ToMatrix(gfxMatrix::Translation(-maskSurfaceRect.TopLeft()));
 
   RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(maskDT);
   MOZ_ASSERT(tmpCtx); // already checked the draw target above
   tmpCtx->SetMatrix(maskSurfaceMatrix);
 
   mMatrixForChildren = GetMaskTransform(aParams.maskedFrame) *
                        aParams.toUserSpace;
 
@@ -148,17 +148,17 @@ nsSVGMaskFrame::GetMaskForMaskedFrame(Ma
     surface = maskSnapshot.forget();
   }
 
   // Moz2D transforms in the opposite direction to Thebes
   if (!maskSurfaceMatrix.Invert()) {
     return nullptr;
   }
 
-  *aParams.maskTransform = ToMatrix(maskSurfaceMatrix);
+  *aParams.maskTransform = maskSurfaceMatrix;
   return surface.forget();
 }
 
 gfxRect
 nsSVGMaskFrame::GetMaskArea(nsIFrame* aMaskedFrame)
 {
   SVGMaskElement *maskElem = static_cast<SVGMaskElement*>(GetContent());
 
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -574,33 +574,33 @@ public:
     }
 
     MOZ_ASSERT(!mTargetCtx,
                "CreateBlendTarget is designed to be used once only.");
 
     mTargetCtx = gfxContext::CreateOrNull(targetDT);
     MOZ_ASSERT(mTargetCtx); // already checked the draw target above
     mTargetCtx->SetMatrix(mSourceCtx->CurrentMatrix() *
-                          gfxMatrix::Translation(-drawRect.TopLeft()));
+                          Matrix::Translation(-drawRect.TopLeft()));
 
     mTargetOffset = drawRect.TopLeft();
 
     return mTargetCtx;
   }
 
   void BlendToTarget()
   {
     MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
     MOZ_ASSERT(mTargetCtx,
                "BlendToTarget should be used after CreateBlendTarget.");
 
     RefPtr<SourceSurface> targetSurf = mTargetCtx->GetDrawTarget()->Snapshot();
 
     gfxContextAutoSaveRestore save(mSourceCtx);
-    mSourceCtx->SetMatrix(gfxMatrix()); // This will be restored right after.
+    mSourceCtx->SetMatrix(Matrix()); // This will be restored right after.
     RefPtr<gfxPattern> pattern =
       new gfxPattern(targetSurf,
                      Matrix::Translation(mTargetOffset.x, mTargetOffset.y));
     mSourceCtx->SetPattern(pattern);
     mSourceCtx->Paint();
   }
 
 private:
@@ -849,18 +849,18 @@ nsSVGUtils::PaintFrameWithEffects(nsIFra
     gfxContextMatrixAutoSaveRestore autoSR(target);
 
     // 'target' is currently scaled such that its user space units are CSS
     // pixels (SVG user space units). But PaintFilteredFrame expects it to be
     // scaled in such a way that its user space units are device pixels. So we
     // have to adjust the scale.
     gfxMatrix reverseScaleMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(aFrame);
     DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
-    target->SetMatrix(reverseScaleMatrix * aTransform *
-                      target->CurrentMatrix());
+    target->SetMatrixDouble(reverseScaleMatrix * aTransform *
+                            target->CurrentMatrixDouble());
 
     SVGPaintCallback paintCallback;
     nsFilterInstance::PaintFilteredFrame(aFrame, target, &paintCallback,
                                          dirtyRegion, aImgParams);
   } else {
      svgFrame->PaintSVG(*target, aTransform, aImgParams, aDirtyRect);
   }
 
@@ -1504,37 +1504,37 @@ nsSVGUtils::MakeFillPatternFor(nsIFrame*
   const DrawTarget* dt = aContext->GetDrawTarget();
 
   nsSVGPaintServerFrame *ps =
     SVGObserverUtils::GetPaintServer(aFrame, &nsStyleSVG::mFill,
                                      SVGObserverUtils::FillProperty());
 
   if (ps) {
     RefPtr<gfxPattern> pattern =
-      ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrix(),
+      ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(),
                                 &nsStyleSVG::mFill, fillOpacity, aImgParams);
     if (pattern) {
       pattern->CacheColorStops(dt);
       aOutPattern->Init(*pattern->GetPattern(dt));
       return;
     }
   }
 
   if (aContextPaint) {
     RefPtr<gfxPattern> pattern;
     switch (style->mFill.Type()) {
     case eStyleSVGPaintType_ContextFill:
       pattern =
         aContextPaint->GetFillPattern(dt, fillOpacity,
-                                      aContext->CurrentMatrix(), aImgParams);
+                                      aContext->CurrentMatrixDouble(), aImgParams);
       break;
     case eStyleSVGPaintType_ContextStroke:
       pattern =
         aContextPaint->GetStrokePattern(dt, fillOpacity,
-                                        aContext->CurrentMatrix(), aImgParams);
+                                        aContext->CurrentMatrixDouble(), aImgParams);
       break;
     default:
       ;
     }
     if (pattern) {
       aOutPattern->Init(*pattern->GetPattern(dt));
       return;
     }
@@ -1580,37 +1580,37 @@ nsSVGUtils::MakeStrokePatternFor(nsIFram
   const DrawTarget* dt = aContext->GetDrawTarget();
 
   nsSVGPaintServerFrame *ps =
     SVGObserverUtils::GetPaintServer(aFrame, &nsStyleSVG::mStroke,
                                      SVGObserverUtils::StrokeProperty());
 
   if (ps) {
     RefPtr<gfxPattern> pattern =
-      ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrix(),
+      ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(),
                                 &nsStyleSVG::mStroke, strokeOpacity, aImgParams);
     if (pattern) {
       pattern->CacheColorStops(dt);
       aOutPattern->Init(*pattern->GetPattern(dt));
       return;
     }
   }
 
   if (aContextPaint) {
     RefPtr<gfxPattern> pattern;
     switch (style->mStroke.Type()) {
     case eStyleSVGPaintType_ContextFill:
       pattern =
         aContextPaint->GetFillPattern(dt, strokeOpacity,
-                                      aContext->CurrentMatrix(), aImgParams);
+                                      aContext->CurrentMatrixDouble(), aImgParams);
       break;
     case eStyleSVGPaintType_ContextStroke:
       pattern =
         aContextPaint->GetStrokePattern(dt, strokeOpacity,
-                                        aContext->CurrentMatrix(), aImgParams);
+                                        aContext->CurrentMatrixDouble(), aImgParams);
       break;
     default:
       ;
     }
     if (pattern) {
       aOutPattern->Init(*pattern->GetPattern(dt));
       return;
     }
--- a/layout/xul/nsButtonBoxFrame.cpp
+++ b/layout/xul/nsButtonBoxFrame.cpp
@@ -37,18 +37,17 @@ nsButtonBoxFrame::nsButtonBoxListener::H
   nsAutoString eventType;
   aEvent->GetType(eventType);
 
   if (eventType.EqualsLiteral("blur")) {
     mButtonBoxFrame->Blurred();
     return NS_OK;
   }
 
-  NS_ABORT();
-
+  MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
   return NS_OK;
 }
 
 //
 // NS_NewXULButtonFrame
 //
 // Creates a new Button frame and returns it
 //
--- a/layout/xul/nsMenuBarListener.cpp
+++ b/layout/xul/nsMenuBarListener.cpp
@@ -597,12 +597,11 @@ nsMenuBarListener::HandleEvent(nsIDOMEve
   }
   if (eventType.EqualsLiteral("mousedown")) {
     return MouseDown(aEvent);
   }
   if (eventType.EqualsLiteral("MozDOMFullscreen:Entered")) {
     return Fullscreen(aEvent);
   }
 
-  NS_ABORT();
-
+  MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
   return NS_OK;
 }
--- a/layout/xul/nsSplitterFrame.cpp
+++ b/layout/xul/nsSplitterFrame.cpp
@@ -584,17 +584,17 @@ nsSplitterFrameInner::HandleEvent(nsIDOM
   if (eventType.EqualsLiteral("mouseup"))
     return MouseUp(aEvent);
   if (eventType.EqualsLiteral("mousedown"))
     return MouseDown(aEvent);
   if (eventType.EqualsLiteral("mousemove") ||
       eventType.EqualsLiteral("mouseout"))
     return MouseMove(aEvent);
 
-  NS_ABORT();
+  MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
   return NS_OK;
 }
 
 nsresult
 nsSplitterFrameInner::MouseUp(nsIDOMEvent* aMouseEvent)
 {
   NS_ENSURE_TRUE(mOuter, NS_OK);
   mPressed = false;
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -2607,18 +2607,17 @@ nsXULPopupManager::HandleEvent(nsIDOMEve
   }
   if (eventType.EqualsLiteral("keydown")) {
     return KeyDown(keyEvent);
   }
   if (eventType.EqualsLiteral("keypress")) {
     return KeyPress(keyEvent);
   }
 
-  NS_ABORT();
-
+  MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
   return NS_OK;
 }
 
 nsresult
 nsXULPopupManager::UpdateIgnoreKeys(bool aIgnoreKeys)
 {
   nsMenuChainItem* item = GetTopVisibleMenu();
   if (item) {
--- a/memory/gtest/TestJemalloc.cpp
+++ b/memory/gtest/TestJemalloc.cpp
@@ -13,16 +13,17 @@
 #include "gtest/gtest.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsCOMPtr.h"
 #include "nsICrashReporter.h"
 #include "nsServiceManagerUtils.h"
 #endif
 
+#ifdef NIGHTLY_BUILD
 #if defined(DEBUG) && !defined(XP_WIN) && !defined(ANDROID)
 #define HAS_GDB_SLEEP_DURATION 1
 extern unsigned int _gdb_sleep_duration;
 #endif
 
 // Death tests are too slow on OSX because of the system crash reporter.
 #ifndef XP_DARWIN
 static void DisableCrashReporter()
@@ -39,16 +40,17 @@ static void DisableCrashReporter()
 // Wrap ASSERT_DEATH_IF_SUPPORTED to disable the crash reporter
 // when entering the subprocess, so that the expected crashes don't
 // create a minidump that the gtest harness will interpret as an error.
 #define ASSERT_DEATH_WRAP(a, b) \
   ASSERT_DEATH_IF_SUPPORTED({ DisableCrashReporter(); a; }, b)
 #else
 #define ASSERT_DEATH_WRAP(a, b)
 #endif
+#endif
 
 using namespace mozilla;
 
 static inline void
 TestOne(size_t size)
 {
   size_t req = size;
   size_t adv = malloc_good_size(req);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
@@ -3,19 +3,16 @@
  * 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/. */
 
 package org.mozilla.gecko.mozglue;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.InputStream;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.text.NumberFormat;
 import java.util.Locale;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 import android.content.Context;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Environment;
@@ -468,25 +465,16 @@ public final class GeckoLoader {
 
     public synchronized static void loadGeckoLibs(final Context context, final String apkName) {
         loadLibsSetupLocked(context);
         loadGeckoLibsNative(apkName);
     }
 
     private static void setupLocaleEnvironment() {
         putenv("LANG=" + Locale.getDefault().toString());
-        NumberFormat nf = NumberFormat.getInstance();
-        if (nf instanceof DecimalFormat) {
-            DecimalFormat df = (DecimalFormat)nf;
-            DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
-
-            putenv("LOCALE_DECIMAL_POINT=" + dfs.getDecimalSeparator());
-            putenv("LOCALE_THOUSANDS_SEP=" + dfs.getGroupingSeparator());
-            putenv("LOCALE_GROUPING=" + (char)df.getGroupingSize());
-        }
     }
 
     @SuppressWarnings("serial")
     public static class AbortException extends Exception {
         public AbortException(String msg) {
             super(msg);
         }
     }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5148,17 +5148,17 @@ pref("dom.push.http2.retryInterval", 500
 // Autodetection is currently only supported on Windows and GTK3
 #if defined(XP_MACOSX)
 pref("dom.w3c_touch_events.enabled", 0);
 #else
 pref("dom.w3c_touch_events.enabled", 2);
 #endif
 
 // W3C draft pointer events
-#if !defined(ANDROID) && defined(EARLY_BETA_OR_EARLIER)
+#if !defined(ANDROID) && defined(NIGHTLY_BUILD)
 pref("dom.w3c_pointer_events.enabled", true);
 #else
 pref("dom.w3c_pointer_events.enabled", false);
 #endif
 
 // Control firing WidgetMouseEvent by handling Windows pointer messages or mouse
 // messages.
 #if defined(XP_WIN)
--- a/netwerk/base/nsStandardURL.cpp
+++ b/netwerk/base/nsStandardURL.cpp
@@ -3049,17 +3049,17 @@ nsStandardURL::SetRef(const nsACString &
     mPath.mLen += shift;
     mRef.mLen = refLen;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsStandardURL::SetDirectory(const nsACString &input)
 {
-    NS_NOTYETIMPLEMENTED("");
+    MOZ_ASSERT_UNREACHABLE("SetDirectory");
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsStandardURL::SetFileName(const nsACString &input)
 {
     ENSURE_MUTABLE();
 
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -767,17 +767,17 @@ HttpBaseChannel::GetContentLength(int64_
 
   *aContentLength = mResponseHead->ContentLength();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::SetContentLength(int64_t value)
 {
-  NS_NOTYETIMPLEMENTED("HttpBaseChannel::SetContentLength");
+  MOZ_ASSERT_UNREACHABLE("HttpBaseChannel::SetContentLength");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::Open(nsIInputStream **aResult)
 {
   NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);
 
--- a/old-configure.in
+++ b/old-configure.in
@@ -5096,16 +5096,17 @@ export CFLAGS
 export CXXFLAGS
 export LDFLAGS
 export HOST_CC
 export HOST_CXX
 export HOST_CFLAGS
 export HOST_CPPFLAGS
 export HOST_CXXFLAGS
 export HOST_LDFLAGS
+export MOZ_PGO
 
 if ! test -e js; then
     mkdir js
 fi
 
 ac_configure_args="$ac_configure_args JS_STANDALONE="
 AC_OUTPUT_SUBDIRS(js/src,$cache_file)
 ac_configure_args="$_SUBDIR_CONFIG_ARGS"
--- a/rdf/base/nsCompositeDataSource.cpp
+++ b/rdf/base/nsCompositeDataSource.cpp
@@ -1063,17 +1063,17 @@ CompositeDataSourceImpl::ArcLabelsOut(ns
     NS_ADDREF(result);
     *aResult = result;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 CompositeDataSourceImpl::GetAllResources(nsISimpleEnumerator** aResult)
 {
-    NS_NOTYETIMPLEMENTED("CompositeDataSourceImpl::GetAllResources");
+    MOZ_ASSERT_UNREACHABLE("CompositeDataSourceImpl::GetAllResources");
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 CompositeDataSourceImpl::GetAllCmds(nsIRDFResource* source,
                                     nsISimpleEnumerator/*<nsIRDFResource>*/** result)
 {
     nsresult rv;
--- a/rdf/datasource/nsFileSystemDataSource.cpp
+++ b/rdf/datasource/nsFileSystemDataSource.cpp
@@ -245,17 +245,17 @@ FileSystemDataSource::GetSource(nsIRDFRe
 
 
 NS_IMETHODIMP
 FileSystemDataSource::GetSources(nsIRDFResource *property,
                                  nsIRDFNode *target,
                                  bool tv,
                                  nsISimpleEnumerator **sources /* out */)
 {
-//  NS_NOTYETIMPLEMENTED("write me");
+//  MOZ_ASSERT_UNREACHABLE("write me");
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 
 
 NS_IMETHODIMP
 FileSystemDataSource::GetTarget(nsIRDFResource *source,
                                 nsIRDFResource *property,
@@ -700,17 +700,17 @@ FileSystemDataSource::HasArcOut(nsIRDFRe
 }
 
 
 
 NS_IMETHODIMP
 FileSystemDataSource::ArcLabelsIn(nsIRDFNode *node,
                             nsISimpleEnumerator ** labels /* out */)
 {
-//  NS_NOTYETIMPLEMENTED("write me");
+//  MOZ_ASSERT_UNREACHABLE("write me");
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 
 
 NS_IMETHODIMP
 FileSystemDataSource::ArcLabelsOut(nsIRDFResource *source,
                    nsISimpleEnumerator **labels /* out */)
@@ -757,17 +757,17 @@ FileSystemDataSource::ArcLabelsOut(nsIRD
     return NS_NewEmptyEnumerator(labels);
 }
 
 
 
 NS_IMETHODIMP
 FileSystemDataSource::GetAllResources(nsISimpleEnumerator** aCursor)
 {
-    NS_NOTYETIMPLEMENTED("sorry!");
+    MOZ_ASSERT_UNREACHABLE("sorry!");
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 
 
 NS_IMETHODIMP
 FileSystemDataSource::AddObserver(nsIRDFObserver *n)
 {
--- a/security/sandbox/common/SandboxSettings.cpp
+++ b/security/sandbox/common/SandboxSettings.cpp
@@ -4,30 +4,39 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozISandboxSettings.h"
 
 #include "mozilla/ModuleUtils.h"
 #include "mozilla/Preferences.h"
 
+#include "prenv.h"
+
 namespace mozilla {
 
 int GetEffectiveContentSandboxLevel() {
+  if (PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) {
+    return 0;
+  }
   int level = Preferences::GetInt("security.sandbox.content.level");
 // On Windows and macOS, enforce a minimum content sandbox level of 1 (except on
 // Nightly, where it can be set to 0).
 #if !defined(NIGHTLY_BUILD) && (defined(XP_WIN) || defined(XP_MACOSX))
   if (level < 1) {
     level = 1;
   }
 #endif
   return level;
 }
 
+bool IsContentSandboxEnabled() {
+  return GetEffectiveContentSandboxLevel() > 0;
+}
+
 class SandboxSettings final : public mozISandboxSettings
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISANDBOXSETTINGS
 
   SandboxSettings() { }
 
--- a/security/sandbox/common/SandboxSettings.h
+++ b/security/sandbox/common/SandboxSettings.h
@@ -5,13 +5,17 @@
 
 #ifndef mozilla_SandboxSettings_h
 #define mozilla_SandboxSettings_h
 
 namespace mozilla {
 
 // Return the current sandbox level. This is the
 // "security.sandbox.content.level" preference, but rounded up to the current
-// minimum allowed level.
+// minimum allowed level. Returns 0 (disabled) if the env var
+// MOZ_DISABLE_CONTENT_SANDBOX is set.
 int GetEffectiveContentSandboxLevel();
 
+// Checks whether the effective content sandbox level is > 0.
+bool IsContentSandboxEnabled();
+
 }
 #endif // mozilla_SandboxPolicies_h
--- a/security/sandbox/linux/Sandbox.h
+++ b/security/sandbox/linux/Sandbox.h
@@ -17,17 +17,17 @@
 
 namespace mozilla {
 
 // This must be called early, while the process is still single-threaded.
 MOZ_EXPORT void SandboxEarlyInit(GeckoProcessType aType);
 
 #ifdef MOZ_CONTENT_SANDBOX
 // Call only if SandboxInfo::CanSandboxContent() returns true.
-// (No-op if MOZ_DISABLE_CONTENT_SANDBOX is set.)
+// (No-op if the sandbox is disabled.)
 // aBrokerFd is the filesystem broker client file descriptor,
 // or -1 to allow direct filesystem access.
 // isFileProcess determines whether we allow system wide file reads.
 MOZ_EXPORT bool SetContentProcessSandbox(int aBrokerFd,
                                          bool aFileProcess,
                                          std::vector<int>& aSyscallWhitelist);
 #endif
 
--- a/security/sandbox/linux/SandboxInfo.cpp
+++ b/security/sandbox/linux/SandboxInfo.cpp
@@ -14,16 +14,17 @@
 #include <sys/stat.h>
 #include <sys/syscall.h>
 #include <sys/wait.h>
 #include <unistd.h>
 
 #include "base/posix/eintr_wrapper.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/SandboxSettings.h"
 #include "sandbox/linux/system_headers/linux_seccomp.h"
 #include "sandbox/linux/system_headers/linux_syscalls.h"
 
 #ifdef MOZ_VALGRIND
 #include <valgrind/valgrind.h>
 #endif
 
 
@@ -221,16 +222,19 @@ SandboxInfo::SandboxInfo() {
       flags |= kHasPrivilegedUserNamespaces;
       if (CanCreateUserNamespace()) {
         flags |= kHasUserNamespaces;
       }
     }
   }
 
 #ifdef MOZ_CONTENT_SANDBOX
+  // We can't use mozilla::IsContentSandboxEnabled() here because a)
+  // libmozsandbox can't depend on libxul, and b) this is called in a static
+  // initializer before the prefences service is ready.
   if (!getenv("MOZ_DISABLE_CONTENT_SANDBOX")) {
     flags |= kEnabledForContent;
   }
   if (getenv("MOZ_PERMISSIVE_CONTENT_SANDBOX")) {
     flags |= kPermissive;
   }
 #endif
 #ifdef MOZ_GMP_SANDBOX
--- a/security/sandbox/linux/SandboxInfo.h
+++ b/security/sandbox/linux/SandboxInfo.h
@@ -20,17 +20,18 @@ public:
   SandboxInfo(const SandboxInfo& aOther) : mFlags(aOther.mFlags) { }
 
   // Flags are checked at initializer time; this returns them.
   static const SandboxInfo& Get() { return sSingleton; }
 
   enum Flags {
     // System call filtering; kernel config option CONFIG_SECCOMP_FILTER.
     kHasSeccompBPF     = 1 << 0,
-    // Config flag MOZ_CONTENT_SANDBOX; env var MOZ_DISABLE_CONTENT_SANDBOX.
+    // Config flag MOZ_CONTENT_SANDBOX; runtime
+    // mozilla::IsContentSandboxEnabled().
     kEnabledForContent = 1 << 1,
     // Config flag MOZ_GMP_SANDBOX; env var MOZ_DISABLE_GMP_SANDBOX.
     kEnabledForMedia   = 1 << 2,
     // Env var MOZ_SANDBOX_LOGGING.
     kVerbose           = 1 << 3,
     // Kernel can atomically set system call filtering on entire thread group.
     kHasSeccompTSync   = 1 << 4,
     // Can this process create user namespaces? (Man page user_namespaces(7).)
--- a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -398,17 +398,17 @@ SandboxBrokerPolicyFactory::GetContentPo
 {
   // Policy entries that vary per-process (currently the only reason
   // that can happen is because they contain the pid) are added here,
   // as well as entries that depend on preferences or paths not available
   // in early startup.
 
   MOZ_ASSERT(NS_IsMainThread());
   // File broker usage is controlled through a pref.
-  if (GetEffectiveContentSandboxLevel() <= 1) {
+  if (!IsContentSandboxEnabled()) {
     return nullptr;
   }
 
   MOZ_ASSERT(mCommonContentPolicy);
   UniquePtr<SandboxBroker::Policy>
     policy(new SandboxBroker::Policy(*mCommonContentPolicy));
 
   // Read any extra paths that will get write permissions,
--- a/services/sync/tests/unit/head_helpers.js
+++ b/services/sync/tests/unit/head_helpers.js
@@ -11,16 +11,17 @@
 /* global Service */
 
 Cu.import("resource://services-common/async.js");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://testing-common/PlacesTestUtils.jsm");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
 Cu.import("resource://gre/modules/ObjectUtils.jsm");
 
 add_task(async function head_setup() {
   // If a test imports Service, make sure it is initialized first.
   if (this.Service) {
     await this.Service.promiseInitialized;
   }
 });
--- a/services/sync/tests/unit/test_bookmark_decline_undecline.js
+++ b/services/sync/tests/unit/test_bookmark_decline_undecline.js
@@ -1,12 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
 Cu.import("resource://gre/modules/BookmarkJSONUtils.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
--- a/services/sync/tests/unit/test_bookmark_duping.js
+++ b/services/sync/tests/unit/test_bookmark_duping.js
@@ -1,12 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
 Cu.import("resource://services-common/async.js");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 Cu.import("resource://services-sync/bookmark_validator.js");
@@ -546,18 +545,18 @@ add_task(async function test_dupe_repare
 
     // The record for folder1 on the server should reference the new GUID.
     let serverRecord1 = getServerRecord(collection, folder1_guid);
     ok(!serverRecord1.children.includes(bmk1_guid));
     ok(serverRecord1.children.includes(newGUID));
 
     // As the incoming parent is missing the item should have been annotated
     // with that missing parent.
-    equal(PlacesUtils.annotations.getItemAnnotation((await store.idForGUID(newGUID)), "sync/parent"),
-          newParentGUID);
+    equal(PlacesUtils.annotations.getItemAnnotation((await store.idForGUID(newGUID)),
+      PlacesSyncUtils.bookmarks.SYNC_PARENT_ANNO), newParentGUID);
 
     // Check the validator. Sadly, this is known to cause a mismatch between
     // the server and client views of the tree.
     let expected = [
       // We haven't fixed the incoming record that referenced the missing parent.
       { name: "orphans", count: 1 },
     ];
     await validate(collection, expected);
--- a/services/sync/tests/unit/test_bookmark_engine.js
+++ b/services/sync/tests/unit/test_bookmark_engine.js
@@ -1,12 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
 Cu.import("resource://gre/modules/BookmarkHTMLUtils.jsm");
 Cu.import("resource://gre/modules/BookmarkJSONUtils.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/service.js");
@@ -108,35 +107,32 @@ add_task(async function test_delete_inva
 });
 
 add_task(async function bad_record_allIDs() {
   let server = new SyncServer();
   server.start();
   await SyncTestingInfrastructure(server);
 
   _("Ensure that bad Places queries don't cause an error in getAllIDs.");
-  let badRecordID = PlacesUtils.bookmarks.insertBookmark(
-      PlacesUtils.bookmarks.toolbarFolder,
-      CommonUtils.makeURI("place:folder=1138"),
-      PlacesUtils.bookmarks.DEFAULT_INDEX,
-      null);
+  let badRecord = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    url: "place:folder=1138",
+  });
 
-  do_check_true(badRecordID > 0);
-  _("Record is " + badRecordID);
-  _("Type: " + PlacesUtils.bookmarks.getItemType(badRecordID));
+  _("Type: " + badRecord.type);
 
   _("Fetching all IDs.");
   let all = await fetchAllRecordIds();
 
   _("All IDs: " + JSON.stringify([...all]));
   do_check_true(all.has("menu"));
   do_check_true(all.has("toolbar"));
 
   _("Clean up.");
-  PlacesUtils.bookmarks.removeItem(badRecordID);
+  await PlacesUtils.bookmarks.eraseEverything();
   await PlacesSyncUtils.bookmarks.reset();
   await promiseStopServer(server);
 });
 
 add_task(async function test_processIncoming_error_orderChildren() {
   _("Ensure that _orderChildren() is called even when _processIncoming() throws an error.");
 
   let engine = new BookmarksEngine(Service);
@@ -144,34 +140,39 @@ add_task(async function test_processInco
   let store  = engine._store;
   let server = await serverForFoo(engine);
   await SyncTestingInfrastructure(server);
 
   let collection = server.user("foo").collection("bookmarks");
 
   try {
 
-    let folder1_id = PlacesUtils.bookmarks.createFolder(
-      PlacesUtils.bookmarks.toolbarFolder, "Folder 1", 0);
-    let folder1_guid = await store.GUIDForId(folder1_id);
+    let folder1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+      title: "Folder 1",
+    });
 
-    let fxuri = CommonUtils.makeURI("http://getfirefox.com/");
-    let tburi = CommonUtils.makeURI("http://getthunderbird.com/");
-
-    let bmk1_id = PlacesUtils.bookmarks.insertBookmark(
-      folder1_id, fxuri, PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
-    let bmk2_id = PlacesUtils.bookmarks.insertBookmark(
-      folder1_id, tburi, PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Thunderbird!");
+    let bmk1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: folder1.guid,
+      url: "http://getfirefox.com/",
+      title: "Get Firefox!",
+    });
+    let bmk2 = await PlacesUtils.bookmarks.insert({
+      parentGuid: folder1.guid,
+      url: "http://getthunderbird.com/",
+      title: "Get Thunderbird!",
+    });
 
     // Create a server record for folder1 where we flip the order of
     // the children.
-    let folder1_record = await store.createRecord(folder1_guid);
+    let folder1_record = await store.createRecord(folder1.guid);
     let folder1_payload = folder1_record.cleartext;
     folder1_payload.children.reverse();
-    collection.insert(folder1_guid, encryptPayload(folder1_payload));
+    collection.insert(folder1.guid, encryptPayload(folder1_payload));
 
     // Create a bogus record that when synced down will provoke a
     // network error which in turn provokes an exception in _processIncoming.
     const BOGUS_GUID = "zzzzzzzzzzzz";
     let bogus_record = collection.insert(BOGUS_GUID, "I'm a bogus record!");
     bogus_record.get = function get() {
       throw new Error("Sync this!");
     };
@@ -185,24 +186,24 @@ add_task(async function test_processInco
     try {
       await sync_engine_and_validate_telem(engine, true);
     } catch (ex) {
       error = ex;
     }
     ok(!!error);
 
     // Verify that the bookmark order has been applied.
-    folder1_record = await store.createRecord(folder1_guid);
+    folder1_record = await store.createRecord(folder1.guid);
     let new_children = folder1_record.children;
-    do_check_eq(new_children.length, 2);
-    do_check_eq(new_children[0], folder1_payload.children[0]);
-    do_check_eq(new_children[1], folder1_payload.children[1]);
+    do_check_matches(new_children,
+      [folder1_payload.children[0], folder1_payload.children[1]]);
 
-    do_check_eq(PlacesUtils.bookmarks.getItemIndex(bmk1_id), 1);
-    do_check_eq(PlacesUtils.bookmarks.getItemIndex(bmk2_id), 0);
+    let localChildIds = await PlacesSyncUtils.bookmarks.fetchChildRecordIds(
+      folder1.guid);
+    do_check_matches(localChildIds, [bmk2.guid, bmk1.guid]);
 
   } finally {
     await store.wipe();
     await engine.resetClient();
     Svc.Prefs.resetBranch("");
     Service.recordManager.clearCache();
     await PlacesSyncUtils.bookmarks.reset();
     await promiseStopServer(server);
@@ -233,65 +234,67 @@ async function test_restoreOrImport(aRep
   await SyncTestingInfrastructure(server);
 
   let collection = server.user("foo").collection("bookmarks");
 
   Svc.Obs.notify("weave:engine:start-tracking"); // We skip usual startup...
 
   try {
 
-    let folder1_id = PlacesUtils.bookmarks.createFolder(
-      PlacesUtils.bookmarks.toolbarFolder, "Folder 1", 0);
-    let folder1_guid = await store.GUIDForId(folder1_id);
-    _("Folder 1: " + folder1_id + ", " + folder1_guid);
-
-    let fxuri = CommonUtils.makeURI("http://getfirefox.com/");
-    let tburi = CommonUtils.makeURI("http://getthunderbird.com/");
+    let folder1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+      title: "Folder 1",
+    });
 
     _("Create a single record.");
-    let bmk1_id = PlacesUtils.bookmarks.insertBookmark(
-      folder1_id, fxuri, PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
-    let bmk1_guid = await store.GUIDForId(bmk1_id);
-    _(`Get Firefox!: ${bmk1_id}, ${bmk1_guid}`);
+    let bmk1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: folder1.guid,
+      url: "http://getfirefox.com/",
+      title: "Get Firefox!",
+    });
+    _(`Get Firefox!: ${bmk1.guid}`);
 
     let dirSvc = Cc["@mozilla.org/file/directory_service;1"]
       .getService(Ci.nsIProperties);
 
     let backupFile = dirSvc.get("TmpD", Ci.nsIFile);
 
     _("Make a backup.");
     backupFile.append("t_b_e_" + Date.now() + ".json");
 
     _(`Backing up to file ${backupFile.path}`);
     await bookmarkUtils.exportToFile(backupFile.path);
 
     _("Create a different record and sync.");
-    let bmk2_id = PlacesUtils.bookmarks.insertBookmark(
-      folder1_id, tburi, PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Thunderbird!");
-    let bmk2_guid = await store.GUIDForId(bmk2_id);
-    _(`Get Thunderbird!: ${bmk2_id}, ${bmk2_guid}`);
+    let bmk2 = await PlacesUtils.bookmarks.insert({
+      parentGuid: folder1.guid,
+      url: "http://getthunderbird.com/",
+      title: "Get Thunderbird!",
+    });
+    _(`Get Thunderbird!: ${bmk2.guid}`);
 
-    PlacesUtils.bookmarks.removeItem(bmk1_id);
+    await PlacesUtils.bookmarks.remove(bmk1.guid);
 
     let error;
     try {
       await sync_engine_and_validate_telem(engine, false);
     } catch (ex) {
       error = ex;
       _("Got error: " + Log.exceptionStr(ex));
     }
     do_check_true(!error);
 
     _("Verify that there's only one bookmark on the server, and it's Thunderbird.");
     // Of course, there's also the Bookmarks Toolbar and Bookmarks Menu...
     let wbos = collection.keys(function(id) {
-      return ["menu", "toolbar", "mobile", "unfiled", folder1_guid].indexOf(id) == -1;
+      return ["menu", "toolbar", "mobile", "unfiled", folder1.guid].indexOf(id) == -1;
     });
     do_check_eq(wbos.length, 1);
-    do_check_eq(wbos[0], bmk2_guid);
+    do_check_eq(wbos[0], bmk2.guid);
 
     _(`Now ${verb} from a backup.`);
     await bookmarkUtils.importFromFile(backupFile, aReplace);
 
     let bookmarksCollection = server.user("foo").collection("bookmarks");
     if (aReplace) {
       _("Verify that we wiped the server.");
       do_check_true(!bookmarksCollection);
@@ -302,34 +305,34 @@ async function test_restoreOrImport(aRep
 
     _("Ensure we have the bookmarks we expect locally.");
     let guids = await fetchAllRecordIds();
     _("GUIDs: " + JSON.stringify([...guids]));
     let bookmarkGuids = new Map();
     let count = 0;
     for (let guid of guids) {
       count++;
-      let id = await store.idForGUID(guid, true);
+      let info = await PlacesUtils.bookmarks.fetch(
+        PlacesSyncUtils.bookmarks.recordIdToGuid(guid));
       // Only one bookmark, so _all_ should be Firefox!
-      if (PlacesUtils.bookmarks.getItemType(id) == PlacesUtils.bookmarks.TYPE_BOOKMARK) {
-        let uri = PlacesUtils.bookmarks.getBookmarkURI(id);
-        _(`Found URI ${uri.spec} for GUID ${guid}`);
-        bookmarkGuids.set(uri.spec, guid);
+      if (info.type == PlacesUtils.bookmarks.TYPE_BOOKMARK) {
+        _(`Found URI ${info.url.href} for GUID ${guid}`);
+        bookmarkGuids.set(info.url.href, guid);
       }
     }
-    do_check_true(bookmarkGuids.has(fxuri.spec));
+    do_check_true(bookmarkGuids.has("http://getfirefox.com/"));
     if (!aReplace) {
-      do_check_true(bookmarkGuids.has(tburi.spec));
+      do_check_true(bookmarkGuids.has("http://getthunderbird.com/"));
     }
 
     _("Have the correct number of IDs locally, too.");
-    let expectedResults = ["menu", "toolbar", "mobile", "unfiled", folder1_id,
-                           bmk1_id];
+    let expectedResults = ["menu", "toolbar", "mobile", "unfiled", folder1.guid,
+                           bmk1.guid];
     if (!aReplace) {
-      expectedResults.push("toolbar", folder1_id, bmk2_id);
+      expectedResults.push("toolbar", folder1.guid, bmk2.guid);
     }
     do_check_eq(count, expectedResults.length);
 
     _("Sync again. This'll wipe bookmarks from the server.");
     try {
       await sync_engine_and_validate_telem(engine, false);
     } catch (ex) {
       error = ex;
@@ -349,23 +352,23 @@ async function test_restoreOrImport(aRep
                                  (wbo.id != "menu") &&
                                  (wbo.id != "toolbar") &&
                                  (wbo.id != "unfiled") &&
                                  (wbo.id != "mobile") &&
                                  (wbo.parentid != "menu"));
                        });
 
     let expectedFX = {
-      id: bookmarkGuids.get(fxuri.spec),
-      bmkUri: fxuri.spec,
+      id: bookmarkGuids.get("http://getfirefox.com/"),
+      bmkUri: "http://getfirefox.com/",
       title: "Get Firefox!"
     };
     let expectedTB = {
-      id: bookmarkGuids.get(tburi.spec),
-      bmkUri: tburi.spec,
+      id: bookmarkGuids.get("http://getthunderbird.com/"),
+      bmkUri: "http://getthunderbird.com/",
       title: "Get Thunderbird!"
     };
 
     let expectedBookmarks;
     if (aReplace) {
       expectedBookmarks = [expectedFX];
     } else {
       expectedBookmarks = [expectedTB, expectedFX];
@@ -453,39 +456,38 @@ add_task(async function test_mismatched_
   newRecord.cleartext = newRecord;
 
   let engine = new BookmarksEngine(Service);
   await engine.initialize();
   let store  = engine._store;
   let server = await serverForFoo(engine);
   await SyncTestingInfrastructure(server);
 
-  _("GUID: " + (await store.GUIDForId(6, true)));
-
   try {
-    let bms = PlacesUtils.bookmarks;
     let oldR = new FakeRecord(BookmarkFolder, oldRecord);
     let newR = new FakeRecord(Livemark, newRecord);
     oldR.parentid = PlacesUtils.bookmarks.toolbarGuid;
     newR.parentid = PlacesUtils.bookmarks.toolbarGuid;
 
     await store.applyIncoming(oldR);
     _("Applied old. It's a folder.");
-    let oldID = await store.idForGUID(oldR.id);
+    let oldID = await PlacesUtils.promiseItemId(oldR.id);
     _("Old ID: " + oldID);
-    do_check_eq(bms.getItemType(oldID), bms.TYPE_FOLDER);
+    let oldInfo = await PlacesUtils.bookmarks.fetch(oldR.id);
+    do_check_eq(oldInfo.type, PlacesUtils.bookmarks.TYPE_FOLDER);
     do_check_false(PlacesUtils.annotations
                               .itemHasAnnotation(oldID, PlacesUtils.LMANNO_FEEDURI));
 
     await store.applyIncoming(newR);
-    let newID = await store.idForGUID(newR.id);
+    let newID = await PlacesUtils.promiseItemId(newR.id);
     _("New ID: " + newID);
 
     _("Applied new. It's a livemark.");
-    do_check_eq(bms.getItemType(newID), bms.TYPE_FOLDER);
+    let newInfo = await PlacesUtils.bookmarks.fetch(newR.id);
+    do_check_eq(newInfo.type, PlacesUtils.bookmarks.TYPE_FOLDER);
     do_check_true(PlacesUtils.annotations
                              .itemHasAnnotation(newID, PlacesUtils.LMANNO_FEEDURI));
 
   } finally {
     await store.wipe();
     await engine.resetClient();
     Svc.Prefs.resetBranch("");
     Service.recordManager.clearCache();
@@ -501,22 +503,24 @@ add_task(async function test_bookmark_gu
   await engine.initialize();
   let store = engine._store;
 
   let server = await serverForFoo(engine);
   let coll   = server.user("foo").collection("bookmarks");
   await SyncTestingInfrastructure(server);
 
   // Add one item to the server.
-  let itemID = PlacesUtils.bookmarks.createFolder(
-    PlacesUtils.bookmarks.toolbarFolder, "Folder 1", 0);
-  let itemGUID = await store.GUIDForId(itemID);
-  let itemRecord = await store.createRecord(itemGUID);
+  let item = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    title: "Folder 1",
+  });
+  let itemRecord = await store.createRecord(item.guid);
   let itemPayload = itemRecord.cleartext;
-  coll.insert(itemGUID, encryptPayload(itemPayload));
+  coll.insert(item.guid, encryptPayload(itemPayload));
 
   engine.lastSync = 1; // So we don't back up.
 
   // Make building the GUID map fail.
 
   let pbt = PlacesUtils.promiseBookmarksTree;
   PlacesUtils.promiseBookmarksTree = function() { return Promise.reject("Nooo"); };
 
@@ -606,21 +610,23 @@ add_task(async function test_misreconcil
   // Log real hard for this test.
   store._log.trace = store._log.debug;
   engine._log.trace = engine._log.debug;
 
   await engine._syncStartup();
 
   // Let's find out where the toolbar is right now.
   let toolbarBefore = await store.createRecord("toolbar", "bookmarks");
-  let toolbarIDBefore = await store.idForGUID("toolbar");
+  let toolbarIDBefore = await PlacesUtils.promiseItemId(
+    PlacesUtils.bookmarks.toolbarGuid);
   do_check_neq(-1, toolbarIDBefore);
 
   let parentGUIDBefore = toolbarBefore.parentid;
-  let parentIDBefore = await store.idForGUID(parentGUIDBefore);
+  let parentIDBefore = await PlacesUtils.promiseItemId(
+    PlacesSyncUtils.bookmarks.recordIdToGuid(parentGUIDBefore));
   do_check_neq(-1, parentIDBefore);
   do_check_eq("string", typeof(parentGUIDBefore));
 
   _("Current parent: " + parentGUIDBefore + " (" + parentIDBefore + ").");
 
   let to_apply = {
     id: "zzzzzzzzzzzz",
     type: "folder",
@@ -636,18 +642,20 @@ add_task(async function test_misreconcil
   _("Applying record.");
   store.applyIncoming(rec);
 
   // Ensure that afterwards, toolbar is still there.
   // As of 2012-12-05, this only passes because Places doesn't use "toolbar" as
   // the real GUID, instead using a generated one. Sync does the translation.
   let toolbarAfter = await store.createRecord("toolbar", "bookmarks");
   let parentGUIDAfter = toolbarAfter.parentid;
-  let parentIDAfter = await store.idForGUID(parentGUIDAfter);
-  do_check_eq((await store.GUIDForId(toolbarIDBefore)), "toolbar");
+  let parentIDAfter = await PlacesUtils.promiseItemId(
+    PlacesSyncUtils.bookmarks.recordIdToGuid(parentGUIDAfter));
+  do_check_eq((await PlacesUtils.promiseItemGuid(toolbarIDBefore)),
+    PlacesUtils.bookmarks.toolbarGuid);
   do_check_eq(parentGUIDBefore, parentGUIDAfter);
   do_check_eq(parentIDBefore, parentIDAfter);
 
   await store.wipe();
   await engine.resetClient();
   await PlacesSyncUtils.bookmarks.reset();
   await promiseStopServer(server);
 });
@@ -755,36 +763,36 @@ add_task(async function test_sync_dateAd
     equal(record5.dateAdded, item5LastModified * 1000,
           "If no dateAdded is provided, lastModified should be used (even if it's in the future)");
 
     // Update item2 and try resyncing it.
     item2.dateAdded = now - 100000;
     collection.insert(item2GUID, encryptPayload(item2.cleartext), now / 1000 - 50);
 
 
-    // Also, add a local bookmark and make sure it's date added makes it up to the server
-    let bzid = PlacesUtils.bookmarks.insertBookmark(
-      PlacesUtils.bookmarksMenuFolderId, CommonUtils.makeURI("https://bugzilla.mozilla.org/"),
-      PlacesUtils.bookmarks.DEFAULT_INDEX, "Bugzilla");
-
-    let bzguid = await PlacesUtils.promiseItemGuid(bzid);
+    // Also, add a local bookmark and make sure its date added makes it up to the server
+    let bz = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.menuGuid,
+      url: "https://bugzilla.mozilla.org/",
+      title: "Bugzilla",
+    });
 
     // last sync did a POST, which doesn't advance its lastModified value.
     // Next sync of the engine doesn't hit info/collections, so lastModified
     // remains stale. Setting it to null side-steps that.
     engine.lastModified = null;
     await sync_engine_and_validate_telem(engine, false);
 
     let newRecord2 = await store.createRecord(item2GUID);
     equal(newRecord2.dateAdded, item2.dateAdded, "dateAdded update should work for earlier date");
 
-    let bzWBO = JSON.parse(JSON.parse(collection._wbos[bzguid].payload).ciphertext);
+    let bzWBO = JSON.parse(JSON.parse(collection._wbos[bz.guid].payload).ciphertext);
     ok(bzWBO.dateAdded, "Locally added dateAdded lost");
 
-    let localRecord = await store.createRecord(bzguid);
+    let localRecord = await store.createRecord(bz.guid);
     equal(bzWBO.dateAdded, localRecord.dateAdded, "dateAdded should not change during upload");
 
     item2.dateAdded += 10000;
     collection.insert(item2GUID, encryptPayload(item2.cleartext), now / 1000 - 10);
 
     engine.lastModified = null;
     await sync_engine_and_validate_telem(engine, false);
 
--- a/services/sync/tests/unit/test_bookmark_invalid.js
+++ b/services/sync/tests/unit/test_bookmark_invalid.js
@@ -14,48 +14,50 @@ add_task(async function setup() {
   store = engine._store;
   tracker = engine._tracker;
 });
 
 add_task(async function test_ignore_invalid_uri() {
   _("Ensure that we don't die with invalid bookmarks.");
 
   // First create a valid bookmark.
-  let bmid = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
-                                                  Services.io.newURI("http://example.com/"),
-                                                  PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                                  "the title");
+  let bmInfo = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    url: "http://example.com/",
+    title: "the title",
+  });
 
   // Now update moz_places with an invalid url.
   await PlacesUtils.withConnectionWrapper("test_ignore_invalid_uri", async function(db) {
     await db.execute(
       `UPDATE moz_places SET url = :url, url_hash = hash(:url)
        WHERE id = (SELECT b.fk FROM moz_bookmarks b
-       WHERE b.id = :id LIMIT 1)`,
-      { id: bmid, url: "<invalid url>" });
+                   WHERE b.guid = :guid)`,
+      { guid: bmInfo.guid, url: "<invalid url>" });
   });
 
   // Ensure that this doesn't throw even though the DB is now in a bad state (a
   // bookmark has an illegal url).
   await engine._buildGUIDMap();
 });
 
 add_task(async function test_ignore_missing_uri() {
   _("Ensure that we don't die with a bookmark referencing an invalid bookmark id.");
 
   // First create a valid bookmark.
-  let bmid = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
-                                                  Services.io.newURI("http://example.com/"),
-                                                  PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                                  "the title");
+  let bmInfo = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    url: "http://example.com/",
+    title: "the title",
+  });
 
   // Now update moz_bookmarks to reference a non-existing places ID
   await PlacesUtils.withConnectionWrapper("test_ignore_missing_uri", async function(db) {
     await db.execute(
       `UPDATE moz_bookmarks SET fk = 999999
-       WHERE id = :id`
-      , { id: bmid });
+       WHERE guid = :guid`
+      , { guid: bmInfo.guid });
   });
 
   // Ensure that this doesn't throw even though the DB is now in a bad state (a
   // bookmark has an illegal url).
   await engine._buildGUIDMap();
 });
--- a/services/sync/tests/unit/test_bookmark_livemarks.js
+++ b/services/sync/tests/unit/test_bookmark_livemarks.js
@@ -81,50 +81,48 @@ add_task(async function test_livemark_de
 
   // Attempt to provoke an error by messing around with the description.
   record.description = null;
   await doRecord(makeLivemark(record));
   record.description = "";
   await doRecord(makeLivemark(record));
 
   // Attempt to provoke an error by adding a bad description anno.
-  let id = await store.idForGUID(record.id);
+  let id = await PlacesUtils.promiseItemId(record.id);
   PlacesUtils.annotations.setItemAnnotation(id, DESCRIPTION_ANNO, "", 0,
                                             PlacesUtils.annotations.EXPIRE_NEVER);
 
   await engine.finalize();
 });
 
 add_task(async function test_livemark_invalid() {
   let engine = new BookmarksEngine(Service);
   await engine.initialize();
   let store = engine._store;
 
   _("Livemarks considered invalid by nsLivemarkService are skipped.");
 
   _("Parent is unknown. Will be set to unfiled.");
   let lateParentRec = makeLivemark(record631361.payload, true);
-  let parentGUID = Utils.makeGUID();
-  lateParentRec.parentid = parentGUID;
-  do_check_eq(-1, (await store.idForGUID(parentGUID)));
+  lateParentRec.parentid = Utils.makeGUID();
 
   await store.create(lateParentRec);
-  let recID = await store.idForGUID(lateParentRec.id, true);
-  do_check_true(recID > 0);
-  do_check_eq(PlacesUtils.bookmarks.getFolderIdForItem(recID),
-              PlacesUtils.bookmarks.unfiledBookmarksFolder);
+  let recInfo = await PlacesUtils.bookmarks.fetch(lateParentRec.id);
+  do_check_eq(recInfo.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
 
   _("No feed URI, which is invalid. Will be skipped.");
   let noFeedURIRec = makeLivemark(record631361.payload, true);
   delete noFeedURIRec.cleartext.feedUri;
   await store.create(noFeedURIRec);
   // No exception, but no creation occurs.
-  do_check_eq(-1, (await store.idForGUID(noFeedURIRec.id, true)));
+  let noFeedURIItem = await PlacesUtils.bookmarks.fetch(noFeedURIRec.id);
+  do_check_null(noFeedURIItem);
 
   _("Parent is a Livemark. Will be skipped.");
   let lmParentRec = makeLivemark(record631361.payload, true);
-  lmParentRec.parentid = await store.GUIDForId(recID);
+  lmParentRec.parentid = recInfo.guid;
   await store.create(lmParentRec);
   // No exception, but no creation occurs.
-  do_check_eq(-1, (await store.idForGUID(lmParentRec.id, true)));
+  let lmParentItem = await PlacesUtils.bookmarks.fetch(lmParentRec.id);
+  do_check_null(lmParentItem);
 
   await engine.finalize();
 });
--- a/services/sync/tests/unit/test_bookmark_places_query_rewriting.js
+++ b/services/sync/tests/unit/test_bookmark_places_query_rewriting.js
@@ -27,28 +27,26 @@ add_task(async function run_test() {
 
   let uri = "place:folder=499&type=7&queryType=1";
   let tagRecord = makeTagRecord("abcdefabcdef", uri);
 
   _("Type: " + tagRecord.type);
   _("Folder name: " + tagRecord.folderName);
   await store.applyIncoming(tagRecord);
 
-  let tags = PlacesUtils.getFolderContents(PlacesUtils.tagsFolderId).root;
-  let tagID;
-  try {
-    for (let i = 0; i < tags.childCount; ++i) {
-      let child = tags.getChild(i);
-      if (child.title == "bar") {
-        tagID = child.itemId;
-      }
-    }
-  } finally {
-    tags.containerOpen = false;
-  }
+  let tagID = -1;
+  let db = await PlacesUtils.promiseDBConnection();
+  let rows = await db.execute(`
+    SELECT id FROM moz_bookmarks
+    WHERE parent = :tagsFolderId AND
+          title = :title`,
+    { tagsFolderId: PlacesUtils.tagsFolderId,
+      title: "bar" });
+  equal(rows.length, 1);
+  tagID = rows[0].getResultByName("id");
 
   _("Tag ID: " + tagID);
   let insertedRecord = await store.createRecord("abcdefabcdef", "bookmarks");
   do_check_eq(insertedRecord.bmkUri, uri.replace("499", tagID));
 
   _("... but not if the type is wrong.");
   let wrongTypeURI = "place:folder=499&type=2&queryType=1";
   let wrongTypeRecord = makeTagRecord("fedcbafedcba", wrongTypeURI);
--- a/services/sync/tests/unit/test_bookmark_repair_responder.js
+++ b/services/sync/tests/unit/test_bookmark_repair_responder.js
@@ -1,13 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
-Cu.import("resource://testing-common/PlacesTestUtils.jsm");
 Cu.import("resource:///modules/PlacesUIUtils.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/bookmark_repair.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
--- a/services/sync/tests/unit/test_bookmark_smart_bookmarks.js
+++ b/services/sync/tests/unit/test_bookmark_smart_bookmarks.js
@@ -4,50 +4,39 @@
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
-const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
-const IOService = Cc["@mozilla.org/network/io-service;1"]
-                .getService(Ci.nsIIOService);
-
 async function newSmartBookmark(parentGuid, url, position, title, queryID) {
   let info = await PlacesUtils.bookmarks.insert({
     parentGuid,
     url,
     position,
     title,
   });
   let id = await PlacesUtils.promiseItemId(info.guid);
-  PlacesUtils.annotations.setItemAnnotation(id, SMART_BOOKMARKS_ANNO,
-                                            queryID, 0,
-                                            PlacesUtils.annotations.EXPIRE_NEVER);
+  PlacesUtils.annotations.setItemAnnotation(id,
+    PlacesSyncUtils.bookmarks.SMART_BOOKMARKS_ANNO, queryID, 0,
+    PlacesUtils.annotations.EXPIRE_NEVER);
   return info;
 }
 
 function smartBookmarkCount() {
   // We do it this way because PlacesUtils.annotations.getItemsWithAnnotation
   // doesn't work the same (or at all?) between 3.6 and 4.0.
   let out = {};
-  PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO, out);
+  PlacesUtils.annotations.getItemsWithAnnotation(
+    PlacesSyncUtils.bookmarks.SMART_BOOKMARKS_ANNO, out);
   return out.value;
 }
 
-function clearBookmarks() {
-  _("Cleaning up existing items.");
-  PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.bookmarks.bookmarksMenuFolder);
-  PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.bookmarks.tagsFolder);
-  PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.bookmarks.toolbarFolder);
-  PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.bookmarks.unfiledBookmarksFolder);
-}
-
 let engine;
 let store;
 
 add_task(async function setup() {
   initTestLogging("Trace");
   Log.repository.getLogger("Sync.Engine.Bookmarks").level = Log.Level.Trace;
 
   await generateNewKeys(Service.collectionKeys);
@@ -60,45 +49,42 @@ add_task(async function setup() {
 });
 
 // Verify that Places smart bookmarks have their annotation uploaded and
 // handled locally.
 add_task(async function test_annotation_uploaded() {
   let server = await serverForFoo(engine);
   await SyncTestingInfrastructure(server);
 
+  _("Cleaning up existing items.");
+  await PlacesUtils.bookmarks.eraseEverything();
+
   let startCount = smartBookmarkCount();
 
   _("Start count is " + startCount);
 
-  if (startCount > 0) {
-    // This can happen in XULRunner.
-    clearBookmarks();
-    _("Start count is now " + startCount);
-  }
-
   _("Create a smart bookmark in the toolbar.");
   let url = "place:sort=" +
             Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING +
             "&maxResults=10";
   let title = "Most Visited";
 
   let mostVisitedInfo = await newSmartBookmark(
     PlacesUtils.bookmarks.toolbarGuid, url, -1, title, "MostVisited");
   let mostVisitedID = await PlacesUtils.promiseItemId(mostVisitedInfo.guid);
 
   _("New item ID: " + mostVisitedID);
   do_check_true(!!mostVisitedID);
 
   let annoValue = PlacesUtils.annotations.getItemAnnotation(mostVisitedID,
-                                              SMART_BOOKMARKS_ANNO);
+    PlacesSyncUtils.bookmarks.SMART_BOOKMARKS_ANNO);
   _("Anno: " + annoValue);
   do_check_eq("MostVisited", annoValue);
 
-  let guid = await store.GUIDForId(mostVisitedID);
+  let guid = await PlacesUtils.promiseItemGuid(mostVisitedID);
   _("GUID: " + guid);
   do_check_true(!!guid);
 
   _("Create record object and verify that it's sane.");
   let record = await store.createRecord(guid);
   do_check_true(record instanceof Bookmark);
   do_check_true(record instanceof BookmarkQuery);
 
@@ -130,48 +116,51 @@ add_task(async function test_annotation_
 
     _("We still have the right count.");
     do_check_eq(smartBookmarkCount(), startCount + 1);
 
     _("Clear local records; now we can't find it.");
 
     // "Clear" by changing attributes: if we delete it, apparently it sticks
     // around as a deleted record...
-    PlacesUtils.bookmarks.setItemTitle(mostVisitedID, "Not Most Visited");
-    PlacesUtils.bookmarks.changeBookmarkURI(
-      mostVisitedID, CommonUtils.makeURI("http://something/else"));
+    await PlacesUtils.bookmarks.update({
+      guid: mostVisitedInfo.guid,
+      title: "Not Most Visited",
+      url: "http://something/else",
+    });
     PlacesUtils.annotations.removeItemAnnotation(mostVisitedID,
-                                                 SMART_BOOKMARKS_ANNO);
+      PlacesSyncUtils.bookmarks.SMART_BOOKMARKS_ANNO);
     await store.wipe();
     await engine.resetClient();
     do_check_eq(smartBookmarkCount(), startCount);
 
     _("Sync. Verify that the downloaded record carries the annotation.");
     await sync_engine_and_validate_telem(engine, false);
 
     _("Verify that the Places DB now has an annotated bookmark.");
     _("Our count has increased again.");
     do_check_eq(smartBookmarkCount(), startCount + 1);
 
     _("Find by GUID and verify that it's annotated.");
-    let newID = await store.idForGUID(serverGUID);
+    let newID = await PlacesUtils.promiseItemId(serverGUID);
     let newAnnoValue = PlacesUtils.annotations.getItemAnnotation(
-      newID, SMART_BOOKMARKS_ANNO);
+      newID, PlacesSyncUtils.bookmarks.SMART_BOOKMARKS_ANNO);
     do_check_eq(newAnnoValue, "MostVisited");
-    do_check_eq(PlacesUtils.bookmarks.getBookmarkURI(newID).spec, url);
+    let newInfo = await PlacesUtils.bookmarks.fetch(serverGUID);
+    do_check_eq(newInfo.url.href, url);
 
     _("Test updating.");
     let newRecord = await store.createRecord(serverGUID);
     do_check_eq(newRecord.queryId, newAnnoValue);
     newRecord.queryId = "LeastVisited";
     collection.insert(serverGUID, encryptPayload(newRecord.cleartext));
     engine.lastModified = collection.timestamp + 1;
     await sync_engine_and_validate_telem(engine, false);
     do_check_eq("LeastVisited", PlacesUtils.annotations.getItemAnnotation(
-      newID, SMART_BOOKMARKS_ANNO));
+      newID, PlacesSyncUtils.bookmarks.SMART_BOOKMARKS_ANNO));
 
   } finally {
     // Clean up.
     await store.wipe();
     Svc.Prefs.resetBranch("");
     Service.recordManager.clearCache();
     await promiseStopServer(server);
   }
--- a/services/sync/tests/unit/test_bookmark_store.js
+++ b/services/sync/tests/unit/test_bookmark_store.js
@@ -1,186 +1,188 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 
-const PARENT_ANNO = "sync/parent";
-
-const fxuri = CommonUtils.makeURI("http://getfirefox.com/");
-const tburi = CommonUtils.makeURI("http://getthunderbird.com/");
-
 add_task(async function test_ignore_specials() {
   _("Ensure that we can't delete bookmark roots.");
 
   let engine = new BookmarksEngine(Service);
   let store = engine._store;
 
   // Belt...
   let record = new BookmarkFolder("bookmarks", "toolbar", "folder");
   record.deleted = true;
   do_check_neq(null, (await PlacesUtils.promiseItemId(
-    PlacesSyncUtils.bookmarks.recordIdToGuid("toolbar"))));
+    PlacesUtils.bookmarks.toolbarGuid)));
 
   await store.applyIncoming(record);
   await store.deletePending();
 
   // Ensure that the toolbar exists.
   do_check_neq(null, (await PlacesUtils.promiseItemId(
-    PlacesSyncUtils.bookmarks.recordIdToGuid("toolbar"))));
+    PlacesUtils.bookmarks.toolbarGuid)));
 
-  // This will fail painfully in getItemType if the deletion worked.
+  // This will fail to build the local tree if the deletion worked.
   await engine._buildGUIDMap();
 
   // Braces...
   await store.remove(record);
   await store.deletePending();
   do_check_neq(null, (await PlacesUtils.promiseItemId(
-    PlacesSyncUtils.bookmarks.recordIdToGuid("toolbar"))));
+    PlacesUtils.bookmarks.toolbarGuid)));
   await engine._buildGUIDMap();
 
   await store.wipe();
 
   await engine.finalize();
 });
 
 add_task(async function test_bookmark_create() {
   let engine = new BookmarksEngine(Service);
   let store = engine._store;
 
   try {
     _("Ensure the record isn't present yet.");
-    let ids = PlacesUtils.bookmarks.getBookmarkIdsForURI(fxuri, {});
-    do_check_eq(ids.length, 0);
+    let item = await PlacesUtils.bookmarks.fetch({
+      url: "http://getfirefox.com/",
+    });
+    do_check_null(item);
 
     _("Let's create a new record.");
     let fxrecord = new Bookmark("bookmarks", "get-firefox1");
-    fxrecord.bmkUri        = fxuri.spec;
+    fxrecord.bmkUri        = "http://getfirefox.com/";
     fxrecord.description   = "Firefox is awesome.";
     fxrecord.title         = "Get Firefox!";
     fxrecord.tags          = ["firefox", "awesome", "browser"];
     fxrecord.keyword       = "awesome";
     fxrecord.loadInSidebar = false;
     fxrecord.parentName    = "Bookmarks Toolbar";
     fxrecord.parentid      = "toolbar";
     await store.applyIncoming(fxrecord);
 
     _("Verify it has been created correctly.");
-    let id = await PlacesUtils.promiseItemId(PlacesSyncUtils.bookmarks.recordIdToGuid(fxrecord.id));
-    do_check_eq((await PlacesUtils.promiseItemGuid(id)), fxrecord.id);
-    do_check_eq(PlacesUtils.bookmarks.getItemType(id),
-                PlacesUtils.bookmarks.TYPE_BOOKMARK);
-    do_check_true(PlacesUtils.bookmarks.getBookmarkURI(id).equals(fxuri));
-    do_check_eq(PlacesUtils.bookmarks.getItemTitle(id), fxrecord.title);
-    do_check_eq(PlacesUtils.annotations.getItemAnnotation(id, "bookmarkProperties/description"),
-                fxrecord.description);
-    do_check_eq(PlacesUtils.bookmarks.getFolderIdForItem(id),
-                PlacesUtils.bookmarks.toolbarFolder);
-    do_check_eq(PlacesUtils.bookmarks.getKeywordForBookmark(id), fxrecord.keyword);
+    item = await PlacesUtils.bookmarks.fetch(fxrecord.id);
+    do_check_eq(item.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+    do_check_eq(item.url.href, "http://getfirefox.com/");
+    do_check_eq(item.title, fxrecord.title);
+    let id = await PlacesUtils.promiseItemId(item.guid);
+    let description = PlacesUtils.annotations.getItemAnnotation(id,
+      PlacesSyncUtils.bookmarks.DESCRIPTION_ANNO);
+    do_check_eq(description, fxrecord.description);
+    do_check_eq(item.parentGuid, PlacesUtils.bookmarks.toolbarGuid);
+    let keyword = await PlacesUtils.keywords.fetch(fxrecord.keyword);
+    do_check_eq(keyword.url.href, "http://getfirefox.com/");
 
     _("Have the store create a new record object. Verify that it has the same data.");
     let newrecord = await store.createRecord(fxrecord.id);
     do_check_true(newrecord instanceof Bookmark);
     for (let property of ["type", "bmkUri", "description", "title",
                           "keyword", "parentName", "parentid"]) {
       do_check_eq(newrecord[property], fxrecord[property]);
     }
     do_check_true(Utils.deepEquals(newrecord.tags.sort(),
                                    fxrecord.tags.sort()));
 
     _("The calculated sort index is based on frecency data.");
     do_check_true(newrecord.sortindex >= 150);
 
     _("Create a record with some values missing.");
     let tbrecord = new Bookmark("bookmarks", "thunderbird1");
-    tbrecord.bmkUri        = tburi.spec;
+    tbrecord.bmkUri        = "http://getthunderbird.com/";
     tbrecord.parentName    = "Bookmarks Toolbar";
     tbrecord.parentid      = "toolbar";
     await store.applyIncoming(tbrecord);
 
     _("Verify it has been created correctly.");
-    id = await PlacesUtils.promiseItemId(PlacesSyncUtils.bookmarks.recordIdToGuid(tbrecord.id));
-    do_check_eq((await PlacesUtils.promiseItemGuid(id)), tbrecord.id);
-    do_check_eq(PlacesUtils.bookmarks.getItemType(id),
-                PlacesUtils.bookmarks.TYPE_BOOKMARK);
-    do_check_true(PlacesUtils.bookmarks.getBookmarkURI(id).equals(tburi));
-    do_check_eq(PlacesUtils.bookmarks.getItemTitle(id), "");
-    let error;
-    try {
-      PlacesUtils.annotations.getItemAnnotation(id, "bookmarkProperties/description");
-    } catch (ex) {
-      error = ex;
-    }
-    do_check_eq(error.result, Cr.NS_ERROR_NOT_AVAILABLE);
-    do_check_eq(PlacesUtils.bookmarks.getFolderIdForItem(id),
-                PlacesUtils.bookmarks.toolbarFolder);
-    do_check_eq(PlacesUtils.bookmarks.getKeywordForBookmark(id), null);
+    item = await PlacesUtils.bookmarks.fetch(tbrecord.id);
+    id = await PlacesUtils.promiseItemId(item.guid);
+    do_check_eq(item.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+    do_check_eq(item.url.href, "http://getthunderbird.com/");
+    do_check_eq(item.title, "");
+    do_check_throws(function() {
+      PlacesUtils.annotations.getItemAnnotation(id,
+        PlacesSyncUtils.bookmarks.DESCRIPTION_ANNO);
+    }, Cr.NS_ERROR_NOT_AVAILABLE);
+    do_check_eq(item.parentGuid, PlacesUtils.bookmarks.toolbarGuid);
+    keyword = await PlacesUtils.keywords.fetch({
+      url: "http://getthunderbird.com/",
+    });
+    do_check_null(keyword);
   } finally {
     _("Clean up.");
     await store.wipe();
     await engine.finalize();
   }
 });
 
 add_task(async function test_bookmark_update() {
   let engine = new BookmarksEngine(Service);
   let store = engine._store;
 
   try {
     _("Create a bookmark whose values we'll change.");
-    let bmk1_id = PlacesUtils.bookmarks.insertBookmark(
-      PlacesUtils.bookmarks.toolbarFolder, fxuri,
-      PlacesUtils.bookmarks.DEFAULT_INDEX,
-      "Get Firefox!");
+    let bmk1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      url: "http://getfirefox.com/",
+      title: "Get Firefox!",
+    });
+    let bmk1_id = await PlacesUtils.promiseItemId(bmk1.guid);
     PlacesUtils.annotations.setItemAnnotation(
-      bmk1_id, "bookmarkProperties/description", "Firefox is awesome.", 0,
-      PlacesUtils.annotations.EXPIRE_NEVER);
-    PlacesUtils.bookmarks.setKeywordForBookmark(bmk1_id, "firefox");
-    let bmk1_guid = await PlacesUtils.promiseItemGuid(bmk1_id);
+      bmk1_id, PlacesSyncUtils.bookmarks.DESCRIPTION_ANNO,
+      "Firefox is awesome.", 0, PlacesUtils.annotations.EXPIRE_NEVER);
+    await PlacesUtils.keywords.insert({
+      url: "http://getfirefox.com/",
+      keyword: "firefox",
+    });
 
     _("Update the record with some null values.");
-    let record = await store.createRecord(bmk1_guid);
+    let record = await store.createRecord(bmk1.guid);
     record.title = null;
     record.description = null;
     record.keyword = null;
     record.tags = null;
     await store.applyIncoming(record);
 
     _("Verify that the values have been cleared.");
     do_check_throws(function() {
       PlacesUtils.annotations.getItemAnnotation(
-        bmk1_id, "bookmarkProperties/description");
+        bmk1_id, PlacesSyncUtils.bookmarks.DESCRIPTION_ANNO);
     }, Cr.NS_ERROR_NOT_AVAILABLE);
-    do_check_eq(PlacesUtils.bookmarks.getItemTitle(bmk1_id), "");
-    do_check_eq(PlacesUtils.bookmarks.getKeywordForBookmark(bmk1_id), null);
+    let item = await PlacesUtils.bookmarks.fetch(bmk1.guid);
+    do_check_eq(item.title, "");
+    let keyword = await PlacesUtils.keywords.fetch({
+      url: "http://getfirefox.com/",
+    });
+    do_check_null(keyword);
   } finally {
     _("Clean up.");
     await store.wipe();
     await engine.finalize();
   }
 });
 
 add_task(async function test_bookmark_createRecord() {
   let engine = Service.engineManager.get("bookmarks");
   let store = engine._store;
 
   try {
     _("Create a bookmark without a description or title.");
-    let bmk1_id = PlacesUtils.bookmarks.insertBookmark(
-      PlacesUtils.bookmarks.toolbarFolder, fxuri,
-      PlacesUtils.bookmarks.DEFAULT_INDEX, null);
-    let bmk1_guid = await PlacesUtils.promiseItemGuid(bmk1_id);
+    let bmk1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      url: "http://getfirefox.com/",
+    });
 
     _("Verify that the record is created accordingly.");
-    let record = await store.createRecord(bmk1_guid);
+    let record = await store.createRecord(bmk1.guid);
     do_check_eq(record.title, "");
     do_check_eq(record.description, null);
     do_check_eq(record.keyword, null);
 
   } finally {
     _("Clean up.");
     await store.wipe();
   }
@@ -194,249 +196,258 @@ add_task(async function test_folder_crea
     _("Create a folder.");
     let folder = new BookmarkFolder("bookmarks", "testfolder-1");
     folder.parentName = "Bookmarks Toolbar";
     folder.parentid   = "toolbar";
     folder.title      = "Test Folder";
     await store.applyIncoming(folder);
 
     _("Verify it has been created correctly.");
-    let id = await PlacesUtils.promiseItemId(PlacesSyncUtils.bookmarks.recordIdToGuid(folder.id));
-    do_check_eq(PlacesUtils.bookmarks.getItemType(id),
-                PlacesUtils.bookmarks.TYPE_FOLDER);
-    do_check_eq(PlacesUtils.bookmarks.getItemTitle(id), folder.title);
-    do_check_eq(PlacesUtils.bookmarks.getFolderIdForItem(id),
-                PlacesUtils.bookmarks.toolbarFolder);
+    let item = await PlacesUtils.bookmarks.fetch(folder.id);
+    do_check_eq(item.type, PlacesUtils.bookmarks.TYPE_FOLDER);
+    do_check_eq(item.title, folder.title);
+    do_check_eq(item.parentGuid, PlacesUtils.bookmarks.toolbarGuid);
 
     _("Have the store create a new record object. Verify that it has the same data.");
     let newrecord = await store.createRecord(folder.id);
     do_check_true(newrecord instanceof BookmarkFolder);
-    for (let property of ["title", "parentName", "parentid"])
+    for (let property of ["title", "parentName", "parentid"]) {
       do_check_eq(newrecord[property], folder[property]);
+    }
 
     _("Folders have high sort index to ensure they're synced first.");
     do_check_eq(newrecord.sortindex, 1000000);
   } finally {
     _("Clean up.");
     await store.wipe();
     await engine.finalize();
   }
 });
 
 add_task(async function test_folder_createRecord() {
   let engine = Service.engineManager.get("bookmarks");
   let store = engine._store;
 
   try {
     _("Create a folder.");
-    let folder1_id = PlacesUtils.bookmarks.createFolder(
-      PlacesUtils.bookmarks.toolbarFolder, "Folder1", 0);
-    let folder1_guid = await PlacesUtils.promiseItemGuid(folder1_id);
+    let folder1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+      title: "Folder1",
+    });
 
     _("Create two bookmarks in that folder without assigning them GUIDs.");
-    let bmk1_id = PlacesUtils.bookmarks.insertBookmark(
-      folder1_id, fxuri, PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
-    let bmk2_id = PlacesUtils.bookmarks.insertBookmark(
-      folder1_id, tburi, PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Thunderbird!");
+    let bmk1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: folder1.guid,
+      url: "http://getfirefox.com/",
+      title: "Get Firefox!",
+    });
+    let bmk2 = await PlacesUtils.bookmarks.insert({
+      parentGuid: folder1.guid,
+      url: "http://getthunderbird.com/",
+      title: "Get Thunderbird!",
+    });
 
     _("Create a record for the folder and verify basic properties.");
-    let record = await store.createRecord(folder1_guid);
+    let record = await store.createRecord(folder1.guid);
     do_check_true(record instanceof BookmarkFolder);
     do_check_eq(record.title, "Folder1");
     do_check_eq(record.parentid, "toolbar");
     do_check_eq(record.parentName, "Bookmarks Toolbar");
 
     _("Verify the folder's children. Ensures that the bookmarks were given GUIDs.");
-    let bmk1_guid = await PlacesUtils.promiseItemGuid(bmk1_id);
-    let bmk2_guid = await PlacesUtils.promiseItemGuid(bmk2_id);
-    do_check_eq(record.children.length, 2);
-    do_check_eq(record.children[0], bmk1_guid);
-    do_check_eq(record.children[1], bmk2_guid);
+    do_check_matches(record.children, [bmk1.guid, bmk2.guid]);
 
   } finally {
     _("Clean up.");
     await store.wipe();
   }
 });
 
 add_task(async function test_deleted() {
   let engine = new BookmarksEngine(Service);
   let store = engine._store;
 
   try {
     _("Create a bookmark that will be deleted.");
-    let bmk1_id = PlacesUtils.bookmarks.insertBookmark(
-      PlacesUtils.bookmarks.toolbarFolder, fxuri,
-      PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
-    let bmk1_guid = await PlacesUtils.promiseItemGuid(bmk1_id);
+    let bmk1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      url: "http://getfirefox.com/",
+      title: "Get Firefox!",
+    });
 
     _("Delete the bookmark through the store.");
-    let record = new PlacesItem("bookmarks", bmk1_guid);
+    let record = new PlacesItem("bookmarks", bmk1.guid);
     record.deleted = true;
     await store.applyIncoming(record);
     await store.deletePending();
     _("Ensure it has been deleted.");
-    let error;
-    try {
-      PlacesUtils.bookmarks.getBookmarkURI(bmk1_id);
-    } catch (ex) {
-      error = ex;
-    }
-    do_check_eq(error.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+    let item = await PlacesUtils.bookmarks.fetch(bmk1.guid);
+    do_check_null(item);
 
-    let newrec = await store.createRecord(bmk1_guid);
+    let newrec = await store.createRecord(bmk1.guid);
     do_check_eq(newrec.deleted, true);
 
   } finally {
     _("Clean up.");
     await store.wipe();
     await engine.finalize();
   }
 });
 
 add_task(async function test_move_folder() {
   let engine = new BookmarksEngine(Service);
   let store = engine._store;
 
   try {
     _("Create two folders and a bookmark in one of them.");
-    let folder1_id = PlacesUtils.bookmarks.createFolder(
-      PlacesUtils.bookmarks.toolbarFolder, "Folder1", 0);
-    let folder1_guid = await PlacesUtils.promiseItemGuid(folder1_id);
-    let folder2_id = PlacesUtils.bookmarks.createFolder(
-      PlacesUtils.bookmarks.toolbarFolder, "Folder2", 0);
-    let folder2_guid = await PlacesUtils.promiseItemGuid(folder2_id);
-    let bmk_id = PlacesUtils.bookmarks.insertBookmark(
-      folder1_id, fxuri, PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
-    let bmk_guid = await PlacesUtils.promiseItemGuid(bmk_id);
+    let folder1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+      title: "Folder1",
+    });
+    let folder2 = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+      title: "Folder2",
+    });
+    let bmk = await PlacesUtils.bookmarks.insert({
+      parentGuid: folder1.guid,
+      url: "http://getfirefox.com/",
+      title: "Get Firefox!",
+    });
 
     _("Get a record, reparent it and apply it to the store.");
-    let record = await store.createRecord(bmk_guid);
-    do_check_eq(record.parentid, folder1_guid);
-    record.parentid = folder2_guid;
+    let record = await store.createRecord(bmk.guid);
+    do_check_eq(record.parentid, folder1.guid);
+    record.parentid = folder2.guid;
     await store.applyIncoming(record);
 
     _("Verify the new parent.");
-    let new_folder_id = PlacesUtils.bookmarks.getFolderIdForItem(bmk_id);
-    do_check_eq((await PlacesUtils.promiseItemGuid(new_folder_id)), folder2_guid);
+    let movedBmk = await PlacesUtils.bookmarks.fetch(bmk.guid);
+    do_check_eq(movedBmk.parentGuid, folder2.guid);
   } finally {
     _("Clean up.");
     await store.wipe();
     await engine.finalize();
   }
 });
 
 add_task(async function test_move_order() {
   let engine = new BookmarksEngine(Service);
   let store = engine._store;
   let tracker = engine._tracker;
 
   // Make sure the tracker is turned on.
   Svc.Obs.notify("weave:engine:start-tracking");
   try {
     _("Create two bookmarks");
-    let bmk1_id = PlacesUtils.bookmarks.insertBookmark(
-      PlacesUtils.bookmarks.toolbarFolder, fxuri,
-      PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
-    let bmk1_guid = await PlacesUtils.promiseItemGuid(bmk1_id);
-    let bmk2_id = PlacesUtils.bookmarks.insertBookmark(
-      PlacesUtils.bookmarks.toolbarFolder, tburi,
-      PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Thunderbird!");
-    let bmk2_guid = await PlacesUtils.promiseItemGuid(bmk2_id);
+    let bmk1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      url: "http://getfirefox.com/",
+      title: "Get Firefox!",
+    });
+    let bmk2 = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      url: "http://getthunderbird.com/",
+      title: "Get Thunderbird!",
+    });
 
     _("Verify order.");
-    do_check_eq(PlacesUtils.bookmarks.getItemIndex(bmk1_id), 0);
-    do_check_eq(PlacesUtils.bookmarks.getItemIndex(bmk2_id), 1);
+    let childIds = await PlacesSyncUtils.bookmarks.fetchChildRecordIds(
+      "toolbar");
+    do_check_matches(childIds, [bmk1.guid, bmk2.guid]);
     let toolbar = await store.createRecord("toolbar");
-    do_check_eq(toolbar.children.length, 2);
-    do_check_eq(toolbar.children[0], bmk1_guid);
-    do_check_eq(toolbar.children[1], bmk2_guid);
+    do_check_matches(toolbar.children, [bmk1.guid, bmk2.guid]);
 
     _("Move bookmarks around.");
     store._childrenToOrder = {};
-    toolbar.children = [bmk2_guid, bmk1_guid];
+    toolbar.children = [bmk2.guid, bmk1.guid];
     await store.applyIncoming(toolbar);
     // Bookmarks engine does this at the end of _processIncoming
     tracker.ignoreAll = true;
     await store._orderChildren();
     tracker.ignoreAll = false;
     delete store._childrenToOrder;
 
     _("Verify new order.");
-    do_check_eq(PlacesUtils.bookmarks.getItemIndex(bmk2_id), 0);
-    do_check_eq(PlacesUtils.bookmarks.getItemIndex(bmk1_id), 1);
+    let newChildIds = await PlacesSyncUtils.bookmarks.fetchChildRecordIds(
+      "toolbar");
+    do_check_matches(newChildIds, [bmk2.guid, bmk1.guid]);
 
   } finally {
     Svc.Obs.notify("weave:engine:stop-tracking");
     _("Clean up.");
     await store.wipe();
     await engine.finalize();
   }
 });
 
 add_task(async function test_orphan() {
   let engine = new BookmarksEngine(Service);
   let store = engine._store;
 
   try {
 
     _("Add a new bookmark locally.");
-    let bmk1_id = PlacesUtils.bookmarks.insertBookmark(
-      PlacesUtils.bookmarks.toolbarFolder, fxuri,
-      PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
-    let bmk1_guid = await PlacesUtils.promiseItemGuid(bmk1_id);
-    do_check_eq(PlacesUtils.bookmarks.getFolderIdForItem(bmk1_id),
-                PlacesUtils.bookmarks.toolbarFolder);
-    let error;
-    try {
-      PlacesUtils.annotations.getItemAnnotation(bmk1_id, PARENT_ANNO);
-    } catch (ex) {
-      error = ex;
-    }
-    do_check_eq(error.result, Cr.NS_ERROR_NOT_AVAILABLE);
+    let bmk1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      url: "http://getfirefox.com/",
+      title: "Get Firefox!",
+    });
+    let bmk1_id = await PlacesUtils.promiseItemId(bmk1.guid);
+    do_check_throws(function() {
+      PlacesUtils.annotations.getItemAnnotation(bmk1_id,
+        PlacesSyncUtils.bookmarks.SYNC_PARENT_ANNO);
+    }, Cr.NS_ERROR_NOT_AVAILABLE);
 
     _("Apply a server record that is the same but refers to non-existent folder.");
-    let record = await store.createRecord(bmk1_guid);
+    let record = await store.createRecord(bmk1.guid);
     record.parentid = "non-existent";
     await store.applyIncoming(record);
 
     _("Verify that bookmark has been flagged as orphan, has not moved.");
-    do_check_eq(PlacesUtils.bookmarks.getFolderIdForItem(bmk1_id),
-                PlacesUtils.bookmarks.toolbarFolder);
-    do_check_eq(PlacesUtils.annotations.getItemAnnotation(bmk1_id, PARENT_ANNO),
-                "non-existent");
+    let item = await PlacesUtils.bookmarks.fetch(bmk1.guid);
+    do_check_eq(item.parentGuid, PlacesUtils.bookmarks.toolbarGuid);
+    let orphanAnno = PlacesUtils.annotations.getItemAnnotation(bmk1_id,
+      PlacesSyncUtils.bookmarks.SYNC_PARENT_ANNO);
+    do_check_eq(orphanAnno, "non-existent");
 
   } finally {
     _("Clean up.");
     await store.wipe();
     await engine.finalize();
   }
 });
 
 add_task(async function test_reparentOrphans() {
   let engine = new BookmarksEngine(Service);
   let store = engine._store;
 
   try {
-    let folder1_id = PlacesUtils.bookmarks.createFolder(
-      PlacesUtils.bookmarks.toolbarFolder, "Folder1", 0);
-    let folder1_guid = await PlacesUtils.promiseItemGuid(folder1_id);
+    let folder1 = await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+      title: "Folder1",
+    });
+    let folder1_id = await PlacesUtils.promiseItemId(folder1.guid);
 
     _("Create a bogus orphan record and write the record back to the store to trigger _reparentOrphans.");
     PlacesUtils.annotations.setItemAnnotation(
-      folder1_id, PARENT_ANNO, folder1_guid, 0,
+      folder1_id, PlacesSyncUtils.bookmarks.SYNC_PARENT_ANNO, folder1.guid, 0,
       PlacesUtils.annotations.EXPIRE_NEVER);
-    let record = await store.createRecord(folder1_guid);
+    let record = await store.createRecord(folder1.guid);
     record.title = "New title for Folder 1";
     store._childrenToOrder = {};
     await store.applyIncoming(record);
 
     _("Verify that is has been marked as an orphan even though it couldn't be moved into itself.");
-    do_check_eq(PlacesUtils.annotations.getItemAnnotation(folder1_id, PARENT_ANNO),
-                folder1_guid);
+    let orphanAnno = PlacesUtils.annotations.getItemAnnotation(folder1_id,
+      PlacesSyncUtils.bookmarks.SYNC_PARENT_ANNO);
+    do_check_eq(orphanAnno, folder1.guid);
 
   } finally {
     _("Clean up.");
     await store.wipe();
     await engine.finalize();
   }
 });
 
@@ -456,24 +467,19 @@ add_task(async function test_empty_query
   await store.applyIncoming(record);
 
   delete record.folderName;
   await store.applyIncoming(record);
 
   await engine.finalize();
 });
 
-function assertDeleted(id) {
-  let error;
-  try {
-    PlacesUtils.bookmarks.getItemType(id);
-  } catch (e) {
-    error = e;
-  }
-  equal(error.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+async function assertDeleted(guid) {
+  let item = await PlacesUtils.bookmarks.fetch(guid);
+  ok(!item);
 }
 
 add_task(async function test_delete_buffering() {
   let engine = new BookmarksEngine(Service);
   let store = engine._store;
 
   await store.wipe();
   await PlacesTestUtils.markBookmarksAsSynced();
@@ -483,86 +489,82 @@ add_task(async function test_delete_buff
     let folder = new BookmarkFolder("bookmarks", "testfolder-1");
     folder.parentName = "Bookmarks Toolbar";
     folder.parentid = "toolbar";
     folder.title = "Test Folder";
     await store.applyIncoming(folder);
 
 
     let fxRecord = new Bookmark("bookmarks", "get-firefox1");
-    fxRecord.bmkUri        = fxuri.spec;
+    fxRecord.bmkUri        = "http://getfirefox.com/";
     fxRecord.title         = "Get Firefox!";
     fxRecord.parentName    = "Test Folder";
     fxRecord.parentid      = "testfolder-1";
 
     let tbRecord = new Bookmark("bookmarks", "get-tndrbrd1");
-    tbRecord.bmkUri        = tburi.spec;
+    tbRecord.bmkUri        = "http://getthunderbird.com";
     tbRecord.title         = "Get Thunderbird!";
     tbRecord.parentName    = "Test Folder";
     tbRecord.parentid      = "testfolder-1";
 
     await store.applyIncoming(fxRecord);
     await store.applyIncoming(tbRecord);
 
-    let folderId = await PlacesUtils.promiseItemId(PlacesSyncUtils.bookmarks.recordIdToGuid(folder.id));
-    let fxRecordId = await PlacesUtils.promiseItemId(PlacesSyncUtils.bookmarks.recordIdToGuid(fxRecord.id));
-    let tbRecordId = await PlacesUtils.promiseItemId(PlacesSyncUtils.bookmarks.recordIdToGuid(tbRecord.id));
-
     _("Check everything was created correctly.");
 
-    equal(PlacesUtils.bookmarks.getItemType(fxRecordId),
-          PlacesUtils.bookmarks.TYPE_BOOKMARK);
-    equal(PlacesUtils.bookmarks.getItemType(tbRecordId),
-          PlacesUtils.bookmarks.TYPE_BOOKMARK);
-    equal(PlacesUtils.bookmarks.getItemType(folderId),
-          PlacesUtils.bookmarks.TYPE_FOLDER);
+    let folderItem = await PlacesUtils.bookmarks.fetch(folder.id);
+    let fxItem = await PlacesUtils.bookmarks.fetch(fxRecord.id);
+    let tbItem = await PlacesUtils.bookmarks.fetch(tbRecord.id);
 
-    equal(PlacesUtils.bookmarks.getFolderIdForItem(fxRecordId), folderId);
-    equal(PlacesUtils.bookmarks.getFolderIdForItem(tbRecordId), folderId);
-    equal(PlacesUtils.bookmarks.getFolderIdForItem(folderId),
-          PlacesUtils.bookmarks.toolbarFolder);
+    equal(fxItem.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+    equal(tbItem.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+    equal(folderItem.type, PlacesUtils.bookmarks.TYPE_FOLDER);
+
+    equal(fxItem.parentGuid, folderItem.guid);
+    equal(tbItem.parentGuid, folderItem.guid);
+    equal(folderItem.parentGuid, PlacesUtils.bookmarks.toolbarGuid);
 
     _("Delete the folder and one bookmark.");
 
     let deleteFolder = new PlacesItem("bookmarks", "testfolder-1");
     deleteFolder.deleted = true;
 
     let deleteFxRecord = new PlacesItem("bookmarks", "get-firefox1");
     deleteFxRecord.deleted = true;
 
     await store.applyIncoming(deleteFolder);
     await store.applyIncoming(deleteFxRecord);
 
     _("Check that we haven't deleted them yet, but that the deletions are queued");
-    // these will throw if we've deleted them
-    equal(PlacesUtils.bookmarks.getItemType(fxRecordId),
-           PlacesUtils.bookmarks.TYPE_BOOKMARK);
+    // these will return `null` if we've deleted them
+    fxItem = await PlacesUtils.bookmarks.fetch(fxRecord.id);
+    ok(fxItem);
 
-    equal(PlacesUtils.bookmarks.getItemType(folderId),
-           PlacesUtils.bookmarks.TYPE_FOLDER);
+    folderItem = await PlacesUtils.bookmarks.fetch(folder.id);
+    ok(folderItem);
 
-    equal(PlacesUtils.bookmarks.getFolderIdForItem(fxRecordId), folderId);
+    equal(fxItem.parentGuid, folderItem.guid);
 
     ok(store._itemsToDelete.has(folder.id));
     ok(store._itemsToDelete.has(fxRecord.id));
     ok(!store._itemsToDelete.has(tbRecord.id));
 
     _("Process pending deletions and ensure that the right things are deleted.");
     let newChangeRecords = await store.deletePending();
 
     deepEqual(Object.keys(newChangeRecords).sort(), ["get-tndrbrd1", "toolbar"]);
 
-    assertDeleted(fxRecordId);
-    assertDeleted(folderId);
+    await assertDeleted(fxItem.guid);
+    await assertDeleted(folderItem.guid);
 
     ok(!store._itemsToDelete.has(folder.id));
     ok(!store._itemsToDelete.has(fxRecord.id));
 
-    equal(PlacesUtils.bookmarks.getFolderIdForItem(tbRecordId),
-          PlacesUtils.bookmarks.toolbarFolder);
+    tbItem = await PlacesUtils.bookmarks.fetch(tbRecord.id);
+    equal(tbItem.parentGuid, PlacesUtils.bookmarks.toolbarGuid);
 
   } finally {
     _("Clean up.");
     await store.wipe();
     await engine.finalize();
   }
 });
 
--- a/services/sync/tests/unit/test_bookmark_tracker.js
+++ b/services/sync/tests/unit/test_bookmark_tracker.js
@@ -1,20 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Cu.import("resource://services-common/utils.js");
-Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://testing-common/PlacesTestUtils.jsm");
 Cu.import("resource:///modules/PlacesUIUtils.jsm");
 
 let engine;
 let store;
 let tracker;
 
 const DAY_IN_MS = 24 * 60 * 60 * 1000;
 
--- a/services/sync/tests/unit/test_engine_changes_during_sync.js
+++ b/services/sync/tests/unit/test_engine_changes_during_sync.js
@@ -1,11 +1,10 @@
 Cu.import("resource://gre/modules/FormHistory.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/engines/history.js");
 Cu.import("resource://services-sync/engines/forms.js");
 Cu.import("resource://services-sync/engines/passwords.js");
 Cu.import("resource://services-sync/engines/prefs.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
--- a/services/sync/tests/unit/test_history_store.js
+++ b/services/sync/tests/unit/test_history_store.js
@@ -1,45 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://testing-common/PlacesTestUtils.jsm");
-Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-common/async.js");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-sync/engines/history.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 
 const TIMESTAMP1 = (Date.now() - 103406528) * 1000;
 const TIMESTAMP2 = (Date.now() - 6592903) * 1000;
 const TIMESTAMP3 = (Date.now() - 123894) * 1000;
 
-function queryPlaces(uri, options) {
-  let query = PlacesUtils.history.getNewQuery();
-  query.uri = uri;
-  let res = PlacesUtils.history.executeQuery(query, options);
-  res.root.containerOpen = true;
-
-  let results = [];
-  for (let i = 0; i < res.root.childCount; i++)
-    results.push(res.root.getChild(i));
-  res.root.containerOpen = false;
-  return results;
-}
-
-function queryHistoryVisits(uri) {
-  let options = PlacesUtils.history.getNewQueryOptions();
-  options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
-  options.resultType = Ci.nsINavHistoryQueryOptions.RESULTS_AS_VISIT;
-  options.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING;
-  return queryPlaces(uri, options);
-}
-
 function promiseOnVisitObserved() {
   return new Promise(res => {
     PlacesUtils.history.addObserver({
       onBeginUpdateBatch: function onBeginUpdateBatch() {},
       onEndUpdateBatch: function onEndUpdateBatch() {},
       onPageChanged: function onPageChanged() {},
       onTitleChanged: function onTitleChanged() {
       },
@@ -113,70 +90,78 @@ add_task(async function test_store() {
   let onVisitObserved = promiseOnVisitObserved();
   await applyEnsureNoFailures([
     {id: fxguid,
      histUri: record.histUri,
      title: "Hol Dir Firefox!",
      visits: [record.visits[0], secondvisit]}
   ]);
   await onVisitObserved;
-  try {
-    let queryres = queryHistoryVisits(fxuri);
-    do_check_eq(queryres.length, 2);
-    do_check_eq(queryres[0].time, TIMESTAMP1);
-    do_check_eq(queryres[0].title, "Hol Dir Firefox!");
-    do_check_eq(queryres[1].time, TIMESTAMP2);
-    do_check_eq(queryres[1].title, "Hol Dir Firefox!");
-  } catch (ex) {
-    PlacesTestUtils.clearHistory();
-    do_throw(ex);
-  }
+  let queryres = await PlacesUtils.history.fetch(fxuri.spec, {
+    includeVisits: true,
+  });
+  do_check_eq(queryres.title, "Hol Dir Firefox!");
+  do_check_matches(queryres.visits, [{
+    date: new Date(TIMESTAMP2 / 1000),
+    transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+  }, {
+    date: new Date(TIMESTAMP1 / 1000),
+    transition: Ci.nsINavHistoryService.TRANSITION_LINK,
+  }]);
+  await PlacesTestUtils.clearHistory();
 });
 
 add_task(async function test_store_create() {
   _("Create a brand new record through the store.");
   tbguid = Utils.makeGUID();
   tburi = CommonUtils.makeURI("http://getthunderbird.com");
   let onVisitObserved = promiseOnVisitObserved();
   await applyEnsureNoFailures([
     {id: tbguid,
      histUri: tburi.spec,
      title: "The bird is the word!",
      visits: [{date: TIMESTAMP3,
                type: Ci.nsINavHistoryService.TRANSITION_TYPED}]}
   ]);
   await onVisitObserved;
-  try {
-    do_check_true((await store.itemExists(tbguid)));
-    do_check_attribute_count(await store.getAllIDs(), 2);
-    let queryres = queryHistoryVisits(tburi);
-    do_check_eq(queryres.length, 1);
-    do_check_eq(queryres[0].time, TIMESTAMP3);
-    do_check_eq(queryres[0].title, "The bird is the word!");
-  } catch (ex) {
-    PlacesTestUtils.clearHistory();
-    do_throw(ex);
-  }
+  do_check_true((await store.itemExists(tbguid)));
+  do_check_attribute_count(await store.getAllIDs(), 1);
+  let queryres = await PlacesUtils.history.fetch(tburi.spec, {
+    includeVisits: true,
+  });
+  do_check_eq(queryres.title, "The bird is the word!");
+  do_check_matches(queryres.visits, [{
+    date: new Date(TIMESTAMP3 / 1000),
+    transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+  }]);
+  await PlacesTestUtils.clearHistory();
 });
 
 add_task(async function test_null_title() {
   _("Make sure we handle a null title gracefully (it can happen in some cases, e.g. for resource:// URLs)");
   let resguid = Utils.makeGUID();
   let resuri = CommonUtils.makeURI("unknown://title");
   await applyEnsureNoFailures([
     {id: resguid,
      histUri: resuri.spec,
      title: null,
      visits: [{date: TIMESTAMP3,
                type: Ci.nsINavHistoryService.TRANSITION_TYPED}]}
   ]);
-  do_check_attribute_count((await store.getAllIDs()), 3);
-  let queryres = queryHistoryVisits(resuri);
-  do_check_eq(queryres.length, 1);
-  do_check_eq(queryres[0].time, TIMESTAMP3);
+  do_check_attribute_count((await store.getAllIDs()), 1);
+
+  let queryres = await PlacesUtils.history.fetch(resuri.spec, {
+    includeVisits: true,
+  });
+  do_check_eq(queryres.title, "");
+  do_check_matches(queryres.visits, [{
+    date: new Date(TIMESTAMP3 / 1000),
+    transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+  }]);
+  await PlacesTestUtils.clearHistory();
 });
 
 add_task(async function test_invalid_records() {
   _("Make sure we handle invalid URLs in places databases gracefully.");
   await PlacesUtils.withConnectionWrapper("test_invalid_record", async function(db) {
     await db.execute(
       "INSERT INTO moz_places "
       + "(url, url_hash, title, rev_host, visit_count, last_visit_date) "
@@ -185,17 +170,17 @@ add_task(async function test_invalid_rec
     // Add the corresponding visit to retain database coherence.
     await db.execute(
       "INSERT INTO moz_historyvisits "
       + "(place_id, visit_date, visit_type, session) "
       + "VALUES ((SELECT id FROM moz_places WHERE url_hash = hash('invalid-uri') AND url = 'invalid-uri'), "
       + TIMESTAMP3 + ", " + Ci.nsINavHistoryService.TRANSITION_TYPED + ", 1)"
     );
   });
-  do_check_attribute_count((await store.getAllIDs()), 4);
+  do_check_attribute_count((await store.getAllIDs()), 1);
 
   _("Make sure we report records with invalid URIs.");
   let invalid_uri_guid = Utils.makeGUID();
   let failed = await store.applyIncomingBatch([{
     id: invalid_uri_guid,
     histUri: ":::::::::::::::",
     title: "Doesn't have a valid URI",
     visits: [{date: TIMESTAMP3,
@@ -332,24 +317,30 @@ add_task(async function test_clamp_visit
   }], "Should not clamp valid visit dates");
 });
 
 add_task(async function test_remove() {
   _("Remove an existent record and a non-existent from the store.");
   await applyEnsureNoFailures([{id: fxguid, deleted: true},
                          {id: Utils.makeGUID(), deleted: true}]);
   do_check_false((await store.itemExists(fxguid)));
-  let queryres = queryHistoryVisits(fxuri);
-  do_check_eq(queryres.length, 0);
+  let queryres = await PlacesUtils.history.fetch(fxuri.spec, {
+    includeVisits: true,
+  });
+  do_check_null(queryres);
 
   _("Make sure wipe works.");
   await store.wipe();
   do_check_empty((await store.getAllIDs()));
-  queryres = queryHistoryVisits(fxuri);
-  do_check_eq(queryres.length, 0);
-  queryres = queryHistoryVisits(tburi);
-  do_check_eq(queryres.length, 0);
+  queryres = await PlacesUtils.history.fetch(fxuri.spec, {
+    includeVisits: true,
+  });
+  do_check_null(queryres);
+  queryres = await PlacesUtils.history.fetch(tburi.spec, {
+    includeVisits: true,
+  });
+  do_check_null(queryres);
 });
 
-add_test(function cleanup() {
+add_task(async function cleanup() {
   _("Clean up.");
-  PlacesTestUtils.clearHistory().then(run_next_test);
+  await PlacesTestUtils.clearHistory();
 });
--- a/services/sync/tests/unit/test_places_guid_downgrade.js
+++ b/services/sync/tests/unit/test_places_guid_downgrade.js
@@ -7,19 +7,16 @@ Cu.import("resource://services-sync/engi
 Cu.import("resource://services-sync/engines/history.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/service.js");
 
 const kDBName = "places.sqlite";
 const storageSvc = Cc["@mozilla.org/storage/service;1"]
                      .getService(Ci.mozIStorageService);
 
-const fxuri = CommonUtils.makeURI("http://getfirefox.com/");
-const tburi = CommonUtils.makeURI("http://getthunderbird.com/");
-
 function setPlacesDatabase(aFileName) {
   removePlacesDatabase();
   _("Copying over places.sqlite.");
   let file = do_get_file(aFileName);
   file.copyTo(gSyncProfile, kDBName);
 }
 
 function removePlacesDatabase() {
@@ -88,36 +85,36 @@ add_test(function test_initial_state() {
 
 add_task(async function test_history_guids() {
   let engine = new HistoryEngine(Service);
   await engine.initialize();
   let store = engine._store;
 
   let places = [
     {
-      uri: fxuri,
+      url: "http://getfirefox.com/",
       title: "Get Firefox!",
       visits: [{
-        visitDate: Date.now() * 1000,
-        transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
+        date: new Date(),
+        transition: Ci.nsINavHistoryService.TRANSITION_LINK
       }]
     },
     {
-      uri: tburi,
+      url: "http://getthunderbird.com/",
       title: "Get Thunderbird!",
       visits: [{
-        visitDate: Date.now() * 1000,
-        transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
+        date: new Date(),
+        transition: Ci.nsINavHistoryService.TRANSITION_LINK
       }]
     }
   ];
 
   async function onVisitAdded() {
-    let fxguid = await store.GUIDForUri(fxuri, true);
-    let tbguid = await store.GUIDForUri(tburi, true);
+    let fxguid = await store.GUIDForUri("http://getfirefox.com/", true);
+    let tbguid = await store.GUIDForUri("http://getthunderbird.com/", true);
     dump("fxguid: " + fxguid + "\n");
     dump("tbguid: " + tbguid + "\n");
 
     _("History: Verify GUIDs are added to the guid column.");
     let db = await PlacesUtils.promiseDBConnection();
     let result = await db.execute(
       "SELECT id FROM moz_places WHERE guid = :guid",
       {guid: fxguid}
@@ -139,71 +136,60 @@ add_task(async function test_history_gui
 
     result = await db.execute(
       "SELECT a.content AS guid FROM moz_annos a WHERE guid = :guid",
       {guid: tbguid}
     );
     do_check_eq(result.length, 0);
   }
 
-  await new Promise((resolve, reject) => {
-    PlacesUtils.asyncHistory.updatePlaces(places, {
-      handleError: function handleError() {
-        do_throw("Unexpected error in adding visit.");
-      },
-      handleResult: function handleResult() {},
-      handleCompletion: () => { onVisitAdded().then(resolve, reject); },
-    });
-  });
+  await PlacesUtils.history.insertMany(places);
+  await onVisitAdded();
 });
 
 add_task(async function test_bookmark_guids() {
-  let engine = new BookmarksEngine(Service);
-  let store = engine._store;
-
-  let fxid = PlacesUtils.bookmarks.insertBookmark(
-    PlacesUtils.bookmarks.toolbarFolder,
-    fxuri,
-    PlacesUtils.bookmarks.DEFAULT_INDEX,
-    "Get Firefox!");
-  let tbid = PlacesUtils.bookmarks.insertBookmark(
-    PlacesUtils.bookmarks.toolbarFolder,
-    tburi,
-    PlacesUtils.bookmarks.DEFAULT_INDEX,
-    "Get Thunderbird!");
-
-  let fxguid = await store.GUIDForId(fxid);
-  let tbguid = await store.GUIDForId(tbid);
+  let fx = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    url: "http://getfirefox.com/",
+    title: "Get Firefox!",
+  });
+  let fxid = await PlacesUtils.promiseItemId(fx.guid);
+  let tb = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    url: "http://getthunderbird.com/",
+    title: "Get Thunderbird!",
+  });
+  let tbid = await PlacesUtils.promiseItemId(tb.guid);
 
   _("Bookmarks: Verify GUIDs are added to the guid column.");
   let db = await PlacesUtils.promiseDBConnection();
   let result = await db.execute(
     "SELECT id FROM moz_bookmarks WHERE guid = :guid",
-    {guid: fxguid}
+    {guid: fx.guid}
   );
   do_check_eq(result.length, 1);
   do_check_eq(result[0].getResultByName("id"), fxid);
 
   result = await db.execute(
     "SELECT id FROM moz_bookmarks WHERE guid = :guid",
-    {guid: tbguid}
+    {guid: tb.guid}
   );
   do_check_eq(result.length, 1);
   do_check_eq(result[0].getResultByName("id"), tbid);
 
   _("Bookmarks: Verify GUIDs weren't added to annotations.");
   result = await db.execute(
     "SELECT a.content AS guid FROM moz_items_annos a WHERE guid = :guid",
-    {guid: fxguid}
+    {guid: fx.guid}
   );
   do_check_eq(result.length, 0);
 
   result = await db.execute(
     "SELECT a.content AS guid FROM moz_items_annos a WHERE guid = :guid",
-    {guid: tbguid}
+    {guid: tb.guid}
   );
   do_check_eq(result.length, 0);
 });
 
 function run_test() {
   setPlacesDatabase("places_v10_from_v11.sqlite");
 
   run_next_test();
--- a/services/sync/tests/unit/test_syncengine_sync.js
+++ b/services/sync/tests/unit/test_syncengine_sync.js
@@ -1,12 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/main.js");
 Cu.import("resource://services-sync/policies.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/resource.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
--- a/services/sync/tests/unit/test_telemetry.js
+++ b/services/sync/tests/unit/test_telemetry.js
@@ -159,32 +159,35 @@ add_task(async function test_processInco
 
 add_task(async function test_uploading() {
   let engine = new BookmarksEngine(Service);
   await engine.initialize();
   let store  = engine._store;
   let server = await serverForFoo(engine);
   await SyncTestingInfrastructure(server);
 
-  let parent = PlacesUtils.toolbarFolderId;
-  let uri = CommonUtils.makeURI("http://getfirefox.com/");
-
-  let bmk_id = PlacesUtils.bookmarks.insertBookmark(parent, uri,
-    PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
+  let bmk = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    url: "http://getfirefox.com/",
+    title: "Get Firefox!",
+  });
 
   try {
     let ping = await sync_engine_and_validate_telem(engine, false);
     ok(!!ping);
     equal(ping.engines.length, 1);
     equal(ping.engines[0].name, "bookmarks");
     ok(!!ping.engines[0].outgoing);
     greater(ping.engines[0].outgoing[0].sent, 0);
     ok(!ping.engines[0].incoming);
 
-    PlacesUtils.bookmarks.setItemTitle(bmk_id, "New Title");
+    await PlacesUtils.bookmarks.update({
+      guid: bmk.guid,
+      title: "New Title",
+    });
 
     await engine.resetClient();
 
     ping = await sync_engine_and_validate_telem(engine, false);
     equal(ping.engines.length, 1);
     equal(ping.engines[0].name, "bookmarks");
     equal(ping.engines[0].outgoing.length, 1);
     ok(!!ping.engines[0].incoming);
--- a/servo/components/config/opts.rs
+++ b/servo/components/config/opts.rs
@@ -564,17 +564,16 @@ pub fn from_cmdline_args(args: &[String]
     let (app_name, args) = args.split_first().unwrap();
 
     let mut opts = Options::new();
     opts.optflag("c", "cpu", "CPU painting");
     opts.optflag("g", "gpu", "GPU painting");
     opts.optopt("o", "output", "Output file", "output.png");
     opts.optopt("s", "size", "Size of tiles", "512");
     opts.optopt("", "device-pixel-ratio", "Device pixels per px", "");
-    opts.optopt("t", "threads", "Number of paint threads", "1");
     opts.optflagopt("p", "profile", "Time profiler flag and either a TSV output filename \
         OR an interval for output to Stdout (blank for Stdout with interval of 5s)", "10 \
         OR time.tsv");
     opts.optflagopt("", "profiler-trace-path",
                     "Path to dump a self-contained HTML timeline of profiler traces",
                     "");
     opts.optflagopt("m", "memory-profile", "Memory profiler flag and output interval", "10");
     opts.optflag("x", "exit", "Exit after load flag");
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -2397,25 +2397,25 @@ fn static_assert() {
     pub fn reset__x_lang(&mut self, other: &Self) {
         self.copy__x_lang_from(other)
     }
 
     ${impl_simple("_moz_script_level", "mScriptLevel")}
     <% impl_simple_type_with_conversion("font_language_override", "mFont.languageOverride") %>
 
     pub fn set_font_variant_alternates(&mut self,
-                                       v: longhands::font_variant_alternates::computed_value::T,
+                                       v: values::computed::font::FontVariantAlternates,
                                        device: &Device) {
         use gecko_bindings::bindings::{Gecko_ClearAlternateValues, Gecko_AppendAlternateValues};
         use gecko_bindings::bindings::Gecko_nsFont_ResetFontFeatureValuesLookup;
         use gecko_bindings::bindings::Gecko_nsFont_SetFontFeatureValuesLookup;
         % for value in "normal swash stylistic ornaments annotation styleset character_variant historical".split():
             use gecko_bindings::structs::NS_FONT_VARIANT_ALTERNATES_${value.upper()};
         % endfor
-        use self::longhands::font_variant_alternates::VariantAlternates;
+        use values::specified::font::VariantAlternates;
 
         unsafe {
             Gecko_ClearAlternateValues(&mut self.gecko.mFont, v.len());
         }
 
         if v.0.is_empty() {
             self.gecko.mFont.variantAlternates = NS_FONT_VARIANT_ALTERNATES_NORMAL as u16;
             unsafe { Gecko_nsFont_ResetFontFeatureValuesLookup(&mut self.gecko.mFont); }
@@ -2466,23 +2466,23 @@ fn static_assert() {
             Gecko_CopyAlternateValuesFrom(&mut self.gecko.mFont, &other.gecko.mFont);
         }
     }
 
     pub fn reset_font_variant_alternates(&mut self, other: &Self) {
         self.copy_font_variant_alternates_from(other)
     }
 
-    pub fn clone_font_variant_alternates(&self) -> longhands::font_variant_alternates::computed_value::T {
+    pub fn clone_font_variant_alternates(&self) -> values::computed::font::FontVariantAlternates {
         use Atom;
         % for value in "normal swash stylistic ornaments annotation styleset character_variant historical".split():
             use gecko_bindings::structs::NS_FONT_VARIANT_ALTERNATES_${value.upper()};
         % endfor
-        use properties::longhands::font_variant_alternates::VariantAlternates;
-        use properties::longhands::font_variant_alternates::VariantAlternatesList;
+        use values::specified::font::VariantAlternates;
+        use values::specified::font::VariantAlternatesList;
         use values::CustomIdent;
 
         if self.gecko.mFont.variantAlternates == NS_FONT_VARIANT_ALTERNATES_NORMAL as u16 {
             return VariantAlternatesList(vec![].into_boxed_slice());
         }
 
         let mut alternates = Vec::with_capacity(self.gecko.mFont.alternateValues.len());
         if self.gecko.mFont.variantAlternates & (NS_FONT_VARIANT_ALTERNATES_HISTORICAL as u16) != 0 {
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -665,214 +665,24 @@ macro_rules! impl_gecko_keyword_conversi
                                 "auto none normal",
                                 products="gecko",
                                 gecko_ffi_name="mFont.kerning",
                                 gecko_constant_prefix="NS_FONT_KERNING",
                                 spec="https://drafts.csswg.org/css-fonts/#propdef-font-kerning",
                                 flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
                                 animation_value_type="discrete")}
 
-<%helpers:longhand name="font-variant-alternates" products="gecko" animation_value_type="discrete"
-                   flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER"
-                   spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-alternates">
-    use properties::longhands::system_font::SystemFont;
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::CustomIdent;
-
-
-    #[derive(Clone, Debug, MallocSizeOf, PartialEq)]
-    pub enum VariantAlternates {
-        Stylistic(CustomIdent),
-        Styleset(Box<[CustomIdent]>),
-        CharacterVariant(Box<[CustomIdent]>),
-        Swash(CustomIdent),
-        Ornaments(CustomIdent),
-        Annotation(CustomIdent),
-        HistoricalForms,
-    }
-
-    #[derive(Clone, Debug, MallocSizeOf, PartialEq)]
-    pub struct VariantAlternatesList(pub Box<[VariantAlternates]>);
-
-    #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
-    pub enum SpecifiedValue {
-        Value(VariantAlternatesList),
-        System(SystemFont)
-    }
-
-    <%self:simple_system_boilerplate name="font_variant_alternates"></%self:simple_system_boilerplate>
-
-    impl ToCss for VariantAlternates {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                % for value in "swash stylistic ornaments annotation".split():
-                VariantAlternates::${to_camel_case(value)}(ref atom) => {
-                    dest.write_str("${value}")?;
-                    dest.write_str("(")?;
-                    atom.to_css(dest)?;
-                    dest.write_str(")")
-                },
-                % endfor
-                % for value in "styleset character-variant".split():
-                VariantAlternates::${to_camel_case(value)}(ref vec) => {
-                    dest.write_str("${value}")?;
-                    dest.write_str("(")?;
-                    let mut iter = vec.iter();
-                    iter.next().unwrap().to_css(dest)?;
-                    for c in iter {
-                        dest.write_str(", ")?;
-                        c.to_css(dest)?;
-                    }
-                    dest.write_str(")")
-                },
-                % endfor
-                VariantAlternates::HistoricalForms => {
-                    dest.write_str("historical-forms")
-                },
-            }
-        }
-    }
-
-    impl ToCss for VariantAlternatesList {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            if self.0.is_empty() {
-                return dest.write_str("normal");
-            }
-
-            let mut iter = self.0.iter();
-            iter.next().unwrap().to_css(dest)?;
-            for alternate in iter {
-                dest.write_str(" ")?;
-                alternate.to_css(dest)?;
-            }
-            Ok(())
-        }
-    }
-
-    impl VariantAlternatesList {
-        /// Returns the length of all variant alternates.
-        pub fn len(&self) -> usize {
-            self.0.iter().fold(0, |acc, alternate| {
-                match *alternate {
-                    % for value in "Swash Stylistic Ornaments Annotation".split():
-                        VariantAlternates::${value}(_) => {
-                            acc + 1
-                        },
-                    % endfor
-                    % for value in "Styleset CharacterVariant".split():
-                        VariantAlternates::${value}(ref slice) => {
-                            acc + slice.len()
-                        }
-                    % endfor
-                    _ => acc,
-                }
-            })
-        }
-    }
-
-    pub mod computed_value {
-        pub type T = super::VariantAlternatesList;
-    }
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        VariantAlternatesList(vec![].into_boxed_slice())
-    }
-    #[inline]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        SpecifiedValue::Value(VariantAlternatesList(vec![].into_boxed_slice()))
-    }
-
-    bitflags! {
-        #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
-        pub struct ParsingFlags: u8 {
-            const NORMAL = 0;
-            const HISTORICAL_FORMS = 0x01;
-            const STYLISTIC = 0x02;
-            const STYLESET = 0x04;
-            const CHARACTER_VARIANT = 0x08;
-            const SWASH = 0x10;
-            const ORNAMENTS = 0x20;
-            const ANNOTATION = 0x40;
-        }
-    }
-
-    /// normal |
-    ///  [ stylistic(<feature-value-name>)           ||
-    ///    historical-forms                          ||
-    ///    styleset(<feature-value-name> #)          ||
-    ///    character-variant(<feature-value-name> #) ||
-    ///    swash(<feature-value-name>)               ||
-    ///    ornaments(<feature-value-name>)           ||
-    ///    annotation(<feature-value-name>) ]
-    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
-                         -> Result<SpecifiedValue, ParseError<'i>> {
-        let mut alternates = Vec::new();
-        if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
-            return Ok(SpecifiedValue::Value(VariantAlternatesList(alternates.into_boxed_slice())));
-        }
-
-        let mut parsed_alternates = ParsingFlags::empty();
-        macro_rules! check_if_parsed(
-            ($input:expr, $flag:path) => (
-                if parsed_alternates.contains($flag) {
-                    return Err($input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
-                }
-                parsed_alternates |= $flag;
-            )
-        );
-        while let Ok(_) = input.try(|input| {
-            // FIXME: remove clone() when lifetimes are non-lexical
-            match input.next()?.clone() {
-                Token::Ident(ref ident) => {
-                    if *ident == "historical-forms" {
-                        check_if_parsed!(input, ParsingFlags::HISTORICAL_FORMS);
-                        alternates.push(VariantAlternates::HistoricalForms);
-                        Ok(())
-                    } else {
-                        return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
-                    }
-                },
-                Token::Function(ref name) => {
-                    input.parse_nested_block(|i| {
-                        match_ignore_ascii_case! { &name,
-                            % for value in "swash stylistic ornaments annotation".split():
-                            "${value}" => {
-                                check_if_parsed!(i, ParsingFlags::${value.upper()});
-                                let location = i.current_source_location();
-                                let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?;
-                                alternates.push(VariantAlternates::${to_camel_case(value)}(ident));
-                                Ok(())
-                            },
-                            % endfor
-                            % for value in "styleset character-variant".split():
-                            "${value}" => {
-                                check_if_parsed!(i, ParsingFlags:: ${to_rust_ident(value).upper()});
-                                let idents = i.parse_comma_separated(|i| {
-                                    let location = i.current_source_location();
-                                    CustomIdent::from_ident(location, i.expect_ident()?, &[])
-                                })?;
-                                alternates.push(VariantAlternates::${to_camel_case(value)}(idents.into_boxed_slice()));
-                                Ok(())
-                            },
-                            % endfor
-                            _ => return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
-                        }
-                    })
-                },
-                _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
-            }
-        }) { }
-
-        if parsed_alternates.is_empty() {
-            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
-        }
-        Ok(SpecifiedValue::Value(VariantAlternatesList(alternates.into_boxed_slice())))
-    }
-</%helpers:longhand>
+${helpers.predefined_type("font-variant-alternates",
+                          "FontVariantAlternates",
+                          products="gecko",
+                          initial_value="computed::FontVariantAlternates::get_initial_value()",
+                          initial_specified_value="specified::FontVariantAlternates::get_initial_specified_value()",
+                          animation_value_type="discrete",
+                          flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
+                          spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-alternates")}
 
 #[cfg(feature = "gecko")]
 macro_rules! exclusive_value {
     (($value:ident, $set:expr) => $ident:path) => {
         if $value.intersects($set) {
             return Err(())
         } else {
             $ident
--- a/servo/components/style/values/computed/font.rs
+++ b/servo/components/style/values/computed/font.rs
@@ -260,16 +260,27 @@ impl ToAnimatedValue for FontSizeAdjust 
     fn from_animated_value(animated: Self::AnimatedValue) -> Self {
         match animated {
             FontSizeAdjust::Number(number) => FontSizeAdjust::Number(number.max(0.)),
             _ => animated
         }
     }
 }
 
+/// Use VariantAlternatesList as computed type of FontVariantAlternates
+pub type FontVariantAlternates = specified::VariantAlternatesList;
+
+impl FontVariantAlternates {
+    #[inline]
+    /// Get initial value with VariantAlternatesList
+    pub fn get_initial_value() -> Self {
+        specified::VariantAlternatesList(vec![].into_boxed_slice())
+    }
+}
+
 impl ToComputedValue for specified::MozScriptMinSize {
     type ComputedValue = MozScriptMinSize;
 
     fn to_computed_value(&self, cx: &Context) -> MozScriptMinSize {
         // this value is used in the computation of font-size, so
         // we use the parent size
         let base_size = FontBaseSize::InheritedStyle;
         match self.0 {
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -31,17 +31,18 @@ use super::specified;
 pub use app_units::Au;
 pub use properties::animated_properties::TransitionProperty;
 #[cfg(feature = "gecko")]
 pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use self::angle::Angle;
 pub use self::background::{BackgroundSize, BackgroundRepeat};
 pub use self::border::{BorderImageSlice, BorderImageWidth, BorderImageSideWidth};
 pub use self::border::{BorderRadius, BorderCornerRadius, BorderSpacing};
-pub use self::font::{FontSize, FontSizeAdjust, FontSynthesis, FontWeight, MozScriptLevel, MozScriptMinSize, XTextZoom};
+pub use self::font::{FontSize, FontSizeAdjust, FontSynthesis, FontWeight, FontVariantAlternates};
+pub use self::font::{MozScriptLevel, MozScriptMinSize, XTextZoom};
 pub use self::box_::{AnimationIterationCount, AnimationName, ScrollSnapType, VerticalAlign};
 pub use self::color::{Color, ColorPropertyValue, RGBAColor};
 pub use self::effects::{BoxShadow, Filter, SimpleShadow};
 pub use self::flex::FlexBasis;
 pub use self::image::{Gradient, GradientItem, Image, ImageLayer, LineDirection, MozImageRect};
 #[cfg(feature = "gecko")]
 pub use self::gecko::ScrollSnapPoint;
 pub use self::rect::LengthOrNumberRect;
--- a/servo/components/style/values/specified/font.rs
+++ b/servo/components/style/values/specified/font.rs
@@ -5,18 +5,21 @@
 //! Specified values for font properties
 
 #[cfg(feature = "gecko")]
 use Atom;
 use app_units::Au;
 use cssparser::{Parser, Token};
 use parser::{Parse, ParserContext};
 use properties::longhands::system_font::SystemFont;
+#[allow(unused_imports)]
+use std::ascii::AsciiExt;
 use std::fmt;
 use style_traits::{ToCss, StyleParseErrorKind, ParseError};
+use values::CustomIdent;
 use values::computed::{font as computed, Context, Length, NonNegativeLength, ToComputedValue};
 use values::specified::{AllowQuirks, LengthOrPercentage, NoCalcLength, Number};
 use values::specified::length::{AU_PER_PT, AU_PER_PX, FontBaseSize};
 
 const DEFAULT_SCRIPT_MIN_SIZE_PT: u32 = 8;
 
 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)]
 /// A specified font-weight value
@@ -605,16 +608,302 @@ impl FontSize {
 
 impl Parse for FontSize {
     /// <length> | <percentage> | <absolute-size> | <relative-size>
     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<FontSize, ParseError<'i>> {
         FontSize::parse_quirky(context, input, AllowQuirks::No)
     }
 }
 
+bitflags! {
+    #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
+    /// Flags of variant alternates in bit
+    struct VariantAlternatesParsingFlags: u8 {
+        /// None of variant alternates enabled
+        const NORMAL = 0;
+        /// Historical forms
+        const HISTORICAL_FORMS = 0x01;
+        /// Stylistic Alternates
+        const STYLISTIC = 0x02;
+        /// Stylistic Sets
+        const STYLESET = 0x04;
+        /// Character Variant
+        const CHARACTER_VARIANT = 0x08;
+        /// Swash glyphs
+        const SWASH = 0x10;
+        /// Ornaments glyphs
+        const ORNAMENTS = 0x20;
+        /// Annotation forms
+        const ANNOTATION = 0x40;
+    }
+}
+
+#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
+/// Set of variant alternates
+pub enum VariantAlternates {
+    /// Enables display of stylistic alternates
+    Stylistic(CustomIdent),
+    /// Enables display with stylistic sets
+    Styleset(Box<[CustomIdent]>),
+    /// Enables display of specific character variants
+    CharacterVariant(Box<[CustomIdent]>),
+    /// Enables display of swash glyphs
+    Swash(CustomIdent),
+    /// Enables replacement of default glyphs with ornaments
+    Ornaments(CustomIdent),
+    /// Enables display of alternate annotation forms
+    Annotation(CustomIdent),
+    /// Enables display of historical forms
+    HistoricalForms,
+}
+
+impl ToCss for VariantAlternates {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            VariantAlternates::Swash(ref atom) => {
+                dest.write_str("swash")?;
+                dest.write_str("(")?;
+                atom.to_css(dest)?;
+                dest.write_str(")")
+            },
+            VariantAlternates::Stylistic(ref atom) => {
+                dest.write_str("stylistic")?;
+                dest.write_str("(")?;
+                atom.to_css(dest)?;
+                dest.write_str(")")
+            },
+            VariantAlternates::Ornaments(ref atom) => {
+                dest.write_str("ornaments")?;
+                dest.write_str("(")?;
+                atom.to_css(dest)?;
+                dest.write_str(")")
+            },
+            VariantAlternates::Annotation(ref atom) => {
+                dest.write_str("annotation")?;
+                dest.write_str("(")?;
+                atom.to_css(dest)?;
+                dest.write_str(")")
+            },
+            VariantAlternates::Styleset(ref vec) => {
+                dest.write_str("styleset")?;
+                dest.write_str("(")?;
+                let mut iter = vec.iter();
+                iter.next().unwrap().to_css(dest)?;
+                for c in iter {
+                    dest.write_str(", ")?;
+                    c.to_css(dest)?;
+                }
+                dest.write_str(")")
+            },
+            VariantAlternates::CharacterVariant(ref vec) => {
+                dest.write_str("character-variant")?;
+                dest.write_str("(")?;
+                let mut iter = vec.iter();
+                iter.next().unwrap().to_css(dest)?;
+                for c in iter {
+                    dest.write_str(", ")?;
+                    c.to_css(dest)?;
+                }
+                dest.write_str(")")
+            },
+            VariantAlternates::HistoricalForms => {
+                dest.write_str("historical-forms")
+            },
+        }
+    }
+}
+
+#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
+/// List of Variant Alternates
+pub struct VariantAlternatesList(pub Box<[VariantAlternates]>);
+
+impl VariantAlternatesList {
+    /// Returns the length of all variant alternates.
+    pub fn len(&self) -> usize {
+        self.0.iter().fold(0, |acc, alternate| {
+            match *alternate {
+                VariantAlternates::Swash(_) | VariantAlternates::Stylistic(_) |
+                VariantAlternates::Ornaments(_) | VariantAlternates::Annotation(_) => {
+                    acc + 1
+                },
+                VariantAlternates::Styleset(ref slice) |
+                VariantAlternates::CharacterVariant(ref slice) => {
+                    acc + slice.len()
+                },
+                _ => acc,
+            }
+        })
+    }
+}
+
+impl ToCss for VariantAlternatesList {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        if self.0.is_empty() {
+            return dest.write_str("normal");
+        }
+
+        let mut iter = self.0.iter();
+        iter.next().unwrap().to_css(dest)?;
+        for alternate in iter {
+            dest.write_str(" ")?;
+            alternate.to_css(dest)?;
+        }
+        Ok(())
+    }
+}
+
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
+/// Control over the selection of these alternate glyphs
+pub enum FontVariantAlternates {
+    /// Use alternative glyph from value
+    Value(VariantAlternatesList),
+    /// Use system font glyph
+    System(SystemFont)
+}
+
+impl FontVariantAlternates {
+    #[inline]
+    /// Get initial specified value with VariantAlternatesList
+    pub fn get_initial_specified_value() -> Self {
+        FontVariantAlternates::Value(VariantAlternatesList(vec![].into_boxed_slice()))
+    }
+
+    /// Get FontVariantAlternates with system font
+    pub fn system_font(f: SystemFont) -> Self {
+        FontVariantAlternates::System(f)
+    }
+
+    /// Get SystemFont of FontVariantAlternates
+    pub fn get_system(&self) -> Option<SystemFont> {
+        if let FontVariantAlternates::System(s) = *self {
+            Some(s)
+        } else {
+            None
+        }
+    }
+}
+
+impl ToComputedValue for FontVariantAlternates {
+    type ComputedValue = computed::FontVariantAlternates;
+
+    fn to_computed_value(&self, _context: &Context) -> computed::FontVariantAlternates {
+        match *self {
+            FontVariantAlternates::Value(ref v) => v.clone(),
+            FontVariantAlternates::System(_) => {
+                #[cfg(feature = "gecko")] {
+                    _context.cached_system_font.as_ref().unwrap().font_variant_alternates.clone()
+                }
+                #[cfg(feature = "servo")] {
+                    unreachable!()
+                }
+            }
+        }
+    }
+
+    fn from_computed_value(other: &computed::FontVariantAlternates) -> Self {
+        FontVariantAlternates::Value(other.clone())
+    }
+}
+
+impl Parse for FontVariantAlternates {
+    /// normal |
+    ///  [ stylistic(<feature-value-name>)           ||
+    ///    historical-forms                          ||
+    ///    styleset(<feature-value-name> #)          ||
+    ///    character-variant(<feature-value-name> #) ||
+    ///    swash(<feature-value-name>)               ||
+    ///    ornaments(<feature-value-name>)           ||
+    ///    annotation(<feature-value-name>) ]
+    fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result<FontVariantAlternates, ParseError<'i>> {
+        let mut alternates = Vec::new();
+        if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
+            return Ok(FontVariantAlternates::Value(VariantAlternatesList(alternates.into_boxed_slice())));
+        }
+
+        let mut parsed_alternates = VariantAlternatesParsingFlags::empty();
+        macro_rules! check_if_parsed(
+            ($input:expr, $flag:path) => (
+                if parsed_alternates.contains($flag) {
+                    return Err($input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
+                }
+                parsed_alternates |= $flag;
+            )
+        );
+        while let Ok(_) = input.try(|input| {
+            // FIXME: remove clone() when lifetimes are non-lexical
+            match input.next()?.clone() {
+                Token::Ident(ref value) if value.eq_ignore_ascii_case("historical-forms") => {
+                    check_if_parsed!(input, VariantAlternatesParsingFlags::HISTORICAL_FORMS);
+                    alternates.push(VariantAlternates::HistoricalForms);
+                    Ok(())
+                },
+                Token::Function(ref name) => {
+                    input.parse_nested_block(|i| {
+                        match_ignore_ascii_case! { &name,
+                            "swash" => {
+                                check_if_parsed!(i, VariantAlternatesParsingFlags::SWASH);
+                                let location = i.current_source_location();
+                                let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?;
+                                alternates.push(VariantAlternates::Swash(ident));
+                                Ok(())
+                            },
+                            "stylistic" => {
+                                check_if_parsed!(i, VariantAlternatesParsingFlags::STYLISTIC);
+                                let location = i.current_source_location();
+                                let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?;
+                                alternates.push(VariantAlternates::Stylistic(ident));
+                                Ok(())
+                            },
+                            "ornaments" => {
+                                check_if_parsed!(i, VariantAlternatesParsingFlags::ORNAMENTS);
+                                let location = i.current_source_location();
+                                let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?;
+                                alternates.push(VariantAlternates::Ornaments(ident));
+                                Ok(())
+                            },
+                            "annotation" => {
+                                check_if_parsed!(i, VariantAlternatesParsingFlags::ANNOTATION);
+                                let location = i.current_source_location();
+                                let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?;
+                                alternates.push(VariantAlternates::Annotation(ident));
+                                Ok(())
+                            },
+                            "styleset" => {
+                                check_if_parsed!(i, VariantAlternatesParsingFlags::STYLESET);
+                                let idents = i.parse_comma_separated(|i| {
+                                    let location = i.current_source_location();
+                                    CustomIdent::from_ident(location, i.expect_ident()?, &[])
+                                })?;
+                                alternates.push(VariantAlternates::Styleset(idents.into_boxed_slice()));
+                                Ok(())
+                            },
+                            "character-variant" => {
+                                check_if_parsed!(i, VariantAlternatesParsingFlags::CHARACTER_VARIANT);
+                                let idents = i.parse_comma_separated(|i| {
+                                    let location = i.current_source_location();
+                                    CustomIdent::from_ident(location, i.expect_ident()?, &[])
+                                })?;
+                                alternates.push(VariantAlternates::CharacterVariant(idents.into_boxed_slice()));
+                                Ok(())
+                            },
+                            _ => return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
+                        }
+                    })
+                },
+                _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
+            }
+        }) { }
+
+        if parsed_alternates.is_empty() {
+            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+        }
+        Ok(FontVariantAlternates::Value(VariantAlternatesList(alternates.into_boxed_slice())))
+    }
+}
+
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
 /// Whether user agents are allowed to synthesize bold or oblique font faces
 /// when a font family lacks bold or italic faces
 pub struct FontSynthesis {
     /// If a `font-weight` is requested that the font family does not contain,
     /// the user agent may synthesize the requested weight from the weights
     /// that do exist in the font family.
     pub weight: bool,
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -25,17 +25,18 @@ use values::specified::calc::CalcNode;
 
 pub use properties::animated_properties::TransitionProperty;
 pub use self::angle::Angle;
 #[cfg(feature = "gecko")]
 pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use self::background::{BackgroundRepeat, BackgroundSize};
 pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth};
 pub use self::border::{BorderImageSideWidth, BorderRadius, BorderSideWidth, BorderSpacing};
-pub use self::font::{FontSize, FontSizeAdjust, FontSynthesis, FontWeight, MozScriptLevel, MozScriptMinSize, XTextZoom};
+pub use self::font::{FontSize, FontSizeAdjust, FontSynthesis, FontWeight, FontVariantAlternates};
+pub use self::font::{MozScriptLevel, MozScriptMinSize, XTextZoom};
 pub use self::box_::{AnimationIterationCount, AnimationName, ScrollSnapType, VerticalAlign};
 pub use self::color::{Color, ColorPropertyValue, RGBAColor};
 pub use self::effects::{BoxShadow, Filter, SimpleShadow};
 pub use self::flex::FlexBasis;
 #[cfg(feature = "gecko")]
 pub use self::gecko::ScrollSnapPoint;
 pub use self::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use self::image::{GradientItem, GradientKind, Image, ImageLayer, MozImageRect};
--- a/taskcluster/ci/beetmover-cdns/kind.yml
+++ b/taskcluster/ci/beetmover-cdns/kind.yml
@@ -14,18 +14,23 @@ kind-dependencies:
 
 job-defaults:
    worker-type:
       by-project:
          mozilla-release: scriptworker-prov-v1/beetmoverworker-v1
          mozilla-beta: scriptworker-prov-v1/beetmoverworker-v1
          default: scriptworker-prov-v1/beetmoverworker-dev
    run-on-projects: []
+   shipping-phase: publish
 
 jobs:
    fennec-push-to-cdns:
       name: fennec_push_to_cdns
       product: fennec
-      run:
-         routes:
-            - index.releases.v1.{branch}.latest.fennec.latest.beetmover_cdns
-            - index.releases.v1.{branch}.{revision}.fennec.{underscore_version}.build{build_number}.beetmover_cdns
+      shipping-product: fennec
+      routes:
+        - index.releases.v1.{branch}.latest.fennec.latest.beetmover_cdns
+        - index.releases.v1.{branch}.{revision}.fennec.{underscore_version}.build{build_number}.beetmover_cdns
       treeherder-platform: Android/opt
+      index:
+         type: release
+         product: fennec
+         job-name: android-api-16-opt
--- a/taskcluster/ci/push-apk-breakpoint/kind.yml
+++ b/taskcluster/ci/push-apk-breakpoint/kind.yml
@@ -12,16 +12,18 @@ kind-dependencies:
   - build-signing
 
 jobs:
     android-push-apk-breakpoint/opt:
         description: PushApk breakpoint. Decides whether APK should be published onto Google Play Store
         attributes:
             build_platform: android-nightly
             nightly: true
+        shipping-phase: ship
+        shipping-product: fennec
         worker-type: # see transforms
         worker:
             implementation: push-apk-breakpoint
         treeherder:
             symbol: pub(Br)
             platform: Android/opt
             tier: 2
             kind: other
--- a/taskcluster/ci/push-apk/kind.yml
+++ b/taskcluster/ci/push-apk/kind.yml
@@ -13,16 +13,18 @@ kind-dependencies:
   - push-apk-breakpoint
 
 jobs:
     push-apk/opt:
         description: Publishes APK onto Google Play Store
         attributes:
             build_platform: android-nightly
             nightly: true
+        shipping-phase: ship
+        shipping-product: fennec
         worker-type: scriptworker-prov-v1/pushapk-v1
         worker:
             upstream-artifacts: # see transforms
             google-play-track: # see transforms
             implementation: push-apk
             dry-run: # see transforms
         scopes: # see transforms
         treeherder:
--- a/taskcluster/ci/release-bouncer-aliases/kind.yml
+++ b/taskcluster/ci/release-bouncer-aliases/kind.yml
@@ -7,37 +7,46 @@ loader: taskgraph.loader.transform:loade
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-uptake-monitoring
 
+job-defaults:
+   shipping-phase: ship
+
 jobs:
    fennec:
       name: fennec_release_bouncer_aliases
       description: Update bouncer aliases job
       worker-type: buildbot-bridge/buildbot-bridge
       run-on-projects: []
+      shipping-product: fennec
       run:
          using: buildbot
          product: fennec
          buildername: release-{branch}-fennec_bouncer_aliases
          release-promotion: true
-         routes:
-            - index.releases.v1.{branch}.latest.fennec.latest.bouncer_submitter
-            - index.releases.v1.{branch}.{revision}.fennec.{underscore_version}.build{build_number}.bouncer_submitter
+      worker:
          properties:
              tuxedo_server_url:
                 by-project:
                     mozilla-beta: https://bounceradmin.mozilla.com/api
                     mozilla-release: https://bounceradmin.mozilla.com/api
                     maple: https://admin-bouncer.stage.mozaws.net/api/
                     default: http://localhost/api
+      routes:
+         - index.releases.v1.{branch}.latest.fennec.latest.bouncer_submitter
+         - index.releases.v1.{branch}.{revision}.fennec.{underscore_version}.build{build_number}.bouncer_submitter
+      index:
+         type: release
+         product: fennec
+         job-name: android-api-16-opt
       notifications:
          completed:
             by-project:
                maple:
                   - "release-drivers-staging"
                try:
                   #- "{task[tags][createdForUser]}"
                default:
--- a/taskcluster/ci/release-bouncer-sub/kind.yml
+++ b/taskcluster/ci/release-bouncer-sub/kind.yml
@@ -3,31 +3,38 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
+job-defaults:
+   shipping-phase: promote
 
 jobs:
    fennec:
       name: fennec_release_bouncer_sub
       description: release bouncer submission job
       worker-type: buildbot-bridge/buildbot-bridge
       run-on-projects: []
+      shipping-product: fennec
       run:
          using: buildbot
          product: fennec
          buildername: release-{branch}-fennec_bncr_sub
          release-promotion: true
-         routes:
-            - index.releases.v1.{branch}.latest.fennec.latest.bouncer_submitter
-            - index.releases.v1.{branch}.{revision}.fennec.{underscore_version}.build{build_number}.bouncer_submitter
+      routes:
+         - index.releases.v1.{branch}.latest.fennec.latest.bouncer_submitter
+         - index.releases.v1.{branch}.{revision}.fennec.{underscore_version}.build{build_number}.bouncer_submitter
+      index:
+         type: release
+         product: fennec
+         job-name: android-api-16-opt
       notifications:
          completed:
             by-project:
                maple:
                   - "release-drivers-staging"
                try:
                   #- "{task[tags][createdForUser]}"
                default:
--- a/taskcluster/ci/release-mark-as-shipped/kind.yml
+++ b/taskcluster/ci/release-mark-as-shipped/kind.yml
@@ -9,31 +9,38 @@ transforms:
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - push-apk
    - release-bouncer-aliases
    - release-version-bump
 
+job-defaults:
+   shipping-phase: ship
 
 jobs:
    fennec:
       name: release-fennec_mark_as_shipped
       description: mark release as shipped in Ship-It
       worker-type: buildbot-bridge/buildbot-bridge
       run-on-projects: []
+      shipping-product: fennec
       run:
          using: buildbot
          product: fennec
          buildername: release-{branch}-fennec_mark_as_shipped
          release-promotion: true
-         routes:
-            - index.releases.v1.{branch}.latest.fennec.latest.mark_as_shipped
-            - index.releases.v1.{branch}.{revision}.fennec.{underscore_version}.build{build_number}.mark_as_shipped
+      routes:
+         - index.releases.v1.{branch}.latest.fennec.latest.mark_as_shipped
+         - index.releases.v1.{branch}.{revision}.fennec.{underscore_version}.build{build_number}.mark_as_shipped
+      index:
+         type: release
+         product: fennec
+         job-name: android-api-16-opt
       notifications:
          completed:
             by-project:
                maple:
                   - "release-drivers-staging"
                try:
                   #- "{task[tags][createdForUser]}"
                default:
--- a/taskcluster/ci/release-notify-promote/kind.yml
+++ b/taskcluster/ci/release-notify-promote/kind.yml
@@ -7,21 +7,25 @@ loader: taskgraph.loader.transform:loade
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
   - beetmover-checksums
   - release-bouncer-sub
 
+job-defaults:
+    shipping-phase: promote
+
 jobs:
    fennec:
       name: notify-release-drivers-promote
       description: Sends email to release-drivers telling release was promoted.
       run-on-projects: []
+      shipping-product: fennec
       worker-type: aws-provisioner-v1/gecko-{level}-b-linux
       worker:
          implementation: docker-worker
          os: linux
          docker-image: "ubuntu:16.10"
          max-run-time: 600
          command:
             - /bin/bash
--- a/taskcluster/ci/release-notify-publish/kind.yml
+++ b/taskcluster/ci/release-notify-publish/kind.yml
@@ -8,21 +8,25 @@ transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - push-apk
    - release-bouncer-aliases
    - release-version-bump
 
+job-defaults:
+   shipping-phase: ship
+
 jobs:
    fennec:
       name: notify-release-drivers-publish
       description: Sends email to release-drivers telling release was published.
       run-on-projects: []
+      shipping-product: fennec
       worker-type: aws-provisioner-v1/gecko-{level}-b-linux
       worker:
          implementation: docker-worker
          os: linux
          docker-image: "ubuntu:16.10"
          max-run-time: 600
          command:
             - /bin/bash
--- a/taskcluster/ci/release-uptake-monitoring/kind.yml
+++ b/taskcluster/ci/release-uptake-monitoring/kind.yml
@@ -7,39 +7,48 @@ loader: taskgraph.loader.transform:loade
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - beetmover-cdns
 
+job-defaults:
+   shipping-phase: publish
+
 jobs:
    fennec:
       name: fennec_release_uptake_monitoring
       description: Uptake monitoring job
       worker-type: buildbot-bridge/buildbot-bridge
       run-on-projects: []
+      shipping-product: fennec
       run:
          using: buildbot
          product: fennec
          buildername: release-{branch}-fennec_uptake_monitoring
          release-promotion: true
-         routes:
-            - index.releases.v1.{branch}.latest.fennec.latest.uptake_monitoring
-            - index.releases.v1.{branch}.{revision}.fennec.{underscore_version}.build{build_number}.uptake_monitoring
+      worker:
          properties:
-             # TODO: Calculate "platforms" dynamically
-             platforms: "android-api-16, android-x86"
-             tuxedo_server_url:
-                by-project:
-                    mozilla-beta: https://bounceradmin.mozilla.com/api
-                    mozilla-release: https://bounceradmin.mozilla.com/api
-                    maple: https://admin-bouncer.stage.mozaws.net/api/
-                    default: http://localhost/api
+            # TODO: Calculate "platforms" dynamically
+            platforms: "android-api-16, android-x86"
+            tuxedo_server_url:
+               by-project:
+                  mozilla-beta: https://bounceradmin.mozilla.com/api
+                  mozilla-release: https://bounceradmin.mozilla.com/api
+                  maple: https://admin-bouncer.stage.mozaws.net/api/
+                  default: http://localhost/api
+      routes:
+         - index.releases.v1.{branch}.latest.fennec.latest.uptake_monitoring
+         - index.releases.v1.{branch}.{revision}.fennec.{underscore_version}.build{build_number}.uptake_monitoring
+      index:
+         type: release
+         product: fennec
+         job-name: android-api-16-opt
       notifications:
          completed:
             by-project:
                maple:
                   - "release-drivers-staging"
                try:
                   #- "{task[tags][createdForUser]}"
                default:
--- a/taskcluster/ci/release-version-bump/kind.yml
+++ b/taskcluster/ci/release-version-bump/kind.yml
@@ -7,30 +7,38 @@ loader: taskgraph.loader.transform:loade
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.job:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - beetmover-cdns
 
+job-defaults:
+   shipping-phase: ship
+
 jobs:
    fennec:
       name: fennec-version-bump
       description: Release Promotion version bump
       worker-type: buildbot-bridge/buildbot-bridge
       run-on-projects: []
+      shipping-product: fennec
       run:
          using: buildbot
          product: fennec
          buildername: release-{branch}-fennec_version_bump
          release-promotion: true
-         routes:
-            - index.releases.v1.{branch}.latest.fennec.latest.version_bump
-            - index.releases.v1.{branch}.{revision}.fennec.{underscore_version}.build{build_number}.version_bump
+      routes:
+         - index.releases.v1.{branch}.latest.fennec.latest.version_bump
+         - index.releases.v1.{branch}.{revision}.fennec.{underscore_version}.build{build_number}.version_bump
+      index:
+         type: release
+         product: fennec
+         job-name: android-api-16-opt
       notifications:
          completed:
             by-project:
                maple:
                   - "release-drivers-staging"
                try:
                   #- "{task[tags][createdForUser]}"
                default:
--- a/taskcluster/docs/attributes.rst
+++ b/taskcluster/docs/attributes.rst
@@ -181,8 +181,16 @@ always_target
 Tasks with this attribute will be included in the ``target_task_graph`` regardless
 of any target task filtering that occurs. When a task is included in this manner
 (i.e it otherwise would have been filtered out), it will be considered for
 optimization even if the ``optimize_target_tasks`` parameter is False.
 
 This is meant to be used for tasks which a developer would almost always want to
 run. Typically these tasks will be short running and have a high risk of causing
 a backout. For example ``lint`` or ``python-unittest`` tasks.
+
+shipping_product
+================
+For release promotion jobs, this is the product we are shipping.
+
+shipping_phase
+==============
+For release promotion jobs, this is the shipping phase (promote, publish, ship).
--- a/taskcluster/docs/parameters.rst
+++ b/taskcluster/docs/parameters.rst
@@ -133,16 +133,25 @@ Optimization
    Any tasks in the graph matching one of the labels will not be optimized out
    of the graph.
 
 ``existing_tasks``
    Specify tasks to optimize out of the graph. This is a dictionary of label to taskId.
    Any tasks in the graph matching one of the labels will use the previously-run
    taskId rather than submitting a new task.
 
+Release Promotion
+-----------------
+
+``build_number``
+   Specify the release promotion build number.
+
+``next_version``
+   Specify the next version for version bump tasks.
+
 Comm Push Information
 ---------------------
 
 These parameters correspond to the repository and revision of the comm-central
 repository to checkout. Their meaning is the same as the corresponding
 parameters for the gecko repository above. They are optional, but if any of
 them are specified, they must all be specified.
 
--- a/taskcluster/taskgraph/actions/release_promotion.py
+++ b/taskcluster/taskgraph/actions/release_promotion.py
@@ -1,18 +1,16 @@
 # -*- coding: utf-8 -*-
 
 # 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, print_function, unicode_literals
 
-import os
-
 from .registry import register_callback_action
 
 from .util import (find_decision_task, find_existing_tasks_from_previous_kinds,
                    find_hg_revision_pushlog_id)
 from taskgraph.util.taskcluster import get_artifact
 from taskgraph.taskgraph import TaskGraph
 from taskgraph.decision import taskgraph_decision
 from taskgraph.parameters import Parameters
@@ -137,26 +135,24 @@ def is_release_promotion_available(param
                 'description': 'Next version.',
                 'default': '',
             },
         },
         "required": ['release_promotion_flavor', 'build_number'],
     }
 )
 def release_promotion_action(parameters, input, task_group_id, task_id, task):
-    os.environ['BUILD_NUMBER'] = str(input['build_number'])
     release_promotion_flavor = input['release_promotion_flavor']
     if release_promotion_flavor in VERSION_BUMP_FLAVORS:
         next_version = str(input.get('next_version', ''))
         if next_version == "":
             raise Exception(
                 "`next_version` property needs to be provided for %s "
                 "targets." % ', '.join(VERSION_BUMP_FLAVORS)
             )
-        os.environ['NEXT_VERSION'] = next_version
     promotion_config = RELEASE_PROMOTION_CONFIG[release_promotion_flavor]
 
     target_tasks_method = input.get(
         'target_tasks_method',
         promotion_config['target_tasks_method'].format(project=parameters['project'])
     )
     previous_graph_kinds = input.get(
         'previous_graph_kinds', promotion_config['previous_graph_kinds']
@@ -180,13 +176,15 @@ def release_promotion_action(parameters,
     parameters = get_artifact(previous_graph_ids[0], "public/parameters.yml")
     full_task_graph = get_artifact(previous_graph_ids[0], "public/full-task-graph.json")
     _, full_task_graph = TaskGraph.from_json(full_task_graph)
     parameters['existing_tasks'] = find_existing_tasks_from_previous_kinds(
         full_task_graph, previous_graph_ids, previous_graph_kinds
     )
     parameters['do_not_optimize'] = do_not_optimize
     parameters['target_tasks_method'] = target_tasks_method
+    parameters['build_number'] = str(input['build_number'])
+    parameters['next_version'] = next_version
 
     # make parameters read-only
     parameters = Parameters(**parameters)
 
     taskgraph_decision({}, parameters=parameters)
--- a/taskcluster/taskgraph/decision.py
+++ b/taskcluster/taskgraph/decision.py
@@ -164,16 +164,18 @@ def get_decision_parameters(options):
     # Define default filter list, as most configurations shouldn't need
     # custom filters.
     parameters['filters'] = [
         'check_servo',
         'target_tasks_method',
     ]
     parameters['existing_tasks'] = {}
     parameters['do_not_optimize'] = []
+    parameters['build_number'] = 1
+    parameters['next_version'] = None
 
     # owner must be an email, but sometimes (e.g., for ffxbld) it is not, in which
     # case, fake it
     if '@' not in parameters['owner']:
         parameters['owner'] += '@noreply.mozilla.org'
 
     # use the pushdate as build_date if given, else use current time
     parameters['build_date'] = parameters['pushdate'] or int(time.time())
--- a/taskcluster/taskgraph/parameters.py
+++ b/taskcluster/taskgraph/parameters.py
@@ -26,26 +26,28 @@ def get_head_ref():
     return get_repository_object(GECKO).head_ref
 
 
 # Please keep this list sorted and in sync with taskcluster/docs/parameters.rst
 # Parameters are of the form: {name: default}
 PARAMETERS = {
     'base_repository': 'https://hg.mozilla.org/mozilla-unified',
     'build_date': lambda: int(time.time()),
+    'build_number': 1,
     'do_not_optimize': [],
     'existing_tasks': {},
     'filters': ['check_servo', 'target_tasks_method'],
     'head_ref': get_head_ref,
     'head_repository': 'https://hg.mozilla.org/mozilla-central',
     'head_rev': get_head_ref,
     'include_nightly': False,
     'level': '3',
     'message': '',
     'moz_build_date': lambda: datetime.now().strftime("%Y%m%d%H%M%S"),
+    'next_version': None,
     'optimize_target_tasks': True,
     'owner': 'nobody@mozilla.com',
     'project': 'mozilla-central',
     'pushdate': lambda: int(time.time()),
     'pushlog_id': '0',
     'release_history': {},
     'target_tasks_method': 'default',
     'try_mode': None,
--- a/taskcluster/taskgraph/target_tasks.py
+++ b/taskcluster/taskgraph/target_tasks.py
@@ -329,16 +329,20 @@ def target_tasks_mozilla_beta_desktop_pr
             return False
 
         # Allow for beta_tasks; these will get optimized out to point to
         # the previous graph using ``previous_graph_ids`` and
         # ``previous_graph_kinds``.
         if task.label in beta_tasks:
             return True
 
+        if task.attributes.get('shipping_product') == 'firefox' and \
+                task.attributes.get('shipping_phase') == 'promote':
+            return True
+
         # TODO: partner repacks
         # TODO: source task
         # TODO: funsize, all but balrog submission
         # TODO: bbb update verify
         # TODO: tc update verify
         # TODO: beetmover push-to-candidates
         # TODO: binary transparency
         # TODO: bouncer sub
@@ -355,16 +359,19 @@ def target_tasks_publish_firefox(full_ta
     filtered_for_candidates = target_tasks_mozilla_beta_desktop_promotion(
         full_task_graph, parameters, graph_config,
     )
 
     def filter(task):
         # Include promotion tasks; these will be optimized out
         if task.label in filtered_for_candidates:
             return True
+        if task.attributes.get('shipping_product') == 'firefox' and \
+                task.attributes.get('shipping_phase') in ('publish', 'ship'):
+            return True
         # TODO: add beetmover push-to-releases
         # TODO: tagging / version bumping
         # TODO: publish to balrog
         # TODO: funsize balrog submission
         # TODO: recompression push-to-releases + balrog
         # TODO: final verify
         # TODO: uptake monitoring
         # TODO: bouncer aliases
@@ -385,57 +392,37 @@ def target_tasks_candidates_fennec(full_
         attr = task.attributes.get
         # Don't ship single locale fennec anymore - Bug 1408083
         if attr("locale") or attr("chunk_locales"):
             return False
         if task.label in filtered_for_project:
             if task.kind not in ('balrog', 'push-apk', 'push-apk-breakpoint'):
                 if task.attributes.get('nightly'):
                     return True
-        if task.task['payload'].get('properties', {}).get('product') == 'fennec':
-            if task.kind in ('release-bouncer-sub',
-                             ):
-                return True
-        if task.task.get('extra', {}).get('product') == 'fennec':
-            if task.kind in ('release-notify-promote',
-                             ):
-                return True
+        if task.attributes.get('shipping_product') == 'fennec' and \
+                task.attributes.get('shipping_phase') == 'promote':
+            return True
 
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(full_task_graph[l])]
 
 
 @_target_task('publish_fennec')
 def target_tasks_publish_fennec(full_task_graph, parameters, graph_config):
     """Select the set of tasks required to publish a candidates build of fennec.
     Previous build deps will be optimized out via action task."""
     filtered_for_candidates = target_tasks_candidates_fennec(
         full_task_graph, parameters, graph_config,
     )
 
     def filter(task):
         # Include candidates build tasks; these will be optimized out
         if task.label in filtered_for_candidates:
             return True
-        if task.task['payload'].get('properties', {}).get('product') == 'fennec':
-            if task.kind in ('release-mark-as-shipped',
-                             'release-bouncer-aliases',
-                             'release-uptake-monitoring',
-                             'release-version-bump',
-                             ):
-                return True
-        if task.task.get('extra', {}).get('product') == 'fennec':
-            if task.kind in ('release-notify-publish',
-                             ):
-                return True
-
-        if task.task['payload'].get('product') == 'fennec':
-            if task.kind in ('beetmover-cdns', ):
-                return True
-
-        if task.kind in ('push-apk', 'push-apk-breakpoint'):
+        if task.attributes.get('shipping_product') == 'fennec' and \
+                task.attributes.get('shipping_phase') in ('ship', 'publish'):
             return True
 
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(full_task_graph[l])]
 
 
 @_target_task('pine_tasks')
 def target_tasks_pine(full_task_graph, parameters, graph_config):
     """Bug 1339179 - no mobile automation needed on pine"""
--- a/taskcluster/taskgraph/transforms/beetmover_cdns.py
+++ b/taskcluster/taskgraph/transforms/beetmover_cdns.py
@@ -3,17 +3,19 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 """
 Transform the beetmover-cdns task into a task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
-from taskgraph.util.schema import validate_schema, Schema
+from taskgraph.util.schema import (
+    optionally_keyed_by, resolve_keyed_by, validate_schema, Schema
+)
 from taskgraph.util.scriptworker import (
     get_beetmover_bucket_scope, get_beetmover_action_scope
 )
 from taskgraph.transforms.job import job_description_schema
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 # Voluptuous uses marker objects as dictionary *keys*, but they are not
@@ -30,21 +32,22 @@ taskref_or_string = Any(
 beetmover_cdns_description_schema = Schema({
     Required('name'): basestring,
     Required('product'): basestring,
     Required('treeherder-platform'): basestring,
     Optional('attributes'): {basestring: object},
     Optional('job-from'): task_description_schema['job-from'],
     Optional('run'): {basestring: object},
     Optional('run-on-projects'): task_description_schema['run-on-projects'],
-    Required('worker-type'): Any(
-        job_description_schema['worker-type'],
-        {'by-project': {basestring: job_description_schema['worker-type']}},
-    ),
+    Required('worker-type'): optionally_keyed_by('project', basestring),
     Optional('dependencies'): {basestring: taskref_or_string},
+    Optional('index'): {basestring: basestring},
+    Optional('routes'): [basestring],
+    Required('shipping-phase'): task_description_schema['shipping-phase'],
+    Required('shipping-product'): task_description_schema['shipping-product'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job['name']
         yield validate_schema(
@@ -63,23 +66,28 @@ def make_beetmover_cdns_description(conf
 
         label = job['name']
         description = (
             "Beetmover push to cdns for '{product}'".format(
                 product=job['product']
             )
         )
 
+        resolve_keyed_by(
+            job, 'worker-type', item_name=job['name'],
+            project=config.params['project']
+        )
+
         bucket_scope = get_beetmover_bucket_scope(config)
         action_scope = get_beetmover_action_scope(config)
 
         task = {
             'label': label,
             'description': description,
-            'worker-type': 'scriptworker-prov-v1/beetmoverworker-dev',
+            'worker-type': job['worker-type'],
             'scopes': [bucket_scope, action_scope],
             'product': job['product'],
             'dependencies': job['dependencies'],
             'attributes': job.get('attributes', {}),
             'run-on-projects': job.get('run-on-projects'),
             'treeherder': treeherder,
         }
 
--- a/taskcluster/taskgraph/transforms/job/__init__.py
+++ b/taskcluster/taskgraph/transforms/job/__init__.py
@@ -55,16 +55,18 @@ job_description_schema = Schema({
     Optional('routes'): task_description_schema['routes'],
     Optional('scopes'): task_description_schema['scopes'],
     Optional('tags'): task_description_schema['tags'],
     Optional('extra'): task_description_schema['extra'],
     Optional('notifications'): task_description_schema['notifications'],
     Optional('treeherder'): task_description_schema['treeherder'],
     Optional('index'): task_description_schema['index'],
     Optional('run-on-projects'): task_description_schema['run-on-projects'],
+    Optional('shipping-phase'): task_description_schema['shipping-phase'],
+    Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('coalesce'): task_description_schema['coalesce'],
     Optional('always-target'): task_description_schema['always-target'],
     Exclusive('optimization', 'optimization'): task_description_schema['optimization'],
     Optional('needs-sccache'): task_description_schema['needs-sccache'],
 
     # The "when" section contains descriptions of the circumstances under which
     # this task should be included in the task graph.  This will be converted
     # into an optimization, so it cannot be specified in a job description that
--- a/taskcluster/taskgraph/transforms/job/buildbot.py
+++ b/taskcluster/taskgraph/transforms/job/buildbot.py
@@ -3,21 +3,20 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 """
 
 Support for running jobs via buildbot.
 
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
-import copy
 import slugid
 from urlparse import urlparse
 
-from taskgraph.util.schema import Schema, optionally_keyed_by, resolve_keyed_by
+from taskgraph.util.schema import Schema
 from taskgraph.util.scriptworker import get_release_config
 from voluptuous import Optional, Required, Any
 
 from taskgraph.transforms.job import run_job_using
 
 
 buildbot_run_schema = Schema({
     Required('using'): 'buildbot',
@@ -25,88 +24,56 @@ buildbot_run_schema = Schema({
     # the buildername to use for buildbot-bridge, will expand {branch} in name from
     # the current project.
     Required('buildername'): basestring,
 
     # the product to use
     Required('product'): Any('firefox', 'mobile', 'fennec', 'devedition', 'thunderbird'),
 
     Optional('release-promotion'): bool,
-    Optional('routes'): [basestring],
-    Optional('properties'): {basestring: optionally_keyed_by('project', basestring)},
 })
 
 
 def bb_release_worker(config, worker, run):
     # props
     release_props = get_release_config(config, force=True)
     repo_path = urlparse(config.params['head_repository']).path.lstrip('/')
     revision = config.params['head_rev']
-    branch = config.params['project']
-    buildername = worker['buildername']
-    underscore_version = release_props['version'].replace('.', '_')
     release_props.update({
         'release_promotion': True,
         'repo_path': repo_path,
         'revision': revision,
         'script_repo_revision': revision,
     })
     worker['properties'].update(release_props)
-    # scopes
-    worker['scopes'] = [
-        "project:releng:buildbot-bridge:builder-name:{}".format(buildername)
-    ]
-    # routes
-    if run.get('routes'):
-        worker['routes'] = []
-        repl_dict = {
-            'branch': branch,
-            'build_number': str(release_props['build_number']),
-            'revision': revision,
-            'underscore_version': underscore_version,
-        }
-        for route in run['routes']:
-            route = route.format(**repl_dict)
-            worker['routes'].append(route)
 
 
 def bb_ci_worker(config, worker):
     worker['properties'].update({
         'who': config.params['owner'],
         'upload_to_task_id': slugid.nice(),
     })
 
 
 @run_job_using('buildbot-bridge', 'buildbot', schema=buildbot_run_schema)
 def mozharness_on_buildbot_bridge(config, job, taskdesc):
-    # resolve by-* keys first
-    fields = [
-        "run.properties.tuxedo_server_url",
-    ]
-    job = copy.deepcopy(job)
-    for field in fields:
-        resolve_keyed_by(job, field, field, **config.params)
     run = job['run']
     worker = taskdesc['worker']
     branch = config.params['project']
     product = run['product']
 
     buildername = run['buildername'].format(branch=branch)
     revision = config.params['head_rev']
 
     worker.update({
         'buildername': buildername,
         'sourcestamp': {
             'branch': branch,
             'repository': config.params['head_repository'],
             'revision': revision,
         },
-        'properties': {
-            'product': product,
-        },
     })
-    if run.get('properties'):
-        worker['properties'].update(run['properties'])
+    worker.setdefault('properties', {})['product'] = product
 
     if run.get('release-promotion'):
         bb_release_worker(config, worker, run)
     else:
         bb_ci_worker(config, worker)
--- a/taskcluster/taskgraph/transforms/job/mozharness.py
+++ b/taskcluster/taskgraph/transforms/job/mozharness.py
@@ -216,18 +216,22 @@ def mozharness_on_generic_worker(config,
     env.update({
         'MOZ_BUILD_DATE': config.params['moz_build_date'],
         'MOZ_SCM_LEVEL': config.params['level'],
         'MOZ_AUTOMATION': '1',
     })
     if run['use-simple-package']:
         env.update({'MOZ_SIMPLE_PACKAGE_NAME': 'target'})
 
+    # The windows generic worker uses batch files to pass environment variables
+    # to commands.  Setting a variable to empty in a batch file unsets, so if
+    # there is no `TRY_COMMIT_MESSAGE`, pass a space instead, so that
+    # mozharness doesn't try to find the commit message on its own.
     if 'try' in config.params['project']:
-        env['TRY_COMMIT_MSG'] = config.params['message']
+        env['TRY_COMMIT_MSG'] = config.params['message'] or 'no commit message'
 
     if not job['attributes']['build_platform'].startswith('win'):
         raise Exception(
             "Task generation for mozharness build jobs currently only supported on Windows"
         )
 
     mh_command = [r'c:\mozilla-build\python\python.exe']
     mh_command.append('\\'.join([r'.\build\src\testing', run['script'].replace('/', '\\')]))
--- a/taskcluster/taskgraph/transforms/push_apk.py
+++ b/taskcluster/taskgraph/transforms/push_apk.py
@@ -5,41 +5,49 @@
 Transform the push-apk kind into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import functools
 
 from taskgraph.transforms.base import TransformSequence
+from taskgraph.transforms.task import task_description_schema
 from taskgraph.util.schema import Schema
 from taskgraph.util.scriptworker import get_push_apk_scope, get_push_apk_track, \
     get_push_apk_dry_run_option, get_push_apk_rollout_percentage
 from taskgraph.util.push_apk import fill_labels_tranform, validate_jobs_schema_transform_partial, \
     validate_dependent_tasks_transform, delete_non_required_fields_transform, generate_dependencies
 
 from voluptuous import Optional, Required
 
 
 transforms = TransformSequence()
 
+# Voluptuous uses marker objects as dictionary *keys*, but they are not
+# comparable, so we cast all of the keys back to regular strings
+task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
+
+
 push_apk_description_schema = Schema({
     # the dependent task (object) for this beetmover job, used to inform beetmover.
     Required('dependent-tasks'): object,
     Required('name'): basestring,
     Required('label'): basestring,
     Required('description'): basestring,
     Required('job-from'): basestring,
     Required('attributes'): object,
     Required('treeherder'): object,
     Required('run-on-projects'): list,
     Required('worker-type'): basestring,
     Required('worker'): object,
     Required('scopes'): None,
     Required('deadline-after'): basestring,
+    Required('shipping-phase'): task_description_schema['shipping-phase'],
+    Required('shipping-product'): task_description_schema['shipping-product'],
     Optional('extra'): object,
 })
 
 validate_jobs_schema_transform = functools.partial(
     validate_jobs_schema_transform_partial,
     push_apk_description_schema,
     'PushApk'
 )
--- a/taskcluster/taskgraph/transforms/push_apk_breakpoint.py
+++ b/taskcluster/taskgraph/transforms/push_apk_breakpoint.py
@@ -5,38 +5,45 @@
 Transform the push-apk-breakpoint kind into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import functools
 
 from taskgraph.transforms.base import TransformSequence
+from taskgraph.transforms.task import task_description_schema
 from taskgraph.util.schema import Schema
 from taskgraph.util.scriptworker import get_push_apk_breakpoint_worker_type
 from taskgraph.util.push_apk import fill_labels_tranform, validate_jobs_schema_transform_partial, \
     validate_dependent_tasks_transform, delete_non_required_fields_transform, generate_dependencies
 from voluptuous import Required
 
 
 transforms = TransformSequence()
 
+# Voluptuous uses marker objects as dictionary *keys*, but they are not
+# comparable, so we cast all of the keys back to regular strings
+task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
+
 push_apk_breakpoint_description_schema = Schema({
     # the dependent task (object) for this beetmover job, used to inform beetmover.
     Required('dependent-tasks'): object,
     Required('name'): basestring,
     Required('label'): basestring,
     Required('description'): basestring,
     Required('job-from'): basestring,
     Required('attributes'): object,
     Required('worker-type'): None,
     Required('worker'): object,
     Required('treeherder'): object,
     Required('run-on-projects'): list,
     Required('deadline-after'): basestring,
+    Required('shipping-phase'): task_description_schema['shipping-phase'],
+    Required('shipping-product'): task_description_schema['shipping-product'],
 })
 
 validate_jobs_schema_transform = functools.partial(
     validate_jobs_schema_transform_partial,
     push_apk_breakpoint_description_schema,
     'PushApkBreakpoint'
 )
 
--- a/taskcluster/taskgraph/transforms/release_deps.py
+++ b/taskcluster/taskgraph/transforms/release_deps.py
@@ -9,18 +9,19 @@ from __future__ import absolute_import, 
 
 from taskgraph.transforms.base import TransformSequence
 
 transforms = TransformSequence()
 
 
 def _get_product(job_or_task):
     # Find the product.
-    # XXX officially support a product attribute that is consistently set.
-    product = job_or_task.get('product')
+    # XXX Once shipping-product is set for nightly builds as well, we can get
+    # rid of this function.
+    product = job_or_task.get('shipping-product', job_or_task.get('product'))
     if 'payload' in job_or_task:
         product = product or job_or_task['payload'].get(
             'product',
             job_or_task['payload'].get('properties', {}).get('product')
         )
 
     if 'run' in job_or_task:
         product = product or job_or_task['run'].get('product')
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -127,27 +127,28 @@ task_description_schema = Schema({
     },
 
     # information for indexing this build so its artifacts can be discovered;
     # if omitted, the build will not be indexed.
     Optional('index'): {
         # the name of the product this build produces
         'product': Any(
             'firefox',
+            'fennec',
             'mobile',
             'static-analysis',
             'devedition',
             'source',
         ),
 
         # the names to use for this job in the TaskCluster index
         'job-name': basestring,
 
         # Type of gecko v2 index to use
-        'type': Any('generic', 'nightly', 'l10n', 'nightly-with-multi-l10n'),
+        'type': Any('generic', 'nightly', 'l10n', 'nightly-with-multi-l10n', 'release'),
 
         # The rank that the task will receive in the TaskCluster
         # index.  A newly completed task supercedes the currently
         # indexed task iff it has a higher rank.  If unspecified,
         # 'by-tier' behavior will be used.
         'rank': Any(
             # Rank is equal the timestamp of the build_date for tier-1
             # tasks, and zero for non-tier-1.  This sorts tier-{2,3}
@@ -165,16 +166,34 @@ task_description_schema = Schema({
         ),
     },
 
     # The `run_on_projects` attribute, defaulting to "all".  This dictates the
     # projects on which this task should be included in the target task set.
     # See the attributes documentation for details.
     Optional('run-on-projects'): [basestring],
 
+    # The `shipping_phase` attribute, defaulting to None. This specifies the
+    # release promotion phase that this task belongs to.
+    Required('shipping-phase', default=None): Any(
+        None,
+        'promote',
+        'publish',
+        'ship',
+    ),
+
+    # The `shipping_product` attribute, defaulting to None. This specifies the
+    # release promotion product that this task belongs to.
+    Required('shipping-product', default=None): Any(
+        None,
+        'devedition',
+        'fennec',
+        'firefox',
+    ),
+
     # Coalescing provides the facility for tasks to be superseded by the same
     # task in a subsequent commit, if the current task backlog reaches an
     # explicit threshold. Both age and size thresholds need to be met in order
     # for coalescing to be triggered.
     Optional('coalesce'): {
         # A unique identifier per job (typically a hash of the job label) in
         # order to partition tasks into appropriate sets for coalescing. This
         # is combined with the project in order to generate a unique coalescing
@@ -406,18 +425,16 @@ task_description_schema = Schema({
         },
         Required('properties'): {
             'product': basestring,
             Optional('build_number'): int,
             Optional('release_promotion'): bool,
             Optional('tuxedo_server_url'): optionally_keyed_by('project', basestring),
             Extra: taskref_or_string,  # additional properties are allowed
         },
-        Optional('scopes'): [basestring],
-        Optional('routes'): [basestring],
     }, {
         Required('implementation'): 'native-engine',
         Required('os'): Any('macosx', 'linux'),
 
         # A link for an executable to download
         Optional('context'): basestring,
 
         # Tells the worker whether machine should reboot
@@ -660,16 +677,22 @@ def superseder_url(config, task):
     size = task['coalesce']['size']
     return SUPERSEDER_URL.format(
         age=age,
         size=size,
         key=key
     )
 
 
+def verify_index_job_name(index):
+    job_name = index['job-name']
+    if job_name not in JOB_NAME_WHITELIST:
+        raise Exception(JOB_NAME_WHITELIST_ERROR.format(job_name))
+
+
 @payload_builder('docker-worker')
 def build_docker_worker_payload(config, task, task_def):
     worker = task['worker']
     level = int(config.params['level'])
 
     image = worker['docker-image']
     if isinstance(image, dict):
         if 'in-tree' in image:
@@ -998,23 +1021,33 @@ def build_macosx_engine_payload(config, 
         raise Exception('needs-sccache not supported in native-engine')
 
 
 @payload_builder('buildbot-bridge')
 def build_buildbot_bridge_payload(config, task, task_def):
     task['extra'].pop('treeherder', None)
     task['extra'].pop('treeherderEnv', None)
     worker = task['worker']
+
+    if worker['properties'].get('tuxedo_server_url'):
+        resolve_keyed_by(
+            worker, 'properties.tuxedo_server_url', worker['buildername'],
+            **config.params
+        )
+
     task_def['payload'] = {
         'buildername': worker['buildername'],
         'sourcestamp': worker['sourcestamp'],
         'properties': worker['properties'],
     }
-    task_def['scopes'].extend(worker.get('scopes', []))
-    task_def['routes'].extend(worker.get('routes', []))
+    task_def.setdefault('scopes', [])
+    if worker['properties'].get('release_promotion'):
+        task_def['scopes'].append(
+            "project:releng:buildbot-bridge:builder-name:{}".format(worker['buildername'])
+        )
 
 
 transforms = TransformSequence()
 
 
 @transforms.add
 def task_name_from_label(config, tasks):
     for task in tasks:
@@ -1035,22 +1068,20 @@ def validate(config, tasks):
             "In task {!r}:".format(task.get('label', '?no-label?')))
 
 
 @index_builder('generic')
 def add_generic_index_routes(config, task):
     index = task.get('index')
     routes = task.setdefault('routes', [])
 
-    job_name = index['job-name']
-    if job_name not in JOB_NAME_WHITELIST:
-        raise Exception(JOB_NAME_WHITELIST_ERROR.format(job_name))
+    verify_index_job_name(index)
 
     subs = config.params.copy()
-    subs['job-name'] = job_name
+    subs['job-name'] = index['job-name']
     subs['build_date_long'] = time.strftime("%Y.%m.%d.%Y%m%d%H%M%S",
                                             time.gmtime(config.params['build_date']))
     subs['product'] = index['product']
 
     project = config.params.get('project')
 
     for tpl in V2_ROUTE_TEMPLATES:
         routes.append(tpl.format(**subs))
@@ -1064,55 +1095,74 @@ def add_generic_index_routes(config, tas
     return task
 
 
 @index_builder('nightly')
 def add_nightly_index_routes(config, task):
     index = task.get('index')
     routes = task.setdefault('routes', [])
 
-    job_name = index['job-name']
-    if job_name not in JOB_NAME_WHITELIST:
-        raise Exception(JOB_NAME_WHITELIST_ERROR.format(job_name))
+    verify_index_job_name(index)
 
     subs = config.params.copy()
-    subs['job-name'] = job_name
+    subs['job-name'] = index['job-name']
     subs['build_date_long'] = time.strftime("%Y.%m.%d.%Y%m%d%H%M%S",
                                             time.gmtime(config.params['build_date']))
     subs['build_date'] = time.strftime("%Y.%m.%d",
                                        time.gmtime(config.params['build_date']))
     subs['product'] = index['product']
 
     for tpl in V2_NIGHTLY_TEMPLATES:
         routes.append(tpl.format(**subs))
 
     # Also add routes for en-US
     task = add_l10n_index_routes(config, task, force_locale="en-US")
 
     return task
 
 
+@index_builder('release')
+def add_release_index_routes(config, task):
+    index = task.get('index')
+    routes = []
+    release_config = get_release_config(config, force=True)
+
+    verify_index_job_name(index)
+
+    subs = config.params.copy()
+    subs['build_number'] = str(release_config['build_number'])
+    subs['revision'] = subs['head_rev']
+    subs['underscore_version'] = release_config['version'].replace('.', '_')
+    subs['product'] = index['product']
+    subs['branch'] = subs['project']
+
+    for rt in task.get('routes', []):
+        routes.append(rt.format(**subs))
+
+    task['routes'] = routes
+
+    return task
+
+
 @index_builder('nightly-with-multi-l10n')
 def add_nightly_multi_index_routes(config, task):
     task = add_nightly_index_routes(config, task)
     task = add_l10n_index_routes(config, task, force_locale="multi")
     return task
 
 
 @index_builder('l10n')
 def add_l10n_index_routes(config, task, force_locale=None):
     index = task.get('index')
     routes = task.setdefault('routes', [])
 
-    job_name = index['job-name']
-    if job_name not in JOB_NAME_WHITELIST:
-        raise Exception(JOB_NAME_WHITELIST_ERROR.format(job_name))
+    verify_index_job_name(index)
 
     subs = config.params.copy()
-    subs['job-name'] = job_name
+    subs['job-name'] = index['job-name']
     subs['build_date_long'] = time.strftime("%Y.%m.%d.%Y%m%d%H%M%S",
                                             time.gmtime(config.params['build_date']))
     subs['product'] = index['product']
 
     locales = task['attributes'].get('chunk_locales',
                                      task['attributes'].get('all_locales'))
     # Some tasks has only one locale set
     if task['attributes'].get('locale'):
@@ -1272,16 +1322,18 @@ def build_task(config, tasks):
                 th_push_link)
 
         # add the payload and adjust anything else as required (e.g., scopes)
         payload_builders[task['worker']['implementation']](config, task, task_def)
 
         attributes = task.get('attributes', {})
         attributes['run_on_projects'] = task.get('run-on-projects', ['all'])
         attributes['always_target'] = task['always-target']
+        attributes['shipping_phase'] = task['shipping-phase']
+        attributes['shipping_product'] = task['shipping-product']
 
         # Set MOZ_AUTOMATION on all jobs.
         if task['worker']['implementation'] in (
             'generic-worker',
             'docker-engine',
             'native-engine',
             'docker-worker',
         ):
--- a/taskcluster/taskgraph/util/scriptworker.py
+++ b/taskcluster/taskgraph/util/scriptworker.py
@@ -409,33 +409,24 @@ get_push_apk_rollout_percentage = functo
 def get_release_config(config, force=False):
     """Get the build number and version for a release task.
 
     Currently only applies to beetmover tasks.
 
     Args:
         config (dict): the task config that defines the target task method.
 
-    Raises:
-        ValueError: if a release graph doesn't define a valid
-            `os.environ['BUILD_NUMBER']`
-
     Returns:
         dict: containing both `build_number` and `version`.  This can be used to
             update `task.payload`.
     """
     release_config = {}
     if force or config.params['target_tasks_method'] in BEETMOVER_RELEASE_TARGET_TASKS:
-        next_version = str(os.environ.get("NEXT_VERSION", ""))
-        if next_version != "":
-            release_config['next_version'] = next_version
-        build_number = str(os.environ.get("BUILD_NUMBER", 1))
-        if not build_number.isdigit():
-            raise ValueError("Release graphs must specify `BUILD_NUMBER` in the environment!")
-        release_config['build_number'] = int(build_number)
+        release_config['next_version'] = str(config.params['next_version'])
+        release_config['build_number'] = int(config.params['build_number'])
         with open(VERSION_PATH, "r") as fh:
             version = fh.readline().rstrip()
         release_config['version'] = version
     return release_config
 
 
 def get_signing_cert_scope_per_platform(build_platform, is_nightly, config):
     if build_platform in (
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -15,17 +15,18 @@ const {classes: Cc, interfaces: Ci, util
 
 this.EXPORTED_SYMBOLS = ["ExtensionParent"];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AppConstants: "resource://gre/modules/AppConstants.jsm",
-  DeferredSave: "resource://gre/modules/DeferredSave.jsm",
+  AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
+  DeferredTask: "resource://gre/modules/DeferredTask.jsm",
   E10SUtils: "resource:///modules/E10SUtils.jsm",
   MessageChannel: "resource://gre/modules/MessageChannel.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   NativeApp: "resource://gre/modules/NativeMessaging.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   Schemas: "resource://gre/modules/Schemas.jsm",
 });
 
@@ -1395,36 +1396,38 @@ let IconDetails = {
 
 StartupCache = {
   DB_NAME: "ExtensionStartupCache",
 
   STORE_NAMES: Object.freeze(["general", "locales", "manifests", "other", "permissions", "schemas"]),
 
   file: OS.Path.join(OS.Constants.Path.localProfileDir, "startupCache", "webext.sc.lz4"),
 
-  get saver() {
-    if (!this._saver) {
+  async _saveNow() {
+    let data = new Uint8Array(aomStartup.encodeBlob(this._data));
+    await OS.File.writeAtomic(this.file, data, {tmpPath: `${this.file}.tmp`});
+  },
+
+  async save() {
+    if (!this._saveTask) {
       OS.File.makeDir(OS.Path.dirname(this.file), {
         ignoreExisting: true,
         from: OS.Constants.Path.localProfileDir,
       });
 
-      this._saver = new DeferredSave(this.file,
-                                     () => this.getBlob(),
-                                     {delay: 5000});
-    }
-    return this._saver;
-  },
+      this._saveTask = new DeferredTask(() => this._saveNow(), 5000);
 
-  async save() {
-    return this.saver.saveChanges();
-  },
-
-  getBlob() {
-    return new Uint8Array(aomStartup.encodeBlob(this._data));
+      AsyncShutdown.profileBeforeChange.addBlocker(
+        "Flush WebExtension StartupCache",
+        async () => {
+          await this._saveTask.finalize();
+          this._saveTask = null;
+        });
+    }
+    return this._saveTask.arm();
   },
 
   _data: null,
   async _readData() {
     let result = new Map();
     try {
       let {buffer} = await OS.File.read(this.file);
 
deleted file mode 100644
--- a/toolkit/mozapps/extensions/DeferredSave.jsm
+++ /dev/null
@@ -1,299 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const Cu = Components.utils;
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/osfile.jsm");
-/* globals OS*/
-Cu.import("resource://gre/modules/PromiseUtils.jsm");
-
-// Make it possible to mock out timers for testing
-var MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-
-this.EXPORTED_SYMBOLS = ["DeferredSave"];
-
-// If delay parameter is not provided, default is 50 milliseconds.
-const DEFAULT_SAVE_DELAY_MS = 50;
-
-Cu.import("resource://gre/modules/Log.jsm");
-// Configure a logger at the parent 'DeferredSave' level to format
-// messages for all the modules under DeferredSave.*
-const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave";
-var parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID);
-parentLogger.level = Log.Level.Warn;
-var formatter = new Log.BasicFormatter();
-// Set parent logger (and its children) to append to
-// the Javascript section of the Browser Console
-parentLogger.addAppender(new Log.ConsoleAppender(formatter));
-// Set parent logger (and its children) to
-// also append to standard out
-parentLogger.addAppender(new Log.DumpAppender(formatter));
-
-// Provide the ability to enable/disable logging
-// messages at runtime.
-// If the "extensions.logging.enabled" preference is
-// missing or 'false', messages at the WARNING and higher
-// severity should be logged to the JS console and standard error.
-// If "extensions.logging.enabled" is set to 'true', messages
-// at DEBUG and higher should go to JS console and standard error.
-Cu.import("resource://gre/modules/Services.jsm");
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
-                                  "resource://gre/modules/AsyncShutdown.jsm");
-
-const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
-const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
-
-/**
-* Preference listener which listens for a change in the
-* "extensions.logging.enabled" preference and changes the logging level of the
-* parent 'addons' level logger accordingly.
-*/
-var PrefObserver = {
- init() {
-   Services.prefs.addObserver(PREF_LOGGING_ENABLED, this);
-   Services.obs.addObserver(this, "xpcom-shutdown");
-   this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
- },
-
- observe(aSubject, aTopic, aData) {
-   if (aTopic == "xpcom-shutdown") {
-     Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
-     Services.obs.removeObserver(this, "xpcom-shutdown");
-   } else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
-     let debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED, false);
-     if (debugLogEnabled) {
-       parentLogger.level = Log.Level.Debug;
-     } else {
-       parentLogger.level = Log.Level.Warn;
-     }
-   }
- }
-};
-
-PrefObserver.init();
-
-/**
- * A module to manage deferred, asynchronous writing of data files
- * to disk. Writing is deferred by waiting for a specified delay after
- * a request to save the data, before beginning to write. If more than
- * one save request is received during the delay, all requests are
- * fulfilled by a single write.
- *
- * @constructor
- * @param {string} aPath
- *        String representing the full path of the file where the data
- *        is to be written.
- * @param {function} aDataProvider
- *        Callback function that takes no argument and returns the data to
- *        be written. If aDataProvider returns an ArrayBufferView, the
- *        bytes it contains are written to the file as is.
- *        If aDataProvider returns a String the data are UTF-8 encoded
- *        and then written to the file.
- * @param {object | integer} [aOptions]
- *        The delay in milliseconds between the first saveChanges() call
- *        that marks the data as needing to be saved, and when the DeferredSave
- *        begins writing the data to disk. Default 50 milliseconds.
- *
- *        Or, an options object containing:
- *         - delay: A delay in milliseconds.
- *         - finalizeAt: An AsyncShutdown blocker during which to
- *           finalize any pending writes.
- */
-this.DeferredSave = function(aPath, aDataProvider, aOptions = {}) {
-  if (typeof aOptions == "number") {
-    aOptions = {delay: aOptions};
-  }
-
-  // Create a new logger (child of 'DeferredSave' logger)
-  // for use by this particular instance of DeferredSave object
-  let leafName = OS.Path.basename(aPath);
-  let logger_id = DEFERREDSAVE_PARENT_LOGGER_ID + "." + leafName;
-  this.logger = Log.repository.getLogger(logger_id);
-
-  // @type {Deferred|null}, null when no data needs to be written
-  // @resolves with the result of OS.File.writeAtomic when all writes complete
-  // @rejects with the error from OS.File.writeAtomic if the write fails,
-  //          or with the error from aDataProvider() if that throws.
-  this._pending = null;
-
-  // @type {Promise}, completes when the in-progress write (if any) completes,
-  //       kept as a resolved promise at other times to simplify logic.
-  //       Because _deferredSave() always uses _writing.then() to execute
-  //       its next action, we don't need a special case for whether a write
-  //       is in progress - if the previous write is complete (and the _writing
-  //       promise is already resolved/rejected), _writing.then() starts
-  //       the next action immediately.
-  //
-  // @resolves with the result of OS.File.writeAtomic
-  // @rejects with the error from OS.File.writeAtomic
-  this._writing = Promise.resolve(0);
-
-  // Are we currently waiting for a write to complete
-  this.writeInProgress = false;
-
-  this._path = aPath;
-  this._dataProvider = aDataProvider;
-
-  this._timer = null;
-
-  // Some counters for telemetry
-  // The total number of times the file was written
-  this.totalSaves = 0;
-
-  // The number of times the data became dirty while
-  // another save was in progress
-  this.overlappedSaves = 0;
-
-  // Error returned by the most recent write (if any)
-  this._lastError = null;
-
-  if (aOptions.delay && (aOptions.delay > 0))
-    this._delay = aOptions.delay;
-  else
-    this._delay = DEFAULT_SAVE_DELAY_MS;
-
-  this._finalizeAt = aOptions.finalizeAt || AsyncShutdown.profileBeforeChange;
-  this._finalize = this._finalize.bind(this);
-  this._finalizeAt.addBlocker(`DeferredSave: writing data to ${aPath}`,
-                              this._finalize);
-};
-
-this.DeferredSave.prototype = {
-  get dirty() {
-    return this._pending || this.writeInProgress;
-  },
-
-  get lastError() {
-    return this._lastError;
-  },
-
-  get path() {
-    return this._path;
-  },
-
-  // Start the pending timer if data is dirty
-  _startTimer() {
-    if (!this._pending) {
-      return;
-    }
-
-      this.logger.debug("Starting timer");
-    if (!this._timer)
-      this._timer = MakeTimer();
-    this._timer.initWithCallback(() => this._timerCallback(),
-                                 this._delay, Ci.nsITimer.TYPE_ONE_SHOT);
-  },
-
-  /**
-   * Mark the current stored data dirty, and schedule a flush to disk
-   * @return A Promise<integer> that will be resolved after the data is written to disk;
-   *         the promise is resolved with the number of bytes written.
-   */
-  saveChanges() {
-      this.logger.debug("Save changes");
-    if (!this._pending) {
-      if (this.writeInProgress) {
-          this.logger.debug("Data changed while write in progress");
-        this.overlappedSaves++;
-      }
-      this._pending = PromiseUtils.defer();
-      // Wait until the most recent write completes or fails (if it hasn't already)
-      // and then restart our timer
-      this._writing.then(count => this._startTimer(), error => this._startTimer());
-    }
-    return this._pending.promise;
-  },
-
-  _timerCallback() {
-    Services.tm.idleDispatchToMainThread(() => this._deferredSave());
-  },
-
-  _deferredSave() {
-    let pending = this._pending;
-    this._pending = null;
-    let writing = this._writing;
-    this._writing = pending.promise;
-
-    // In either the success or the exception handling case, we don't need to handle
-    // the error from _writing here; it's already being handled in another then()
-    let toSave = null;
-    try {
-      toSave = this._dataProvider();
-    } catch (e) {
-        this.logger.error("Deferred save dataProvider failed", e);
-      writing.catch(error => {})
-        .then(count => {
-          pending.reject(e);
-        });
-      return;
-    }
-
-    writing.catch(error => { return 0; })
-    .then(count => {
-        this.logger.debug("Starting write");
-      this.totalSaves++;
-      this.writeInProgress = true;
-
-      OS.File.writeAtomic(this._path, toSave, {tmpPath: this._path + ".tmp"})
-      .then(
-        result => {
-          this._lastError = null;
-          this.writeInProgress = false;
-              this.logger.debug("Write succeeded");
-          pending.resolve(result);
-        },
-        error => {
-          this._lastError = error;
-          this.writeInProgress = false;
-              this.logger.warn("Write failed", error);
-          pending.reject(error);
-        });
-    });
-  },
-
-  /**
-   * Immediately save the dirty data to disk, skipping
-   * the delay of normal operation. Note that the write
-   * still happens asynchronously in the worker
-   * thread from OS.File.
-   *
-   * There are four possible situations:
-   * 1) Nothing to flush
-   * 2) Data is not currently being written, in-memory copy is dirty
-   * 3) Data is currently being written, in-memory copy is clean
-   * 4) Data is being written and in-memory copy is dirty
-   *
-   * @return Promise<integer> that will resolve when all in-memory data
-   *         has finished being flushed, returning the number of bytes
-   *         written. If all in-memory data is clean, completes with the
-   *         result of the most recent write.
-   */
-  flush() {
-    // If we have pending changes, cancel our timer and set up the write
-    // immediately (_deferredSave queues the write for after the most
-    // recent write completes, if it hasn't already)
-    if (this._pending) {
-        this.logger.debug("Flush called while data is dirty");
-      if (this._timer) {
-        this._timer.cancel();
-        this._timer = null;
-      }
-      this._deferredSave();
-    }
-
-    return this._writing;
-  },
-
-  _finalize() {
-    return this.flush().catch(Cu.reportError);
-  },
-
-};
--- a/toolkit/mozapps/extensions/internal/AddonRepository.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonRepository.jsm
@@ -4,32 +4,29 @@
 
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource://gre/modules/AddonManager.jsm");
-/* globals AddonManagerPrivate*/
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "OS",
-                                  "resource://gre/modules/osfile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave",
-                                  "resource://gre/modules/DeferredSave.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
-                                  "resource://gre/modules/Preferences.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
-                                  "resource://gre/modules/ServiceRequest.jsm");
-
+XPCOMUtils.defineLazyModuleGetters(this, {
+  AddonManager: "resource://gre/modules/AddonManager.jsm",
+  AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm",
+  AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
+  DeferredTask: "resource://gre/modules/DeferredTask.jsm",
+  Services: "resource://gre/modules/Services.jsm",
+  ServiceRequest: "resource://gre/modules/ServiceRequest.jsm",
+  NetUtil: "resource://gre/modules/NetUtil.jsm",
+  OS: "resource://gre/modules/osfile.jsm",
+  Preferences: "resource://gre/modules/Preferences.jsm",
+});
 
 this.EXPORTED_SYMBOLS = [ "AddonRepository" ];
 
 const PREF_GETADDONS_CACHE_ENABLED       = "extensions.getAddons.cache.enabled";
 const PREF_GETADDONS_CACHE_TYPES         = "extensions.getAddons.cache.types";
 const PREF_GETADDONS_CACHE_ID_ENABLED    = "extensions.%ID%.getAddons.cache.enabled";
 const PREF_GETADDONS_BROWSEADDONS        = "extensions.getAddons.browseAddons";
 const PREF_GETADDONS_BYIDS               = "extensions.getAddons.get.url";
@@ -1522,16 +1519,19 @@ this.AddonRepository = {
 
   flush() {
     return AddonDatabase.flush();
   }
 };
 
 var AddonDatabase = {
   connectionPromise: null,
+  _saveTask: null,
+  _blockerAdded: false,
+
   // the in-memory database
   DB: BLANK_DB(),
 
   /**
    * A getter to retrieve the path to the DB
    */
   get jsonFile() {
     return OS.Path.join(OS.Constants.Path.profileDir, FILE_DATABASE);
@@ -1571,17 +1571,17 @@ var AddonDatabase = {
        } catch (e) {
          if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
            logger.debug("No " + FILE_DATABASE + " found.");
          } else {
            logger.error(`Malformed ${FILE_DATABASE}: ${e} - resetting to empty`);
          }
 
          // Create a blank addons.json file
-         this._saveDBToDisk();
+         this.save();
 
          Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
          return this.DB;
        }
 
        Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
 
        // We use _insertAddon manually instead of calling
@@ -1610,83 +1610,102 @@ var AddonDatabase = {
    */
   shutdown(aSkipFlush) {
     if (!this.connectionPromise) {
       return Promise.resolve();
     }
 
     this.connectionPromise = null;
 
-    if (aSkipFlush) {
+    if (aSkipFlush || !this._saveTask) {
       return Promise.resolve();
     }
-    return this.Writer.flush();
+
+    let promise = this._saveTask.finalize();
+    this._saveTask = null;
+    return promise;
   },
 
   /**
    * Asynchronously deletes the database, shutting down the connection
    * first if initialized
    *
    * @param  aCallback
    *         An optional callback to call once complete
    * @return Promise{null} resolves when the database has been deleted
    */
   delete(aCallback) {
     this.DB = BLANK_DB();
 
-    this._deleting = this.Writer.flush()
-      .catch(() => {})
-      // shutdown(true) never rejects
-      .then(() => this.shutdown(true))
+    if (this._saveTask) {
+      this._saveTask.disarm();
+      this._saveTask = null;
+    }
+
+    // shutdown(true) never rejects
+    this._deleting = this.shutdown(true)
       .then(() => OS.File.remove(this.jsonFile, {}))
       .catch(error => logger.error("Unable to delete Addon Repository file " +
                                  this.jsonFile, error))
       .then(() => this._deleting = null)
       .then(aCallback);
     return this._deleting;
   },
 
-  toJSON() {
+  async _saveNow() {
     let json = {
       schema: this.DB.schema,
       addons: []
     };
 
     for (let [, value] of this.DB.addons)
       json.addons.push(value);
 
-    return json;
+    await OS.File.writeAtomic(this.jsonFile, JSON.stringify(json),
+                              {tmpPath: `${this.jsonFile}.tmp`});
   },
 
-  /*
-   * This is a deferred task writer that is used
-   * to batch operations done within 50ms of each
-   * other and thus generating only one write to disk
-   */
-  get Writer() {
-    delete this.Writer;
-    this.Writer = new DeferredSave(
-      this.jsonFile,
-      () => { return JSON.stringify(this); },
-      DB_BATCH_TIMEOUT_MS
-    );
-    return this.Writer;
+  save() {
+    if (!this._saveTask) {
+      this._saveTask = new DeferredTask(() => this._saveNow(), DB_BATCH_TIMEOUT_MS);
+
+      if (!this._blockerAdded) {
+        AsyncShutdown.profileBeforeChange.addBlocker(
+          "Flush AddonRepository",
+          async () => {
+            if (!this._saveTask) {
+              return;
+            }
+            await this._saveTask.finalize();
+            this._saveTask = null;
+          });
+        this._blockerAdded = true;
+      }
+    }
+    this._saveTask.arm();
   },
 
   /**
    * Flush any pending I/O on the addons.json file
    * @return: Promise{null}
    *          Resolves when the pending I/O (writing out or deleting
    *          addons.json) completes
    */
   flush() {
     if (this._deleting) {
       return this._deleting;
     }
-    return this.Writer.flush();
+
+    if (this._saveTask) {
+      let promise = this._saveTask.finalize();
+      this._saveTask = null;
+      return promise;
+    }
+
+    return Promise.resolve();
   },
 
   /**
    * Asynchronously retrieve all add-ons from the database
    * @return: Promise{Map}
    *          Resolves when the add-ons are retrieved from the database
    */
   retrieveStoredData() {
@@ -1718,25 +1737,22 @@ var AddonDatabase = {
    *
    * @param  aAddons
    *         The array of add-ons to insert
    * @param  aCallback
    *         An optional callback to call once complete
    */
   async insertAddons(aAddons, aCallback) {
     await this.openConnection();
-    await this._insertAddons(aAddons, aCallback);
-  },
 
-  async _insertAddons(aAddons, aCallback) {
     for (let addon of aAddons) {
       this._insertAddon(addon);
     }
 
-    await this._saveDBToDisk();
+    this.save();
     aCallback && aCallback();
   },
 
   /**
    * Inserts an individual add-on into the database. If the add-on already
    * exists in the database (by id), then the specified add-on will not be
    * inserted.
    *
@@ -1863,28 +1879,16 @@ var AddonDatabase = {
         addon._unsupportedProperties[remainingProperty] =
           aObj[remainingProperty];
     }
 
     return addon;
   },
 
   /**
-   * Write the in-memory DB to disk, after waiting for
-   * the DB_BATCH_TIMEOUT_MS timeout.
-   *
-   * @return Promise A promise that resolves after the
-   *                 write to disk has completed.
-   */
-  _saveDBToDisk() {
-    return this.Writer.saveChanges().catch(
-      e => logger.error("SaveDBToDisk failed", e));
-  },
-
-  /**
    * Make a developer object from a vanilla
    * JS object from the JSON database
    *
    * @param  aObj
    *         The JS object to use
    * @return The created developer
    */
   _makeDeveloper(aObj) {
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -611,17 +611,17 @@ var AddonTestUtils = {
         // Clear any crash report annotations
         this.appInfo.annotations = {};
 
         // Force the XPIProvider provider to reload to better
         // simulate real-world usage.
         let XPIscope = Cu.import("resource://gre/modules/addons/XPIProvider.jsm", {});
         // This would be cleaner if I could get it as the rejection reason from
         // the AddonManagerInternal.shutdown() promise
-        let shutdownError = XPIscope.XPIProvider._shutdownError;
+        let shutdownError = XPIscope.XPIDatabase._saveError;
 
         AddonManagerPrivate.unregisterProvider(XPIscope.XPIProvider);
         Cu.unload("resource://gre/modules/addons/XPIProvider.jsm");
         Cu.unload("resource://gre/modules/addons/XPIInstall.jsm");
 
         // We need to set this in order reset the startup service, which
         // is only possible when running in automation.
         Services.prefs.setBoolPref(PREF_DISABLE_SECURITY, true);
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -2086,18 +2086,17 @@ this.XPIProvider = {
     try {
       AddonManagerPrivate.recordTimestamp("XPI_startup_begin");
 
       logger.debug("startup");
       this.runPhase = XPI_STARTING;
       this.installs = new Set();
       this.installLocations = [];
       this.installLocationsByName = {};
-      // Hook for tests to detect when saving database at shutdown time fails
-      this._shutdownError = null;
+
       // Clear this at startup for xpcshell test restarts
       this._telemetryDetails = {};
       // Register our details structure with AddonManager
       AddonManagerPrivate.setTelemetryDetails("XPI", this._telemetryDetails);
 
       let hasRegistry = ("nsIWindowsRegKey" in Ci);
 
       let enabledScopes = Services.prefs.getIntPref(PREF_EM_ENABLED_SCOPES,
@@ -2374,21 +2373,17 @@ this.XPIProvider = {
     this.installs = null;
     this.installLocations = null;
     this.installLocationsByName = null;
 
     // This is needed to allow xpcshell tests to simulate a restart
     this.extensionsActive = false;
     this._addonFileMap.clear();
 
-    try {
-      await XPIDatabase.shutdown();
-    } catch (err) {
-      this._shutdownError = err;
-    }
+    await XPIDatabase.shutdown();
   },
 
   cleanupTemporaryAddons() {
     let tempLocation = XPIStates.getLocation(TemporaryInstallLocation.name);
     if (tempLocation) {
       for (let [id, addon] of tempLocation.entries()) {
         tempLocation.delete(id);
 
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -11,28 +11,27 @@
           flushChromeCaches, descriptorToPath */
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cr = Components.results;
 var Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/AddonManager.jsm");
-/* globals AddonManagerPrivate*/
 
-XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
-                                  "resource://gre/modules/addons/AddonRepository.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
-                                  "resource://gre/modules/FileUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave",
-                                  "resource://gre/modules/DeferredSave.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "OS",
-                                  "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  AddonManager: "resource://gre/modules/AddonManager.jsm",
+  AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm",
+  AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm",
+  DeferredTask: "resource://gre/modules/DeferredTask.jsm",
+  FileUtils: "resource://gre/modules/FileUtils.jsm",
+  OS: "resource://gre/modules/osfile.jsm",
+  Services: "resource://gre/modules/Services.jsm",
+});
+
 XPCOMUtils.defineLazyServiceGetter(this, "Blocklist",
                                    "@mozilla.org/extensions/blocklist;1",
                                    Ci.nsIBlocklistService);
 
 Cu.import("resource://gre/modules/Log.jsm");
 const LOGGER_ID = "addons.xpi-utils";
 
 const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
@@ -275,84 +274,86 @@ this.XPIDatabase = {
   initialized: false,
   // The database file
   jsonFile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_JSON_DB], true),
   // Migration data loaded from an old version of the database.
   migrateData: null,
   // Active add-on directories loaded from extensions.ini and prefs at startup.
   activeBundles: null,
 
+  _saveTask: null,
+
   // Saved error object if we fail to read an existing database
   _loadError: null,
 
+  // Saved error object if we fail to save the database
+  _saveError: null,
+
   // Error reported by our most recent attempt to read or write the database, if any
   get lastError() {
     if (this._loadError)
       return this._loadError;
-    if (this._deferredSave)
-      return this._deferredSave.lastError;
+    if (this._saveError)
+      return this._saveError;
     return null;
   },
 
+  async _saveNow() {
+    try {
+      let json = JSON.stringify(this);
+      let path = this.jsonFile.path;
+      await OS.File.writeAtomic(path, json, {tmpPath: `${path}.tmp`});
+
+      if (!this._schemaVersionSet) {
+        // Update the XPIDB schema version preference the first time we
+        // successfully save the database.
+        logger.debug("XPI Database saved, setting schema version preference to " + DB_SCHEMA);
+        Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
+        this._schemaVersionSet = true;
+
+        // Reading the DB worked once, so we don't need the load error
+        this._loadError = null;
+      }
+    } catch (error) {
+      logger.warn("Failed to save XPI database", error);
+      this._saveError = error;
+      throw error;
+    }
+  },
+
   /**
    * Mark the current stored data dirty, and schedule a flush to disk
    */
   saveChanges() {
     if (!this.initialized) {
       throw new Error("Attempt to use XPI database when it is not initialized");
     }
 
     if (XPIProvider._closing) {
       // use an Error here so we get a stack trace.
       let err = new Error("XPI database modified after shutdown began");
       logger.warn(err);
       AddonManagerPrivate.recordSimpleMeasure("XPIDB_late_stack", Log.stackTrace(err));
     }
 
-    if (!this._deferredSave) {
-      this._deferredSave = new DeferredSave(this.jsonFile.path,
-                                            () => JSON.stringify(this),
-                                            ASYNC_SAVE_DELAY_MS);
+    if (!this._saveTask) {
+      this._saveTask = new DeferredTask(() => this._saveNow(),
+                                        ASYNC_SAVE_DELAY_MS);
     }
 
-    let promise = this._deferredSave.saveChanges();
-    if (!this._schemaVersionSet) {
-      this._schemaVersionSet = true;
-      promise = promise.then(
-        count => {
-          // Update the XPIDB schema version preference the first time we successfully
-          // save the database.
-          logger.debug("XPI Database saved, setting schema version preference to " + DB_SCHEMA);
-          Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
-          // Reading the DB worked once, so we don't need the load error
-          this._loadError = null;
-        },
-        error => {
-          // Need to try setting the schema version again later
-          this._schemaVersionSet = false;
-          // this._deferredSave.lastError has the most recent error so we don't
-          // need this any more
-          this._loadError = null;
+    this._saveTask.arm();
+  },
 
-          throw error;
-        });
+  async finalize() {
+    // handle the "in memory only" and "saveChanges never called" cases
+    if (!this._saveTask) {
+      return;
     }
 
-    promise.catch(error => {
-      logger.warn("Failed to save XPI database", error);
-    });
-  },
-
-  flush() {
-    // handle the "in memory only" and "saveChanges never called" cases
-    if (!this._deferredSave) {
-      return Promise.resolve(0);
-    }
-
-    return this._deferredSave.flush();
+    await this._saveTask.finalize();
   },
 
   /**
    * Converts the current internal state of the XPI addon database to
    * a JSON.stringify()-ready structure
    */
   toJSON() {
     if (!this.addonDB) {
@@ -653,49 +654,36 @@ this.XPIDatabase = {
     logger.debug("shutdown");
     if (this.initialized) {
       // If our last database I/O had an error, try one last time to save.
       if (this.lastError)
         this.saveChanges();
 
       this.initialized = false;
 
-      if (this._deferredSave) {
-        AddonManagerPrivate.recordSimpleMeasure(
-            "XPIDB_saves_total", this._deferredSave.totalSaves);
-        AddonManagerPrivate.recordSimpleMeasure(
-            "XPIDB_saves_overlapped", this._deferredSave.overlappedSaves);
-        AddonManagerPrivate.recordSimpleMeasure(
-            "XPIDB_saves_late", this._deferredSave.dirty ? 1 : 0);
-      }
-
       // If we're shutting down while still loading, finish loading
       // before everything else!
       if (this._dbPromise) {
         await this._dbPromise;
       }
 
-      // Await and pending DB writes and finish cleaning up.
-      try {
-        await this.flush();
-      } catch (error) {
-        logger.error("Flush of XPI database failed", error);
-        AddonManagerPrivate.recordSimpleMeasure("XPIDB_shutdownFlush_failed", 1);
+      // Await any pending DB writes and finish cleaning up.
+      await this.finalize();
+
+      if (this._saveError) {
         // If our last attempt to read or write the DB failed, force a new
         // extensions.ini to be written to disk on the next startup
         Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
-
-        throw error;
       }
 
       // Clear out the cached addons data loaded from JSON
       delete this.addonDB;
       delete this._dbPromise;
       // same for the deferred save
-      delete this._deferredSave;
+      delete this._saveTask;
       // re-enable the schema version setter
       delete this._schemaVersionSet;
     }
   },
 
   /**
    * Asynchronously list all addons that match the filter function
    * @param  aFilter
--- a/toolkit/mozapps/extensions/moz.build
+++ b/toolkit/mozapps/extensions/moz.build
@@ -32,17 +32,16 @@ EXTRA_COMPONENTS += [
 
 EXTRA_PP_COMPONENTS += [
     'extensions.manifest',
 ]
 
 EXTRA_JS_MODULES += [
     'AddonManager.jsm',
     'ChromeManifestParser.jsm',
-    'DeferredSave.jsm',
     'LightweightThemeManager.jsm',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 EXPORTS.mozilla += [
     'AddonContentPolicy.h',
     'AddonManagerStartup.h',
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js
+++ /dev/null
@@ -1,545 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// Test behaviour of module to perform deferred save of data
-// files to disk
-
-"use strict";
-
-const testFile = gProfD.clone();
-testFile.append("DeferredSaveTest");
-
-Components.utils.import("resource://gre/modules/Promise.jsm");
-
-var DSContext = Components.utils.import("resource://gre/modules/DeferredSave.jsm", {});
-var DeferredSave = DSContext.DeferredSave;
-
-// Test wrapper to let us do promise/task based testing of DeferredSave
-function DeferredSaveTester(aDataProvider) {
-  let tester = {
-    // Deferred for the promise returned by the mock writeAtomic
-    waDeferred: null,
-
-    // The most recent data "written" by the mock OS.File.writeAtomic
-    writtenData: undefined,
-
-    dataToSave: "Data to save",
-
-    save: (aData, aWriteHandler) => {
-      tester.writeHandler = aWriteHandler || writer;
-      tester.dataToSave = aData;
-      return tester.saver.saveChanges();
-    },
-
-    flush: (aWriteHandler) => {
-      tester.writeHandler = aWriteHandler || writer;
-      return tester.saver.flush();
-    },
-
-    get lastError() {
-      return tester.saver.lastError;
-    }
-  };
-
-  // Default write handler for most cases where the test case doesn't need
-  // to do anything while the write is in progress; just completes the write
-  // on the next event loop
-  function writer(aTester) {
-    do_print("default write callback");
-    let length = aTester.writtenData.length;
-    do_execute_soon(() => aTester.waDeferred.resolve(length));
-  }
-
-  if (!aDataProvider)
-    aDataProvider = () => tester.dataToSave;
-
-  tester.saver = new DeferredSave(testFile.path, aDataProvider);
-
-  // Install a mock for OS.File.writeAtomic to let us control the async
-  // behaviour of the promise
-  DSContext.OS.File.writeAtomic = function mock_writeAtomic(aFile, aData, aOptions) {
-      do_print("writeAtomic: " + aFile + " data: '" + aData + "', " + aOptions.toSource());
-      tester.writtenData = aData;
-      tester.waDeferred = Promise.defer();
-      tester.writeHandler(tester);
-      return tester.waDeferred.promise;
-    };
-
-  return tester;
-}
-
-/**
- * Install a mock nsITimer factory that triggers on the next spin of
- * the event loop after it is scheduled
- */
-function setQuickMockTimer() {
-  let quickTimer = {
-    initWithCallback(aFunction, aDelay, aType) {
-      do_print("Starting quick timer, delay = " + aDelay);
-      do_execute_soon(aFunction);
-    },
-    cancel() {
-      do_throw("Attempted to cancel a quickMockTimer");
-    }
-  };
-  DSContext.MakeTimer = () => {
-    do_print("Creating quick timer");
-    return quickTimer;
-  };
-}
-
-/**
- * Install a mock nsITimer factory in DeferredSave.jsm, returning a promise that resolves
- * when the client code sets the timer. Test cases can use this to wait for client code to
- * be ready for a timer event, and then signal the event by calling mockTimer.callback().
- * This could use some enhancement; clients can re-use the returned timer,
- * but with this implementation it's not possible for the test to wait for
- * a second call to initWithCallback() on the re-used timer.
- * @return Promise{mockTimer} that resolves when initWithCallback()
- *         is called
- */
-function setPromiseMockTimer() {
-  return new Promise(resolve => {
-    let mockTimer = {
-      callback: null,
-      delay: null,
-      type: null,
-      isCancelled: false,
-
-      initWithCallback(aFunction, aDelay, aType) {
-        do_print("Starting timer, delay = " + aDelay);
-        this.callback = aFunction;
-        this.delay = aDelay;
-        this.type = aType;
-        // cancelled timers can be re-used
-        this.isCancelled = false;
-        resolve(this);
-      },
-      cancel() {
-        do_print("Cancelled mock timer");
-        this.callback = null;
-        this.delay = null;
-        this.type = null;
-        this.isCancelled = true;
-        // If initWithCallback was never called, resolve to let tests check for cancel
-        resolve(this);
-      }
-    };
-    DSContext.MakeTimer = () => {
-      do_print("Creating mock timer");
-      return mockTimer;
-    };
-  });
-}
-
-/**
- * Return a Promise<null> that resolves after the specified number of milliseconds
- */
-function delay(aDelayMS) {
-  return new Promise(resolve => {
-    do_timeout(aDelayMS, () => resolve(null));
-  });
-}
-
-// Modify set data once, ask for save, make sure it saves cleanly
-add_task(async function test_basic_save_succeeds() {
-  setQuickMockTimer();
-  let tester = DeferredSaveTester();
-  let data = "Test 1 Data";
-
-  await tester.save(data);
-  do_check_eq(tester.writtenData, data);
-  do_check_eq(1, tester.saver.totalSaves);
-});
-
-// Two saves called during the same event loop, both with callbacks
-// Make sure we save only the second version of the data
-add_task(async function test_two_saves() {
-  setQuickMockTimer();
-  let tester = DeferredSaveTester();
-  let firstCallback_happened = false;
-  let firstData = "Test first save";
-  let secondData = "Test second save";
-
-  // first save should not resolve until after the second one is called,
-  // so we can't just yield this promise
-  tester.save(firstData).then(count => {
-    do_check_eq(secondData, tester.writtenData);
-    do_check_false(firstCallback_happened);
-    firstCallback_happened = true;
-  }, do_report_unexpected_exception);
-
-  await tester.save(secondData);
-  do_check_true(firstCallback_happened);
-  do_check_eq(secondData, tester.writtenData);
-  do_check_eq(1, tester.saver.totalSaves);
-});
-
-// Two saves called with a delay in between, both with callbacks
-// Make sure we save the second version of the data
-add_task(async function test_two_saves_delay() {
-  let timerPromise = setPromiseMockTimer();
-  let tester = DeferredSaveTester();
-  let firstCallback_happened = false;
-  let delayDone = false;
-
-  let firstData = "First data to save with delay";
-  let secondData = "Modified data to save with delay";
-
-  tester.save(firstData).then(count => {
-    do_check_false(firstCallback_happened);
-    do_check_true(delayDone);
-    do_check_eq(secondData, tester.writtenData);
-    firstCallback_happened = true;
-  }, do_report_unexpected_exception);
-
-  // Wait a short time to let async events possibly spawned by the
-  // first tester.save() to run
-  await delay(2);
-  delayDone = true;
-  // request to save modified data
-  let saving = tester.save(secondData);
-  // Yield to wait for client code to set the timer
-  let activeTimer = await timerPromise;
-  // and then trigger it
-  activeTimer.callback();
-  // now wait for the DeferredSave to finish saving
-  await saving;
-  do_check_true(firstCallback_happened);
-  do_check_eq(secondData, tester.writtenData);
-  do_check_eq(1, tester.saver.totalSaves);
-  do_check_eq(0, tester.saver.overlappedSaves);
-});
-
-// Test case where OS.File immediately reports an error when the write begins
-// Also check that the "error" getter correctly returns the error
-// Then do a write that succeeds, and make sure the error is cleared
-add_task(async function test_error_immediate() {
-  let tester = DeferredSaveTester();
-  let testError = new Error("Forced failure");
-  function writeFail(aTester) {
-    aTester.waDeferred.reject(testError);
-  }
-
-  setQuickMockTimer();
-  await tester.save("test_error_immediate", writeFail).then(
-    count => do_throw("Did not get expected error"),
-    error => do_check_eq(testError.message, error.message)
-    );
-  do_check_eq(testError, tester.lastError);
-
-  // This write should succeed and clear the error
-  await tester.save("test_error_immediate succeeds");
-  do_check_eq(null, tester.lastError);
-  // The failed save attempt counts in our total
-  do_check_eq(2, tester.saver.totalSaves);
-});
-
-// Save one set of changes, then while the write is in progress, modify the
-// data two more times. Test that we re-write the dirty data exactly once
-// after the first write succeeds
-add_task(async function dirty_while_writing() {
-  let tester = DeferredSaveTester();
-  let firstData = "First data";
-  let secondData = "Second data";
-  let thirdData = "Third data";
-  let firstCallback_happened = false;
-  let secondCallback_happened = false;
-  let writer = await new Promise(resolve => {
-
-    function writeCallback(aTester) {
-      resolve(aTester.waDeferred);
-    }
-
-    setQuickMockTimer();
-    do_print("First save");
-    tester.save(firstData, writeCallback).then(
-      count => {
-        do_check_false(firstCallback_happened);
-        do_check_false(secondCallback_happened);
-        do_check_eq(tester.writtenData, firstData);
-        firstCallback_happened = true;
-      }, do_report_unexpected_exception);
-
-    do_print("waiting for writer");
-  });
-  do_print("Write started");
-
-  // Delay