Merge inbound to mozilla-central r=merge a=merge
authorshindli <shindli@mozilla.com>
Sat, 06 Jan 2018 23:46:15 +0200
changeset 449897 36aaa86e37bac0db15a499a7f48a83bf9dff5602
parent 449879 a3c7056dc085263bd19617215295c49612e0b072 (current diff)
parent 449896 6bf9c25d174c929577ea431248a5d770ef22ecd5 (diff)
child 449898 814510bf944793895b0e7b6bcb137314a9fe650f
child 449901 c23677250e786ce2cccfc92149c667f65ec37d18
child 449922 b15f238d5ff11e4c8bc35db407fbd48e04cfa163
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone59.0a1
first release with
nightly linux32
36aaa86e37ba / 59.0a1 / 20180106220101 / files
nightly linux64
36aaa86e37ba / 59.0a1 / 20180106220101 / files
nightly mac
36aaa86e37ba / 59.0a1 / 20180106220101 / files
nightly win32
36aaa86e37ba / 59.0a1 / 20180106220101 / files
nightly win64
36aaa86e37ba / 59.0a1 / 20180106220101 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central r=merge a=merge
dom/grid/test/chrome/test_grid_repeats.html
--- a/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
+++ b/browser/components/extensions/test/browser/browser_ext_url_overrides_newtab.js
@@ -306,16 +306,21 @@ add_task(async function test_new_tab_res
   ok(panel.getAttribute("panelopen") != "true",
      "The notification panel is closed after click");
   is(getNotificationSetting(extensionId), null,
      "The New Tab notification is not set after restoring the settings");
   is(addon.userDisabled, true, "The extension is now disabled");
   is(gBrowser.currentURI.spec, "about:newtab",
      "The user has been redirected to about:newtab");
 
+  // Wait for the next event tick to make sure the remaining part of the test
+  // is not executed inside tabbrowser's onLocationChange.
+  // See bug 1416153 for more details.
+  await TestUtils.waitForTick();
+
   // Reopen a browser tab and verify that there's no doorhanger.
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
   let newTabOpened = waitForNewTab();
   BrowserOpenTab();
   await newTabOpened;
 
   ok(panel.getAttribute("panelopen") != "true",
      "The notification panel is not opened after keeping the changes");
@@ -426,16 +431,21 @@ add_task(async function test_new_tab_res
   ok(panel.getAttribute("panelopen") != "true",
      "The notification panel is closed after restoring the second time");
   is(getNotificationSetting(extensionOneId), null,
      "The New Tab notification is not set after restoring the settings");
   is(addonOne.userDisabled, true, "The extension is now disabled");
   is(gBrowser.currentURI.spec, "about:newtab",
      "The user is now on the original New Tab URL since all extensions are disabled");
 
+  // Wait for the next event tick to make sure the remaining part of the test
+  // is not executed inside tabbrowser's onLocationChange.
+  // See bug 1416153 for more details.
+  await TestUtils.waitForTick();
+
   // Reopen a browser tab and verify that there's no doorhanger.
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
   let newTabOpened = waitForNewTab();
   BrowserOpenTab();
   await newTabOpened;
 
   ok(panel.getAttribute("panelopen") != "true",
      "The notification panel is not opened after keeping the changes");
--- a/dom/grid/GridLines.cpp
+++ b/dom/grid/GridLines.cpp
@@ -132,25 +132,21 @@ GridLines::SetLineInfo(const ComputedGri
 
       // Get the line names for the current line. aLineInfo->mNames
       // may contain duplicate names. This is intentional, since grid
       // layout works fine with duplicate names, and we don't want to
       // detect and remove duplicates in layout since it is an O(n^2)
       // problem. We do the work here since this is only run when
       // requested by devtools, and slowness here will not affect
       // normal browsing.
-      nsTArray<nsString> possiblyDuplicateLineNames(
+      const nsTArray<nsString>& possiblyDuplicateLineNames(
         aLineInfo->mNames.SafeElementAt(i, nsTArray<nsString>()));
 
-      // Add the possiblyDuplicateLineNames one at a time to filter
-      // out the duplicates.
       nsTArray<nsString> lineNames;
-      for (const auto& name : possiblyDuplicateLineNames) {
-        AddLineNameIfNotPresent(lineNames, name);
-      }
+      AddLineNamesIfNotPresent(lineNames, possiblyDuplicateLineNames);
 
       // Add in names from grid areas where this line is used as a boundary.
       for (auto area : aAreas) {
         bool haveNameToAdd = false;
         nsAutoString nameToAdd;
         area->GetName(nameToAdd);
         if (aIsRow) {
           if (area->RowStart() == line1Index) {
@@ -182,16 +178,26 @@ GridLines::SetLineInfo(const ComputedGri
                                                aLineInfo,
                                                lastTrackEdge,
                                                repeatIndex,
                                                numRepeatTracks,
                                                leadingTrackCount,
                                                lineNames);
       }
 
+      // If this line is the one that ends a repeat, then add
+      // in the mNamesFollowingRepeat names from aLineInfo.
+      if (numRepeatTracks > 0 &&
+          i == (aTrackInfo->mRepeatFirstTrack +
+                aTrackInfo->mNumLeadingImplicitTracks +
+                numRepeatTracks - numAddedLines)) {
+        AddLineNamesIfNotPresent(lineNames,
+                                 aLineInfo->mNamesFollowingRepeat);
+      }
+
       RefPtr<GridLine> line = new GridLine(this);
       mLines.AppendElement(line);
       MOZ_ASSERT(line1Index > 0, "line1Index must be positive.");
       bool isBeforeFirstExplicit =
         (line1Index <= aTrackInfo->mNumLeadingImplicitTracks);
       bool isAfterLastExplicit = line1Index > (leadingTrackCount + 1);
       // Calculate an actionable line number for this line, that could be used
       // in a css grid property to align a grid item or area at that line.
--- a/dom/grid/test/chrome.ini
+++ b/dom/grid/test/chrome.ini
@@ -1,8 +1,9 @@
 [chrome/test_grid_areas.html]
 [chrome/test_grid_fragmentation.html]
 [chrome/test_grid_implicit.html]
 [chrome/test_grid_lines.html]
 [chrome/test_grid_line_numbers.html]
 [chrome/test_grid_object.html]
-[chrome/test_grid_repeats.html]
+[chrome/test_grid_repeat_auto_fit.html]
+[chrome/test_grid_repeat_auto_fill.html]
 [chrome/test_grid_tracks.html]
new file mode 100644
--- /dev/null
+++ b/dom/grid/test/chrome/test_grid_repeat_auto_fill.html
@@ -0,0 +1,614 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+<style>
+body {
+  margin: 40px;
+}
+.wrapper {
+  display: grid;
+  width: 600px;
+  grid-gap: 0px;
+  grid-auto-column: 50px;
+  background-color: #f00;
+}
+.grid1 {
+  grid-template-columns: 50px 0px repeat(auto-fill, 100px);
+}
+.grid2 {
+  grid-template-columns: 50px 0px [real-before] repeat(auto-fill, [before] 100px [after]) [real-after] 0px [final];
+}
+.grid3 {
+  grid-template-columns: repeat(3, 66px) [real-before] repeat(auto-fill, [before] 100px [after]) [real-after];
+}
+.grid4 {
+  grid-template-columns: repeat(2, 100px) repeat(auto-fill, 50px);
+}
+.grid5 {
+  grid-template-columns: [real-before] repeat(auto-fill, [before] 100px [after]) [real-after];
+}
+.grid6 {
+  grid-template-columns: [first] 0px [real-before] repeat(auto-fill, [before] 100px [after]) [real-after];
+}
+.grid7 {
+  grid-template-columns: [real-before before] repeat(auto-fill, [before] 100px [after]) [after real-after] 0px [final];
+}
+.grid8 {
+  grid-template-columns: [real-before] repeat(auto-fill, [before] 1000px [after]) [real-after];
+}
+.grid9 {
+  grid-template-columns: [real-before] repeat(auto-fill, 100px [after]) [real-after];
+}
+.grid10 {
+  grid-template-columns: [real-before] repeat(auto-fill, [before] 100px) [real-after];
+}
+.grid11 {
+  grid-template-columns: 10px [real-before] repeat(auto-fill, [before] 1000px [after]) [real-after];
+}
+.grid12 {
+  grid-template-columns: 10px [real-before] repeat(auto-fill, [before] 1000px [after]) [real-after] 10px;
+}
+.grid13 {
+  grid-template-columns: 10px [real-before] repeat(auto-fill, [before] 1000px [after]) 10px;
+}
+.grid14 {
+  grid-template-columns: [real-before] repeat(auto-fill, [before] 1000px [after]) 10px;
+}
+.grid15 {
+  grid-template-columns: repeat(auto-fill, [before] 1000px [after]) 10px;
+}
+.grid16 {
+  grid-template-columns: repeat(auto-fill, [before] 1000px [after]) [real-after] 10px;
+}
+.grid17 {
+  grid-template-columns: repeat(auto-fill, [before] 1000px [after]) [real-after] 10px [final];
+}
+.grid18 {
+  grid-template-columns: repeat(auto-fill, [before] 1000px [after]) 10px [final];
+}
+.grid19 {
+  grid-template-columns: repeat(auto-fill, [before] 1000px);
+}
+
+.box {
+  background-color: #444;
+  color: #fff;
+}
+.a {
+  grid-column: auto;
+}
+.b {
+  grid-column: 4;
+}
+.c {
+  grid-column: 6;
+}
+.d {
+  grid-column: 7;
+}
+.e {
+  grid-column: 5;
+}
+.f {
+  grid-column: -9;
+}
+
+</style>
+
+<script>
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+function testLines(elementName, grid, expectedValues) {
+  is(grid.cols.lines.length, expectedValues.length, elementName + " has expected number of lines.");
+
+  for (let i = 0; i < grid.cols.lines.length; i++) {
+    // 'number' is optional.
+    if (typeof(expectedValues[i].number) != "undefined") {
+      is(grid.cols.lines[i].number, expectedValues[i].number, elementName + " line " + (i + 1) + " has expected number.");
+    } else {
+      // If 'number' is omitted, assume that first line is line 1 and increase from there.
+      is(grid.cols.lines[i].number, (i + 1), elementName + " line " + (i + 1) + " has expected number.");
+    }
+
+    // 'negativeNumber' is optional.
+    if (typeof(expectedValues[i].negativeNumber) != "undefined") {
+      // Check for the supplied number.
+      is(grid.cols.lines[i].negativeNumber, expectedValues[i].negativeNumber, elementName + " line " + (i + 1) + " has expected negativeNumber.");
+    }
+
+    // 'start' is optional.
+    if (typeof(expectedValues[i].start) != "undefined") {
+      is(grid.cols.lines[i].start, expectedValues[i].start, elementName + " line " + (i + 1) + " has expected start.");
+    }
+
+    // 'breadth' is optional.
+    if (typeof(expectedValues[i].breadth) != "undefined") {
+      is(grid.cols.lines[i].breadth, 0, elementName + " line " + (i + 1) + " has zero breadth.");
+    }
+
+    // 'names' is optional.
+    if (typeof(expectedValues[i].names) != "undefined") {
+      is(grid.cols.lines[i].names + "", expectedValues[i].names, elementName + " line " + (i + 1) + " has expected names.");
+    }
+
+    // 'todo_names' is optional.
+    if (typeof(expectedValues[i].todo_names) != "undefined") {
+      todo_is(grid.cols.lines[i].names + "", expectedValues[i].todo_names, elementName + " line " + (i + 1) + " has expected names.");
+    }
+  }
+}
+
+function runTests() {
+  let wrapper;
+  let grid;
+  let expectedValues;
+
+  wrapper = document.getElementById("wrapper1");
+  grid = wrapper.getGridFragments()[0];
+
+  // test auto-fill count
+  is(grid.cols.tracks.length, 7, "Grid column track array reports removed auto-fill columns.");
+
+  // test resolved value of grid-template-columns
+  let templateColumnsText = getComputedStyle(wrapper).gridTemplateColumns;
+  is(templateColumnsText, "50px 0px 100px 100px 100px 100px 100px",
+    "Resolved value of grid-template-columns is as expected.");
+
+  // test starts, breadths, and states
+  expectedValues = [
+    { "start": 0,
+      "breadth": 50,
+      "state": "static" },
+    { "start": 50,
+      "breadth": 0,
+      "state": "static" },
+    { "start": 50,
+      "breadth": 100,
+      "state": "repeat" },
+    { "start": 150,
+      "breadth": 100,
+      "state": "repeat" },
+    { "start": 250,
+      "breadth": 100,
+      "state": "repeat" },
+    { "start": 350,
+      "breadth": 100,
+      "state": "repeat" },
+    { "start": 450,
+      "breadth": 100,
+      "state": "repeat" },
+  ];
+  for (let i = 0; i < grid.cols.tracks.length; i++) {
+    is(grid.cols.tracks[i].start, expectedValues[i].start, "Column " + (i + 1) + " has expected start.");
+    is(grid.cols.tracks[i].breadth, expectedValues[i].breadth, "Column " + (i + 1) + " has expected breadth.");
+    is(grid.cols.tracks[i].state, expectedValues[i].state, "Column " + (i + 1) + " has expected state.");
+  }
+
+
+  wrapper = document.getElementById("wrapper2");
+  grid = wrapper.getGridFragments()[0];
+
+  // test auto-fill count
+  is(grid.cols.lines.length, 9, "Grid column line array reports removed auto-fill columns.");
+
+  // test resolved value of grid-template-columns
+  templateColumnsText = getComputedStyle(wrapper).gridTemplateColumns;
+  is(templateColumnsText, "50px 0px [real-before before] 100px [after before] 100px [after before] 100px [after before] 100px [after before] 100px [after real-after] 0px [final]",
+    "Resolved value of grid-template-columns is as expected.");
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "" },
+    { "start": 50,
+      "names": "" },
+    { "start": 50,
+      "names": "real-before,before" },
+    { "start": 150,
+      "names": "after,before" },
+    { "start": 250,
+      "names": "after,before" },
+    { "start": 350,
+      "names": "after,before" },
+    { "start": 450,
+      "names": "after,before" },
+    { "start": 550,
+      "names": "after,real-after" },
+    { "start": 550,
+      "names": "final" },
+  ];
+  testLines("wrapper2", grid, expectedValues);
+
+
+  wrapper = document.getElementById("wrapper3");
+  grid = wrapper.getGridFragments()[0];
+
+  // test resolved value of grid-template-columns
+  templateColumnsText = getComputedStyle(wrapper).gridTemplateColumns;
+  is(templateColumnsText, "66px 66px 66px [real-before before] 100px [after before] 100px [after before] 100px [after before] 100px [after real-after]",
+    "Resolved value of grid-template-columns is as expected.");
+
+
+  wrapper = document.getElementById("wrapper4");
+  grid = wrapper.getGridFragments()[0];
+
+  // test auto-fill count of tracks
+  is(grid.cols.tracks.length, 10, "Grid column track array respects auto-fill columns.");
+
+  if (grid.cols.tracks.length == 10) {
+    // test for static and repeat
+    is(grid.cols.tracks[1].state, "static", "Grid column track 2 is marked as static.");
+    is(grid.cols.tracks[2].state, "repeat", "Grid column track 3 is marked as repeat.");
+  }
+
+
+  wrapper = document.getElementById("wrapper5");
+  grid = wrapper.getGridFragments()[0];
+
+  // test resolved value of grid-template-columns
+  templateColumnsText = getComputedStyle(wrapper).gridTemplateColumns;
+  is(templateColumnsText, "[real-before before] 100px [after before] 100px [after before] 100px [after before] 100px [after before] 100px [after before] 100px [after real-after]", "Resolved value of grid-template-columns is as expected.");
+
+
+  wrapper = document.getElementById("wrapper6");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "first" },
+    { "start": 0,
+      "names": "real-before,before" },
+    { "start": 100,
+      "names": "after,before" },
+    { "start": 200,
+      "names": "after,before" },
+    { "start": 300,
+      "names": "after,before" },
+    { "start": 400,
+      "names": "after,before" },
+    { "start": 500,
+      "names": "after,before" },
+    { "start": 600,
+      "names": "after,real-after" },
+  ];
+  testLines("wrapper6", grid, expectedValues);
+
+
+  wrapper = document.getElementById("wrapper7");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "real-before,before" },
+    { "start": 100,
+      "names": "after,before" },
+    { "start": 200,
+      "names": "after,before" },
+    { "start": 300,
+      "names": "after,before" },
+    { "start": 400,
+      "names": "after,before" },
+    { "start": 500,
+      "names": "after,before" },
+    { "start": 600,
+      "names": "after,real-after" },
+    { "start": 600,
+      "names": "final" },
+  ];
+  testLines("wrapper7", grid, expectedValues);
+
+
+  wrapper = document.getElementById("wrapper8");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "real-before,before" },
+    { "start": 1000,
+      "names": "after,real-after" },
+  ];
+  testLines("wrapper8", grid, expectedValues);
+
+
+  wrapper = document.getElementById("wrapper8b");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "number": 0 },
+    { "number": 0 },
+    { "number": 0 },
+    { "number": 0 },
+    { "number": 0 },
+    { "number": 0 },
+    { "number": 0 },
+    { "number": 1,
+      "names": "real-before,before" },
+    { "number": 2,
+      "names": "after,real-after" },
+  ];
+  testLines("wrapper8b", grid, expectedValues);
+
+
+  wrapper = document.getElementById("wrapper9");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "real-before" },
+    { "start": 100,
+      "names": "after" },
+    { "start": 200,
+      "names": "after" },
+    { "start": 300,
+      "names": "after" },
+    { "start": 400,
+      "names": "after" },
+    { "start": 500,
+      "names": "after" },
+    { "start": 600,
+      "names": "after,real-after" },
+  ];
+  testLines("wrapper9", grid, expectedValues);
+
+
+  wrapper = document.getElementById("wrapper10");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "real-before,before" },
+    { "start": 100,
+      "names": "before" },
+    { "start": 200,
+      "names": "before" },
+    { "start": 300,
+      "names": "before" },
+    { "start": 400,
+      "names": "before" },
+    { "start": 500,
+      "names": "before" },
+    { "start": 600,
+      "names": "real-after" },
+  ];
+  testLines("wrapper10", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper11");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "" },
+    { "start": 10,
+      "names": "real-before,before" },
+    { "start": 1010,
+      "names": "after,real-after" },
+  ];
+  testLines("wrapper11", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper12");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "" },
+    { "start": 10,
+      "names": "real-before,before" },
+    { "start": 1010,
+      "names": "after,real-after" },
+    { "start": 1020,
+      "names": "" },
+  ];
+  testLines("wrapper12", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper13");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "" },
+    { "start": 10,
+      "names": "real-before,before" },
+    { "start": 1010,
+      "names": "after" },
+    { "start": 1020,
+      "names": "" },
+  ];
+  testLines("wrapper13", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper14");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "real-before,before" },
+    { "start": 1000,
+      "names": "after" },
+    { "start": 1010,
+      "names": "" },
+  ];
+  testLines("wrapper14", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper15");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "before" },
+    { "start": 1000,
+      "names": "after" },
+    { "start": 1010,
+      "names": "" },
+  ];
+  testLines("wrapper15", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper16");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "before" },
+    { "start": 1000,
+      "names": "after,real-after" },
+    { "start": 1010,
+      "names": "" },
+  ];
+  testLines("wrapper16", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper17");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "before" },
+    { "start": 1000,
+      "names": "after,real-after" },
+    { "start": 1010,
+      "names": "final" },
+  ];
+  testLines("wrapper17", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper18");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "before" },
+    { "start": 1000,
+      "names": "after" },
+    { "start": 1010,
+      "names": "final" },
+  ];
+  testLines("wrapper18", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper19");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "before" },
+    { "start": 1000,
+      "names": "" },
+  ];
+  testLines("wrapper19", grid, expectedValues);
+
+  SimpleTest.finish();
+}
+</script>
+</head>
+<body onLoad="runTests();">
+
+  <div id="wrapper1" class="wrapper grid1">
+    <div id="boxB" class="box b">B</div>
+  </div>
+
+  <br/>
+  <div id="wrapper2" class="wrapper grid2">
+    <div id="boxB" class="box b">B</div>
+    <div id="boxC" class="box c">C</div>
+  </div>
+
+  <br/>
+  <div id="wrapper3" class="wrapper grid3">
+    <div id="boxB" class="box b">B</div>
+    <div id="boxC" class="box c">C</div>
+    <div id="boxD" class="box d">D</div>
+  </div>
+
+  <br/>
+  <div id="wrapper4" class="wrapper grid4">
+    <div id="boxA" class="box a">A</div>
+  </div>
+
+  <br/>
+  <div id="wrapper5" class="wrapper grid5">
+  </div>
+
+  <br/>
+  <div id="wrapper6" class="wrapper grid6">
+    <div id="boxB" class="box b">B</div>
+  </div>
+
+  <br/>
+  <div id="wrapper7" class="wrapper grid7">
+    <div id="boxB" class="box b">B</div>
+  </div>
+
+  <br/>
+  <div id="wrapper8" class="wrapper grid8">
+  </div>
+
+  <br/>
+  <div id="wrapper8b" class="wrapper grid8">
+    <div id="boxF" class="box f">F</div>
+  </div>
+
+  <br/>
+  <div id="wrapper9" class="wrapper grid9">
+    <div id="boxB" class="box b">B</div>
+    <div id="boxE" class="box e">E</div>
+  </div>
+
+  <br/>
+  <div id="wrapper10" class="wrapper grid10">
+    <div id="boxB" class="box b">B</div>
+    <div id="boxE" class="box e">E</div>
+  </div>
+
+  <br/>
+  <div id="wrapper11" class="wrapper grid11">
+  </div>
+
+  <br/>
+  <div id="wrapper12" class="wrapper grid12">
+  </div>
+
+  <br/>
+  <div id="wrapper13" class="wrapper grid13">
+  </div>
+
+  <br/>
+  <div id="wrapper14" class="wrapper grid14">
+  </div>
+
+  <br/>
+  <div id="wrapper15" class="wrapper grid15">
+  </div>
+
+  <br/>
+  <div id="wrapper16" class="wrapper grid16">
+  </div>
+
+  <br/>
+  <div id="wrapper17" class="wrapper grid17">
+  </div>
+
+  <br/>
+  <div id="wrapper18" class="wrapper grid18">
+  </div>
+
+  <br/>
+  <div id="wrapper19" class="wrapper grid19">
+  </div>
+
+</body>
+</html>
rename from dom/grid/test/chrome/test_grid_repeats.html
rename to dom/grid/test/chrome/test_grid_repeat_auto_fit.html
--- a/dom/grid/test/chrome/test_grid_repeats.html
+++ b/dom/grid/test/chrome/test_grid_repeat_auto_fit.html
@@ -41,17 +41,41 @@ body {
 }
 .grid9 {
   grid-template-columns: [real-before] repeat(auto-fit, 100px [after]) [real-after];
 }
 .grid10 {
   grid-template-columns: [real-before] repeat(auto-fit, [before] 100px) [real-after];
 }
 .grid11 {
-  grid-template-columns: repeat(auto-fit, 100px);
+  grid-template-columns: 10px [real-before] repeat(auto-fit, [before] 1000px [after]) [real-after];
+}
+.grid12 {
+  grid-template-columns: 10px [real-before] repeat(auto-fit, [before] 1000px [after]) [real-after] 10px;
+}
+.grid13 {
+  grid-template-columns: 10px [real-before] repeat(auto-fit, [before] 1000px [after]) 10px;
+}
+.grid14 {
+  grid-template-columns: [real-before] repeat(auto-fit, [before] 1000px [after]) 10px;
+}
+.grid15 {
+  grid-template-columns: repeat(auto-fit, [before] 1000px [after]) 10px;
+}
+.grid16 {
+  grid-template-columns: repeat(auto-fit, [before] 1000px [after]) [real-after] 10px;
+}
+.grid17 {
+  grid-template-columns: repeat(auto-fit, [before] 1000px [after]) [real-after] 10px [final];
+}
+.grid18 {
+  grid-template-columns: repeat(auto-fit, [before] 1000px [after]) 10px [final];
+}
+.grid19 {
+  grid-template-columns: repeat(auto-fit, [before] 1000px);
 }
 
 .box {
   background-color: #444;
   color: #fff;
 }
 .a {
   grid-column: auto;
@@ -282,23 +306,43 @@ function runTests() {
 
 
   wrapper = document.getElementById("wrapper8");
   grid = wrapper.getGridFragments()[0];
 
   // test starts and names
   expectedValues = [
     { "start": 0,
-      "todo_names": "real-before,before" },
+      "names": "real-before,before" },
     { "start": 0,
-      "todo_names": "after,real-after" },
+      "names": "after,real-after" },
   ];
   testLines("wrapper8", grid, expectedValues);
 
 
+  wrapper = document.getElementById("wrapper8b");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "number": 0 },
+    { "number": 0 },
+    { "number": 0 },
+    { "number": 0 },
+    { "number": 0 },
+    { "number": 0 },
+    { "number": 0 },
+    { "number": 1, 
+      "names": "real-before,before" },
+    { "number": 2,
+      "names": "after,real-after" },
+  ];
+  testLines("wrapper8b", grid, expectedValues);
+
+
   wrapper = document.getElementById("wrapper9");
   grid = wrapper.getGridFragments()[0];
 
   // test starts and names
   expectedValues = [
     { "start": 0,
       "names": "real-before" },
     { "start": 0,
@@ -334,16 +378,144 @@ function runTests() {
       "names": "before" },
     { "start": 200,
       "names": "before" },
     { "start": 200,
       "names": "real-after" },
   ];
   testLines("wrapper10", grid, expectedValues);
 
+  wrapper = document.getElementById("wrapper11");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "" },
+    { "start": 10,
+      "names": "real-before,before" },
+    { "start": 10,
+      "names": "after,real-after" },
+  ];
+  testLines("wrapper11", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper12");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "" },
+    { "start": 10,
+      "names": "real-before,before" },
+    { "start": 10,
+      "names": "after,real-after" },
+    { "start": 20,
+      "names": "" },
+  ];
+  testLines("wrapper12", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper13");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "" },
+    { "start": 10,
+      "names": "real-before,before" },
+    { "start": 10,
+      "names": "after" },
+    { "start": 20,
+      "names": "" },
+  ];
+  testLines("wrapper13", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper14");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "real-before,before" },
+    { "start": 0,
+      "names": "after" },
+    { "start": 10,
+      "names": "" },
+  ];
+  testLines("wrapper14", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper15");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "before" },
+    { "start": 0,
+      "names": "after" },
+    { "start": 10,
+      "names": "" },
+  ];
+  testLines("wrapper15", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper16");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "before" },
+    { "start": 0,
+      "names": "after,real-after" },
+    { "start": 10,
+      "names": "" },
+  ];
+  testLines("wrapper16", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper17");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "before" },
+    { "start": 0,
+      "names": "after,real-after" },
+    { "start": 10,
+      "names": "final" },
+  ];
+  testLines("wrapper17", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper18");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "before" },
+    { "start": 0,
+      "names": "after" },
+    { "start": 10,
+      "names": "final" },
+  ];
+  testLines("wrapper18", grid, expectedValues);
+
+  wrapper = document.getElementById("wrapper19");
+  grid = wrapper.getGridFragments()[0];
+
+  // test starts and names
+  expectedValues = [
+    { "start": 0,
+      "names": "before" },
+    { "start": 0,
+      "names": "" },
+  ];
+  testLines("wrapper19", grid, expectedValues);
+
   SimpleTest.finish();
 }
 </script>
 </head>
 <body onLoad="runTests();">
 
   <div id="wrapper1" class="wrapper grid1">
     <div id="boxB" class="box b">B</div>
@@ -381,21 +553,62 @@ function runTests() {
     <div id="boxB" class="box b">B</div>
   </div>
 
   <br/>
   <div id="wrapper8" class="wrapper grid8">
   </div>
 
   <br/>
+  <div id="wrapper8b" class="wrapper grid8">
+    <div id="boxF" class="box f">F</div>
+  </div>
+
+  <br/>
   <div id="wrapper9" class="wrapper grid9">
     <div id="boxB" class="box b">B</div>
     <div id="boxE" class="box e">E</div>
   </div>
 
   <br/>
   <div id="wrapper10" class="wrapper grid10">
     <div id="boxB" class="box b">B</div>
     <div id="boxE" class="box e">E</div>
   </div>
 
+  <br/>
+  <div id="wrapper11" class="wrapper grid11">
+  </div>
+
+  <br/>
+  <div id="wrapper12" class="wrapper grid12">
+  </div>
+
+  <br/>
+  <div id="wrapper13" class="wrapper grid13">
+  </div>
+
+  <br/>
+  <div id="wrapper14" class="wrapper grid14">
+  </div>
+
+  <br/>
+  <div id="wrapper15" class="wrapper grid15">
+  </div>
+
+  <br/>
+  <div id="wrapper16" class="wrapper grid16">
+  </div>
+
+  <br/>
+  <div id="wrapper17" class="wrapper grid17">
+  </div>
+
+  <br/>
+  <div id="wrapper18" class="wrapper grid18">
+  </div>
+
+  <br/>
+  <div id="wrapper19" class="wrapper grid19">
+  </div>
+
 </body>
 </html>
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -1486,33 +1486,27 @@ nsresult mozInlineSpellChecker::DoSpellC
   if (!mTextEditor) {
     return NS_ERROR_FAILURE;
   }
 
   int32_t wordsChecked = 0;
   PRTime beginTime = PR_Now();
 
   nsAutoString wordText;
-  RefPtr<nsRange> wordRange;
+  NodeOffsetRange wordNodeOffsetRange;
   bool dontCheckWord;
-  while (NS_SUCCEEDED(aWordUtil.GetNextWord(wordText,
-                                            getter_AddRefs(wordRange),
+  while (NS_SUCCEEDED(aWordUtil.GetNextWord(wordText, &wordNodeOffsetRange,
                                             &dontCheckWord)) &&
-         wordRange) {
+         !wordNodeOffsetRange.Empty()) {
 
     // get the range for the current word.
-    nsINode *beginNode;
-    nsINode *endNode;
-    int32_t beginOffset, endOffset;
-
-    ErrorResult erv;
-    beginNode = wordRange->GetStartContainer(erv);
-    endNode = wordRange->GetEndContainer(erv);
-    beginOffset = wordRange->GetStartOffset(erv);
-    endOffset = wordRange->GetEndOffset(erv);
+    nsINode* beginNode = wordNodeOffsetRange.Begin().mNode;
+    nsINode* endNode = wordNodeOffsetRange.End().mNode;
+    int32_t beginOffset = wordNodeOffsetRange.Begin().mOffset;
+    int32_t endOffset = wordNodeOffsetRange.End().mOffset;
 
     // see if we've done enough words in this round and run out of time.
     if (wordsChecked >= INLINESPELL_MINIMUM_WORDS_BEFORE_TIMEOUT &&
         PR_Now() > PRTime(beginTime + kMaxSpellCheckTimeInUsec)) {
       // stop checking, our time limit has been exceeded.
       #ifdef DEBUG_INLINESPELL
         printf("We have run out of the time, schedule next round.");
       #endif
@@ -1530,16 +1524,17 @@ nsresult mozInlineSpellChecker::DoSpellC
 
 #ifdef DEBUG_INLINESPELL
     printf("->Got word \"%s\"", NS_ConvertUTF16toUTF8(wordText).get());
     if (dontCheckWord)
       printf(" (not checking)");
     printf("\n");
 #endif
 
+    ErrorResult erv;
     // see if there is a spellcheck range that already intersects the word
     // and remove it. We only need to remove old ranges, so don't bother if
     // there were no ranges when we started out.
     if (originalRangeCount > 0) {
       // likewise, if this word is inside new text, we won't bother testing
       if (!aStatus->mCreatedRange ||
           !aStatus->mCreatedRange->IsPointInRange(*beginNode, beginOffset, erv)) {
         nsTArray<RefPtr<nsRange>> ranges;
@@ -1577,25 +1572,29 @@ nsresult mozInlineSpellChecker::DoSpellC
     bool isMisspelled;
     aWordUtil.NormalizeWord(wordText);
     nsresult rv = mSpellCheck->CheckCurrentWordNoSuggest(wordText,
                                                          &isMisspelled);
     if (NS_FAILED(rv))
       continue;
 
     wordsChecked++;
-
     if (isMisspelled) {
       // misspelled words count extra toward the max
-      AddRange(aSpellCheckSelection, wordRange);
-
-      aStatus->mWordCount ++;
-      if (aStatus->mWordCount >= mMaxMisspellingsPerCheck ||
-          SpellCheckSelectionIsFull()) {
-        break;
+      RefPtr<nsRange> wordRange;
+      // If we somehow can't make a range for this word, just ignore it.
+      if(NS_SUCCEEDED(aWordUtil.MakeRange(wordNodeOffsetRange.Begin(),
+                                          wordNodeOffsetRange.End(),
+                                          getter_AddRefs(wordRange)))) {
+        AddRange(aSpellCheckSelection, wordRange);
+        aStatus->mWordCount++;
+        if (aStatus->mWordCount >= mMaxMisspellingsPerCheck ||
+            SpellCheckSelectionIsFull()) {
+          break;
+        }
       }
     }
   }
 
   return NS_OK;
 }
 
 // An RAII helper that calls ChangeNumPendingSpellChecks on destruction.
--- a/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
@@ -226,16 +226,24 @@ mozInlineSpellWordUtil::EnsureWords()
 
 nsresult
 mozInlineSpellWordUtil::MakeRangeForWord(const RealWord& aWord, nsRange** aRange)
 {
   NodeOffset begin = MapSoftTextOffsetToDOMPosition(aWord.mSoftTextOffset, HINT_BEGIN);
   NodeOffset end = MapSoftTextOffsetToDOMPosition(aWord.EndOffset(), HINT_END);
   return MakeRange(begin, end, aRange);
 }
+void
+mozInlineSpellWordUtil::MakeNodeOffsetRangeForWord(const RealWord& aWord,
+                                         NodeOffsetRange* aNodeOffsetRange)
+{
+  NodeOffset begin = MapSoftTextOffsetToDOMPosition(aWord.mSoftTextOffset, HINT_BEGIN);
+  NodeOffset end = MapSoftTextOffsetToDOMPosition(aWord.EndOffset(), HINT_END);
+  *aNodeOffsetRange = NodeOffsetRange(begin, end);
+}
 
 // mozInlineSpellWordUtil::GetRangeForWord
 
 nsresult
 mozInlineSpellWordUtil::GetRangeForWord(nsIDOMNode* aWordNode,
                                         int32_t aWordOffset,
                                         nsRange** aRange)
 {
@@ -284,34 +292,34 @@ NormalizeWord(const nsAString& aInput, i
 
 // mozInlineSpellWordUtil::GetNextWord
 //
 //    FIXME-optimization: we shouldn't have to generate a range every single
 //    time. It would be better if the inline spellchecker didn't require a
 //    range unless the word was misspelled. This may or may not be possible.
 
 nsresult
-mozInlineSpellWordUtil::GetNextWord(nsAString& aText, nsRange** aRange,
+mozInlineSpellWordUtil::GetNextWord(nsAString& aText,
+                                    NodeOffsetRange* aNodeOffsetRange,
                                     bool* aSkipChecking)
 {
 #ifdef DEBUG_SPELLCHECK
   printf("GetNextWord called; mNextWordIndex=%d\n", mNextWordIndex);
 #endif
 
   if (mNextWordIndex < 0 ||
       mNextWordIndex >= int32_t(mRealWords.Length())) {
     mNextWordIndex = -1;
-    *aRange = nullptr;
+    *aNodeOffsetRange = NodeOffsetRange();
     *aSkipChecking = true;
     return NS_OK;
   }
 
   const RealWord& word = mRealWords[mNextWordIndex];
-  nsresult rv = MakeRangeForWord(word, aRange);
-  NS_ENSURE_SUCCESS(rv, rv);
+  MakeNodeOffsetRangeForWord(word, aNodeOffsetRange);
   ++mNextWordIndex;
   *aSkipChecking = !word.mCheckableWord;
   ::NormalizeWord(mSoftText, word.mSoftTextOffset, word.mLength, aText);
 
 #ifdef DEBUG_SPELLCHECK
   printf("GetNextWord returning: %s (skip=%d)\n",
          NS_ConvertUTF16toUTF8(aText).get(), *aSkipChecking);
 #endif
@@ -960,17 +968,17 @@ FindLastNongreaterOffset(const nsTArray<
     // Every mapping had offset greater than aSoftTextOffset.
     MOZ_ASSERT(aContainer[*aIndex].mSoftTextOffset > aSoftTextOffset);
   }
   return true;
 }
 
 } // namespace
 
-mozInlineSpellWordUtil::NodeOffset
+NodeOffset
 mozInlineSpellWordUtil::MapSoftTextOffsetToDOMPosition(int32_t aSoftTextOffset,
                                                        DOMMapHint aHint)
 {
   NS_ASSERTION(mSoftTextValid, "Soft text must be valid if we're to map out of it");
   if (!mSoftTextValid)
     return NodeOffset(nullptr, -1);
 
   // Find the last mapping, if any, such that mSoftTextOffset <= aSoftTextOffset
--- a/extensions/spellcheck/src/mozInlineSpellWordUtil.h
+++ b/extensions/spellcheck/src/mozInlineSpellWordUtil.h
@@ -16,16 +16,63 @@
 
 class nsRange;
 class nsINode;
 
 namespace mozilla {
 class TextEditor;
 } // namespace mozilla
 
+struct NodeOffset
+{
+  nsINode* mNode;
+  int32_t  mOffset;
+
+  NodeOffset(): mNode(nullptr), mOffset(0) {}
+  NodeOffset(nsINode* aNode, int32_t aOffset)
+    : mNode(aNode), mOffset(aOffset) {}
+
+  bool operator==(const NodeOffset& aOther) const
+  {
+    return mNode == aOther.mNode && mOffset == aOther.mOffset;
+  }
+
+  bool operator!=(const NodeOffset& aOther) const
+  {
+    return !(*this == aOther);
+  }
+};
+
+class NodeOffsetRange
+{
+private:
+  NodeOffset mBegin;
+  NodeOffset mEnd;
+  bool mEmpty;
+public:
+  NodeOffsetRange() : mEmpty(true) {}
+  NodeOffsetRange(NodeOffset b, NodeOffset e)
+    : mBegin(b), mEnd(e), mEmpty(false) {}
+
+  NodeOffset Begin()
+  {
+    return mBegin;
+  }
+
+  NodeOffset End()
+  {
+    return mEnd;
+  }
+
+  bool Empty()
+  {
+    return mEmpty;
+  }
+};
+
 /**
  *    This class extracts text from the DOM and builds it into a single string.
  *    The string includes whitespace breaks whereever non-inline elements begin
  *    and end. This string is broken into "real words", following somewhat
  *    complex rules; for example substrings that look like URLs or
  *    email addresses are treated as single words, but otherwise many kinds of
  *    punctuation are treated as word separators. GetNextWord provides a way
  *    to iterate over these "real words".
@@ -39,32 +86,16 @@ class TextEditor;
  *    3. Call SetPosition to initialize the current position inside the
  *       previously given range.
  *    4. Call GetNextWord over and over until it returns false.
  */
 
 class mozInlineSpellWordUtil
 {
 public:
-  struct NodeOffset {
-    nsINode* mNode;
-    int32_t  mOffset;
-
-    NodeOffset(nsINode* aNode, int32_t aOffset) :
-      mNode(aNode), mOffset(aOffset) {}
-
-    bool operator==(const NodeOffset& aOther) const {
-      return mNode == aOther.mNode && mOffset == aOther.mOffset;
-    }
-
-    bool operator!=(const NodeOffset& aOther) const {
-      return !(*this == aOther);
-    }
-  };
-
   mozInlineSpellWordUtil()
     : mRootNode(nullptr),
       mSoftBegin(nullptr, 0), mSoftEnd(nullptr, 0),
       mNextWordIndex(-1), mSoftTextValid(false) {}
 
   nsresult Init(mozilla::TextEditor* aTextEditor);
 
   nsresult SetEnd(nsINode* aEndNode, int32_t aEndOffset);
@@ -80,21 +111,25 @@ public:
   // inside the word, you should check the range.
   //
   // THIS CHANGES THE CURRENT POSITION AND RANGE. It is designed to be called
   // before you actually generate the range you are interested in and iterate
   // the words in it.
   nsresult GetRangeForWord(nsIDOMNode* aWordNode, int32_t aWordOffset,
                            nsRange** aRange);
 
+  // Convenience functions, object must be initialized
+  nsresult MakeRange(NodeOffset aBegin, NodeOffset aEnd, nsRange** aRange);
+
   // Moves to the the next word in the range, and retrieves it's text and range.
   // An empty word and a nullptr range are returned when we are done checking.
   // aSkipChecking will be set if the word is "special" and shouldn't be
   // checked (e.g., an email address).
-  nsresult GetNextWord(nsAString& aText, nsRange** aRange,
+  nsresult GetNextWord(nsAString& aText,
+                       NodeOffsetRange* aNodeOffsetRange,
                        bool* aSkipChecking);
 
   // Call to normalize some punctuation. This function takes an autostring
   // so we can access characters directly.
   static void NormalizeWord(nsAString& aWord);
 
   nsIDOMDocument* GetDOMDocument() const { return mDOMDocument; }
   nsIDocument* GetDocument() const { return mDocument; }
@@ -170,14 +205,14 @@ private:
 
   // build mSoftText and mSoftTextDOMMapping
   void BuildSoftText();
   // Build mRealWords array
   nsresult BuildRealWords();
 
   nsresult SplitDOMWord(int32_t aStart, int32_t aEnd);
 
-  // Convenience functions, object must be initialized
-  nsresult MakeRange(NodeOffset aBegin, NodeOffset aEnd, nsRange** aRange);
   nsresult MakeRangeForWord(const RealWord& aWord, nsRange** aRange);
+  void MakeNodeOffsetRangeForWord(const RealWord& aWord,
+                                  NodeOffsetRange* aNodeOffsetRange);
 };
 
 #endif
--- a/gfx/2d/ScaledFontFontconfig.cpp
+++ b/gfx/2d/ScaledFontFontconfig.cpp
@@ -392,17 +392,17 @@ ScaledFontFontconfig::CreateFromInstance
     FcPatternAddFTFace(pattern, FC_FT_FACE, face);
   } else {
     FcPatternAddString(pattern, FC_FILE, reinterpret_cast<const FcChar8*>(aUnscaledFont->GetFile()));
     FcPatternAddInteger(pattern, FC_INDEX, aUnscaledFont->GetIndex());
   }
   FcPatternAddDouble(pattern, FC_PIXEL_SIZE, aSize);
   aInstanceData.SetupPattern(pattern);
 
-  cairo_font_face_t* font = cairo_ft_font_face_create_for_pattern(pattern);
+  cairo_font_face_t* font = cairo_ft_font_face_create_for_pattern(pattern, nullptr, 0);
   if (cairo_font_face_status(font) != CAIRO_STATUS_SUCCESS) {
     gfxWarning() << "Failed creating Cairo font face for Fontconfig pattern";
     FcPatternDestroy(pattern);
     return nullptr;
   }
 
   if (aNativeFontResource) {
     // Bug 1362117 - Cairo may keep the font face alive after the owning NativeFontResource
--- a/gfx/cairo/cairo/src/cairo-ft-font.c
+++ b/gfx/cairo/cairo/src/cairo-ft-font.c
@@ -49,16 +49,17 @@
 #include "cairo-fontconfig-private.h"
 
 #include <ft2build.h>
 #include FT_FREETYPE_H
 #include FT_OUTLINE_H
 #include FT_IMAGE_H
 #include FT_BITMAP_H
 #include FT_TRUETYPE_TABLES_H
+#include FT_MULTIPLE_MASTERS_H
 #if HAVE_FT_GLYPHSLOT_EMBOLDEN
 #include FT_SYNTHESIS_H
 #endif
 
 #if HAVE_FT_LIBRARY_SETLCDFILTER
 #include FT_LCD_FILTER_H
 #endif
 
@@ -159,16 +160,20 @@ struct _cairo_ft_unscaled_font {
 
     cairo_bool_t from_face; /* was the FT_Face provided by user? */
     FT_Face face;	    /* provided or cached face */
 
     /* only set if from_face is false */
     char *filename;
     int id;
 
+    /* For variation fonts, the variation coordinates to apply to each axis. */
+    const FT_Fixed *var_coords;
+    int num_var_coords;
+
     /* We temporarily scale the unscaled font as needed */
     cairo_bool_t have_scale;
     cairo_matrix_t current_scale;
     double x_scale;		/* Extracted X scale factor */
     double y_scale;             /* Extracted Y scale factor */
     cairo_bool_t have_shape;	/* true if the current scale has a non-scale component*/
     cairo_matrix_t current_shape;
     FT_Matrix Current_Shape;
@@ -355,30 +360,36 @@ static void
     CAIRO_MUTEX_UNLOCK (_cairo_ft_unscaled_font_map_mutex);
 }
 
 static void
 _cairo_ft_unscaled_font_init_key (cairo_ft_unscaled_font_t *key,
 				  cairo_bool_t              from_face,
 				  char			   *filename,
 				  int			    id,
+                                  const FT_Fixed           *var_coords,
+                                  int                       num_var_coords,
 				  FT_Face		    face)
 {
     unsigned long hash;
 
     key->from_face = from_face;
     key->filename = filename;
     key->id = id;
     key->face = face;
+    key->var_coords = var_coords;
+    key->num_var_coords = num_var_coords;
 
     hash = _cairo_hash_string (filename);
     /* the constants are just arbitrary primes */
     hash += ((unsigned long) id) * 1607;
     hash += ((unsigned long) face) * 2137;
 
+    hash = _cairo_hash_bytes (hash, var_coords, num_var_coords * sizeof(FT_Fixed));
+
     key->base.hash_entry.hash = hash;
 }
 
 /**
  * _cairo_ft_unscaled_font_init:
  *
  * Initialize a #cairo_ft_unscaled_font_t.
  *
@@ -398,35 +409,37 @@ static void
  * parallel in the from_face case, (where the calling code would have
  * to do its own mapping to ensure similar sharing).
  **/
 static cairo_status_t
 _cairo_ft_unscaled_font_init (cairo_ft_unscaled_font_t *unscaled,
 			      cairo_bool_t              from_face,
 			      const char	       *filename,
 			      int			id,
+                              const FT_Fixed           *var_coords,
+                              int                       num_var_coords,
 			      FT_Face			face)
 {
     _cairo_unscaled_font_init (&unscaled->base,
 			       &cairo_ft_unscaled_font_backend);
 
     if (from_face) {
 	unscaled->from_face = TRUE;
-	_cairo_ft_unscaled_font_init_key (unscaled, TRUE, NULL, 0, face);
+	_cairo_ft_unscaled_font_init_key (unscaled, TRUE, NULL, 0, var_coords, num_var_coords, face);
     } else {
 	char *filename_copy;
 
 	unscaled->from_face = FALSE;
 	unscaled->face = NULL;
 
 	filename_copy = strdup (filename);
 	if (unlikely (filename_copy == NULL))
 	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
-	_cairo_ft_unscaled_font_init_key (unscaled, FALSE, filename_copy, id, NULL);
+	_cairo_ft_unscaled_font_init_key (unscaled, FALSE, filename_copy, id, var_coords, num_var_coords, NULL);
     }
 
     unscaled->have_scale = FALSE;
     CAIRO_MUTEX_INIT (unscaled->mutex);
     unscaled->lock_count = 0;
 
     unscaled->faces = NULL;
 
@@ -449,33 +462,43 @@ static void
 {
     assert (unscaled->face == NULL);
 
     if (unscaled->filename) {
 	free (unscaled->filename);
 	unscaled->filename = NULL;
     }
 
+    if (unscaled->var_coords) {
+        free (unscaled->var_coords);
+        unscaled->var_coords = NULL;
+    }
+
     CAIRO_MUTEX_FINI (unscaled->mutex);
 }
 
 static int
 _cairo_ft_unscaled_font_keys_equal (const void *key_a,
 				    const void *key_b)
 {
     const cairo_ft_unscaled_font_t *unscaled_a = key_a;
     const cairo_ft_unscaled_font_t *unscaled_b = key_b;
 
     if (unscaled_a->id == unscaled_b->id &&
-	unscaled_a->from_face == unscaled_b->from_face)
+	unscaled_a->from_face == unscaled_b->from_face &&
+        unscaled_a->num_var_coords == unscaled_b->num_var_coords)
     {
         if (unscaled_a->from_face)
 	    return unscaled_a->face == unscaled_b->face;
 
-	if (unscaled_a->filename == NULL && unscaled_b->filename == NULL)
+        if (unscaled_a->num_var_coords > 0 &&
+            (memcmp (unscaled_a->var_coords, unscaled_b->var_coords,
+                     unscaled_a->num_var_coords * sizeof(FT_Fixed)) != 0))
+            return FALSE;
+	else if (unscaled_a->filename == NULL && unscaled_b->filename == NULL)
 	    return TRUE;
 	else if (unscaled_a->filename == NULL || unscaled_b->filename == NULL)
 	    return FALSE;
 	else
 	    return (strcmp (unscaled_a->filename, unscaled_b->filename) == 0);
     }
 
     return FALSE;
@@ -484,44 +507,57 @@ static int
 /* Finds or creates a #cairo_ft_unscaled_font_t for the filename/id from
  * pattern.  Returns a new reference to the unscaled font.
  */
 static cairo_status_t
 _cairo_ft_unscaled_font_create_internal (cairo_bool_t from_face,
 					 char *filename,
 					 int id,
 					 FT_Face font_face,
+                                         const FT_Fixed *var_coords,
+                                         int num_var_coords,
 					 cairo_ft_unscaled_font_t **out)
 {
     cairo_ft_unscaled_font_t key, *unscaled;
     cairo_ft_unscaled_font_map_t *font_map;
+    FT_Fixed* new_var_coords = NULL;
     cairo_status_t status;
 
     font_map = _cairo_ft_unscaled_font_map_lock ();
     if (unlikely (font_map == NULL))
 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
-    _cairo_ft_unscaled_font_init_key (&key, from_face, filename, id, font_face);
+    _cairo_ft_unscaled_font_init_key (&key, from_face, filename, id, var_coords, num_var_coords, font_face);
 
     /* Return existing unscaled font if it exists in the hash table. */
     unscaled = _cairo_hash_table_lookup (font_map->hash_table,
 					 &key.base.hash_entry);
     if (unscaled != NULL) {
 	_cairo_unscaled_font_reference (&unscaled->base);
 	goto DONE;
     }
 
     /* Otherwise create it and insert into hash table. */
     unscaled = malloc (sizeof (cairo_ft_unscaled_font_t));
     if (unlikely (unscaled == NULL)) {
 	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
 	goto UNWIND_FONT_MAP_LOCK;
     }
 
-    status = _cairo_ft_unscaled_font_init (unscaled, from_face, filename, id, font_face);
+    /* If we have variation coordinate data, make a copy to save in the unscaled_font */
+    if (var_coords && num_var_coords) {
+        new_var_coords = malloc (num_var_coords * sizeof(FT_Fixed));
+        if (unlikely (!new_var_coords)) {
+            status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
+            goto UNWIND_VAR_COORDS;
+        }
+        memcpy (new_var_coords, var_coords, num_var_coords * sizeof(FT_Fixed));
+    }
+
+    status = _cairo_ft_unscaled_font_init (unscaled, from_face, filename, id, new_var_coords, num_var_coords, font_face);
     if (unlikely (status))
 	goto UNWIND_UNSCALED_MALLOC;
 
     assert (unscaled->base.hash_entry.hash == key.base.hash_entry.hash);
     status = _cairo_hash_table_insert (font_map->hash_table,
 				       &unscaled->base.hash_entry);
     if (unlikely (status))
 	goto UNWIND_UNSCALED_FONT_INIT;
@@ -530,25 +566,28 @@ DONE:
     _cairo_ft_unscaled_font_map_unlock ();
     *out = unscaled;
     return CAIRO_STATUS_SUCCESS;
 
 UNWIND_UNSCALED_FONT_INIT:
     _cairo_ft_unscaled_font_fini (unscaled);
 UNWIND_UNSCALED_MALLOC:
     free (unscaled);
+UNWIND_VAR_COORDS:
+    free (new_var_coords);
 UNWIND_FONT_MAP_LOCK:
     _cairo_ft_unscaled_font_map_unlock ();
     return status;
 }
 
 
 #if CAIRO_HAS_FC_FONT
 static cairo_status_t
 _cairo_ft_unscaled_font_create_for_pattern (FcPattern *pattern,
+                                            const FT_Fixed *var_coords, int num_var_coords,
 					    cairo_ft_unscaled_font_t **out)
 {
     FT_Face font_face = NULL;
     char *filename = NULL;
     int id = 0;
     FcResult ret;
 
     ret = FcPatternGetFTFace (pattern, FC_FT_FACE, 0, &font_face);
@@ -571,25 +610,27 @@ static cairo_status_t
 
     /* The pattern contains neither a face nor a filename, resolve it later. */
     *out = NULL;
     return CAIRO_STATUS_SUCCESS;
 
 DONE:
     return _cairo_ft_unscaled_font_create_internal (font_face != NULL,
 						    filename, id, font_face,
+                                                    var_coords, num_var_coords,
 						    out);
 }
 #endif
 
 static cairo_status_t
-_cairo_ft_unscaled_font_create_from_face (FT_Face face,
+_cairo_ft_unscaled_font_create_from_face (FT_Face face, const FT_Fixed *var_coords, int num_var_coords,
 					  cairo_ft_unscaled_font_t **out)
 {
-    return _cairo_ft_unscaled_font_create_internal (TRUE, NULL, 0, face, out);
+    return _cairo_ft_unscaled_font_create_internal (TRUE, NULL, 0, face,
+                                                    var_coords, num_var_coords, out);
 }
 
 static void
 _cairo_ft_unscaled_font_destroy (void *abstract_font)
 {
     cairo_ft_unscaled_font_t *unscaled  = abstract_font;
     cairo_ft_unscaled_font_map_t *font_map;
 
@@ -679,16 +720,29 @@ cairo_warn FT_Face
     if (!face)
     {
 	unscaled->lock_count--;
 	CAIRO_MUTEX_UNLOCK (unscaled->mutex);
 	_cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
 	return NULL;
     }
 
+    if (unscaled->var_coords) {
+        typedef FT_UInt (*SetCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*);
+        static SetCoordsFunc setCoords;
+        static cairo_bool_t firstTime = TRUE;
+        if (firstTime) {
+            firstTime = FALSE;
+            (SetCoordsFunc)dlsym(RTLD_DEFAULT, "FT_Set_Var_Design_Coordinates");
+        }
+        if (setCoords) {
+            (*setCoords)(face, unscaled->num_var_coords, unscaled->var_coords);
+        }
+    }
+
     unscaled->face = face;
 
     font_map->num_open_faces++;
 
     return face;
 }
 
 
@@ -3061,17 +3115,17 @@ static cairo_font_face_t *
 	/* We failed to find any font. Substitute twin so that the user can
 	 * see something (and hopefully recognise that the font is missing)
 	 * and not just receive a NO_MEMORY error during rendering.
 	 */
 	font_face = _cairo_font_face_twin_create_fallback ();
 	goto FREE_PATTERN;
     }
 
-    status = _cairo_ft_unscaled_font_create_for_pattern (resolved, &unscaled);
+    status = _cairo_ft_unscaled_font_create_for_pattern (resolved, NULL, 0, &unscaled);
     if (unlikely (status || unscaled == NULL)) {
 	font_face = (cairo_font_face_t *)&_cairo_font_face_nil;
 	goto FREE_RESOLVED;
     }
 
     _get_pattern_ft_options (resolved, &ft_options);
     font_face = _cairo_ft_font_face_create (unscaled, &ft_options);
     _cairo_unscaled_font_destroy (&unscaled->base);
@@ -3118,24 +3172,27 @@ FREE_PATTERN:
  * time of the returned #cairo_font_face_t.  See
  * cairo_ft_font_face_create_for_ft_face() for an exmaple of how to couple
  * the life time of the FT_Face to that of the cairo font-face.
  *
  * Return value: a newly created #cairo_font_face_t. Free with
  *  cairo_font_face_destroy() when you are done using it.
  **/
 cairo_font_face_t *
-cairo_ft_font_face_create_for_pattern (FcPattern *pattern)
+cairo_ft_font_face_create_for_pattern (FcPattern *pattern,
+                                       const FT_Fixed *var_coords, int num_var_coords)
 {
     cairo_ft_unscaled_font_t *unscaled;
     cairo_font_face_t *font_face;
     cairo_ft_options_t ft_options;
     cairo_status_t status;
 
-    status = _cairo_ft_unscaled_font_create_for_pattern (pattern, &unscaled);
+    status = _cairo_ft_unscaled_font_create_for_pattern (pattern,
+                                                         var_coords, num_var_coords,
+                                                         &unscaled);
     if (unlikely (status))
 	return (cairo_font_face_t *) &_cairo_font_face_nil;
     if (unlikely (unscaled == NULL)) {
 	/* Store the pattern.  We will resolve it and create unscaled
 	 * font when creating scaled fonts */
 	status = _cairo_ft_font_face_create_for_pattern (pattern,
 							 &font_face);
 	if (unlikely (status))
@@ -3195,24 +3252,28 @@ cairo_ft_font_face_create_for_pattern (F
  * }
  * </programlisting></informalexample>
  *
  * Return value: a newly created #cairo_font_face_t. Free with
  *  cairo_font_face_destroy() when you are done using it.
  **/
 cairo_font_face_t *
 cairo_ft_font_face_create_for_ft_face (FT_Face         face,
-				       int             load_flags)
+				       int             load_flags,
+                                       const FT_Fixed *var_coords,
+                                       int             num_var_coords)
 {
     cairo_ft_unscaled_font_t *unscaled;
     cairo_font_face_t *font_face;
     cairo_ft_options_t ft_options;
     cairo_status_t status;
 
-    status = _cairo_ft_unscaled_font_create_from_face (face, &unscaled);
+    status = _cairo_ft_unscaled_font_create_from_face (face,
+                                                       var_coords, num_var_coords,
+                                                       &unscaled);
     if (unlikely (status))
 	return (cairo_font_face_t *)&_cairo_font_face_nil;
 
     ft_options.load_flags = load_flags;
     ft_options.extra_flags = 0;
     _cairo_font_options_init_default (&ft_options.base);
 
     font_face = _cairo_ft_font_face_create (unscaled, &ft_options);
--- a/gfx/cairo/cairo/src/cairo-ft.h
+++ b/gfx/cairo/cairo/src/cairo-ft.h
@@ -49,28 +49,32 @@
 #if CAIRO_HAS_FC_FONT
 #include <fontconfig/fontconfig.h>
 #endif
 
 CAIRO_BEGIN_DECLS
 
 cairo_public cairo_font_face_t *
 cairo_ft_font_face_create_for_ft_face (FT_Face         face,
-				       int             load_flags);
+				       int             load_flags,
+                                       const FT_Fixed *var_coords,
+                                       int             num_var_coords);
 
 cairo_public FT_Face
 cairo_ft_scaled_font_lock_face (cairo_scaled_font_t *scaled_font);
 
 cairo_public void
 cairo_ft_scaled_font_unlock_face (cairo_scaled_font_t *scaled_font);
 
 #if CAIRO_HAS_FC_FONT
 
 cairo_public cairo_font_face_t *
-cairo_ft_font_face_create_for_pattern (FcPattern *pattern);
+cairo_ft_font_face_create_for_pattern (FcPattern      *pattern,
+                                       const FT_Fixed *var_coords,
+                                       int             num_var_coords);
 
 cairo_public void
 cairo_ft_font_options_substitute (const cairo_font_options_t *options,
 				  FcPattern                  *pattern);
 
 #endif
 
 CAIRO_END_DECLS
--- a/gfx/thebes/gfxFT2FontBase.cpp
+++ b/gfx/thebes/gfxFT2FontBase.cpp
@@ -5,32 +5,37 @@
 
 #include "gfxFT2FontBase.h"
 #include "gfxFT2Utils.h"
 #include "harfbuzz/hb.h"
 #include "mozilla/Likely.h"
 #include "gfxFontConstants.h"
 #include "gfxFontUtils.h"
 #include <algorithm>
+#include <dlfcn.h>
 
 #include FT_TRUETYPE_TAGS_H
 #include FT_TRUETYPE_TABLES_H
+#include FT_ADVANCES_H
+#include FT_MULTIPLE_MASTERS_H
 
 #ifndef FT_FACE_FLAG_COLOR
 #define FT_FACE_FLAG_COLOR ( 1L << 14 )
 #endif
 
 using namespace mozilla::gfx;
 
 gfxFT2FontBase::gfxFT2FontBase(const RefPtr<UnscaledFontFreeType>& aUnscaledFont,
                                cairo_scaled_font_t *aScaledFont,
                                gfxFontEntry *aFontEntry,
-                               const gfxFontStyle *aFontStyle)
+                               const gfxFontStyle *aFontStyle,
+                               bool aEmbolden)
     : gfxFont(aUnscaledFont, aFontEntry, aFontStyle, kAntialiasDefault, aScaledFont)
     , mSpaceGlyph(0)
+    , mEmbolden(aEmbolden)
 {
     cairo_scaled_font_reference(mScaledFont);
 
     InitMetrics();
 }
 
 gfxFT2FontBase::~gfxFT2FontBase()
 {
@@ -153,16 +158,32 @@ gfxFT2FontBase::GetCharExtents(char aCha
 {
     FT_UInt gid = GetGlyph(aChar);
     if (gid) {
         GetGlyphExtents(gid, aExtents);
     }
     return gid;
 }
 
+/**
+ * Get glyph id and width for a simple character.
+ * The return value is the glyph id of that glyph or zero if no such glyph
+ * exists.  aWidth is only set when this returns a non-zero glyph id.
+ * This is just for use during initialization, and doesn't use the width cache.
+ */
+uint32_t
+gfxFT2FontBase::GetCharWidth(char aChar, gfxFloat* aWidth)
+{
+    FT_UInt gid = GetGlyph(aChar);
+    if (gid) {
+        *aWidth = FLOAT_FROM_16_16(GetFTGlyphAdvance(gid));
+    }
+    return gid;
+}
+
 void
 gfxFT2FontBase::InitMetrics()
 {
     mFUnitsConvFactor = 0.0;
 
     if (MOZ_UNLIKELY(GetStyle()->size <= 0.0) ||
         MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0)) {
         memset(&mMetrics, 0, sizeof(mMetrics)); // zero initialize
@@ -197,16 +218,33 @@ gfxFT2FontBase::InitMetrics()
         mMetrics.underlineOffset = -underlineSize;
         mMetrics.strikeoutOffset = 0.25 * emHeight;
         mMetrics.strikeoutSize = underlineSize;
 
         SanitizeMetrics(&mMetrics, false);
         return;
     }
 
+    if (!mStyle.variationSettings.IsEmpty()) {
+        SetupVarCoords(face, mStyle.variationSettings, &mCoords);
+        if (!mCoords.IsEmpty()) {
+            typedef FT_UInt (*SetCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*);
+            static SetCoordsFunc setCoords;
+            static bool firstTime = true;
+            if (firstTime) {
+                firstTime = false;
+                setCoords = (SetCoordsFunc)
+                    dlsym(RTLD_DEFAULT, "FT_Set_Var_Design_Coordinates");
+            }
+            if (setCoords) {
+                (*setCoords)(face, mCoords.Length(), mCoords.Elements());
+            }
+        }
+    }
+
     const FT_Size_Metrics& ftMetrics = face->size->metrics;
 
     mMetrics.maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender);
     mMetrics.maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender);
     mMetrics.maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance);
     gfxFloat lineHeight = FLOAT_FROM_26_6(ftMetrics.height);
 
     gfxFloat emHeight;
@@ -349,34 +387,35 @@ gfxFT2FontBase::InitMetrics()
     } else {
         mMetrics.capHeight = mMetrics.maxAscent;
     }
 
     // Release the face lock to safely load glyphs with GetCharExtents if
     // necessary without recursively locking.
     cairo_ft_scaled_font_unlock_face(GetCairoScaledFont());
 
-    cairo_text_extents_t extents;
-    mSpaceGlyph = GetCharExtents(' ', &extents);
+    gfxFloat width;
+    mSpaceGlyph = GetCharWidth(' ', &width);
     if (mSpaceGlyph) {
-        mMetrics.spaceWidth = extents.x_advance;
+        mMetrics.spaceWidth = width;
     } else {
         mMetrics.spaceWidth = mMetrics.maxAdvance; // guess
     }
 
-    if (GetCharExtents('0', &extents)) {
-        mMetrics.zeroOrAveCharWidth = extents.x_advance;
+    if (GetCharWidth('0', &width)) {
+        mMetrics.zeroOrAveCharWidth = width;
     } else {
         mMetrics.zeroOrAveCharWidth = 0.0;
     }
 
     // Prefering a measured x over sxHeight because sxHeight doesn't consider
     // hinting, but maybe the x extents are not quite right in some fancy
     // script fonts.  CSS 2.1 suggests possibly using the height of an "o",
     // which would have a more consistent glyph across fonts.
+    cairo_text_extents_t extents;
     if (GetCharExtents('x', &extents) && extents.y_bearing < 0.0) {
         mMetrics.xHeight = -extents.y_bearing;
         mMetrics.aveCharWidth =
             std::max(mMetrics.aveCharWidth, extents.x_advance);
     }
 
     if (GetCharExtents('H', &extents) && extents.y_bearing < 0.0) {
         mMetrics.capHeight = -extents.y_bearing;
@@ -463,23 +502,78 @@ gfxFT2FontBase::GetGlyph(uint32_t unicod
             return GetGlyph(unicode);
         }
         return 0;
     }
 
     return GetGlyph(unicode);
 }
 
+FT_Fixed
+gfxFT2FontBase::GetFTGlyphAdvance(uint16_t aGID)
+{
+    gfxFT2LockedFace face(this);
+    int32_t flags =
+        gfxPlatform::GetPlatform()->FontHintingEnabled()
+            ? FT_LOAD_ADVANCE_ONLY
+            : FT_LOAD_ADVANCE_ONLY | FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING;
+    FT_Fixed advance = 0;
+    // FT_Get_Advance is not reliable with variations until FreeType 2.8.2,
+    // so fall back to calling FT_Load_Glyph and reading the glyph slot's
+    // linearHoriAdvance.
+    // See https://savannah.nongnu.org/bugs/index.php?52683.
+    static uint32_t sFTVersion = 0;
+    if (!sFTVersion) {
+        FT_Int major, minor, patch;
+        FT_Library_Version(face.get()->glyph->library, &major, &minor, &patch);
+        sFTVersion = (major << 16) | (minor << 8) | patch;
+    }
+    if (sFTVersion < 0x020802 &&
+        (face.get()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) {
+        mozilla::DebugOnly<FT_Error> ftError =
+            FT_Load_Glyph(face.get(), aGID, flags);
+        MOZ_ASSERT(!ftError);
+        advance = face.get()->glyph->linearHoriAdvance;
+    } else {
+        mozilla::DebugOnly<FT_Error> ftError =
+            FT_Get_Advance(face.get(), aGID, flags, &advance);
+        MOZ_ASSERT(!ftError);
+    }
+
+    // If freetype emboldening is being used, and it's not a zero-width glyph,
+    // adjust the advance to account for the increased width.
+    if (mEmbolden && advance > 0) {
+        // This is the embolden "strength" used by FT_GlyphSlot_Embolden,
+        // converted from 26.6 to 16.16
+        FT_Fixed strength = 1024 *
+            FT_MulFix(face.get()->units_per_EM,
+                      face.get()->size->metrics.y_scale) / 24;
+        advance += strength;
+    }
+
+    return advance;
+}
+
 int32_t
 gfxFT2FontBase::GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID)
 {
-    cairo_text_extents_t extents;
-    GetGlyphExtents(aGID, &extents);
-    // convert to 16.16 fixed point
-    return NS_lround(0x10000 * extents.x_advance);
+    if (!mGlyphWidths) {
+        mGlyphWidths =
+            mozilla::MakeUnique<nsDataHashtable<nsUint32HashKey,int32_t>>(128);
+    }
+
+    int32_t width;
+    if (mGlyphWidths->Get(aGID, &width)) {
+        return width;
+    }
+
+    width = GetFTGlyphAdvance(aGID);
+    mGlyphWidths->Put(aGID, width);
+
+    return width;
 }
 
 bool
 gfxFT2FontBase::SetupCairoFont(DrawTarget* aDrawTarget)
 {
     // The scaled font ctm is not relevant right here because
     // cairo_set_scaled_font does not record the scaled font itself, but
     // merely the font_face, font_matrix, font_options.  The scaled_font used
@@ -510,8 +604,45 @@ gfxFT2FontBase::SetupCairoFont(DrawTarge
     //
     // I can't see any significant difference in printing, irrespective of
     // what is set here.  It's too late to change things here as measuring has
     // already taken place.  We should really be measuring with a different
     // font for pdf and ps surfaces (bug 403513).
     cairo_set_scaled_font(gfxFont::RefCairo(aDrawTarget), cairoFont);
     return true;
 }
+
+// For variation fonts, figure out the variation coordinates to be applied
+// for each axis, in freetype's order (which may not match the order of
+// axes in mStyle.variationSettings, so we need to search by axis tag).
+/*static*/
+void
+gfxFT2FontBase::SetupVarCoords(FT_Face aFace,
+                               const nsTArray<gfxFontVariation>& aVariations,
+                               nsTArray<FT_Fixed>* aCoords)
+{
+    aCoords->TruncateLength(0);
+    if (aFace->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) {
+        typedef FT_UInt (*GetVarFunc)(FT_Face, FT_MM_Var**);
+        static GetVarFunc getVar;
+        static bool firstTime = true;
+        if (firstTime) {
+            firstTime = false;
+            getVar = (GetVarFunc)dlsym(RTLD_DEFAULT, "FT_Get_MM_Var");
+        }
+        FT_MM_Var* ftVar;
+        if (getVar && FT_Err_Ok == (*getVar)(aFace, &ftVar)) {
+            for (unsigned i = 0; i < ftVar->num_axis; ++i) {
+                aCoords->AppendElement(ftVar->axis[i].def);
+                for (const auto& v : aVariations) {
+                    if (ftVar->axis[i].tag == v.mTag) {
+                        FT_Fixed val = v.mValue * 0x10000;
+                        val = std::min(val, ftVar->axis[i].maximum);
+                        val = std::max(val, ftVar->axis[i].minimum);
+                        (*aCoords)[i] = val;
+                        break;
+                    }
+                }
+            }
+            free(ftVar);
+        }
+    }
+}
--- a/gfx/thebes/gfxFT2FontBase.h
+++ b/gfx/thebes/gfxFT2FontBase.h
@@ -6,23 +6,26 @@
 #ifndef GFX_FT2FONTBASE_H
 #define GFX_FT2FONTBASE_H
 
 #include "cairo.h"
 #include "gfxContext.h"
 #include "gfxFont.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/UnscaledFontFreeType.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
 
 class gfxFT2FontBase : public gfxFont {
 public:
     gfxFT2FontBase(const RefPtr<mozilla::gfx::UnscaledFontFreeType>& aUnscaledFont,
                    cairo_scaled_font_t *aScaledFont,
                    gfxFontEntry *aFontEntry,
-                   const gfxFontStyle *aFontStyle);
+                   const gfxFontStyle *aFontStyle,
+                   bool aEmbolden);
     virtual ~gfxFT2FontBase();
 
     uint32_t GetGlyph(uint32_t aCharCode);
     void GetGlyphExtents(uint32_t aGlyph,
                          cairo_text_extents_t* aExtents);
     virtual uint32_t GetSpaceGlyph() override;
     virtual bool ProvidesGetGlyph() const override { return true; }
     virtual uint32_t GetGlyph(uint32_t unicode,
@@ -30,20 +33,36 @@ public:
     virtual bool ProvidesGlyphWidths() const override { return true; }
     virtual int32_t GetGlyphWidth(DrawTarget& aDrawTarget,
                                   uint16_t aGID) override;
 
     virtual bool SetupCairoFont(DrawTarget* aDrawTarget) override;
 
     virtual FontType GetType() const override { return FONT_TYPE_FT2; }
 
+    static void SetupVarCoords(FT_Face aFace,
+                               const nsTArray<gfxFontVariation>& aVariations,
+                               nsTArray<FT_Fixed>* aCoords);
+
 private:
     uint32_t GetCharExtents(char aChar, cairo_text_extents_t* aExtents);
+    uint32_t GetCharWidth(char aChar, gfxFloat* aWidth);
+    FT_Fixed GetFTGlyphAdvance(uint16_t aGID);
     void InitMetrics();
 
 protected:
     virtual const Metrics& GetHorizontalMetrics() override;
 
     uint32_t mSpaceGlyph;
     Metrics mMetrics;
+    bool    mEmbolden;
+
+    // For variation/multiple-master fonts, this will be an array of the values
+    // for each axis, as specified by mStyle.variationSettings (or the font's
+    // default for axes not present in variationSettings). Values here are in
+    // freetype's 16.16 fixed-point format, and clamped to the valid min/max
+    // range reported by the face.
+    nsTArray<FT_Fixed> mCoords;
+
+    mozilla::UniquePtr<nsDataHashtable<nsUint32HashKey,int32_t>> mGlyphWidths;
 };
 
 #endif /* GFX_FT2FONTBASE_H */
--- a/gfx/thebes/gfxFT2FontList.cpp
+++ b/gfx/thebes/gfxFT2FontList.cpp
@@ -385,17 +385,17 @@ FT2FontEntry::CreateFontEntry(FT_Face aF
     fe->mFilename = aFilename;
     fe->mFTFontIndex = aIndex;
 
     if (aFontData) {
         fe->mFTFace = aFace;
         int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ?
                     FT_LOAD_DEFAULT :
                     (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
-        fe->mFontFace = cairo_ft_font_face_create_for_ft_face(aFace, flags);
+        fe->mFontFace = cairo_ft_font_face_create_for_ft_face(aFace, flags, nullptr, 0);
         FTUserFontData *userFontData = new FTUserFontData(aFace, aFontData);
         cairo_font_face_set_user_data(fe->mFontFace, &sFTUserFontDataKey,
                                       userFontData, FTFontDestroyFunc);
     }
 
     return fe;
 }
 
@@ -429,17 +429,17 @@ FT2FontEntry::CairoFontFace()
         AutoFTFace face(this);
         if (!face) {
             return nullptr;
         }
         mFTFace = face.forget();
         int flags = gfxPlatform::GetPlatform()->FontHintingEnabled() ?
                     FT_LOAD_DEFAULT :
                     (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
-        mFontFace = cairo_ft_font_face_create_for_ft_face(face, flags);
+        mFontFace = cairo_ft_font_face_create_for_ft_face(face, flags, nullptr, 0);
         FTUserFontData *userFontData = new FTUserFontData(face, face.FontData());
         cairo_font_face_set_user_data(mFontFace, &sFTUserFontDataKey,
                                       userFontData, FTFontDestroyFunc);
     }
     return mFontFace;
 }
 
 // Copied/modified from similar code in gfxMacPlatformFontList.mm:
--- a/gfx/thebes/gfxFT2Fonts.cpp
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -161,20 +161,21 @@ gfxFT2Font::AddRange(const char16_t *aTe
     }
 }
 
 gfxFT2Font::gfxFT2Font(const RefPtr<UnscaledFontFreeType>& aUnscaledFont,
                        cairo_scaled_font_t *aCairoFont,
                        FT2FontEntry *aFontEntry,
                        const gfxFontStyle *aFontStyle,
                        bool aNeedsBold)
-    : gfxFT2FontBase(aUnscaledFont, aCairoFont, aFontEntry, aFontStyle)
+    : gfxFT2FontBase(aUnscaledFont, aCairoFont, aFontEntry, aFontStyle, false)
     , mCharGlyphCache(32)
 {
     NS_ASSERTION(mFontEntry, "Unable to find font entry for font.  Something is whack.");
+    // TODO: use FreeType emboldening instead of multi-strike?
     mApplySyntheticBold = aNeedsBold;
 }
 
 gfxFT2Font::~gfxFT2Font()
 {
 }
 
 already_AddRefed<ScaledFont>
--- a/gfx/thebes/gfxFcPlatformFontList.cpp
+++ b/gfx/thebes/gfxFcPlatformFontList.cpp
@@ -224,17 +224,17 @@ MapFcWidth(int aFcWidth)
 }
 
 gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName,
                                                FcPattern* aFontPattern,
                                                bool aIgnoreFcCharmap)
         : gfxFontEntry(aFaceName), mFontPattern(aFontPattern),
           mFTFace(nullptr), mFTFaceInitialized(false),
           mIgnoreFcCharmap(aIgnoreFcCharmap),
-          mAspect(0.0), mFontData(nullptr)
+          mAspect(0.0), mFontData(nullptr), mLength(0)
 {
     // italic
     int slant;
     if (FcPatternGetInteger(aFontPattern, FC_SLANT, 0, &slant) != FcResultMatch) {
         slant = FC_SLANT_ROMAN;
     }
     if (slant == FC_SLANT_OBLIQUE) {
         mStyle = NS_FONT_STYLE_OBLIQUE;
@@ -259,65 +259,89 @@ gfxFontconfigFontEntry::gfxFontconfigFon
 
 gfxFontEntry*
 gfxFontconfigFontEntry::Clone() const
 {
     MOZ_ASSERT(!IsUserFont(), "we can only clone installed fonts!");
     return new gfxFontconfigFontEntry(Name(), mFontPattern, mIgnoreFcCharmap);
 }
 
-gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName,
-                                               uint16_t aWeight,
-                                               int16_t aStretch,
-                                               uint8_t aStyle,
-                                               const uint8_t *aData,
-                                               FT_Face aFace)
-    : gfxFontEntry(aFaceName),
-      mFTFace(aFace), mFTFaceInitialized(true),
-      mIgnoreFcCharmap(true),
-      mAspect(0.0), mFontData(aData)
+static FcPattern*
+CreatePatternForFace(FT_Face aFace)
 {
-    mWeight = aWeight;
-    mStyle = aStyle;
-    mStretch = aStretch;
-    mIsDataUserFont = true;
-
     // Use fontconfig to fill out the pattern from the FTFace.
     // The "file" argument cannot be nullptr (in fontconfig-2.6.0 at
     // least). The dummy file passed here is removed below.
     //
     // When fontconfig scans the system fonts, FcConfigGetBlanks(nullptr)
     // is passed as the "blanks" argument, which provides that unexpectedly
     // blank glyphs are elided.  Here, however, we pass nullptr for
     // "blanks", effectively assuming that, if the font has a blank glyph,
     // then the author intends any associated character to be rendered
     // blank.
-    mFontPattern = FcFreeTypeQueryFace(mFTFace, ToFcChar8Ptr(""), 0, nullptr);
+    FcPattern* pattern =
+        FcFreeTypeQueryFace(aFace, ToFcChar8Ptr(""), 0, nullptr);
     // given that we have a FT_Face, not really sure this is possible...
-    if (!mFontPattern) {
-        mFontPattern = FcPatternCreate();
+    if (!pattern) {
+        pattern = FcPatternCreate();
     }
-    FcPatternDel(mFontPattern, FC_FILE);
-    FcPatternDel(mFontPattern, FC_INDEX);
+    FcPatternDel(pattern, FC_FILE);
+    FcPatternDel(pattern, FC_INDEX);
 
     // Make a new pattern and store the face in it so that cairo uses
     // that when creating a cairo font face.
-    FcPatternAddFTFace(mFontPattern, FC_FT_FACE, mFTFace);
+    FcPatternAddFTFace(pattern, FC_FT_FACE, aFace);
+
+    return pattern;
+}
+
+static FT_Face
+CreateFaceForPattern(FcPattern* aPattern)
+{
+    FcChar8 *filename;
+    if (FcPatternGetString(aPattern, FC_FILE, 0, &filename) != FcResultMatch) {
+        return nullptr;
+    }
+    int index;
+    if (FcPatternGetInteger(aPattern, FC_INDEX, 0, &index) != FcResultMatch) {
+        index = 0; // default to 0 if not found in pattern
+    }
+    return Factory::NewFTFace(nullptr, ToCharPtr(filename), index);
+}
+
+gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName,
+                                               uint16_t aWeight,
+                                               int16_t aStretch,
+                                               uint8_t aStyle,
+                                               const uint8_t *aData,
+                                               uint32_t aLength,
+                                               FT_Face aFace)
+    : gfxFontEntry(aFaceName),
+      mFTFace(aFace), mFTFaceInitialized(true),
+      mIgnoreFcCharmap(true),
+      mAspect(0.0), mFontData(aData), mLength(aLength)
+{
+    mWeight = aWeight;
+    mStyle = aStyle;
+    mStretch = aStretch;
+    mIsDataUserFont = true;
+
+    mFontPattern = CreatePatternForFace(mFTFace);
 
     mUserFontData = new FTUserFontData(mFTFace, mFontData);
 }
 
 gfxFontconfigFontEntry::gfxFontconfigFontEntry(const nsAString& aFaceName,
                                                FcPattern* aFontPattern,
                                                uint16_t aWeight,
                                                int16_t aStretch,
                                                uint8_t aStyle)
         : gfxFontEntry(aFaceName), mFontPattern(aFontPattern),
           mFTFace(nullptr), mFTFaceInitialized(false),
-          mAspect(0.0), mFontData(nullptr)
+          mAspect(0.0), mFontData(nullptr), mLength(0)
 {
     mWeight = aWeight;
     mStyle = aStyle;
     mStretch = aStretch;
     mIsLocalUserFont = true;
 
     // The proper setting of mIgnoreFcCharmap is tricky for fonts loaded
     // via src:local()...
@@ -689,18 +713,29 @@ gfxFontconfigFontEntry::CreateScaledFont
                         aStyle->allowSyntheticStyle;
 
     if (needsOblique) {
         // disable embedded bitmaps (mimics behavior in 90-synthetic.conf)
         FcPatternDel(aRenderPattern, FC_EMBEDDED_BITMAP);
         FcPatternAddBool(aRenderPattern, FC_EMBEDDED_BITMAP, FcFalse);
     }
 
+    AutoTArray<FT_Fixed,8> coords;
+    if (!aStyle->variationSettings.IsEmpty()) {
+        FT_Face ftFace = GetFTFace();
+        if (ftFace) {
+            gfxFT2FontBase::SetupVarCoords(ftFace, aStyle->variationSettings,
+                                           &coords);
+        }
+    }
+
     cairo_font_face_t *face =
-        cairo_ft_font_face_create_for_pattern(aRenderPattern);
+        cairo_ft_font_face_create_for_pattern(aRenderPattern,
+                                              coords.Elements(),
+                                              coords.Length());
 
     if (mFontData) {
         // for data fonts, add the face/data pointer to the cairo font face
         // so that it gets deleted whenever cairo decides
         NS_ASSERTION(mFTFace, "FT_Face is null when setting user data");
         NS_ASSERTION(mUserFontData, "user font data is null when setting user data");
         mUserFontData.get()->AddRef();
         if (cairo_font_face_set_user_data(face,
@@ -882,19 +917,50 @@ gfxFontconfigFontEntry::CreateFontInstan
     if (!pattern) {
         NS_WARNING("Failed to create Fontconfig pattern for font instance");
         return nullptr;
     }
 
     double size = ChooseFontSize(this, *aFontStyle);
     FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size);
 
+    FT_Face face = mFTFace;
+    FcPattern* fontPattern = mFontPattern;
+    if (face && face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) {
+        // For variation fonts, we create a new FT_Face and FcPattern here
+        // so that variation coordinates from the style can be applied
+        // without affecting other font instances created from the same
+        // entry (font resource).
+        if (mFontData) {
+            // For user fonts: create a new FT_Face from the font data, and then
+            // make a pattern from that.
+            face = Factory::NewFTFaceFromData(nullptr, mFontData, mLength, 0);
+            fontPattern = CreatePatternForFace(face);
+        } else {
+            // For system fonts: create a new FT_Face and store it in a copy of
+            // the original mFontPattern.
+            fontPattern = FcPatternDuplicate(mFontPattern);
+            face = CreateFaceForPattern(fontPattern);
+            if (face) {
+                FcPatternAddFTFace(fontPattern, FC_FT_FACE, face);
+            } else {
+                // I don't think CreateFaceForPattern above should ever fail,
+                // but just in case let's fall back here.
+                face = mFTFace;
+            }
+        }
+    }
+
     PreparePattern(pattern, aFontStyle->printerFont);
     nsAutoRef<FcPattern> renderPattern
-        (FcFontRenderPrepare(nullptr, pattern, mFontPattern));
+        (FcFontRenderPrepare(nullptr, pattern, fontPattern));
+    if (fontPattern != mFontPattern) {
+        // Discard temporary pattern used for variation support
+        FcPatternDestroy(fontPattern);
+    }
     if (!renderPattern) {
         NS_WARNING("Failed to prepare Fontconfig pattern for font instance");
         return nullptr;
     }
 
     cairo_scaled_font_t* scaledFont =
         CreateScaledFont(renderPattern, size, aFontStyle, aNeedsBold);
 
@@ -909,65 +975,60 @@ gfxFontconfigFontEntry::CreateFontInstan
         }
     }
 
     RefPtr<UnscaledFontFontconfig> unscaledFont =
         mUnscaledFontCache.Lookup(ToCharPtr(file), index);
     if (!unscaledFont) {
         unscaledFont =
             mFontData ?
-                new UnscaledFontFontconfig(mFTFace) :
+                new UnscaledFontFontconfig(face) :
                 new UnscaledFontFontconfig(ToCharPtr(file), index);
         mUnscaledFontCache.Add(unscaledFont);
     }
 
     gfxFont* newFont =
         new gfxFontconfigFont(unscaledFont, scaledFont,
                               renderPattern, size,
                               this, aFontStyle, aNeedsBold);
     cairo_scaled_font_destroy(scaledFont);
 
     return newFont;
 }
 
+FT_Face
+gfxFontconfigFontEntry::GetFTFace()
+{
+    if (!mFTFaceInitialized) {
+        mFTFaceInitialized = true;
+        mFTFace = CreateFaceForPattern(mFontPattern);
+    }
+    return mFTFace;
+}
+
 nsresult
 gfxFontconfigFontEntry::CopyFontTable(uint32_t aTableTag,
                                       nsTArray<uint8_t>& aBuffer)
 {
     NS_ASSERTION(!mIsDataUserFont,
                  "data fonts should be reading tables directly from memory");
 
-    if (!mFTFaceInitialized) {
-        mFTFaceInitialized = true;
-        FcChar8 *filename;
-        if (FcPatternGetString(mFontPattern, FC_FILE, 0, &filename) != FcResultMatch) {
-            return NS_ERROR_FAILURE;
-        }
-        int index;
-        if (FcPatternGetInteger(mFontPattern, FC_INDEX, 0, &index) != FcResultMatch) {
-            index = 0; // default to 0 if not found in pattern
-        }
-        mFTFace = Factory::NewFTFace(nullptr, ToCharPtr(filename), index);
-        if (!mFTFace) {
-            return NS_ERROR_FAILURE;
-        }
-    }
-
-    if (!mFTFace) {
+    FT_Face face = GetFTFace();
+    if (!face) {
         return NS_ERROR_NOT_AVAILABLE;
     }
 
     FT_ULong length = 0;
-    if (FT_Load_Sfnt_Table(mFTFace, aTableTag, 0, nullptr, &length) != 0) {
+    if (FT_Load_Sfnt_Table(face, aTableTag, 0, nullptr, &length) != 0) {
         return NS_ERROR_NOT_AVAILABLE;
     }
     if (!aBuffer.SetLength(length, fallible)) {
         return NS_ERROR_OUT_OF_MEMORY;
     }
-    if (FT_Load_Sfnt_Table(mFTFace, aTableTag, 0, aBuffer.Elements(), &length) != 0) {
+    if (FT_Load_Sfnt_Table(face, aTableTag, 0, aBuffer.Elements(), &length) != 0) {
         aBuffer.Clear();
         return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
 }
 
 void
@@ -1224,17 +1285,17 @@ gfxFontconfigFontFamily::AddFacesToFontL
 
 gfxFontconfigFont::gfxFontconfigFont(const RefPtr<UnscaledFontFontconfig>& aUnscaledFont,
                                      cairo_scaled_font_t *aScaledFont,
                                      FcPattern *aPattern,
                                      gfxFloat aAdjustedSize,
                                      gfxFontEntry *aFontEntry,
                                      const gfxFontStyle *aFontStyle,
                                      bool aNeedsBold)
-    : gfxFT2FontBase(aUnscaledFont, aScaledFont, aFontEntry, aFontStyle)
+    : gfxFT2FontBase(aUnscaledFont, aScaledFont, aFontEntry, aFontStyle, aNeedsBold)
     , mPattern(aPattern)
 {
     mAdjustedSize = aAdjustedSize;
 }
 
 gfxFontconfigFont::~gfxFontconfigFont()
 {
 }
@@ -1686,17 +1747,17 @@ gfxFcPlatformFontList::MakePlatformFont(
     }
     if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE)) {
         Factory::ReleaseFTFace(face);
         free((void*)aFontData);
         return nullptr;
     }
 
     return new gfxFontconfigFontEntry(aFontName, aWeight, aStretch,
-                                      aStyle, aFontData, face);
+                                      aStyle, aFontData, aLength, face);
 }
 
 bool
 gfxFcPlatformFontList::FindAndAddFamilies(const nsAString& aFamily,
                                           nsTArray<gfxFontFamily*>* aOutput,
                                           FindFamiliesFlags aFlags,
                                           gfxFontStyle* aStyle,
                                           gfxFloat aDevToCssSize)
--- a/gfx/thebes/gfxFcPlatformFontList.h
+++ b/gfx/thebes/gfxFcPlatformFontList.h
@@ -91,32 +91,35 @@ public:
 
     // used for data fonts where the fontentry takes ownership
     // of the font data and the FT_Face
     explicit gfxFontconfigFontEntry(const nsAString& aFaceName,
                                     uint16_t aWeight,
                                     int16_t aStretch,
                                     uint8_t aStyle,
                                     const uint8_t *aData,
+                                    uint32_t aLength,
                                     FT_Face aFace);
 
     // used for @font-face local system fonts with explicit patterns
     explicit gfxFontconfigFontEntry(const nsAString& aFaceName,
                                     FcPattern* aFontPattern,
                                     uint16_t aWeight,
                                     int16_t aStretch,
                                     uint8_t aStyle);
 
     gfxFontEntry* Clone() const override;
 
     FcPattern* GetPattern() { return mFontPattern; }
 
     nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr) override;
     bool TestCharacterMap(uint32_t aCh) override;
 
+    FT_Face GetFTFace();
+
     hb_blob_t* GetFontTable(uint32_t aTableTag) override;
 
     void ForgetHBFace() override;
     void ReleaseGrFace(gr_face* aFace) override;
 
     double GetAspect();
 
 protected:
@@ -156,16 +159,17 @@ protected:
     // include color glyphs that fontconfig would overlook, and for fonts
     // loaded via @font-face.
     bool      mIgnoreFcCharmap;
 
     double    mAspect;
 
     // data font
     const uint8_t* mFontData;
+    uint32_t       mLength;
 
     class UnscaledFontCache
     {
     public:
         already_AddRefed<mozilla::gfx::UnscaledFontFontconfig>
         Lookup(const char* aFile, uint32_t aIndex);
 
         void Add(const RefPtr<mozilla::gfx::UnscaledFontFontconfig>& aUnscaledFont) {
--- a/js/src/jit-test/tests/ion/recover-cow-arrays.js
+++ b/js/src/jit-test/tests/ion/recover-cow-arrays.js
@@ -121,35 +121,73 @@ function array2ContentBail2(i) {
     var a = [1, 2];
     assertEq(a[0], 1);
     assertEq(a[1], 2);
     resumeHere(i);
     assertRecoveredOnBailout(a, true);
     return a.length;
 }
 
-// We don't handle COW array writes
 function arrayWrite1(i) {
     var a = [1, 2];
     a[0] = 42;
     assertEq(a[0], 42);
     assertEq(a[1], 2);
-    assertRecoveredOnBailout(a, false);
+    assertRecoveredOnBailout(a, true);
     return a.length;
 }
 
+// We don't handle length sets yet.
 function arrayWrite2(i) {
     var a = [1, 2];
     a.length = 1;
     assertEq(a[0], 1);
     assertEq(a[1], undefined);
     assertRecoveredOnBailout(a, false);
     return a.length;
 }
 
+function arrayWrite3(i) {
+    var a = [1, 2, 0];
+    if (i % 2 === 1)
+	a[0] = 2;
+    assertEq(a[0], 1 + (i % 2));
+    assertRecoveredOnBailout(a, true);
+    if (i % 2 === 1)
+	bailout();
+    assertEq(a[0], 1 + (i % 2));
+    return a.length;
+}
+
+function arrayWrite4(i) {
+    var a = [1, 2, 0];
+    for (var x = 0; x < 2; x++) {
+	if (x % 2 === 1)
+	    bailout();
+	else
+	    a[0] = a[0] + 1;
+    }
+    assertEq(a[0], 2);
+    assertEq(a[1], 2);
+    assertRecoveredOnBailout(a, true);
+    return a.length;
+}
+
+function arrayWriteDoubles(i) {
+    var a = [0, 0, 0];
+    a[0] = 3.14;
+    // MConvertElementsToDoubles is only used for loads inside a loop.
+    for (var x = 0; x < 2; x++) {
+        assertEq(a[0], 3.14);
+        assertEq(a[1], 0);
+    }
+    assertRecoveredOnBailout(a, true);
+    return a.length;
+}
+
 // Check escape analysis in case of holes.
 function arrayHole0(i) {
     var a = [1,,3];
     assertEq(a[0], 1);
     assertEq(a[1], undefined);
     assertEq(a[2], 3);
     // need to check for holes.
     assertRecoveredOnBailout(a, false);
@@ -195,16 +233,19 @@ for (var i = 0; i < 100; i++) {
     array1ContentBail0(i);
     array1ContentBail1(i);
     array2Content(i);
     array2ContentBail0(i);
     array2ContentBail1(i);
     array2ContentBail2(i);
     arrayWrite1(i);
     arrayWrite2(i);
+    arrayWrite3(i);
+    arrayWrite4(i);
+    arrayWriteDoubles(i);
     arrayHole0(i);
     arrayAlloc(i);
 }
 
 // If arr[1] is not defined, then we fallback on the prototype which instead of
 // returning undefined, returns "0".
 Object.defineProperty(Array.prototype, 1, {
   value: 100,
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -1757,34 +1757,41 @@ RArrayState::RArrayState(CompactBufferRe
 bool
 RArrayState::recover(JSContext* cx, SnapshotIterator& iter) const
 {
     RootedValue result(cx);
     ArrayObject* object = &iter.read().toObject().as<ArrayObject>();
     uint32_t initLength = iter.read().toInt32();
 
     if (!object->denseElementsAreCopyOnWrite()) {
+        MOZ_ASSERT(object->getDenseInitializedLength() == 0,
+                   "initDenseElement call below relies on this");
         object->setDenseInitializedLength(initLength);
+
         for (size_t index = 0; index < numElements(); index++) {
             Value val = iter.read();
 
             if (index >= initLength) {
                 MOZ_ASSERT(val.isUndefined());
                 continue;
             }
 
             object->initDenseElement(index, val);
         }
     } else {
-        MOZ_ASSERT(object->getDenseInitializedLength() == numElements());
-        MOZ_ASSERT(initLength == numElements());
+        MOZ_RELEASE_ASSERT(object->getDenseInitializedLength() == numElements());
+        MOZ_RELEASE_ASSERT(initLength == numElements());
 
         for (size_t index = 0; index < numElements(); index++) {
             Value val = iter.read();
-            MOZ_RELEASE_ASSERT(object->getDenseElement(index) == val);
+            if (object->getDenseElement(index) == val)
+                continue;
+            if (!object->maybeCopyElementsForWrite(cx))
+                return false;
+            object->setDenseElement(index, val);
         }
     }
 
     result.setObject(*object);
     iter.storeInstructionResult(result);
     return true;
 }
 
--- a/js/src/jit/ScalarReplacement.cpp
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -870,18 +870,20 @@ IndexOf(MDefinition* ins, int32_t* res)
         return false;
     *res = indexDefConst->toInt32();
     return true;
 }
 
 // Returns False if the elements is not escaped and if it is optimizable by
 // ScalarReplacementOfArray.
 static bool
-IsElementEscaped(MElements* def, uint32_t arraySize, bool copyOnWrite)
+IsElementEscaped(MDefinition* def, uint32_t arraySize)
 {
+    MOZ_ASSERT(def->isElements() || def->isConvertElementsToDoubles());
+
     JitSpewDef(JitSpew_Escape, "Check elements\n", def);
     JitSpewIndent spewIndent(JitSpew_Escape);
 
     for (MUseIterator i(def->usesBegin()); i != def->usesEnd(); i++) {
         // The MIRType::Elements cannot be captured in a resume point as
         // it does not represent a value allocation.
         MDefinition* access = (*i)->consumer()->toDefinition();
 
@@ -914,21 +916,16 @@ IsElementEscaped(MElements* def, uint32_
                 return true;
             }
             break;
           }
 
           case MDefinition::Opcode::StoreElement: {
             MOZ_ASSERT(access->toStoreElement()->elements() == def);
 
-            if (copyOnWrite) {
-                JitSpewDef(JitSpew_Escape, "write to COW\n", access);
-                return true;
-            }
-
             // If we need hole checks, then the array cannot be escaped
             // as the array might refer to the prototype chain to look
             // for properties, thus it might do additional side-effects
             // which are not reflected by the alias set, is we are
             // bailing on holes.
             if (access->toStoreElement()->needsHoleCheck()) {
                 JitSpewDef(JitSpew_Escape,
                            "has a store element with a hole check\n", access);
@@ -951,32 +948,35 @@ IsElementEscaped(MElements* def, uint32_
             if (access->toStoreElement()->value()->type() == MIRType::MagicHole) {
                 JitSpewDef(JitSpew_Escape, "has a store element with an magic-hole constant\n", access);
                 return true;
             }
             break;
           }
 
           case MDefinition::Opcode::SetInitializedLength:
-            if (copyOnWrite) {
-                JitSpewDef(JitSpew_Escape, "write to COW\n", access);
-                return true;
-            }
-
             MOZ_ASSERT(access->toSetInitializedLength()->elements() == def);
             break;
 
           case MDefinition::Opcode::InitializedLength:
             MOZ_ASSERT(access->toInitializedLength()->elements() == def);
             break;
 
           case MDefinition::Opcode::ArrayLength:
             MOZ_ASSERT(access->toArrayLength()->elements() == def);
             break;
 
+          case MDefinition::Opcode::ConvertElementsToDoubles:
+            MOZ_ASSERT(access->toConvertElementsToDoubles()->elements() == def);
+            if (IsElementEscaped(access, arraySize)) {
+                JitSpewDef(JitSpew_Escape, "is indirectly escaped by\n", access);
+                return true;
+            }
+            break;
+
           default:
             JitSpewDef(JitSpew_Escape, "is escaped by\n", access);
             return true;
         }
     }
     JitSpew(JitSpew_Escape, "Elements is not escaped");
     return false;
 }
@@ -988,34 +988,36 @@ IsOptimizableArrayInstruction(MInstructi
 }
 
 // Returns False if the array is not escaped and if it is optimizable by
 // ScalarReplacementOfArray.
 //
 // For the moment, this code is dumb as it only supports arrays which are not
 // changing length, with only access with known constants.
 static bool
-IsArrayEscaped(MInstruction* ins)
+IsArrayEscaped(MInstruction* ins, MInstruction* newArray)
 {
     MOZ_ASSERT(ins->type() == MIRType::Object);
-    MOZ_ASSERT(IsOptimizableArrayInstruction(ins));
+    MOZ_ASSERT(IsOptimizableArrayInstruction(ins) ||
+               ins->isMaybeCopyElementsForWrite());
+    MOZ_ASSERT(IsOptimizableArrayInstruction(newArray));
 
     JitSpewDef(JitSpew_Escape, "Check array\n", ins);
     JitSpewIndent spewIndent(JitSpew_Escape);
 
     uint32_t length;
-    if (ins->isNewArray()) {
-        if (!ins->toNewArray()->templateObject()) {
+    if (newArray->isNewArray()) {
+        if (!newArray->toNewArray()->templateObject()) {
             JitSpew(JitSpew_Escape, "No template object defined.");
             return true;
         }
 
-        length = ins->toNewArray()->length();
+        length = newArray->toNewArray()->length();
     } else {
-        length = ins->toNewArrayCopyOnWrite()->templateObject()->length();
+        length = newArray->toNewArrayCopyOnWrite()->templateObject()->length();
     }
 
     if (length >= 16) {
         JitSpew(JitSpew_Escape, "Array has too many elements");
         return true;
     }
 
     // Check if the object is escaped. If the object is not the first argument
@@ -1032,24 +1034,34 @@ IsArrayEscaped(MInstruction* ins)
             continue;
         }
 
         MDefinition* def = consumer->toDefinition();
         switch (def->op()) {
           case MDefinition::Opcode::Elements: {
             MElements *elem = def->toElements();
             MOZ_ASSERT(elem->object() == ins);
-            if (IsElementEscaped(elem, length, ins->isNewArrayCopyOnWrite())) {
+            if (IsElementEscaped(elem, length)) {
                 JitSpewDef(JitSpew_Escape, "is indirectly escaped by\n", elem);
                 return true;
             }
 
             break;
           }
 
+          case MDefinition::Opcode::MaybeCopyElementsForWrite: {
+            MMaybeCopyElementsForWrite* copied = def->toMaybeCopyElementsForWrite();
+            MOZ_ASSERT(copied->object() == ins);
+            if (IsArrayEscaped(copied, ins)) {
+                JitSpewDef(JitSpew_Escape, "is indirectly escaped by\n", copied);
+                return true;
+            }
+            break;
+          }
+
           // This instruction is a no-op used to verify that scalar replacement
           // is working as expected in jit-test.
           case MDefinition::Opcode::AssertRecoveredOnBailout:
             break;
 
           default:
             JitSpewDef(JitSpew_Escape, "is escaped by\n", def);
             return true;
@@ -1109,16 +1121,18 @@ class ArrayMemoryView : public MDefiniti
   public:
     void visitResumePoint(MResumePoint* rp);
     void visitArrayState(MArrayState* ins);
     void visitStoreElement(MStoreElement* ins);
     void visitLoadElement(MLoadElement* ins);
     void visitSetInitializedLength(MSetInitializedLength* ins);
     void visitInitializedLength(MInitializedLength* ins);
     void visitArrayLength(MArrayLength* ins);
+    void visitMaybeCopyElementsForWrite(MMaybeCopyElementsForWrite* ins);
+    void visitConvertElementsToDoubles(MConvertElementsToDoubles* ins);
 };
 
 const char* ArrayMemoryView::phaseName = "Scalar Replacement of Array";
 
 ArrayMemoryView::ArrayMemoryView(TempAllocator& alloc, MInstruction* arr)
   : alloc_(alloc),
     undefinedVal_(nullptr),
     length_(nullptr),
@@ -1401,16 +1415,57 @@ ArrayMemoryView::visitArrayLength(MArray
         arr_->block()->insertBefore(arr_, length_);
     }
     ins->replaceAllUsesWith(length_);
 
     // Remove original instruction.
     discardInstruction(ins, elements);
 }
 
+void
+ArrayMemoryView::visitMaybeCopyElementsForWrite(MMaybeCopyElementsForWrite* ins)
+{
+    MOZ_ASSERT(ins->numOperands() == 1);
+    MOZ_ASSERT(ins->type() == MIRType::Object);
+
+    // Skip guards on other objects.
+    if (ins->object() != arr_)
+        return;
+
+    // Nothing to do here: RArrayState::recover will copy the elements if
+    // needed.
+
+    // Replace the guard with the array.
+    ins->replaceAllUsesWith(arr_);
+
+    // Remove original instruction.
+    ins->block()->discard(ins);
+}
+
+void
+ArrayMemoryView::visitConvertElementsToDoubles(MConvertElementsToDoubles* ins)
+{
+    MOZ_ASSERT(ins->numOperands() == 1);
+    MOZ_ASSERT(ins->type() == MIRType::Elements);
+
+    // Skip other array objects.
+    MDefinition* elements = ins->elements();
+    if (!isArrayStateElements(elements))
+        return;
+
+    // We don't have to do anything else here: MConvertElementsToDoubles just
+    // exists to allow MLoadELement to use masm.loadDouble (without checking
+    // for int32 elements), but since we're using scalar replacement for the
+    // elements that doesn't matter.
+    ins->replaceAllUsesWith(elements);
+
+    // Remove original instruction.
+    ins->block()->discard(ins);
+}
+
 bool
 ScalarReplacement(MIRGenerator* mir, MIRGraph& graph)
 {
     EmulateStateOf<ObjectMemoryView> replaceObject(mir, graph);
     EmulateStateOf<ArrayMemoryView> replaceArray(mir, graph);
     bool addedPhi = false;
 
     for (ReversePostorderIterator block = graph.rpoBegin(); block != graph.rpoEnd(); block++) {
@@ -1423,17 +1478,17 @@ ScalarReplacement(MIRGenerator* mir, MIR
                 ObjectMemoryView view(graph.alloc(), *ins);
                 if (!replaceObject.run(view))
                     return false;
                 view.assertSuccess();
                 addedPhi = true;
                 continue;
             }
 
-            if (IsOptimizableArrayInstruction(*ins) && !IsArrayEscaped(*ins)) {
+            if (IsOptimizableArrayInstruction(*ins) && !IsArrayEscaped(*ins, *ins)) {
                 ArrayMemoryView view(graph.alloc(), *ins);
                 if (!replaceArray.run(view))
                     return false;
                 view.assertSuccess();
                 addedPhi = true;
                 continue;
             }
         }
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -1628,30 +1628,24 @@ struct nsGridContainerFrame::Tracks
       const uint32_t repeatAutoStart = aGridTemplate.mRepeatAutoIndex;
       const uint32_t repeatAutoEnd = (repeatAutoStart + repeatTrackCount);
       const int32_t repeatEndDelta = int32_t(repeatTrackCount - 1);
 
       if (aIndex <= repeatAutoStart) {
         if (aIndex < lineNameLists.Length()) {
           lineNames.AppendElements(lineNameLists[aIndex]);
         }
-        if (aIndex == repeatAutoEnd) {
-          uint32_t i = aIndex + 1;
-          if (i < lineNameLists.Length()) {
-            lineNames.AppendElements(lineNameLists[i]);
-          }
-        }
       }
       if (aIndex <= repeatAutoEnd && aIndex > repeatAutoStart) {
         lineNames.AppendElements(aGridTemplate.mRepeatAutoLineNameListAfter);
       }
       if (aIndex < repeatAutoEnd && aIndex >= repeatAutoStart) {
         lineNames.AppendElements(aGridTemplate.mRepeatAutoLineNameListBefore);
       }
-      if (aIndex >= repeatAutoEnd && aIndex > repeatAutoStart) {
+      if (aIndex > repeatAutoEnd && aIndex > repeatAutoStart) {
         uint32_t i = aIndex - repeatEndDelta;
         if (i < lineNameLists.Length()) {
           lineNames.AppendElements(lineNameLists[i]);
         }
       }
     }
 
     return lineNames;
@@ -6236,20 +6230,32 @@ nsGridContainerFrame::Reflow(nsPresConte
       nsTArray<nsString> explicitNames =
         gridReflowInput.mCols.GetExplicitLineNamesAtIndex(
           gridColTemplate,
           gridReflowInput.mColFunctions,
           col - gridReflowInput.mColFunctions.mExplicitGridOffset);
 
       columnLineNames.AppendElement(explicitNames);
     }
+    // Get the explicit names that follow a repeat auto declaration.
+    nsTArray<nsString> colNamesFollowingRepeat;
+    if (gridColTemplate.HasRepeatAuto()) {
+      // The line name list after the repeatAutoIndex holds the line names
+      // for the first explicit line after the repeat auto declaration.
+      uint32_t repeatAutoEnd = gridColTemplate.mRepeatAutoIndex + 1;
+      MOZ_ASSERT(repeatAutoEnd < gridColTemplate.mLineNameLists.Length());
+      colNamesFollowingRepeat.AppendElements(
+        gridColTemplate.mLineNameLists[repeatAutoEnd]);
+    }
+
     ComputedGridLineInfo* columnLineInfo = new ComputedGridLineInfo(
       Move(columnLineNames),
       gridColTemplate.mRepeatAutoLineNameListBefore,
-      gridColTemplate.mRepeatAutoLineNameListAfter);
+      gridColTemplate.mRepeatAutoLineNameListAfter,
+      Move(colNamesFollowingRepeat));
     SetProperty(GridColumnLineInfo(), columnLineInfo);
 
     // Generate row lines next.
     capacity = gridReflowInput.mRows.mSizes.Length();
     const nsStyleGridTemplate& gridRowTemplate =
       gridReflowInput.mGridStyle->GridTemplateRows();
     nsTArray<nsTArray<nsString>> rowLineNames(capacity);
     for (row = 0; row <= gridReflowInput.mRows.mSizes.Length(); row++) {
@@ -6257,20 +6263,32 @@ nsGridContainerFrame::Reflow(nsPresConte
       nsTArray<nsString> explicitNames =
         gridReflowInput.mRows.GetExplicitLineNamesAtIndex(
           gridRowTemplate,
           gridReflowInput.mRowFunctions,
           row - gridReflowInput.mRowFunctions.mExplicitGridOffset);
 
       rowLineNames.AppendElement(explicitNames);
     }
+    // Get the explicit names that follow a repeat auto declaration.
+    nsTArray<nsString> rowNamesFollowingRepeat;
+    if (gridRowTemplate.HasRepeatAuto()) {
+      // The line name list after the repeatAutoIndex holds the line names
+      // for the first explicit line after the repeat auto declaration.
+      uint32_t repeatAutoEnd = gridRowTemplate.mRepeatAutoIndex + 1;
+      MOZ_ASSERT(repeatAutoEnd < gridRowTemplate.mLineNameLists.Length());
+      rowNamesFollowingRepeat.AppendElements(
+        gridRowTemplate.mLineNameLists[repeatAutoEnd]);
+    }
+
     ComputedGridLineInfo* rowLineInfo = new ComputedGridLineInfo(
       Move(rowLineNames),
       gridRowTemplate.mRepeatAutoLineNameListBefore,
-      gridRowTemplate.mRepeatAutoLineNameListAfter);
+      gridRowTemplate.mRepeatAutoLineNameListAfter,
+      Move(rowNamesFollowingRepeat));
     SetProperty(GridRowLineInfo(), rowLineInfo);
 
     // Generate area info for explicit areas. Implicit areas are handled
     // elsewhere.
     if (gridReflowInput.mGridStyle->mGridTemplateAreas) {
       nsTArray<css::GridNamedArea>* areas = new nsTArray<css::GridNamedArea>(
           gridReflowInput.mGridStyle->mGridTemplateAreas->mNamedAreas);
       SetProperty(ExplicitNamedAreasProperty(), areas);
--- a/layout/generic/nsGridContainerFrame.h
+++ b/layout/generic/nsGridContainerFrame.h
@@ -65,24 +65,27 @@ struct ComputedGridTrackInfo
   nsTArray<bool> mRemovedRepeatTracks;
   uint32_t mRepeatFirstTrack;
 };
 
 struct ComputedGridLineInfo
 {
   explicit ComputedGridLineInfo(nsTArray<nsTArray<nsString>>&& aNames,
                                 const nsTArray<nsString>& aNamesBefore,
-                                const nsTArray<nsString>& aNamesAfter)
+                                const nsTArray<nsString>& aNamesAfter,
+                                nsTArray<nsString>&& aNamesFollowingRepeat)
     : mNames(aNames)
     , mNamesBefore(aNamesBefore)
     , mNamesAfter(aNamesAfter)
+    , mNamesFollowingRepeat(aNamesFollowingRepeat)
   {}
   nsTArray<nsTArray<nsString>> mNames;
   nsTArray<nsString> mNamesBefore;
   nsTArray<nsString> mNamesAfter;
+  nsTArray<nsString> mNamesFollowingRepeat;
 };
 } // namespace mozilla
 
 class nsGridContainerFrame final : public nsContainerFrame
 {
 public:
   NS_DECL_FRAMEARENA_HELPERS(nsGridContainerFrame)
   NS_DECL_QUERYFRAME
--- a/mfbt/Char16.h
+++ b/mfbt/Char16.h
@@ -61,17 +61,17 @@ public:
   {
     return const_cast<wchar_t*>(reinterpret_cast<const wchar_t*>(mPtr));
   }
 
   operator const void*() const
   {
     return mPtr;
   }
-  MOZ_IMPLICIT operator bool() const
+  explicit operator bool() const
   {
     return mPtr != nullptr;
   }
 
   /* Explicit cast operators to allow things like (char16_t*)str. */
   explicit operator char16_t*() const
   {
     return const_cast<char16_t*>(mPtr);
--- a/toolkit/components/places/PlacesRemoteTabsAutocompleteProvider.jsm
+++ b/toolkit/components/places/PlacesRemoteTabsAutocompleteProvider.jsm
@@ -12,19 +12,24 @@
 this.EXPORTED_SYMBOLS = ["PlacesRemoteTabsAutocompleteProvider"];
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "weaveXPCService", function() {
-  return Cc["@mozilla.org/weave/service;1"]
-           .getService(Ci.nsISupports)
-           .wrappedJSObject;
+  try {
+    return Cc["@mozilla.org/weave/service;1"]
+             .getService(Ci.nsISupports)
+             .wrappedJSObject;
+  } catch (ex) {
+    // The app didn't build Sync.
+  }
+  return null;
 });
 
 XPCOMUtils.defineLazyGetter(this, "Weave", () => {
   try {
     let {Weave} = Cu.import("resource://services-sync/main.js", {});
     return Weave;
   } catch (ex) {
     // The app didn't build Sync.
@@ -111,17 +116,17 @@ Services.obs.addObserver(observe, "weave
 Services.prefs.addObserver(PREF_SHOW_REMOTE_ICONS, observe);
 observe(null, "nsPref:changed", PREF_SHOW_REMOTE_ICONS);
 
 // This public object is a static singleton.
 this.PlacesRemoteTabsAutocompleteProvider = {
   // a promise that resolves with an array of matching remote tabs.
   getMatches(searchString) {
     // If Sync isn't configured we bail early.
-    if (!weaveXPCService.ready || !weaveXPCService.enabled) {
+    if (!weaveXPCService || !weaveXPCService.ready || !weaveXPCService.enabled) {
       return Promise.resolve([]);
     }
 
     let re = new RegExp(escapeRegExp(searchString), "i");
     let matches = [];
     let { tabs, clients } = ensureItems();
     for (let [url, { clientId, tab }] of tabs) {
       let title = tab.title;