Merge m-c to autoland, a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 01 Sep 2016 17:54:52 -0700
changeset 312367 e12415c1560179174bd3bdaca11dd20e6b97d35a
parent 312365 8f3ac8b34fa9d8b6cdd1783022e257b18913b9cf (current diff)
parent 312324 475f0ee625239e06c5794d4cf34e88d6ee2fde31 (diff)
child 312445 83133b8e14e63f6e9e78daa09a90a1810867060a
push id20447
push userkwierso@gmail.com
push dateFri, 02 Sep 2016 20:36:44 +0000
treeherderfx-team@969397f22187 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone51.0a1
Merge m-c to autoland, a=merge
dom/tests/mochitest/bugs/test_bug411103.html
gfx/layers/ipc/TextureForwarder.cpp
--- a/accessible/atk/UtilInterface.cpp
+++ b/accessible/atk/UtilInterface.cpp
@@ -246,33 +246,33 @@ mai_key_snooper(GtkWidget *the_widget, G
     }
     g_free(info);
     return (consumed ? 1 : 0);
 }
 
 static guint sKey_snooper_id = 0;
 
 static guint
-mai_util_add_key_event_listener (AtkKeySnoopFunc listener,
-                                 gpointer data)
+mai_util_add_key_event_listener(AtkKeySnoopFunc listener, gpointer data)
 {
-  if (MOZ_UNLIKELY(!listener))
+  if (MOZ_UNLIKELY(!listener)) {
     return 0;
+  }
 
-    static guint key=0;
+  static guint key = 0;
 
-    if (!sKey_listener_list) {
-        sKey_listener_list = g_hash_table_new(nullptr, nullptr);
-        sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data);
-    }
-    AtkKeySnoopFuncPointer atkKeySnoop;
-    atkKeySnoop.func_ptr = listener;
-    g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER (key++),
-                        atkKeySnoop.data);
-    return key;
+  if (!sKey_listener_list) {
+    sKey_listener_list = g_hash_table_new(nullptr, nullptr);
+    sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data);
+  }
+  AtkKeySnoopFuncPointer atkKeySnoop;
+  atkKeySnoop.func_ptr = listener;
+  g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER(key++),
+                      atkKeySnoop.data);
+  return key;
 }
 
 static void
 mai_util_remove_key_event_listener (guint remove_listener)
 {
     if (!sKey_listener_list) {
         // atk-bridge is initialized with gail (e.g. yelp)
         // try gail_remove_key_event_listener
--- a/accessible/atk/nsMaiHyperlink.cpp
+++ b/accessible/atk/nsMaiHyperlink.cpp
@@ -183,28 +183,29 @@ getUriCB(AtkHyperlink *aLink, gint aLink
 
   return g_strdup(cautoStr.get());
 }
 
 AtkObject *
 getObjectCB(AtkHyperlink *aLink, gint aLinkIndex)
 {
   MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
-  if (!maiLink)
+  if (!maiLink) {
     return nullptr;
+  }
 
-    if (Accessible* hyperlink = maiLink->GetAccHyperlink()) {
-      Accessible* anchor = hyperlink->AnchorAt(aLinkIndex);
-      NS_ENSURE_TRUE(anchor, nullptr);
+  if (Accessible* hyperlink = maiLink->GetAccHyperlink()) {
+    Accessible* anchor = hyperlink->AnchorAt(aLinkIndex);
+    NS_ENSURE_TRUE(anchor, nullptr);
 
-      return AccessibleWrap::GetAtkObject(anchor);
-    }
+    return AccessibleWrap::GetAtkObject(anchor);
+  }
 
-    ProxyAccessible* anchor = maiLink->Proxy()->AnchorAt(aLinkIndex);
-    return anchor ? GetWrapperFor(anchor) : nullptr;
+  ProxyAccessible* anchor = maiLink->Proxy()->AnchorAt(aLinkIndex);
+  return anchor ? GetWrapperFor(anchor) : nullptr;
 }
 
 gint
 getEndIndexCB(AtkHyperlink *aLink)
 {
   MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
   if (!maiLink)
     return false;
--- a/accessible/atk/nsMaiInterfaceDocument.cpp
+++ b/accessible/atk/nsMaiInterfaceDocument.cpp
@@ -56,24 +56,25 @@ getDocumentLocaleCB(AtkDocument *aDocume
   }
 
   return locale.IsEmpty() ? nullptr : AccessibleWrap::ReturnString(locale);
 }
 
 static inline GSList *
 prependToList(GSList *aList, const char *const aName, const nsAutoString &aValue)
 {
-  if (aValue.IsEmpty())
+  if (aValue.IsEmpty()) {
     return aList;
+  }
 
-    // libspi will free these
-    AtkAttribute *atkAttr = (AtkAttribute *)g_malloc(sizeof(AtkAttribute));
-    atkAttr->name = g_strdup(aName);
-    atkAttr->value = g_strdup(NS_ConvertUTF16toUTF8(aValue).get());
-    return g_slist_prepend(aList, atkAttr);
+  // libspi will free these
+  AtkAttribute *atkAttr = (AtkAttribute *)g_malloc(sizeof(AtkAttribute));
+  atkAttr->name = g_strdup(aName);
+  atkAttr->value = g_strdup(NS_ConvertUTF16toUTF8(aValue).get());
+  return g_slist_prepend(aList, atkAttr);
 }
 
 AtkAttributeSet *
 getDocumentAttributesCB(AtkDocument *aDocument)
 {
   nsAutoString url;
   nsAutoString w3cDocType;
   nsAutoString mimeType;
--- a/browser/components/contextualidentity/test/browser/browser_windowName.js
+++ b/browser/components/contextualidentity/test/browser/browser_windowName.js
@@ -42,28 +42,28 @@ add_task(function* test() {
   info("Opening a window from the first tab...");
   yield ContentTask.spawn(browser1, { url: BASE_URI + '?new' }, function(opts) {
     yield (new content.window.wrappedJSObject.Promise(resolve => {
       let w = content.window.wrappedJSObject.open(opts.url, 'tab-2');
       w.onload = function() { resolve(); }
     }));
   });
 
-  is(browser1.contentDocument.title, '?old', "Tab1 title must be 'old'");
-  is(browser1.contentDocument.nodePrincipal.userContextId, 1, "Tab1 UCI must be 1");
+  is(browser1.contentTitle, '?old', "Tab1 title must be 'old'");
+  is(browser1.contentPrincipal.userContextId, 1, "Tab1 UCI must be 1");
 
-  is(browser2.contentDocument.title, '?old', "Tab2 title must be 'old'");
-  is(browser2.contentDocument.nodePrincipal.userContextId, 2, "Tab2 UCI must be 2");
+  is(browser2.contentTitle, '?old', "Tab2 title must be 'old'");
+  is(browser2.contentPrincipal.userContextId, 2, "Tab2 UCI must be 2");
 
   let found = false;
   for (let i = 0; i < gBrowser.tabContainer.childNodes.length; ++i) {
     let tab = gBrowser.tabContainer.childNodes[i];
     let browser = gBrowser.getBrowserForTab(tab);
-    if (browser.contentDocument.title == '?new') {
-      is(browser.contentDocument.nodePrincipal.userContextId, 1, "Tab3 UCI must be 1");
+    if (browser.contentTitle == '?new') {
+      is(browser.contentPrincipal.userContextId, 1, "Tab3 UCI must be 1");
       isnot(browser, browser1, "Tab3 is not browser 1");
       isnot(browser, browser2, "Tab3 is not browser 2");
       gBrowser.removeTab(tab);
       found = true;
       break;
     }
   }
 
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -111,17 +111,16 @@
       <panelmultiview id="downloadsPanel-multiView"
                       mainViewId="downloadsPanel-mainView"
                       align="stretch">
 
         <panelview id="downloadsPanel-mainView"
                    flex="1"
                    align="stretch">
           <richlistbox id="downloadsListBox"
-                       class="plain"
                        context="downloadsContextMenu"
                        onmouseover="DownloadsView.onDownloadMouseOver(event);"
                        onmouseout="DownloadsView.onDownloadMouseOut(event);"
                        oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
                        ondragstart="DownloadsView.onDownloadDragStart(event);"/>
           <description id="emptyDownloads"
                        mousethrough="always">
              &downloadsPanelEmpty.label;
@@ -147,25 +146,25 @@
                                  max="100"
                                  mode="normal" />
                   <description id="downloadsSummaryDetails"
                                crop="end"/>
                 </vbox>
               </hbox>
               <hbox id="downloadsFooterButtons">
                 <button id="downloadsHistory"
-                        class="plain downloadsPanelFooterButton"
+                        class="downloadsPanelFooterButton"
                         label="&downloadsHistory.label;"
                         accesskey="&downloadsHistory.accesskey;"
                         flex="1"
                         oncommand="DownloadsPanel.showDownloadsHistory();"/>
                 <toolbarseparator id="downloadsFooterButtonsSplitter"
                         class="downloadsDropmarkerSplitter"/>
                 <button id="downloadsFooterDropmarker"
-                        class="plain downloadsPanelFooterButton downloadsDropmarker"
+                        class="downloadsPanelFooterButton downloadsDropmarker"
                         type="menu">
                   <menupopup id="downloadSubPanel"
                              onpopupshowing="DownloadsPanel.onFooterPopupShowing(event);"
                              onpopuphidden="DownloadsPanel.onFooterPopupHidden(event);"
                              position="after_end">
                     <menuitem id="downloadsDropdownItemClearList"
                               command="downloadsCmd_clearList"
                               label="&cmd.clearList2.label;"/>
@@ -185,22 +184,22 @@
           <description id="downloadsPanel-blockedSubview-title"/>
           <description id="downloadsPanel-blockedSubview-details1"/>
           <description id="downloadsPanel-blockedSubview-details2"/>
           <spacer flex="1"/>
           <hbox id="downloadsPanel-blockedSubview-buttons"
                 class="downloadsPanelFooter"
                 align="stretch">
             <button id="downloadsPanel-blockedSubview-openButton"
-                    class="plain downloadsPanelFooterButton"
+                    class="downloadsPanelFooterButton"
                     command="downloadsCmd_unblockAndOpen"
                     flex="1"/>
             <toolbarseparator/>
             <button id="downloadsPanel-blockedSubview-deleteButton"
-                    class="plain downloadsPanelFooterButton"
+                    class="downloadsPanelFooterButton"
                     oncommand="DownloadsBlockedSubview.confirmBlock();"
                     default="true"
                     flex="1"/>
           </hbox>
         </panelview>
 
       </panelmultiview>
 
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,3 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.5.385
+Current extension version is: 1.5.413
--- a/browser/extensions/pdfjs/content/PdfJs.jsm
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -89,16 +89,17 @@ function initializeDefaultPreferences() 
   "disableStream": false,
   "disableAutoFetch": false,
   "disableFontFace": false,
   "disableTextLayer": false,
   "useOnlyCssZoom": false,
   "externalLinkTarget": 0
 }
 
+
   var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.');
   var defaultValue;
   for (var key in DEFAULT_PREFERENCES) {
     defaultValue = DEFAULT_PREFERENCES[key];
     switch (typeof defaultValue) {
       case 'boolean':
         defaultBranch.setBoolPref(key, defaultValue);
         break;
--- a/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
+++ b/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
@@ -47,16 +47,17 @@ var DEFAULT_PREFERENCES =
   "disableStream": false,
   "disableAutoFetch": false,
   "disableFontFace": false,
   "disableTextLayer": false,
   "useOnlyCssZoom": false,
   "externalLinkTarget": 0
 }
 
+
 var PdfjsChromeUtils = {
   // For security purposes when running remote, we restrict preferences
   // content can access.
   _allowedPrefNames: Object.keys(DEFAULT_PREFERENCES),
   _ppmm: null,
   _mmg: null,
 
   /*
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf', ['exports
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdf = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.5.385';
-var pdfjsBuild = 'a9c37c2';
+var pdfjsVersion = '1.5.413';
+var pdfjsBuild = '6bb95e3';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -2835,17 +2835,18 @@ var renderTextLayer = (function renderTe
   var MAX_TEXT_DIVS_TO_RENDER = 100000;
 
   var NonWhitespaceRegexp = /\S/;
 
   function isAllWhitespace(str) {
     return !NonWhitespaceRegexp.test(str);
   }
 
-  function appendText(textDivs, viewport, geom, styles) {
+  function appendText(textDivs, viewport, geom, styles, bounds,
+                      enhanceTextSelection) {
     var style = styles[geom.fontName];
     var textDiv = document.createElement('div');
     textDivs.push(textDiv);
     if (isAllWhitespace(geom.str)) {
       textDiv.dataset.isWhitespace = true;
       return;
     }
     var tx = Util.transform(viewport.transform, geom.transform);
@@ -2891,30 +2892,59 @@ var renderTextLayer = (function renderTe
     // lots of such divs a lot faster.
     if (geom.str.length > 1) {
       if (style.vertical) {
         textDiv.dataset.canvasWidth = geom.height * viewport.scale;
       } else {
         textDiv.dataset.canvasWidth = geom.width * viewport.scale;
       }
     }
+    if (enhanceTextSelection) {
+      var angleCos = 1, angleSin = 0;
+      if (angle !== 0) {
+        angleCos = Math.cos(angle);
+        angleSin = Math.sin(angle);
+      }
+      var divWidth = (style.vertical ? geom.height : geom.width) *
+                     viewport.scale;
+      var divHeight = fontHeight;
+
+      var m, b;
+      if (angle !== 0) {
+        m = [angleCos, angleSin, -angleSin, angleCos, left, top];
+        b = Util.getAxialAlignedBoundingBox([0, 0, divWidth, divHeight], m);
+      } else {
+        b = [left, top, left + divWidth, top + divHeight];
+      }
+
+      bounds.push({
+        left: b[0],
+        top: b[1],
+        right: b[2],
+        bottom: b[3],
+        div: textDiv,
+        size: [divWidth, divHeight],
+        m: m
+      });
+    }
   }
 
   function render(task) {
     if (task._canceled) {
       return;
     }
     var textLayerFrag = task._container;
     var textDivs = task._textDivs;
     var capability = task._capability;
     var textDivsLength = textDivs.length;
 
     // No point in rendering many divs as it would make the browser
     // unusable even after the divs are rendered.
     if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
+      task._renderingDone = true;
       capability.resolve();
       return;
     }
 
     var canvas = document.createElement('canvas');
     canvas.mozOpaque = true;
     var ctx = canvas.getContext('2d', {alpha: false});
 
@@ -2932,54 +2962,326 @@ var renderTextLayer = (function renderTe
       // Only build font string and set to context if different from last.
       if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
         ctx.font = fontSize + ' ' + fontFamily;
         lastFontSize = fontSize;
         lastFontFamily = fontFamily;
       }
 
       var width = ctx.measureText(textDiv.textContent).width;
+      textDiv.dataset.originalWidth = width;
       textLayerFrag.appendChild(textDiv);
-      var transform;
-      if (textDiv.dataset.canvasWidth !== undefined && width > 0) {
-        // Dataset values come of type string.
-        var textScale = textDiv.dataset.canvasWidth / width;
-        transform = 'scaleX(' + textScale + ')';
-      } else {
-        transform = '';
-      }
-      var rotation = textDiv.dataset.angle;
-      if (rotation) {
-        transform = 'rotate(' + rotation + 'deg) ' + transform;
-      }
-      if (transform) {
-        CustomStyle.setProp('transform' , textDiv, transform);
-      }
-    }
+       var transform;
+       if (textDiv.dataset.canvasWidth !== undefined && width > 0) {
+        //  Dataset values come of type string.
+         var textScale = textDiv.dataset.canvasWidth / width;
+         transform = 'scaleX(' + textScale + ')';
+       } else {
+         transform = '';
+       }
+       var rotation = textDiv.dataset.angle;
+       if (rotation) {
+         transform = 'rotate(' + rotation + 'deg) ' + transform;
+       }
+       if (transform) {
+         textDiv.dataset.originalTransform = transform;
+         CustomStyle.setProp('transform' , textDiv, transform);
+       }
+    }
+    task._renderingDone = true;
     capability.resolve();
   }
 
+  function expand(bounds, viewport) {
+    var expanded = expandBounds(viewport.width, viewport.height, bounds);
+    for (var i = 0; i < expanded.length; i++) {
+      var div = bounds[i].div;
+      if (!div.dataset.angle) {
+        div.dataset.paddingLeft = bounds[i].left - expanded[i].left;
+        div.dataset.paddingTop = bounds[i].top - expanded[i].top;
+        div.dataset.paddingRight = expanded[i].right - bounds[i].right;
+        div.dataset.paddingBottom = expanded[i].bottom - bounds[i].bottom;
+        continue;
+      }
+      // Box is rotated -- trying to find padding so rotated div will not
+      // exceed its expanded bounds.
+      var e = expanded[i], b = bounds[i];
+      var m = b.m, c = m[0], s = m[1];
+      // Finding intersections with expanded box.
+      var points = [[0, 0], [0, b.size[1]], [b.size[0], 0], b.size];
+      var ts = new Float64Array(64);
+      points.forEach(function (p, i) {
+        var t = Util.applyTransform(p, m);
+        ts[i + 0] = c && (e.left - t[0]) / c;
+        ts[i + 4] = s && (e.top - t[1]) / s;
+        ts[i + 8] = c && (e.right - t[0]) / c;
+        ts[i + 12] = s && (e.bottom - t[1]) / s;
+
+        ts[i + 16] = s && (e.left - t[0]) / -s;
+        ts[i + 20] = c && (e.top - t[1]) / c;
+        ts[i + 24] = s && (e.right - t[0]) / -s;
+        ts[i + 28] = c && (e.bottom - t[1]) / c;
+
+        ts[i + 32] = c && (e.left - t[0]) / -c;
+        ts[i + 36] = s && (e.top - t[1]) / -s;
+        ts[i + 40] = c && (e.right - t[0]) / -c;
+        ts[i + 44] = s && (e.bottom - t[1]) / -s;
+
+        ts[i + 48] = s && (e.left - t[0]) / s;
+        ts[i + 52] = c && (e.top - t[1]) / -c;
+        ts[i + 56] = s && (e.right - t[0]) / s;
+        ts[i + 60] = c && (e.bottom - t[1]) / -c;
+      });
+      var findPositiveMin = function (ts, offset, count) {
+        var result = 0;
+          for (var i = 0; i < count; i++) {
+            var t = ts[offset++];
+            if (t > 0) {
+              result = result ? Math.min(t, result) : t;
+            }
+          }
+          return result;
+      };
+      // Not based on math, but to simplify calculations, using cos and sin
+      // absolute values to not exceed the box (it can but insignificantly).
+      var boxScale = 1 + Math.min(Math.abs(c), Math.abs(s));
+      div.dataset.paddingLeft = findPositiveMin(ts, 32, 16) / boxScale;
+      div.dataset.paddingTop = findPositiveMin(ts, 48, 16) / boxScale;
+      div.dataset.paddingRight = findPositiveMin(ts, 0, 16) / boxScale;
+      div.dataset.paddingBottom = findPositiveMin(ts, 16, 16) / boxScale;
+    }
+  }
+
+  function expandBounds(width, height, boxes) {
+    var bounds = boxes.map(function (box, i) {
+      return {
+        x1: box.left,
+        y1: box.top,
+        x2: box.right,
+        y2: box.bottom,
+        index: i,
+        x1New: undefined,
+        x2New: undefined
+      };
+    });
+    expandBoundsLTR(width, bounds);
+    var expanded = new Array(boxes.length);
+    bounds.forEach(function (b) {
+      var i = b.index;
+      expanded[i] = {
+        left: b.x1New,
+        top: 0,
+        right: b.x2New,
+        bottom: 0
+      };
+    });
+
+    // Rotating on 90 degrees and extending extended boxes. Reusing the bounds
+    // array and objects.
+    boxes.map(function (box, i) {
+      var e = expanded[i], b = bounds[i];
+      b.x1 = box.top;
+      b.y1 = width - e.right;
+      b.x2 = box.bottom;
+      b.y2 = width - e.left;
+      b.index = i;
+      b.x1New = undefined;
+      b.x2New = undefined;
+    });
+    expandBoundsLTR(height, bounds);
+
+    bounds.forEach(function (b) {
+      var i = b.index;
+      expanded[i].top = b.x1New;
+      expanded[i].bottom = b.x2New;
+    });
+    return expanded;
+  }
+
+  function expandBoundsLTR(width, bounds) {
+    // Sorting by x1 coordinate and walk by the bounds in the same order.
+    bounds.sort(function (a, b) { return a.x1 - b.x1 || a.index - b.index; });
+
+    // First we see on the horizon is a fake boundary.
+    var fakeBoundary = {
+      x1: -Infinity,
+      y1: -Infinity,
+      x2: 0,
+      y2: Infinity,
+      index: -1,
+      x1New: 0,
+      x2New: 0
+    };
+    var horizon = [{
+      start: -Infinity,
+      end: Infinity,
+      boundary: fakeBoundary
+    }];
+
+    bounds.forEach(function (boundary) {
+      // Searching for the affected part of horizon.
+      // TODO red-black tree or simple binary search
+      var i = 0;
+      while (i < horizon.length && horizon[i].end <= boundary.y1) {
+        i++;
+      }
+      var j = horizon.length - 1;
+      while(j >= 0 && horizon[j].start >= boundary.y2) {
+        j--;
+      }
+
+      var horizonPart, affectedBoundary;
+      var q, k, maxXNew = -Infinity;
+      for (q = i; q <= j; q++) {
+        horizonPart = horizon[q];
+        affectedBoundary = horizonPart.boundary;
+        var xNew;
+        if (affectedBoundary.x2 > boundary.x1) {
+          // In the middle of the previous element, new x shall be at the
+          // boundary start. Extending if further if the affected bondary
+          // placed on top of the current one.
+          xNew = affectedBoundary.index > boundary.index ?
+            affectedBoundary.x1New : boundary.x1;
+        } else if (affectedBoundary.x2New === undefined) {
+          // We have some space in between, new x in middle will be a fair
+          // choice.
+          xNew = (affectedBoundary.x2 + boundary.x1) / 2;
+        } else {
+          // Affected boundary has x2new set, using it as new x.
+          xNew = affectedBoundary.x2New;
+        }
+        if (xNew > maxXNew) {
+          maxXNew = xNew;
+        }
+      }
+
+      // Set new x1 for current boundary.
+      boundary.x1New = maxXNew;
+
+      // Adjusts new x2 for the affected boundaries.
+      for (q = i; q <= j; q++) {
+        horizonPart = horizon[q];
+        affectedBoundary = horizonPart.boundary;
+        if (affectedBoundary.x2New === undefined) {
+          // Was not set yet, choosing new x if possible.
+          if (affectedBoundary.x2 > boundary.x1) {
+            // Current and affected boundaries intersect. If affected boundary
+            // is placed on top of the current, shrinking the affected.
+            if (affectedBoundary.index > boundary.index) {
+              affectedBoundary.x2New = affectedBoundary.x2;
+            }
+          } else {
+            affectedBoundary.x2New = maxXNew;
+          }
+        } else if (affectedBoundary.x2New > maxXNew) {
+          // Affected boundary is touching new x, pushing it back.
+          affectedBoundary.x2New = Math.max(maxXNew, affectedBoundary.x2);
+        }
+      }
+
+      // Fixing the horizon.
+      var changedHorizon = [], lastBoundary = null;
+      for (q = i; q <= j; q++) {
+        horizonPart = horizon[q];
+        affectedBoundary = horizonPart.boundary;
+        // Checking which boundary will be visible.
+        var useBoundary = affectedBoundary.x2 > boundary.x2 ?
+          affectedBoundary : boundary;
+        if (lastBoundary === useBoundary) {
+          // Merging with previous.
+          changedHorizon[changedHorizon.length - 1].end = horizonPart.end;
+        } else {
+          changedHorizon.push({
+            start: horizonPart.start,
+            end: horizonPart.end,
+            boundary: useBoundary
+          });
+          lastBoundary = useBoundary;
+        }
+      }
+      if (horizon[i].start < boundary.y1) {
+        changedHorizon[0].start = boundary.y1;
+        changedHorizon.unshift({
+          start: horizon[i].start,
+          end: boundary.y1,
+          boundary: horizon[i].boundary
+        });
+      }
+      if (boundary.y2 < horizon[j].end) {
+        changedHorizon[changedHorizon.length - 1].end = boundary.y2;
+        changedHorizon.push({
+          start: boundary.y2,
+          end: horizon[j].end,
+          boundary: horizon[j].boundary
+        });
+      }
+
+      // Set x2 new of boundary that is no longer visible (see overlapping case
+      // above).
+      // TODO more efficient, e.g. via reference counting.
+      for (q = i; q <= j; q++) {
+        horizonPart = horizon[q];
+        affectedBoundary = horizonPart.boundary;
+        if (affectedBoundary.x2New !== undefined) {
+          continue;
+        }
+        var used = false;
+        for (k = i - 1; !used && k >= 0 &&
+        horizon[k].start >= affectedBoundary.y1; k--) {
+          used = horizon[k].boundary === affectedBoundary;
+        }
+        for (k = j + 1; !used && k < horizon.length &&
+        horizon[k].end <= affectedBoundary.y2; k++) {
+          used = horizon[k].boundary === affectedBoundary;
+        }
+        for (k = 0; !used && k < changedHorizon.length; k++) {
+          used = changedHorizon[k].boundary === affectedBoundary;
+        }
+        if (!used) {
+          affectedBoundary.x2New = maxXNew;
+        }
+      }
+
+      Array.prototype.splice.apply(horizon,
+        [i, j - i + 1].concat(changedHorizon));
+    });
+
+    // Set new x2 for all unset boundaries.
+    horizon.forEach(function (horizonPart) {
+      var affectedBoundary = horizonPart.boundary;
+      if (affectedBoundary.x2New === undefined) {
+        affectedBoundary.x2New = Math.max(width, affectedBoundary.x2);
+      }
+    });
+  }
+
   /**
    * Text layer rendering task.
    *
    * @param {TextContent} textContent
    * @param {HTMLElement} container
    * @param {PageViewport} viewport
    * @param {Array} textDivs
+   * @param {boolean} enhanceTextSelection
    * @private
    */
-  function TextLayerRenderTask(textContent, container, viewport, textDivs) {
+  function TextLayerRenderTask(textContent, container, viewport, textDivs,
+                               enhanceTextSelection) {
     this._textContent = textContent;
     this._container = container;
     this._viewport = viewport;
     textDivs = textDivs || [];
     this._textDivs = textDivs;
+    this._renderingDone = false;
     this._canceled = false;
     this._capability = createPromiseCapability();
     this._renderTimer = null;
+    this._bounds = [];
+    this._enhanceTextSelection = !!enhanceTextSelection;
+    this._expanded = false;
   }
   TextLayerRenderTask.prototype = {
     get promise() {
       return this._capability.promise;
     },
 
     cancel: function TextLayer_cancel() {
       this._canceled = true;
@@ -2990,44 +3292,104 @@ var renderTextLayer = (function renderTe
       this._capability.reject('canceled');
     },
 
     _render: function TextLayer_render(timeout) {
       var textItems = this._textContent.items;
       var styles = this._textContent.styles;
       var textDivs = this._textDivs;
       var viewport = this._viewport;
+      var enhanceTextSelection = this._enhanceTextSelection;
+
       for (var i = 0, len = textItems.length; i < len; i++) {
-        appendText(textDivs, viewport, textItems[i], styles);
+        appendText(textDivs, viewport, textItems[i], styles, this._bounds,
+                   enhanceTextSelection);
       }
 
       if (!timeout) { // Render right away
         render(this);
       } else { // Schedule
         var self = this;
         this._renderTimer = setTimeout(function() {
           render(self);
           self._renderTimer = null;
         }, timeout);
       }
-    }
+    },
+
+    expandTextDivs: function TextLayer_expandTextDivs(expandDivs) {
+      if (!this._enhanceTextSelection || !this._renderingDone) {
+        return;
+      }
+      if (!this._expanded) {
+        expand(this._bounds, this._viewport);
+        this._expanded = true;
+        this._bounds.length = 0;
+      }
+      if (expandDivs) {
+        for (var i = 0, ii = this._textDivs.length; i < ii; i++) {
+          var div = this._textDivs[i];
+          var transform;
+          var width = div.dataset.originalWidth;
+          if (div.dataset.canvasWidth !== undefined && width > 0) {
+            // Dataset values come of type string.
+            var textScale = div.dataset.canvasWidth / width;
+            transform = 'scaleX(' + textScale + ')';
+          } else {
+            transform = '';
+          }
+          var rotation = div.dataset.angle;
+          if (rotation) {
+            transform = 'rotate(' + rotation + 'deg) ' + transform;
+          }
+          if (div.dataset.paddingLeft) {
+            div.style.paddingLeft =
+              (div.dataset.paddingLeft / textScale) + 'px';
+            transform += ' translateX(' +
+              (-div.dataset.paddingLeft / textScale) + 'px)';
+          }
+          if (div.dataset.paddingTop) {
+            div.style.paddingTop = div.dataset.paddingTop + 'px';
+            transform += ' translateY(' + (-div.dataset.paddingTop) + 'px)';
+          }
+          if (div.dataset.paddingRight) {
+            div.style.paddingRight =
+            div.dataset.paddingRight / textScale + 'px';
+          }
+          if (div.dataset.paddingBottom) {
+            div.style.paddingBottom = div.dataset.paddingBottom + 'px';
+          }
+          if (transform) {
+            CustomStyle.setProp('transform' , div, transform);
+          }
+        }
+      } else {
+        for (i = 0, ii = this._textDivs.length; i < ii; i++) {
+          div = this._textDivs[i];
+          div.style.padding = 0;
+          transform = div.dataset.originalTransform || '';
+          CustomStyle.setProp('transform', div, transform);
+        }
+      }
+    },
   };
 
 
   /**
    * Starts rendering of the text layer.
    *
    * @param {TextLayerRenderParameters} renderParameters
    * @returns {TextLayerRenderTask}
    */
   function renderTextLayer(renderParameters) {
     var task = new TextLayerRenderTask(renderParameters.textContent,
                                        renderParameters.container,
                                        renderParameters.viewport,
-                                       renderParameters.textDivs);
+                                       renderParameters.textDivs,
+                                       renderParameters.enhanceTextSelection);
     task._render(renderParameters.timeout);
     return task;
   }
 
   return renderTextLayer;
 })();
 
 exports.renderTextLayer = renderTextLayer;
@@ -6168,17 +6530,23 @@ var CanvasGraphics = (function CanvasGra
           ctx.clip();
         }
         this.pendingClip = null;
       }
       ctx.beginPath();
     },
     getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) {
       if (this.cachedGetSinglePixelWidth === null) {
+        // NOTE: The `save` and `restore` commands used below is a workaround
+        // that is necessary in order to prevent `mozCurrentTransformInverse`
+        // from intermittently returning incorrect values in Firefox, see:
+        // https://github.com/mozilla/pdf.js/issues/7188.
+        this.ctx.save();
         var inverse = this.ctx.mozCurrentTransformInverse;
+        this.ctx.restore();
         // max of the current horizontal and vertical scale
         this.cachedGetSinglePixelWidth = Math.sqrt(Math.max(
           (inverse[0] * inverse[0] + inverse[1] * inverse[1]),
           (inverse[2] * inverse[2] + inverse[3] * inverse[3])));
       }
       return this.cachedGetSinglePixelWidth;
     },
     getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) {
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf.worker', ['
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdfWorker = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.5.385';
-var pdfjsBuild = 'a9c37c2';
+var pdfjsVersion = '1.5.413';
+var pdfjsBuild = '6bb95e3';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -27307,16 +27307,17 @@ var ProblematicCharRanges = new Int32Arr
   0x25CC, 0x25CD,
   0x3000, 0x3001,
   // Chars that is used in complex-script shaping.
   0xAA60, 0xAA80,
   // Specials Unicode block.
   0xFFF0, 0x10000
 ]);
 
+
 /**
  * 'Font' is the class the outside world should use, it encapsulate all the font
  * decoding logics whatever type it is (assuming the font type is supported).
  *
  * For example to read a Type1 font and to attach it to the document:
  *   var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
  *   type1Font.bind();
  */
@@ -37263,19 +37264,27 @@ var PartialEvaluator = (function Partial
           }
           textState = stateManager.state;
           var fn = operation.fn;
           args = operation.args;
           var advance, diff;
 
           switch (fn | 0) {
             case OPS.setFont:
+              // Optimization to ignore multiple identical Tf commands.
+              var fontNameArg = args[0].name, fontSizeArg = args[1];
+              if (textState.font && fontNameArg === textState.fontName &&
+                  fontSizeArg === textState.fontSize) {
+                break;
+              }
+
               flushTextContentItem();
-              textState.fontSize = args[1];
-              next(handleSetFont(args[0].name, null));
+              textState.fontName = fontNameArg;
+              textState.fontSize = fontSizeArg;
+              next(handleSetFont(fontNameArg, null));
               return;
             case OPS.setTextRise:
               flushTextContentItem();
               textState.textRise = args[0];
               break;
             case OPS.setHScale:
               flushTextContentItem();
               textState.textHScale = args[0] / 100;
@@ -37482,16 +37491,17 @@ var PartialEvaluator = (function Partial
                 break;
               }
               var gState = extGState.get(dictName.name);
               if (!isDict(gState)) {
                 break;
               }
               var gStateFont = gState.get('Font');
               if (gStateFont) {
+                textState.fontName = null;
                 textState.fontSize = gStateFont[1];
                 next(handleSetFont(null, gStateFont[0]));
                 return;
               }
               break;
           } // switch
         } // while
         if (stop) {
@@ -38401,16 +38411,17 @@ var StateManager = (function StateManage
     }
   };
   return StateManager;
 })();
 
 var TextState = (function TextStateClosure() {
   function TextState() {
     this.ctm = new Float32Array(IDENTITY_MATRIX);
+    this.fontName = null;
     this.fontSize = 0;
     this.font = null;
     this.fontMatrix = FONT_IDENTITY_MATRIX;
     this.textMatrix = IDENTITY_MATRIX.slice();
     this.textLineMatrix = IDENTITY_MATRIX.slice();
     this.charSpacing = 0;
     this.wordSpacing = 0;
     this.leading = 0;
@@ -39167,16 +39178,17 @@ var isInt = sharedUtil.isInt;
 var isValidUrl = sharedUtil.isValidUrl;
 var stringToBytes = sharedUtil.stringToBytes;
 var stringToPDFString = sharedUtil.stringToPDFString;
 var stringToUTF8String = sharedUtil.stringToUTF8String;
 var warn = sharedUtil.warn;
 var Dict = corePrimitives.Dict;
 var isDict = corePrimitives.isDict;
 var isName = corePrimitives.isName;
+var isRef = corePrimitives.isRef;
 var Stream = coreStream.Stream;
 var ColorSpace = coreColorSpace.ColorSpace;
 var ObjectLoader = coreObj.ObjectLoader;
 var FileSpec = coreObj.FileSpec;
 var OperatorList = coreEvaluator.OperatorList;
 
 /**
  * @class
@@ -39184,32 +39196,36 @@ var OperatorList = coreEvaluator.Operato
  */
 function AnnotationFactory() {}
 AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
   /**
    * @param {XRef} xref
    * @param {Object} ref
    * @returns {Annotation}
    */
-  create: function AnnotationFactory_create(xref, ref) {
+  create: function AnnotationFactory_create(xref, ref,
+                                            uniquePrefix, idCounters) {
     var dict = xref.fetchIfRef(ref);
     if (!isDict(dict)) {
       return;
     }
+    var id = isRef(ref) ? ref.toString() :
+                          'annot_' + (uniquePrefix || '') + (++idCounters.obj);
 
     // Determine the annotation's subtype.
     var subtype = dict.get('Subtype');
     subtype = isName(subtype) ? subtype.name : null;
 
     // Return the right annotation object based on the subtype and field type.
     var parameters = {
       xref: xref,
       dict: dict,
-      ref: ref,
+      ref: isRef(ref) ? ref : null,
       subtype: subtype,
+      id: id,
     };
 
     switch (subtype) {
       case 'Link':
         return new LinkAnnotation(parameters);
 
       case 'Text':
         return new TextAnnotation(parameters);
@@ -39303,17 +39319,17 @@ var Annotation = (function AnnotationClo
     this.setFlags(dict.get('F'));
     this.setRectangle(dict.getArray('Rect'));
     this.setColor(dict.getArray('C'));
     this.setBorderStyle(dict);
     this.appearance = getDefaultAppearance(dict);
 
     // Expose public properties using a data object.
     this.data = {};
-    this.data.id = params.ref.toString();
+    this.data.id = params.id;
     this.data.subtype = params.subtype;
     this.data.annotationFlags = this.flags;
     this.data.rect = this.rectangle;
     this.data.color = this.color;
     this.data.borderStyle = this.borderStyle;
     this.data.hasAppearance = !!this.appearance;
   }
 
@@ -40122,16 +40138,17 @@ var Page = (function PageClosure() {
 
   function Page(pdfManager, xref, pageIndex, pageDict, ref, fontCache) {
     this.pdfManager = pdfManager;
     this.pageIndex = pageIndex;
     this.pageDict = pageDict;
     this.xref = xref;
     this.ref = ref;
     this.fontCache = fontCache;
+    this.uniquePrefix = 'p' + this.pageIndex + '_';
     this.idCounters = {
       obj: 0
     };
     this.evaluatorOptions = pdfManager.evaluatorOptions;
     this.resourcesPromise = null;
   }
 
   Page.prototype = {
@@ -40269,17 +40286,17 @@ var Page = (function PageClosure() {
         'XObject',
         'Font'
         // ProcSet
         // Properties
       ]);
 
       var partialEvaluator = new PartialEvaluator(pdfManager, this.xref,
                                                   handler, this.pageIndex,
-                                                  'p' + this.pageIndex + '_',
+                                                  this.uniquePrefix,
                                                   this.idCounters,
                                                   this.fontCache,
                                                   this.evaluatorOptions);
 
       var dataPromises = Promise.all([contentStreamPromise, resourcesPromise]);
       var pageListPromise = dataPromises.then(function(data) {
         var contentStream = data[0];
         var opList = new OperatorList(intent, handler, self.pageIndex);
@@ -40336,17 +40353,17 @@ var Page = (function PageClosure() {
       ]);
 
       var dataPromises = Promise.all([contentStreamPromise,
                                       resourcesPromise]);
       return dataPromises.then(function(data) {
         var contentStream = data[0];
         var partialEvaluator = new PartialEvaluator(pdfManager, self.xref,
                                                     handler, self.pageIndex,
-                                                    'p' + self.pageIndex + '_',
+                                                    self.uniquePrefix,
                                                     self.idCounters,
                                                     self.fontCache,
                                                     self.evaluatorOptions);
 
         return partialEvaluator.getTextContent(contentStream,
                                                task,
                                                self.resources,
                                                /* stateManager = */ null,
@@ -40371,17 +40388,19 @@ var Page = (function PageClosure() {
     },
 
     get annotations() {
       var annotations = [];
       var annotationRefs = this.getInheritedPageProp('Annots') || [];
       var annotationFactory = new AnnotationFactory();
       for (var i = 0, n = annotationRefs.length; i < n; ++i) {
         var annotationRef = annotationRefs[i];
-        var annotation = annotationFactory.create(this.xref, annotationRef);
+        var annotation = annotationFactory.create(this.xref, annotationRef,
+                                                  this.uniquePrefix,
+                                                  this.idCounters);
         if (annotation) {
           annotations.push(annotation);
         }
       }
       return shadow(this, 'annotations', annotations);
     }
   };
 
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -948,16 +948,17 @@ exports.PDFRenderingQueue = PDFRendering
   "disableRange": false,
   "disableStream": false,
   "disableAutoFetch": false,
   "disableFontFace": false,
   "disableTextLayer": false,
   "useOnlyCssZoom": false,
   "externalLinkTarget": 0
 }
+
   );
 
 function cloneObj(obj) {
   var result = {};
   for (var i in obj) {
     if (Object.prototype.hasOwnProperty.call(obj, i)) {
       result[i] = obj[i];
     }
@@ -3236,17 +3237,17 @@ var PDFFindController = (function PDFFin
       }.bind(this));
     },
 
     updatePage: function PDFFindController_updatePage(index) {
       if (this.selected.pageIdx === index) {
         // If the page is selected, scroll the page into view, which triggers
         // rendering the page, which adds the textLayer. Once the textLayer is
         // build, it will scroll onto the selected match.
-        this.pdfViewer.scrollPageIntoView(index + 1);
+        this.pdfViewer.currentPageNumber = index + 1;
       }
 
       var page = this.pdfViewer.getPageView(index);
       if (page.textLayer) {
         page.textLayer.updateMatches();
       }
     },
 
@@ -4671,17 +4672,20 @@ var PDFLinkService = (function PDFLinkSe
           self._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
           (destRef + 1);
         if (pageNumber) {
           if (pageNumber > self.pagesCount) {
             console.error('PDFLinkService_navigateTo: ' +
                           'Trying to navigate to a non-existent page.');
             return;
           }
-          self.pdfViewer.scrollPageIntoView(pageNumber, dest);
+          self.pdfViewer.scrollPageIntoView({
+            pageNumber: pageNumber,
+            destArray: dest,
+          });
 
           if (self.pdfHistory) {
             // Update the browsing history.
             self.pdfHistory.push({
               dest: dest,
               hash: destString,
               page: pageNumber
             });
@@ -4797,17 +4801,21 @@ var PDFLinkService = (function PDFLinkSe
               }
             } else {
               console.error('PDFLinkService_setHash: \'' + zoomArg +
                             '\' is not a valid zoom value.');
             }
           }
         }
         if (dest) {
-          this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
+          this.pdfViewer.scrollPageIntoView({
+            pageNumber: pageNumber || this.page,
+            destArray: dest,
+            allowNegativeOffset: true,
+          });
         } else if (pageNumber) {
           this.page = pageNumber; // simple page
         }
         if ('pagemode' in params) {
           this.eventBus.dispatch('pagemode', {
             source: this,
             mode: params.pagemode
           });
@@ -5023,16 +5031,18 @@ var TEXT_LAYER_RENDER_DELAY = 200; // ms
  * @property {HTMLDivElement} container - The viewer element.
  * @property {EventBus} eventBus - The application event bus.
  * @property {number} id - The page unique ID (normally its number).
  * @property {number} scale - The page scale display.
  * @property {PageViewport} defaultViewport - The page viewport.
  * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
  * @property {IPDFTextLayerFactory} textLayerFactory
  * @property {IPDFAnnotationLayerFactory} annotationLayerFactory
+ * @property {boolean} enhanceTextSelection - Turns on the text selection
+ * enhancement. The default is `false`.
  */
 
 /**
  * @class
  * @implements {IRenderableView}
  */
 var PDFPageView = (function PDFPageViewClosure() {
   /**
@@ -5042,25 +5052,27 @@ var PDFPageView = (function PDFPageViewC
   function PDFPageView(options) {
     var container = options.container;
     var id = options.id;
     var scale = options.scale;
     var defaultViewport = options.defaultViewport;
     var renderingQueue = options.renderingQueue;
     var textLayerFactory = options.textLayerFactory;
     var annotationLayerFactory = options.annotationLayerFactory;
+    var enhanceTextSelection = options.enhanceTextSelection || false;
 
     this.id = id;
     this.renderingId = 'page' + id;
 
     this.rotation = 0;
     this.scale = scale || DEFAULT_SCALE;
     this.viewport = defaultViewport;
     this.pdfPageRotate = defaultViewport.rotation;
     this.hasRestrictedScaling = false;
+    this.enhanceTextSelection = enhanceTextSelection;
 
     this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
     this.renderingQueue = renderingQueue;
     this.textLayerFactory = textLayerFactory;
     this.annotationLayerFactory = annotationLayerFactory;
 
     this.renderingState = RenderingStates.INITIAL;
     this.resume = null;
@@ -5366,19 +5378,19 @@ var PDFPageView = (function PDFPageViewC
         textLayerDiv.style.height = canvasWrapper.style.height;
         if (this.annotationLayer && this.annotationLayer.div) {
           // annotationLayer needs to stay on top
           div.insertBefore(textLayerDiv, this.annotationLayer.div);
         } else {
           div.appendChild(textLayerDiv);
         }
 
-        textLayer = this.textLayerFactory.createTextLayerBuilder(textLayerDiv,
-                                                                 this.id - 1,
-                                                                 this.viewport);
+        textLayer = this.textLayerFactory.
+          createTextLayerBuilder(textLayerDiv, this.id - 1, this.viewport,
+                                 this.enhanceTextSelection);
       }
       this.textLayer = textLayer;
 
       var resolveRenderPromise, rejectRenderPromise;
       var promise = new Promise(function (resolve, reject) {
         resolveRenderPromise = resolve;
         rejectRenderPromise = reject;
       });
@@ -5775,16 +5787,18 @@ exports.PDFThumbnailViewer = PDFThumbnai
 
 /**
  * @typedef {Object} TextLayerBuilderOptions
  * @property {HTMLDivElement} textLayerDiv - The text layer container.
  * @property {EventBus} eventBus - The application event bus.
  * @property {number} pageIndex - The page index.
  * @property {PageViewport} viewport - The viewport of the text layer.
  * @property {PDFFindController} findController
+ * @property {boolean} enhanceTextSelection - Option to turn on improved
+ * text selection.
  */
 
 /**
  * TextLayerBuilder provides text-selection functionality for the PDF.
  * It does this by creating overlay divs over the PDF text. These divs
  * contain text that matches the PDF text they are overlaying. This object
  * also provides a way to highlight text that is being searched for.
  * @class
@@ -5797,26 +5811,29 @@ var TextLayerBuilder = (function TextLay
     this.divContentDone = false;
     this.pageIdx = options.pageIndex;
     this.pageNumber = this.pageIdx + 1;
     this.matches = [];
     this.viewport = options.viewport;
     this.textDivs = [];
     this.findController = options.findController || null;
     this.textLayerRenderTask = null;
+    this.enhanceTextSelection = options.enhanceTextSelection;
     this._bindMouse();
   }
 
   TextLayerBuilder.prototype = {
     _finishRendering: function TextLayerBuilder_finishRendering() {
       this.renderingDone = true;
 
-      var endOfContent = document.createElement('div');
-      endOfContent.className = 'endOfContent';
-      this.textLayerDiv.appendChild(endOfContent);
+      if (!this.enhanceTextSelection) {
+        var endOfContent = document.createElement('div');
+        endOfContent.className = 'endOfContent';
+        this.textLayerDiv.appendChild(endOfContent);
+      }
 
       this.eventBus.dispatch('textlayerrendered', {
         source: this,
         pageNumber: this.pageNumber
       });
     },
 
     /**
@@ -5836,17 +5853,18 @@ var TextLayerBuilder = (function TextLay
 
       this.textDivs = [];
       var textLayerFrag = document.createDocumentFragment();
       this.textLayerRenderTask = pdfjsLib.renderTextLayer({
         textContent: this.textContent,
         container: textLayerFrag,
         viewport: this.viewport,
         textDivs: this.textDivs,
-        timeout: timeout
+        timeout: timeout,
+        enhanceTextSelection: this.enhanceTextSelection,
       });
       this.textLayerRenderTask.promise.then(function () {
         this.textLayerDiv.appendChild(textLayerFrag);
         this._finishRendering();
         this.updateMatches();
       }.bind(this), function (reason) {
         // canceled or failed to render text layer -- skipping errors
       });
@@ -6054,24 +6072,33 @@ var TextLayerBuilder = (function TextLay
 
     /**
      * Fixes text selection: adds additional div where mouse was clicked.
      * This reduces flickering of the content if mouse slowly dragged down/up.
      * @private
      */
     _bindMouse: function TextLayerBuilder_bindMouse() {
       var div = this.textLayerDiv;
+      var self = this;
       div.addEventListener('mousedown', function (e) {
+        if (self.enhanceTextSelection && self.textLayerRenderTask) {
+          self.textLayerRenderTask.expandTextDivs(true);
+          return;
+        }
         var end = div.querySelector('.endOfContent');
         if (!end) {
           return;
         }
         end.classList.add('active');
       });
       div.addEventListener('mouseup', function (e) {
+        if (self.enhanceTextSelection && self.textLayerRenderTask) {
+          self.textLayerRenderTask.expandTextDivs(false);
+          return;
+        }
         var end = div.querySelector('.endOfContent');
         if (!end) {
           return;
         }
         end.classList.remove('active');
       });
     },
   };
@@ -6083,23 +6110,26 @@ var TextLayerBuilder = (function TextLay
  * @implements IPDFTextLayerFactory
  */
 function DefaultTextLayerFactory() {}
 DefaultTextLayerFactory.prototype = {
   /**
    * @param {HTMLDivElement} textLayerDiv
    * @param {number} pageIndex
    * @param {PageViewport} viewport
+   * @param {boolean} enhanceTextSelection
    * @returns {TextLayerBuilder}
    */
-  createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
+  createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport,
+                                    enhanceTextSelection) {
     return new TextLayerBuilder({
       textLayerDiv: textLayerDiv,
       pageIndex: pageIndex,
-      viewport: viewport
+      viewport: viewport,
+      enhanceTextSelection: enhanceTextSelection
     });
   }
 };
 
 exports.TextLayerBuilder = TextLayerBuilder;
 exports.DefaultTextLayerFactory = DefaultTextLayerFactory;
 }));
 
@@ -6267,16 +6297,18 @@ var DEFAULT_CACHE_SIZE = 10;
  * @property {EventBus} eventBus - The application event bus.
  * @property {IPDFLinkService} linkService - The navigation/linking service.
  * @property {DownloadManager} downloadManager - (optional) The download
  *   manager component.
  * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
  *   queue object.
  * @property {boolean} removePageBorders - (optional) Removes the border shadow
  *   around the pages. The default is false.
+ * @property {boolean} enhanceTextSelection - (optional) Enables the improved
+ *   text selection behaviour. The default is `false`.
  */
 
 /**
  * Simple viewer control to display PDF content/pages.
  * @class
  * @implements {IRenderableView}
  */
 var PDFViewer = (function pdfViewer() {
@@ -6318,16 +6350,17 @@ var PDFViewer = (function pdfViewer() {
    */
   function PDFViewer(options) {
     this.container = options.container;
     this.viewer = options.viewer || options.container.firstElementChild;
     this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
     this.linkService = options.linkService || new SimpleLinkService();
     this.downloadManager = options.downloadManager || null;
     this.removePageBorders = options.removePageBorders || false;
+    this.enhanceTextSelection = options.enhanceTextSelection || false;
 
     this.defaultRenderingQueue = !options.renderingQueue;
     if (this.defaultRenderingQueue) {
       // Custom rendering queue is not specified, using default one
       this.renderingQueue = new PDFRenderingQueue();
       this.renderingQueue.setViewer(this);
     } else {
       this.renderingQueue = options.renderingQueue;
@@ -6543,17 +6576,18 @@ var PDFViewer = (function pdfViewer() {
           var pageView = new PDFPageView({
             container: this.viewer,
             eventBus: this.eventBus,
             id: pageNum,
             scale: scale,
             defaultViewport: viewport.clone(),
             renderingQueue: this.renderingQueue,
             textLayerFactory: textLayerFactory,
-            annotationLayerFactory: this
+            annotationLayerFactory: this,
+            enhanceTextSelection: this.enhanceTextSelection,
           });
           bindOnAfterAndBeforeDraw(pageView);
           this._pages.push(pageView);
         }
 
         var linkService = this.linkService;
 
         // Fetch all the pages since the viewport is needed before printing
@@ -6649,17 +6683,21 @@ var PDFViewer = (function pdfViewer() {
       if (!noScroll) {
         var page = this._currentPageNumber, dest;
         if (this._location && !pdfjsLib.PDFJS.ignoreCurrentPositionOnZoom &&
             !(this.isInPresentationMode || this.isChangingPresentationMode)) {
           page = this._location.pageNumber;
           dest = [null, { name: 'XYZ' }, this._location.left,
                   this._location.top, null];
         }
-        this.scrollPageIntoView(page, dest);
+        this.scrollPageIntoView({
+          pageNumber: page,
+          destArray: dest,
+          allowNegativeOffset: true,
+        });
       }
 
       this._setScaleDispatchEvent(newScale, newValue, preset);
 
       if (this.defaultRenderingQueue) {
         this.update();
       }
     },
@@ -6722,33 +6760,47 @@ var PDFViewer = (function pdfViewer() {
         this._setScale(this._currentScaleValue, true);
       }
 
       var pageView = this._pages[this._currentPageNumber - 1];
       scrollIntoView(pageView.div);
     },
 
     /**
+     * @typedef ScrollPageIntoViewParameters
+     * @param {number} pageNumber - The page number.
+     * @param {Array} destArray - (optional) The original PDF destination array,
+     *   in the format: <page-ref> </XYZ|/FitXXX> <args..>
+     * @param {boolean} allowNegativeOffset - (optional) Allow negative page
+     *   offsets. The default value is `false`.
+     */
+
+    /**
      * Scrolls page into view.
-     * @param {number} pageNumber
-     * @param {Array} dest - (optional) original PDF destination array:
-     *   <page-ref> </XYZ|FitXXX> <args..>
+     * @param {ScrollPageIntoViewParameters} params
      */
-    scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber,
-                                                              dest) {
+    scrollPageIntoView: function PDFViewer_scrollPageIntoView(params) {
       if (!this.pdfDocument) {
         return;
       }
+      var pageNumber = params.pageNumber || 0;
+      var dest = params.destArray || null;
+      var allowNegativeOffset = params.allowNegativeOffset || false;
 
       if (this.isInPresentationMode || !dest) {
         this._setCurrentPageNumber(pageNumber, /* resetCurrentPageView */ true);
         return;
       }
 
       var pageView = this._pages[pageNumber - 1];
+      if (!pageView) {
+        console.error('PDFViewer_scrollPageIntoView: ' +
+                      'Invalid "pageNumber" parameter.');
+        return;
+      }
       var x = 0, y = 0;
       var width = 0, height = 0, widthScale, heightScale;
       var changeOrientation = (pageView.rotation % 180 === 0 ? false : true);
       var pageWidth = (changeOrientation ? pageView.height : pageView.width) /
         pageView.scale / CSS_UNITS;
       var pageHeight = (changeOrientation ? pageView.width : pageView.height) /
         pageView.scale / CSS_UNITS;
       var scale = 0;
@@ -6819,16 +6871,23 @@ var PDFViewer = (function pdfViewer() {
 
       var boundingRect = [
         pageView.viewport.convertToViewportPoint(x, y),
         pageView.viewport.convertToViewportPoint(x + width, y + height)
       ];
       var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
       var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
 
+      if (!allowNegativeOffset) {
+        // Some bad PDF generators will create destinations with e.g. top values
+        // that exceeds the page height. Ensure that offsets are not negative,
+        // to prevent a previous page from becoming visible (fixes bug 874482).
+        left = Math.max(left, 0);
+        top = Math.max(top, 0);
+      }
       scrollIntoView(pageView.div, { left: left, top: top });
     },
 
     _updateLocation: function (firstPage) {
       var currentScale = this._currentScale;
       var currentScaleValue = this._currentScaleValue;
       var normalizedScaleValue =
         parseFloat(currentScaleValue) === currentScale ?
@@ -6989,23 +7048,26 @@ var PDFViewer = (function pdfViewer() {
     },
 
     /**
      * @param {HTMLDivElement} textLayerDiv
      * @param {number} pageIndex
      * @param {PageViewport} viewport
      * @returns {TextLayerBuilder}
      */
-    createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
+    createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport,
+                                      enhanceTextSelection) {
       return new TextLayerBuilder({
         textLayerDiv: textLayerDiv,
         eventBus: this.eventBus,
         pageIndex: pageIndex,
         viewport: viewport,
-        findController: this.isInPresentationMode ? null : this.findController
+        findController: this.isInPresentationMode ? null : this.findController,
+        enhanceTextSelection: this.isInPresentationMode ? false :
+                                                          enhanceTextSelection,
       });
     },
 
     /**
      * @param {HTMLDivElement} pageDiv
      * @param {PDFPage} pdfPage
      * @returns {AnnotationLayerBuilder}
      */
@@ -7086,16 +7148,17 @@ var getGlobalEventBus = domEventsLib.get
 
 var DEFAULT_SCALE_DELTA = 1.1;
 var MIN_SCALE = 0.25;
 var MAX_SCALE = 10.0;
 var SCALE_SELECT_CONTAINER_PADDING = 8;
 var SCALE_SELECT_PADDING = 22;
 var PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading';
 var DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT = 5000;
+var ENHANCE_TEXT_SELECTION = false;
 
 function configure(PDFJS) {
   PDFJS.imageResourcesPath = './images/';
   PDFJS.workerSrc = '../build/pdf.worker.js';
   PDFJS.cMapUrl = '../web/cmaps/';
   PDFJS.cMapPacked = true;
 }
 
@@ -7186,17 +7249,18 @@ var PDFViewerApplication = {
     var container = appConfig.mainContainer;
     var viewer = appConfig.viewerContainer;
     this.pdfViewer = new PDFViewer({
       container: container,
       viewer: viewer,
       eventBus: eventBus,
       renderingQueue: pdfRenderingQueue,
       linkService: pdfLinkService,
-      downloadManager: downloadManager
+      downloadManager: downloadManager,
+      enhanceTextSelection: ENHANCE_TEXT_SELECTION,
     });
     pdfRenderingQueue.setViewer(this.pdfViewer);
     pdfLinkService.setViewer(this.pdfViewer);
 
     var thumbnailContainer = appConfig.sidebar.thumbnailView;
     this.pdfThumbnailViewer = new PDFThumbnailViewer({
       container: thumbnailContainer,
       renderingQueue: pdfRenderingQueue,
@@ -8079,17 +8143,17 @@ var PDFViewerApplication = {
   rotatePages: function pdfViewRotatePages(delta) {
     var pageNumber = this.page;
     this.pageRotation = (this.pageRotation + 360 + delta) % 360;
     this.pdfViewer.pagesRotation = this.pageRotation;
     this.pdfThumbnailViewer.pagesRotation = this.pageRotation;
 
     this.forceRendering();
 
-    this.pdfViewer.scrollPageIntoView(pageNumber);
+    this.pdfViewer.currentPageNumber = pageNumber;
   },
 
   requestPresentationMode: function pdfViewRequestPresentationMode() {
     if (!this.pdfPresentationMode) {
       return;
     }
     this.pdfPresentationMode.request();
   },
@@ -8103,17 +8167,17 @@ var PDFViewerApplication = {
     }
     this.pdfPresentationMode.mouseScroll(delta);
   },
 
   /**
    * @typedef UpdateUIToolbarParameters
    * @property {number} pageNumber
    * @property {string} scaleValue
-   * @property {scale} scale
+   * @property {number} scale
    * @property {boolean} resetNumPages
    */
 
   /**
    * @param {Object} UpdateUIToolbarParameters
    * @private
    */
   _updateUIToolbar: function (params) {
@@ -8154,18 +8218,18 @@ var PDFViewerApplication = {
     toolbarConfig.pageNumber.value = pageNumber;
 
     toolbarConfig.previous.disabled = (pageNumber <= 1);
     toolbarConfig.next.disabled = (pageNumber >= pagesCount);
 
     toolbarConfig.firstPage.disabled = (pageNumber <= 1);
     toolbarConfig.lastPage.disabled = (pageNumber >= pagesCount);
 
-    toolbarConfig.zoomOut.disabled = (scale === MIN_SCALE);
-    toolbarConfig.zoomIn.disabled = (scale === MAX_SCALE);
+    toolbarConfig.zoomOut.disabled = (scale <= MIN_SCALE);
+    toolbarConfig.zoomIn.disabled = (scale >= MAX_SCALE);
 
     selectScaleOption(scaleValue, scale);
   },
 
   bindEvents: function pdfViewBindEvents() {
     var eventBus = this.eventBus;
 
     eventBus.on('resize', webViewerResize);
@@ -8470,17 +8534,17 @@ function webViewerNamedAction(e) {
   if (!PDFViewerApplication.initialized) {
     return;
   }
   // Processing couple of named actions that might be useful.
   // See also PDFLinkService.executeNamedAction
   var action = e.action;
   switch (action) {
     case 'GoToPage':
-      PDFViewerApplication.appConfig.toolbar.pageNumber.focus();
+      PDFViewerApplication.appConfig.toolbar.pageNumber.select();
       break;
 
     case 'Find':
       if (!PDFViewerApplication.supportsIntegratedFind) {
         PDFViewerApplication.findBar.toggle();
       }
       break;
   }
--- a/browser/themes/osx/controlcenter/panel.css
+++ b/browser/themes/osx/controlcenter/panel.css
@@ -12,16 +12,20 @@
 .identity-popup-expander:-moz-focusring {
   padding: 2px;
 }
 
 .identity-popup-expander:-moz-focusring > .button-box {
   @hudButtonFocused@
 }
 
+.identity-popup-permission-remove-button:-moz-focusring {
+  box-shadow: @focusRingShadow@;
+}
+
 #identity-popup-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
   border-bottom-right-radius: 3.5px;
 }
 
 #identity-popup-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews:-moz-locale-dir(rtl) {
   border-bottom-right-radius: 0;
   border-bottom-left-radius: 3.5px;
 }
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -402,37 +402,37 @@ description#identity-popup-content-verif
   border-width: 0;
   border-radius: 50%;
   min-width: 0;
   padding: 2px;
   background-color: transparent;
 }
 
 .identity-popup-permission-remove-button > .button-box {
-  border-width: 0;
   padding: 0;
+  -moz-appearance: none;
 }
 
 .identity-popup-permission-remove-button > .button-box > .button-icon {
   margin: 0;
   width: 16px;
   height: 16px;
   list-style-image: url(chrome://browser/skin/panel-icons.svg#cancel);
   filter: url(chrome://browser/skin/filters.svg#fill);
   fill: graytext;
 }
 
 .identity-popup-permission-remove-button > .button-box > .button-text {
   display: none;
 }
 
 /* swap foreground / background colors on hover */
-.identity-popup-permission-remove-button:hover {
+.identity-popup-permission-remove-button:not(:-moz-focusring):hover {
   background-color: graytext;
 }
 
-.identity-popup-permission-remove-button:hover > .button-box > .button-icon {
+.identity-popup-permission-remove-button:not(:-moz-focusring):hover > .button-box > .button-icon {
   fill: -moz-field;
 }
 
-.identity-popup-permission-remove-button:hover:active {
+.identity-popup-permission-remove-button:not(:-moz-focusring):hover:active {
   background-color: -moz-fieldtext;
 }
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -22,16 +22,19 @@
 #downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
   padding: 0;
 }
 
 #downloadsListBox {
   background: transparent;
   padding: 4px;
   color: inherit;
+  -moz-appearance: none;
+  margin: 0;
+  border: none;
 }
 
 #emptyDownloads {
   padding: 16px 25px;
   margin: 0;
   /* The panel can be wider than this description after the blocked subview is
      shown, so center the text. */
   text-align: center;
@@ -53,16 +56,17 @@
 .downloadsPanelFooterButton {
   -moz-appearance: none;
   background-color: transparent;
   color: inherit;
   margin: 0;
   padding: 0;
   min-width: 0;
   min-height: 40px;
+  border: none;
 }
 
 .downloadsPanelFooterButton:hover {
   outline: 1px solid hsla(210,4%,10%,.07);
   background-color: hsla(210,4%,10%,.07);
 }
 
 .downloadsPanelFooterButton:hover:active,
@@ -80,41 +84,54 @@
 .downloadsPanelFooterButton[default]:hover {
   background-color: #0675d3;
 }
 
 .downloadsPanelFooterButton[default]:hover:active {
   background-color: #0568ba;
 }
 
-#downloadsPanel[hasdownloads] #downloadsHistory {
-  padding-left: 58px !important;
+.downloadsPanelFooterButton > .button-box {
+  padding: 0;
+  margin: 0;
+  border: none;
+}
+
+#downloadsHistory {
+  padding-inline-start: 10px;
+  padding-inline-end: 10px;
+}
+
+#downloadsPanel[hasdownloads] #downloadsFooterButtons:not(.downloadsHideDropmarker) > #downloadsHistory {
+  padding-inline-start: 68px;
 }
 
 toolbarseparator.downloadsDropmarkerSplitter {
   margin: 7px 0;
 }
 
 #downloadsFooter:hover toolbarseparator.downloadsDropmarkerSplitter,
 #downloadsFooter[showingdropdown] toolbarseparator {
   margin: 0;
 }
 
 .downloadsDropmarker {
-  padding: 0 19px !important;
+  padding: 0 21px;
 }
 
 .downloadsDropmarker > .button-box > hbox {
   display: none;
 }
 
 .downloadsDropmarker > .button-box > .button-menu-dropmarker {
   /* This is to override the linux !important */
   -moz-appearance: none !important;
   display: -moz-box;
+  padding: 0;
+  margin: 0;
 }
 
 .downloadsDropmarker > .button-box > .button-menu-dropmarker > .dropmarker-icon {
   width: 16px;
   height: 16px;
   list-style-image: url("chrome://browser/skin/downloads/menubutton-dropmarker.svg");
   filter: url("chrome://browser/skin/filters.svg#fill");
   fill: currentColor;
--- a/browser/themes/windows/downloads/downloads.css
+++ b/browser/themes/windows/downloads/downloads.css
@@ -50,21 +50,16 @@
   outline: 1px -moz-dialogtext dotted;
   outline-offset: -1px;
 }
 
 @keyfocus@ #downloadsSummary:focus {
   outline-offset: -5px;
 }
 
-.downloadsPanelFooterButton > .button-box {
-  /* Hide the border so we don't display an inner focus ring. */
-  border: none;
-}
-
 /*** List items and similar elements in the summary ***/
 
 :root {
   --downloads-item-height: 7em;
   --downloads-item-border-top-color: hsla(0,0%,100%,.3);
   --downloads-item-border-bottom-color: hsla(220,18%,51%,.25);
   --downloads-item-font-size-factor: 0.9;
   --downloads-item-target-margin-bottom: 6px;
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1541,30 +1541,38 @@ CanvasRenderingContext2D::RestoreClipsAn
 }
 
 CanvasRenderingContext2D::RenderingMode
 CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect,
                                        RenderingMode aRenderingMode)
 {
   if (AlreadyShutDown()) {
     gfxCriticalError() << "Attempt to render into a Canvas2d after shutdown.";
-    EnsureErrorTarget();
-    mTarget = sErrorTarget;
+    SetErrorState();
     return aRenderingMode;
   }
 
   // This would make no sense, so make sure we don't get ourselves in a mess
   MOZ_ASSERT(mRenderingMode != RenderingMode::DefaultBackendMode);
 
   RenderingMode mode = (aRenderingMode == RenderingMode::DefaultBackendMode) ? mRenderingMode : aRenderingMode;
 
   if (mTarget && mode == mRenderingMode) {
     return mRenderingMode;
   }
 
+  // Check that the dimensions are sane
+  if (mWidth > gfxPrefs::MaxCanvasSize() ||
+      mHeight > gfxPrefs::MaxCanvasSize() ||
+      mWidth < 0 || mHeight < 0) {
+
+    SetErrorState();
+    return aRenderingMode;
+  }
+
   // If the next drawing command covers the entire canvas, we can skip copying
   // from the previous frame and/or clearing the canvas.
   gfx::Rect canvasRect(0, 0, mWidth, mHeight);
   bool canDiscardContent = aCoveredRect &&
     CurrentState().transform.TransformBounds(*aCoveredRect).Contains(canvasRect);
 
   // If a clip is active we don't know for sure that the next drawing command
   // will really cover the entire canvas.
@@ -1577,138 +1585,234 @@ CanvasRenderingContext2D::EnsureTarget(c
         canDiscardContent = false;
         break;
       }
     }
   }
 
   ScheduleStableStateCallback();
 
-  // we'll do a few extra things at the end of this method if we changed the
-  // buffer provider.
-  RefPtr<PersistentBufferProvider> oldBufferProvider = mBufferProvider;
+  IntRect persistedRect = canDiscardContent ? IntRect()
+                                            : IntRect(0, 0, mWidth, mHeight);
 
   if (mBufferProvider && mode == mRenderingMode) {
-    auto persistedRect = canDiscardContent ? IntRect()
-                                           : IntRect(0, 0, mWidth, mHeight);
     mTarget = mBufferProvider->BorrowDrawTarget(persistedRect);
 
     if (mTarget && !mBufferProvider->PreservesDrawingState()) {
       RestoreClipsAndTransformToTarget();
     }
 
     if (mTarget) {
       return mode;
     }
   }
 
+  RefPtr<DrawTarget> newTarget;
+  RefPtr<PersistentBufferProvider> newProvider;
+
+  if (mode == RenderingMode::OpenGLBackendMode &&
+      !TrySkiaGLTarget(newTarget, newProvider)) {
+    // Fall back to software.
+    mode = RenderingMode::SoftwareBackendMode;
+  }
+
+  if (mode == RenderingMode::SoftwareBackendMode &&
+      !TrySharedTarget(newTarget, newProvider) &&
+      !TryBasicTarget(newTarget, newProvider)) {
+    SetErrorState();
+    return mode;
+  }
+
+  MOZ_ASSERT(newTarget);
+  MOZ_ASSERT(newProvider);
+
+  mTarget = newTarget.forget();
+  mBufferProvider = newProvider.forget();
+
+  RegisterAllocation();
+
+  // Skia expects the unused X channel to contains 0 even for opaque operations
+  // so we can't skip clearing in that case, even if we are going to cover the
+  // entire canvas in the next drawing operation.
+  if (!canDiscardContent || mTarget->GetBackendType() == gfx::BackendType::SKIA) {
+    mTarget->ClearRect(canvasRect);
+  }
+
+  RestoreClipsAndTransformToTarget();
+
+  // Force a full layer transaction since we didn't have a layer before
+  // and now we might need one.
+  if (mCanvasElement) {
+    mCanvasElement->InvalidateCanvas();
+  }
+  // Calling Redraw() tells our invalidation machinery that the entire
+  // canvas is already invalid, which can speed up future drawing.
+  Redraw();
+
+  return mode;
+}
+
+void
+CanvasRenderingContext2D::SetInitialState()
+{
+  // Set up the initial canvas defaults
+  mPathBuilder = nullptr;
+  mPath = nullptr;
+  mDSPathBuilder = nullptr;
+
+  mStyleStack.Clear();
+  ContextState *state = mStyleStack.AppendElement();
+  state->globalAlpha = 1.0;
+
+  state->colorStyles[Style::FILL] = NS_RGB(0,0,0);
+  state->colorStyles[Style::STROKE] = NS_RGB(0,0,0);
+  state->shadowColor = NS_RGBA(0,0,0,0);
+}
+
+void
+CanvasRenderingContext2D::SetErrorState()
+{
+  EnsureErrorTarget();
+
+  if (mTarget && mTarget != sErrorTarget) {
+    gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
+  }
+
+  mTarget = sErrorTarget;
+  mBufferProvider = nullptr;
+
+  // clear transforms, clips, etc.
+  SetInitialState();
+}
+
+void
+CanvasRenderingContext2D::RegisterAllocation()
+{
+  // XXX - It would make more sense to track the allocation in
+  // PeristentBufferProvider, rather than here.
+  static bool registered = false;
+  if (!registered) {
+    registered = true;
+    RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
+  }
+
+  gCanvasAzureMemoryUsed += mWidth * mHeight * 4;
+  JSContext* context = nsContentUtils::GetCurrentJSContext();
+  if (context) {
+    JS_updateMallocCounter(context, mWidth * mHeight * 4);
+  }
+}
+
+static already_AddRefed<LayerManager>
+LayerManagerFromCanvasElement(nsINode* aCanvasElement)
+{
+  if (!aCanvasElement || !aCanvasElement->OwnerDoc()) {
+    return nullptr;
+  }
+
+  return nsContentUtils::PersistentLayerManagerForDocument(aCanvasElement->OwnerDoc());
+}
+
+bool
+CanvasRenderingContext2D::TrySkiaGLTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+                                          RefPtr<layers::PersistentBufferProvider>& aOutProvider)
+{
+  aOutDT = nullptr;
+  aOutProvider = nullptr;
+
+
   mIsSkiaGL = false;
 
-   // Check that the dimensions are sane
   IntSize size(mWidth, mHeight);
-  if (!mTarget &&
-      size.width <= gfxPrefs::MaxCanvasSize() &&
-      size.height <= gfxPrefs::MaxCanvasSize() &&
-      size.width >= 0 && size.height >= 0) {
-    SurfaceFormat format = GetSurfaceFormat();
-    nsIDocument* ownerDoc = nullptr;
-    if (mCanvasElement) {
-      ownerDoc = mCanvasElement->OwnerDoc();
-    }
-
-    RefPtr<LayerManager> layerManager = nullptr;
-
-    if (ownerDoc) {
-      layerManager =
-        nsContentUtils::PersistentLayerManagerForDocument(ownerDoc);
-    }
-
-    if (layerManager) {
-      if (mode == RenderingMode::OpenGLBackendMode &&
-          gfxPlatform::GetPlatform()->UseAcceleratedCanvas() &&
-          CheckSizeForSkiaGL(size)) {
-        DemoteOldestContextIfNecessary();
-        mBufferProvider = nullptr;
+  if (!gfxPlatform::GetPlatform()->UseAcceleratedCanvas() ||
+      !CheckSizeForSkiaGL(size)) {
+
+    return false;
+  }
+
+
+  RefPtr<LayerManager> layerManager = LayerManagerFromCanvasElement(mCanvasElement);
+
+  if (!layerManager) {
+    return false;
+  }
+
+  DemoteOldestContextIfNecessary();
+  mBufferProvider = nullptr;
 
 #if USE_SKIA_GPU
-        SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
-
-        if (glue && glue->GetGrContext() && glue->GetGLContext()) {
-          mTarget = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(), size, format);
-          if (mTarget) {
-            AddDemotableContext(this);
-            mBufferProvider = new PersistentBufferProviderBasic(mTarget);
-            mIsSkiaGL = true;
-          } else {
-            gfxCriticalNote << "Failed to create a SkiaGL DrawTarget, falling back to software " << size << ", " << format;
-            mode = RenderingMode::SoftwareBackendMode;
-          }
-        }
+  SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
+  if (!glue || !glue->GetGrContext() || !glue->GetGLContext()) {
+    return false;
+  }
+
+  SurfaceFormat format = GetSurfaceFormat();
+  aOutDT = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(),
+                                                      size, format);
+  if (!aOutDT) {
+    return false;
+    gfxCriticalNote << "Failed to create a SkiaGL DrawTarget, falling back to software\n";
+  }
+
+  MOZ_ASSERT(aOutDT->GetType() == DrawTargetType::HARDWARE_RASTER);
+
+  AddDemotableContext(this);
+  aOutProvider = new PersistentBufferProviderBasic(aOutDT);
+  mIsSkiaGL = true;
+  // Drop a note in the debug builds if we ever use accelerated Skia canvas.
+  gfxWarningOnce() << "Using SkiaGL canvas.";
 #endif
-      }
-
-      if (!mBufferProvider) {
-        mTarget = nullptr;
-        mBufferProvider = layerManager->CreatePersistentBufferProvider(size, format);
-      }
-    }
-
-    if (mBufferProvider) {
-      mTarget = mBufferProvider->BorrowDrawTarget(IntRect(IntPoint(), IntSize(mWidth, mHeight)));
-    } else if (!mTarget) {
-      mTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(size, format);
-      mode = RenderingMode::SoftwareBackendMode;
-    }
-  }
-
-  if (mTarget) {
-    // We changed the buffer provider.
-    // XXX - It would make more sense to track the allocation in
-    // PeristentBufferProvider, rather than here.
-    if (mBufferProvider != oldBufferProvider) {
-      static bool registered = false;
-      if (!registered) {
-        registered = true;
-        RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
-      }
-
-      gCanvasAzureMemoryUsed += mWidth * mHeight * 4;
-      JSContext* context = nsContentUtils::GetCurrentJSContext();
-      if (context) {
-        JS_updateMallocCounter(context, mWidth * mHeight * 4);
-      }
-
-      mTarget->ClearRect(canvasRect);
-
-      // Force a full layer transaction since we didn't have a layer before
-      // and now we might need one.
-      if (mCanvasElement) {
-        mCanvasElement->InvalidateCanvas();
-      }
-      // Calling Redraw() tells our invalidation machinery that the entire
-      // canvas is already invalid, which can speed up future drawing.
-      Redraw();
-    }
-
-    if (mBufferProvider != oldBufferProvider || !mBufferProvider ||
-        !mBufferProvider->PreservesDrawingState()) {
-      RestoreClipsAndTransformToTarget();
-    }
-  } else {
-    EnsureErrorTarget();
-    mTarget = sErrorTarget;
-    mBufferProvider = nullptr;
-  }
-
-  // Drop a note in the debug builds if we ever use accelerated Skia canvas.
-  if (mIsSkiaGL && mTarget && mTarget->GetType() == DrawTargetType::HARDWARE_RASTER) {
-    gfxWarningOnce() << "Using SkiaGL canvas.";
-  }
-
-  return mode;
+
+  // could still be null if USE_SKIA_GPU is not #defined.
+  return !!aOutDT;
+}
+
+bool
+CanvasRenderingContext2D::TrySharedTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+                                          RefPtr<layers::PersistentBufferProvider>& aOutProvider)
+{
+  aOutDT = nullptr;
+  aOutProvider = nullptr;
+
+  if (!mCanvasElement || !mCanvasElement->OwnerDoc()) {
+    return false;
+  }
+
+  RefPtr<LayerManager> layerManager = LayerManagerFromCanvasElement(mCanvasElement);
+
+  if (!layerManager) {
+    return false;
+  }
+
+  aOutProvider = layerManager->CreatePersistentBufferProvider(GetSize(), GetSurfaceFormat());
+
+  if (!aOutProvider) {
+    return false;
+  }
+
+  // We can pass an empty persisted rect since we just created the buffer
+  // provider (nothing to restore).
+  aOutDT = aOutProvider->BorrowDrawTarget(IntRect());
+  MOZ_ASSERT(aOutDT);
+
+  return !!aOutDT;
+}
+
+bool
+CanvasRenderingContext2D::TryBasicTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+                                         RefPtr<layers::PersistentBufferProvider>& aOutProvider)
+{
+  aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(GetSize(),
+                                                                       GetSurfaceFormat());
+  if (!aOutDT) {
+    return false;
+  }
+
+  aOutProvider = new PersistentBufferProviderBasic(aOutDT);
+  return true;
 }
 
 int32_t
 CanvasRenderingContext2D::GetWidth() const
 {
   return mWidth;
 }
 
@@ -1758,43 +1862,32 @@ CanvasRenderingContext2D::ClearTarget(bo
   Reset();
 
   if (aRetainBuffer) {
     mBufferProvider = provider;
   }
 
   mResetLayer = true;
 
-  // set up the initial canvas defaults
-  mStyleStack.Clear();
-  mPathBuilder = nullptr;
-  mPath = nullptr;
-  mDSPathBuilder = nullptr;
-
-  ContextState *state = mStyleStack.AppendElement();
-  state->globalAlpha = 1.0;
-
-  state->colorStyles[Style::FILL] = NS_RGB(0,0,0);
-  state->colorStyles[Style::STROKE] = NS_RGB(0,0,0);
-  state->shadowColor = NS_RGBA(0,0,0,0);
+  SetInitialState();
 
   // For vertical writing-mode, unless text-orientation is sideways,
   // we'll modify the initial value of textBaseline to 'middle'.
   RefPtr<nsStyleContext> canvasStyle;
   if (mCanvasElement && mCanvasElement->IsInUncomposedDoc()) {
     nsCOMPtr<nsIPresShell> presShell = GetPresShell();
     if (presShell) {
       canvasStyle =
         nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement,
                                                       nullptr,
                                                       presShell);
       if (canvasStyle) {
         WritingMode wm(canvasStyle);
         if (wm.IsVertical() && !wm.IsSideways()) {
-          state->textBaseline = TextBaseline::MIDDLE;
+          CurrentState().textBaseline = TextBaseline::MIDDLE;
         }
       }
     }
   }
 }
 
 void
 CanvasRenderingContext2D::ReturnTarget(bool aForceReset)
@@ -1915,17 +2008,17 @@ CanvasRenderingContext2D::GetImageBuffer
     snapshot = mBufferProvider->BorrowSnapshot();
   } else {
     EnsureTarget();
     snapshot = mTarget->Snapshot();
   }
 
   if (snapshot) {
     RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
-    if (data && data->GetSize() == IntSize(mWidth, mHeight)) {
+    if (data && data->GetSize() == GetSize()) {
       *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
       ret = SurfaceToPackedBGRA(data);
     }
   }
 
   if (!mTarget && mBufferProvider) {
     mBufferProvider->ReturnSnapshot(snapshot.forget());
   }
@@ -2757,17 +2850,17 @@ CanvasRenderingContext2D::UpdateFilter()
   // The filter might reference an SVG filter that is declared inside this
   // document. Flush frames so that we'll have an nsSVGFilterFrame to work
   // with.
   presShell->FlushPendingNotifications(Flush_Frames);
 
   CurrentState().filter =
     nsFilterInstance::GetFilterDescription(mCanvasElement,
       CurrentState().filterChain,
-      CanvasUserSpaceMetrics(IntSize(mWidth, mHeight),
+      CanvasUserSpaceMetrics(GetSize(),
                              CurrentState().fontFont,
                              CurrentState().fontLanguage,
                              CurrentState().fontExplicitLanguage,
                              presShell->GetPresContext()),
       gfxRect(0, 0, mWidth, mHeight),
       CurrentState().filterAdditionalImages);
 }
 
@@ -5793,40 +5886,16 @@ CanvasRenderingContext2D::SkiaGLTex() co
 void CanvasRenderingContext2D::RemoveDrawObserver()
 {
   if (mDrawObserver) {
     delete mDrawObserver;
     mDrawObserver = nullptr;
   }
 }
 
-PersistentBufferProvider*
-CanvasRenderingContext2D::GetBufferProvider(LayerManager* aManager)
-{
-  if (AlreadyShutDown()) {
-    return nullptr;
-  }
-
-  if (mBufferProvider) {
-    return mBufferProvider;
-  }
-
-  if (mTarget) {
-    mBufferProvider = new PersistentBufferProviderBasic(mTarget);
-    return mBufferProvider;
-  }
-
-  if (aManager && !mIsSkiaGL) {
-    mBufferProvider = aManager->CreatePersistentBufferProvider(gfx::IntSize(mWidth, mHeight),
-                                                               GetSurfaceFormat());
-  }
-
-  return mBufferProvider;
-}
-
 already_AddRefed<Layer>
 CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                          Layer *aOldLayer,
                                          LayerManager *aManager,
                                          bool aMirror /* = false */)
 {
   if (aMirror) {
     // Not supported for CanvasRenderingContext2D
@@ -5864,18 +5933,17 @@ CanvasRenderingContext2D::GetCanvasLayer
       if (skiaGLTex) {
         SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
         MOZ_ASSERT(glue);
         data.mGLContext = glue->GetGLContext();
         data.mFrontbufferGLTex = skiaGLTex;
       }
     }
 
-    PersistentBufferProvider *provider = GetBufferProvider(aManager);
-    data.mBufferProvider = provider;
+    data.mBufferProvider = mBufferProvider;
 
     if (userData &&
         userData->IsForContext(this) &&
         static_cast<CanvasLayer*>(aOldLayer)->IsDataValid(data)) {
       RefPtr<Layer> ret = aOldLayer;
       return ret.forget();
     }
   }
@@ -5901,35 +5969,34 @@ CanvasRenderingContext2D::GetCanvasLayer
   // The userData will receive DidTransactionCallbacks, which flush the
   // the invalidation state to indicate that the canvas is up to date.
   userData = new CanvasRenderingContext2DUserData(this);
   canvasLayer->SetDidTransactionCallback(
           CanvasRenderingContext2DUserData::DidTransactionCallback, userData);
   canvasLayer->SetUserData(&g2DContextLayerUserData, userData);
 
   CanvasLayer::Data data;
-  data.mSize = nsIntSize(mWidth, mHeight);
+  data.mSize = GetSize();
   data.mHasAlpha = !mOpaque;
 
   canvasLayer->SetPreTransactionCallback(
           CanvasRenderingContext2DUserData::PreTransactionCallback, userData);
 
 
   if (mIsSkiaGL) {
       GLuint skiaGLTex = SkiaGLTex();
       if (skiaGLTex) {
         SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
         MOZ_ASSERT(glue);
         data.mGLContext = glue->GetGLContext();
         data.mFrontbufferGLTex = skiaGLTex;
       }
   }
 
-  PersistentBufferProvider *provider = GetBufferProvider(aManager);
-  data.mBufferProvider = provider;
+  data.mBufferProvider = mBufferProvider;
 
   canvasLayer->Initialize(data);
   uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0;
   canvasLayer->SetContentFlags(flags);
   canvasLayer->Updated();
 
   mResetLayer = false;
 
@@ -5956,17 +6023,17 @@ bool
 CanvasRenderingContext2D::IsContextCleanForFrameCapture()
 {
   return !mIsCapturedFrameInvalid;
 }
 
 bool
 CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager* aManager)
 {
-  return !aManager->CanUseCanvasLayerForSize(IntSize(mWidth, mHeight));
+  return !aManager->CanUseCanvasLayerForSize(GetSize());
 }
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPath, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPath, Release)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath, mParent)
 
 CanvasPath::CanvasPath(nsISupports* aParent)
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -424,16 +424,17 @@ public:
 
   // Eventually this should be deprecated. Keeping for now to keep the binding functional.
   void Demote();
 
   nsresult Redraw();
 
   virtual int32_t GetWidth() const override;
   virtual int32_t GetHeight() const override;
+  gfx::IntSize GetSize() const { return gfx::IntSize(mWidth, mHeight); }
 
   // nsICanvasRenderingContextInternal
   /**
     * Gets the pres shell from either the canvas element or the doc shell
     */
   virtual nsIPresShell *GetPresShell() override {
     if (mCanvasElement) {
       return mCanvasElement->OwnerDoc()->GetShell();
@@ -458,17 +459,16 @@ public:
       *aPremultAlpha = true;
     }
     return mTarget->Snapshot();
   }
 
   NS_IMETHOD SetIsOpaque(bool aIsOpaque) override;
   bool GetIsOpaque() override { return mOpaque; }
   NS_IMETHOD Reset() override;
-  mozilla::layers::PersistentBufferProvider* GetBufferProvider(mozilla::layers::LayerManager* aManager);
   already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                          Layer* aOldLayer,
                                          LayerManager* aManager,
                                          bool aMirror = false) override;
   virtual bool ShouldForceInactiveLayer(LayerManager* aManager) override;
   void MarkContextClean() override;
   void MarkContextCleanForFrameCapture() override;
   bool IsContextCleanForFrameCapture() override;
@@ -640,16 +640,31 @@ protected:
    *
    * Returns the actual rendering mode being used by the created target.
    */
   RenderingMode EnsureTarget(const gfx::Rect* aCoveredRect = nullptr,
                              RenderingMode aRenderMode = RenderingMode::DefaultBackendMode);
 
   void RestoreClipsAndTransformToTarget();
 
+  bool TrySkiaGLTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+                       RefPtr<layers::PersistentBufferProvider>& aOutProvider);
+
+  bool TrySharedTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+                       RefPtr<layers::PersistentBufferProvider>& aOutProvider);
+
+  bool TryBasicTarget(RefPtr<gfx::DrawTarget>& aOutDT,
+                      RefPtr<layers::PersistentBufferProvider>& aOutProvider);
+
+  void RegisterAllocation();
+
+  void SetInitialState();
+
+  void SetErrorState();
+
   /**
    * This method is run at the end of the event-loop spin where
    * ScheduleStableStateCallback was called.
    *
    * We use it to unlock resources that need to be locked while drawing.
    */
   void OnStableState();
 
--- a/dom/canvas/test/webgl-mochitest/ensure-exts/test_EXT_disjoint_timer_query.html
+++ b/dom/canvas/test/webgl-mochitest/ensure-exts/test_EXT_disjoint_timer_query.html
@@ -6,14 +6,14 @@
     <link rel='stylesheet' href='/tests/SimpleTest/test.css'>
     <script src='ensure-ext.js'></script>
   </head>
   <body>
     <script>
 
 'use strict';
 Lastly_WithDraftExtsEnabled(function() {
-    EnsureExt('EXT_disjoint_timer_query');
+    EnsureExtFor('webgl', 'EXT_disjoint_timer_query');
 });
 
     </script>
   </body>
 </html>
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -278,34 +278,36 @@ DataTransfer::SetEffectAllowedInt(uint32
 
 NS_IMETHODIMP
 DataTransfer::GetMozUserCancelled(bool* aUserCancelled)
 {
   *aUserCancelled = MozUserCancelled();
   return NS_OK;
 }
 
-FileList*
+already_AddRefed<FileList>
 DataTransfer::GetFiles(ErrorResult& aRv)
 {
-  return mItems->Files();
+  return mItems->Files(nsContentUtils::SubjectPrincipal());
 }
 
 NS_IMETHODIMP
 DataTransfer::GetFiles(nsIDOMFileList** aFileList)
 {
   if (!aFileList) {
     return NS_ERROR_FAILURE;
   }
 
-  ErrorResult rv;
-  RefPtr<FileList> files = GetFiles(rv);
-  if (NS_WARN_IF(rv.Failed())) {
-    return rv.StealNSResult();
-  }
+  // The XPCOM interface is only avaliable to system code, and thus we can
+  // assume the system principal. This is consistent with the previous behavour
+  // of this function, which also assumed the system principal.
+  //
+  // This code is also called from C++ code, which expects it to have a System
+  // Principal, and thus the SubjectPrincipal cannot be used.
+  RefPtr<FileList> files = mItems->Files(nsContentUtils::GetSystemPrincipal());
 
   files.forget(aFileList);
   return NS_OK;
 }
 
 already_AddRefed<DOMStringList>
 DataTransfer::GetTypes(ErrorResult& aRv) const
 {
@@ -661,16 +663,37 @@ DataTransfer::MozGetDataAt(JSContext* aC
 
   JS::Rooted<JS::Value> result(aCx);
   if (!VariantToJsval(aCx, data, aRetval)) {
     aRv = NS_ERROR_FAILURE;
     return;
   }
 }
 
+/* static */ bool
+DataTransfer::PrincipalMaySetData(const nsAString& aType,
+                                  nsIVariant* aData,
+                                  nsIPrincipal* aPrincipal)
+{
+  if (!nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+    DataTransferItem::eKind kind = DataTransferItem::KindFromData(aData);
+    if (kind == DataTransferItem::KIND_OTHER) {
+      NS_WARNING("Disallowing adding non string/file types to DataTransfer");
+      return false;
+    }
+
+    if (aType.EqualsASCII(kFileMime) ||
+        aType.EqualsASCII(kFilePromiseMime)) {
+      NS_WARNING("Disallowing adding x-moz-file or x-moz-file-promize types to DataTransfer");
+      return false;
+    }
+  }
+  return true;
+}
+
 nsresult
 DataTransfer::SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData,
                                 uint32_t aIndex,
                                 nsIPrincipal* aSubjectPrincipal)
 {
   if (aFormat.IsEmpty()) {
     return NS_OK;
   }
@@ -692,30 +715,18 @@ DataTransfer::SetDataAtInternal(const ns
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
   // Don't allow the custom type to be assigned.
   if (aFormat.EqualsLiteral(kCustomTypesMime)) {
     return NS_ERROR_TYPE_ERR;
   }
 
-  // Don't allow non-chrome to add non-string or file data. We'll block file
-  // promises as well which are used internally for drags to the desktop.
-  if (!nsContentUtils::IsSystemPrincipal(aSubjectPrincipal)) {
-    if (aFormat.EqualsLiteral(kFilePromiseMime) ||
-        aFormat.EqualsLiteral(kFileMime)) {
-      return NS_ERROR_DOM_SECURITY_ERR;
-    }
-
-    uint16_t type;
-    aData->GetDataType(&type);
-    if (type == nsIDataType::VTYPE_INTERFACE ||
-        type == nsIDataType::VTYPE_INTERFACE_IS) {
-      return NS_ERROR_DOM_SECURITY_ERR;
-    }
+  if (!PrincipalMaySetData(aFormat, aData, aSubjectPrincipal)) {
+    return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   return SetDataWithPrincipal(aFormat, aData, aIndex, aSubjectPrincipal);
 }
 
 void
 DataTransfer::MozSetDataAt(JSContext* aCx, const nsAString& aFormat,
                            JS::Handle<JS::Value> aData,
@@ -831,17 +842,17 @@ DataTransfer::GetFilesAndDirectories(Err
     return nullptr;
   }
 
   RefPtr<Promise> p = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  RefPtr<FileList> files = mItems->Files();
+  RefPtr<FileList> files = mItems->Files(nsContentUtils::SubjectPrincipal());
   if (NS_WARN_IF(!files)) {
     return nullptr;
   }
 
   Sequence<RefPtr<File>> filesSeq;
   files->ToSequence(filesSeq, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -141,17 +141,17 @@ public:
   void GetData(const nsAString& aFormat, nsAString& aData, ErrorResult& aRv);
 
   void SetData(const nsAString& aFormat, const nsAString& aData,
                ErrorResult& aRv);
 
   void ClearData(const mozilla::dom::Optional<nsAString>& aFormat,
                  mozilla::ErrorResult& aRv);
 
-  FileList* GetFiles(mozilla::ErrorResult& aRv);
+  already_AddRefed<FileList> GetFiles(mozilla::ErrorResult& aRv);
 
   already_AddRefed<Promise> GetFilesAndDirectories(ErrorResult& aRv);
 
   already_AddRefed<Promise> GetFiles(bool aRecursiveFlag, ErrorResult& aRv);
 
 
   void AddElement(Element& aElement, mozilla::ErrorResult& aRv);
 
@@ -266,16 +266,20 @@ public:
   nsresult Clone(nsISupports* aParent, EventMessage aEventMessage,
                  bool aUserCancelled, bool aIsCrossDomainSubFrameDrop,
                  DataTransfer** aResult);
 
   // converts some formats used for compatibility in aInFormat into aOutFormat.
   // Text and text/unicode become text/plain, and URL becomes text/uri-list
   void GetRealFormat(const nsAString& aInFormat, nsAString& aOutFormat) const;
 
+  static bool PrincipalMaySetData(const nsAString& aFormat,
+                                  nsIVariant* aData,
+                                  nsIPrincipal* aPrincipal);
+
 protected:
 
   // caches text and uri-list data formats that exist in the drag service or
   // clipboard for retrieval later.
   nsresult CacheExternalData(const char* aFormat, uint32_t aIndex,
                              nsIPrincipal* aPrincipal, bool aHidden);
 
   // caches the formats that exist in the drag service that were added by an
--- a/dom/events/DataTransferItem.cpp
+++ b/dom/events/DataTransferItem.cpp
@@ -98,41 +98,46 @@ DataTransferItem::SetData(nsIVariant* aD
         break;
       }
     }
 
     mData = nullptr;
     return;
   }
 
-  mKind = KIND_OTHER;
   mData = aData;
+  mKind = KindFromData(mData);
+}
 
+/* static */ DataTransferItem::eKind
+DataTransferItem::KindFromData(nsIVariant* aData)
+{
   nsCOMPtr<nsISupports> supports;
   nsresult rv = aData->GetAsISupports(getter_AddRefs(supports));
   if (NS_SUCCEEDED(rv) && supports) {
     // Check if we have one of the supported file data formats
     if (nsCOMPtr<nsIDOMBlob>(do_QueryInterface(supports)) ||
         nsCOMPtr<BlobImpl>(do_QueryInterface(supports)) ||
         nsCOMPtr<nsIFile>(do_QueryInterface(supports))) {
-      mKind = KIND_FILE;
-      return;
+      return KIND_FILE;
     }
   }
 
   nsAutoString string;
   // If we can't get the data type as a string, that means that the object
   // should be considered to be of the "other" type. This is impossible
   // through the APIs defined by the spec, but we provide extra Moz* APIs,
   // which allow setting of non-string data. We determine whether we can
   // consider it a string, by calling GetAsAString, and checking for success.
   rv = aData->GetAsAString(string);
   if (NS_SUCCEEDED(rv)) {
-    mKind = KIND_STRING;
+    return KIND_STRING;
   }
+
+  return KIND_OTHER;
 }
 
 void
 DataTransferItem::FillInExternalData()
 {
   if (mData) {
     return;
   }
@@ -230,36 +235,44 @@ DataTransferItem::FillInExternalData()
     NS_WARNING("Clipboard data provided by the OS does not match predicted kind");
   }
 #endif
 }
 
 already_AddRefed<File>
 DataTransferItem::GetAsFile(ErrorResult& aRv)
 {
+  return GetAsFileWithPrincipal(nsContentUtils::SubjectPrincipal(), aRv);
+}
+
+already_AddRefed<File>
+DataTransferItem::GetAsFileWithPrincipal(nsIPrincipal* aPrincipal, ErrorResult& aRv)
+{
   if (mKind != KIND_FILE) {
     return nullptr;
   }
 
-  nsCOMPtr<nsIVariant> data = Data(nsContentUtils::SubjectPrincipal(), aRv);
+  // This is done even if we have an mCachedFile, as it performs the necessary
+  // permissions checks to ensure that we are allowed to access this type.
+  nsCOMPtr<nsIVariant> data = Data(aPrincipal, aRv);
   if (NS_WARN_IF(!data || aRv.Failed())) {
     return nullptr;
   }
 
-  nsCOMPtr<nsISupports> supports;
-  aRv = data->GetAsISupports(getter_AddRefs(supports));
-  MOZ_ASSERT(!aRv.Failed() && supports,
-             "File objects should be stored as nsISupports variants");
-  if (aRv.Failed() || !supports) {
-    return nullptr;
-  }
-
   // Generate the dom::File from the stored data, caching it so that the
   // same object is returned in the future.
   if (!mCachedFile) {
+    nsCOMPtr<nsISupports> supports;
+    aRv = data->GetAsISupports(getter_AddRefs(supports));
+    MOZ_ASSERT(!aRv.Failed() && supports,
+               "File objects should be stored as nsISupports variants");
+    if (aRv.Failed() || !supports) {
+      return nullptr;
+    }
+
     if (nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(supports)) {
       Blob* blob = static_cast<Blob*>(domBlob.get());
       mCachedFile = blob->ToFile();
     } else if (nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(supports)) {
       MOZ_ASSERT(blobImpl->IsFile());
       mCachedFile = File::Create(mDataTransfer, blobImpl);
     } else if (nsCOMPtr<nsIFile> ifile = do_QueryInterface(supports)) {
       mCachedFile = File::CreateFromFile(mDataTransfer, ifile);
@@ -270,17 +283,24 @@ DataTransferItem::GetAsFile(ErrorResult&
 
   RefPtr<File> file = mCachedFile;
   return file.forget();
 }
 
 already_AddRefed<FileSystemEntry>
 DataTransferItem::GetAsEntry(ErrorResult& aRv)
 {
-  RefPtr<File> file = GetAsFile(aRv);
+  return GetAsEntryWithPrincipal(nsContentUtils::SubjectPrincipal(), aRv);
+}
+
+already_AddRefed<FileSystemEntry>
+DataTransferItem::GetAsEntryWithPrincipal(nsIPrincipal* aPrincipal,
+                                          ErrorResult& aRv)
+{
+  RefPtr<File> file = GetAsFileWithPrincipal(aPrincipal, aRv);
   if (NS_WARN_IF(aRv.Failed()) || !file) {
     return nullptr;
   }
 
   nsCOMPtr<nsIGlobalObject> global;
   // This is annoying, but DataTransfer may have various things as parent.
   nsCOMPtr<EventTarget> target =
     do_QueryInterface(mDataTransfer->GetParentObject());
--- a/dom/events/DataTransferItem.h
+++ b/dom/events/DataTransferItem.h
@@ -43,17 +43,20 @@ public:
     , mDataTransfer(aDataTransfer)
   {
     MOZ_ASSERT(mDataTransfer, "Must be associated with a DataTransfer");
   }
 
   already_AddRefed<DataTransferItem> Clone(DataTransfer* aDataTransfer) const;
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  // NOTE: This accesses the subject principal, and should not be called from C++
   void GetAsString(FunctionStringCallback* aCallback, ErrorResult& aRv);
+
   void GetKind(nsAString& aKind) const
   {
     switch (mKind) {
     case KIND_FILE:
       aKind = NS_LITERAL_STRING("file");
       return;
     case KIND_STRING:
       aKind = NS_LITERAL_STRING("string");
@@ -74,19 +77,25 @@ public:
   {
     return mKind;
   }
   void SetKind(eKind aKind)
   {
     mKind = aKind;
   }
 
+  // NOTE: This accesses the subject principal, and should not be called from C++
   already_AddRefed<File> GetAsFile(ErrorResult& aRv);
+  already_AddRefed<File> GetAsFileWithPrincipal(nsIPrincipal* aPrincipal,
+                                                ErrorResult& aRv);
 
+  // NOTE: This accesses the subject principal, and should not be called from C++
   already_AddRefed<FileSystemEntry> GetAsEntry(ErrorResult& aRv);
+  already_AddRefed<FileSystemEntry> GetAsEntryWithPrincipal(nsIPrincipal* aPrincipal,
+                                                            ErrorResult& aRv);
 
   DataTransfer* GetParentObject() const
   {
     return mDataTransfer;
   }
 
   nsIPrincipal* Principal() const
   {
@@ -115,16 +124,18 @@ public:
   {
     return mChromeOnly;
   }
   void SetChromeOnly(bool aChromeOnly)
   {
     mChromeOnly = aChromeOnly;
   }
 
+  static eKind KindFromData(nsIVariant* aData);
+
 private:
   ~DataTransferItem() {}
   already_AddRefed<File> CreateFileFromInputStream(nsIInputStream* aStream);
 
   // The index in the 2d mIndexedItems array
   uint32_t mIndex;
 
   bool mChromeOnly;
--- a/dom/events/DataTransferItemList.cpp
+++ b/dom/events/DataTransferItemList.cpp
@@ -148,21 +148,27 @@ DataTransferItemList::Add(const nsAStrin
     return nullptr;
   }
 
   nsCOMPtr<nsIVariant> data(new storage::TextVariant(aData));
 
   nsAutoString format;
   mDataTransfer->GetRealFormat(aType, format);
 
+  nsIPrincipal* subjectPrincipal = nsContentUtils::SubjectPrincipal();
+
+  if (!DataTransfer::PrincipalMaySetData(format, data, subjectPrincipal)) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
+
   // We add the textual data to index 0. We set aInsertOnly to true, as we don't
   // want to update an existing entry if it is already present, as per the spec.
   RefPtr<DataTransferItem> item =
-    SetDataWithPrincipal(format, data, 0,
-                         nsContentUtils::SubjectPrincipal(),
+    SetDataWithPrincipal(format, data, 0, subjectPrincipal,
                          /* aInsertOnly = */ true,
                          /* aHidden = */ false,
                          aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
   MOZ_ASSERT(item->Kind() != DataTransferItem::KIND_FILE);
 
@@ -178,42 +184,82 @@ DataTransferItemList::Add(File& aData, E
 
   nsCOMPtr<nsISupports> supports = do_QueryObject(&aData);
   nsCOMPtr<nsIWritableVariant> data = new nsVariant();
   data->SetAsISupports(supports);
 
   nsAutoString type;
   aData.GetType(type);
 
+  nsIPrincipal* subjectPrincipal = nsContentUtils::SubjectPrincipal();
+
+  if (!DataTransfer::PrincipalMaySetData(type, data, subjectPrincipal)) {
+    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return nullptr;
+  }
 
   // We need to add this as a new item, as multiple files can't exist in the
   // same item in the Moz DataTransfer layout. It will be appended at the end of
   // the internal specced layout.
   uint32_t index = mIndexedItems.Length();
   RefPtr<DataTransferItem> item =
-    SetDataWithPrincipal(type, data, index,
-                         nsContentUtils::SubjectPrincipal(),
-                         true, false, aRv);
+    SetDataWithPrincipal(type, data, index, subjectPrincipal,
+                         /* aInsertOnly = */ true,
+                         /* aHidden = */ false,
+                         aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
   MOZ_ASSERT(item->Kind() == DataTransferItem::KIND_FILE);
 
   return item;
 }
 
-FileList*
-DataTransferItemList::Files()
+already_AddRefed<FileList>
+DataTransferItemList::Files(nsIPrincipal* aPrincipal)
 {
+  // The DataTransfer can hold data with varying principals, coming from
+  // different windows. This means that permissions checks need to be made when
+  // accessing data from the DataTransfer. With the accessor methods, this is
+  // checked by DataTransferItem::Data(), however with files, we keep a cached
+  // live copy of the files list for spec compliance.
+  //
+  // A DataTransfer is only exposed to one webpage, and chrome code. The chrome
+  // code should be able to see all files on the DataTransfer, while the webpage
+  // should only be able to see the files it can see. As chrome code doesn't
+  // need as strict spec compliance as web visible code, we generate a new
+  // FileList object every time you access the Files list from chrome code, but
+  // re-use the cached one when accessing from non-chrome code.
+  //
+  // It is not legal to expose an identical DataTransfer object is to multiple
+  // different principals without using the `Clone` method or similar to copy it
+  // first. If that happens, this method will assert, and return nullptr in
+  // release builds. If this functionality is required in the future, a more
+  // advanced caching mechanism for the FileList objects will be required.
+  RefPtr<FileList> files;
+  if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+    files = new FileList(static_cast<nsIDOMDataTransfer*>(mDataTransfer));
+    GenerateFiles(files, aPrincipal);
+    return files.forget();
+  }
+
   if (!mFiles) {
     mFiles = new FileList(static_cast<nsIDOMDataTransfer*>(mDataTransfer));
+    mFilesPrincipal = aPrincipal;
     RegenerateFiles();
   }
 
-  return mFiles;
+  if (!aPrincipal->Subsumes(mFilesPrincipal)) {
+    MOZ_ASSERT(false, "This DataTransfer should only be accessed by the system "
+               "and a single principal");
+    return nullptr;
+  }
+
+  files = mFiles;
+  return files.forget();
 }
 
 void
 DataTransferItemList::MozRemoveByTypeAt(const nsAString& aType,
                                         uint32_t aIndex,
                                         ErrorResult& aRv)
 {
   if (NS_WARN_IF(mDataTransfer->IsReadOnly() ||
@@ -486,30 +532,39 @@ DataTransferItemList::RegenerateFiles()
   // list. That way we can avoid the unnecessary work if the user never touches
   // the files list.
   if (mFiles) {
     // We clear the list rather than performing smaller updates, because it
     // simplifies the logic greatly on this code path, which should be very
     // infrequently used.
     mFiles->Clear();
 
-    uint32_t count = Length();
-    for (uint32_t i = 0; i < count; i++) {
-      ErrorResult rv;
-      bool found;
-      RefPtr<DataTransferItem> item = IndexedGetter(i, found, rv);
-      if (NS_WARN_IF(!found || rv.Failed())) {
+    DataTransferItemList::GenerateFiles(mFiles, mFilesPrincipal);
+  }
+}
+
+void
+DataTransferItemList::GenerateFiles(FileList* aFiles,
+                                    nsIPrincipal* aFilesPrincipal)
+{
+  MOZ_ASSERT(aFiles);
+  MOZ_ASSERT(aFilesPrincipal);
+  uint32_t count = Length();
+  for (uint32_t i = 0; i < count; i++) {
+    ErrorResult rv;
+    bool found;
+    RefPtr<DataTransferItem> item = IndexedGetter(i, found, rv);
+    if (NS_WARN_IF(!found || rv.Failed())) {
+      continue;
+    }
+
+    if (item->Kind() == DataTransferItem::KIND_FILE) {
+      RefPtr<File> file = item->GetAsFileWithPrincipal(aFilesPrincipal, rv);
+      if (NS_WARN_IF(rv.Failed() || !file)) {
         continue;
       }
-
-      if (item->Kind() == DataTransferItem::KIND_FILE) {
-        RefPtr<File> file = item->GetAsFile(rv);
-        if (NS_WARN_IF(rv.Failed() || !file)) {
-          continue;
-        }
-        mFiles->Append(file);
-      }
+      aFiles->Append(file);
     }
   }
 }
 
 } // namespace mozilla
 } // namespace dom
--- a/dom/events/DataTransferItemList.h
+++ b/dom/events/DataTransferItemList.h
@@ -66,17 +66,17 @@ public:
 
   void Clear(ErrorResult& aRv);
 
   already_AddRefed<DataTransferItem>
   SetDataWithPrincipal(const nsAString& aType, nsIVariant* aData,
                        uint32_t aIndex, nsIPrincipal* aPrincipal,
                        bool aInsertOnly, bool aHidden, ErrorResult& aRv);
 
-  FileList* Files();
+  already_AddRefed<FileList> Files(nsIPrincipal* aPrincipal);
 
   // Moz-style helper methods for interacting with the stored data
   void MozRemoveByTypeAt(const nsAString& aType, uint32_t aIndex,
                          ErrorResult& aRv);
   DataTransferItem* MozItemByTypeAt(const nsAString& aType, uint32_t aIndex);
   const nsTArray<RefPtr<DataTransferItem>>* MozItemsAt(uint32_t aIndex);
   uint32_t MozItemCount() const;
 
@@ -89,22 +89,25 @@ public:
 
 private:
   void ClearDataHelper(DataTransferItem* aItem, uint32_t aIndexHint,
                        uint32_t aMozOffsetHint, ErrorResult& aRv);
   DataTransferItem* AppendNewItem(uint32_t aIndex, const nsAString& aType,
                                   nsIVariant* aData, nsIPrincipal* aPrincipal,
                                   bool aHidden);
   void RegenerateFiles();
+  void GenerateFiles(FileList* aFiles, nsIPrincipal* aFilesPrincipal);
 
   ~DataTransferItemList() {}
 
   RefPtr<DataTransfer> mDataTransfer;
   bool mIsExternal;
   RefPtr<FileList> mFiles;
+  // The principal for which mFiles is cached
+  nsCOMPtr<nsIPrincipal> mFilesPrincipal;
   nsTArray<RefPtr<DataTransferItem>> mItems;
   nsTArray<nsTArray<RefPtr<DataTransferItem>>> mIndexedItems;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_DataTransferItemList_h
--- a/dom/events/test/test_DataTransferItemList.html
+++ b/dom/events/test/test_DataTransferItemList.html
@@ -74,16 +74,18 @@
       is(files[0], file, "It should be the same file as the file we originally created");
       is(file, e.dataTransfer.mozGetDataAt("text/html", 1),
          "It should be stored in index 1 for mozGetDataAt");
 
       var file2 = new File(['<a id="c"><b id="d">yo!</b></a>'], "myotherfile.html",
                            {type: "text/html"});
       dtList.add(file2);
 
+      todo(files.length == 2, "This test has chrome privileges, so the FileList objects aren't updated live");
+      files = e.dataTransfer.files;
       is(files.length, 2, "The files property should have been updated in place");
       is(files[1], file2, "It should be the same file as the file we originally created");
       is(file2, e.dataTransfer.mozGetDataAt("text/html", 2),
          "It should be stored in index 2 for mozGetDataAt");
 
       var oldLength = dtList.length;
       var randomString = "foo!";
       e.dataTransfer.mozSetDataAt("random/string", randomString, 3);
@@ -93,16 +95,19 @@
       var file3 = new File(['<a id="e"><b id="f">heya!</b></a>'], "yetanotherfile.html",
                            {type: "text/html"});
       e.dataTransfer.mozSetDataAt("random/string", file3, 3);
       is(oldLength + 1, dtList.length,
          "Replacing the entry with a file should add it to the list!");
       is(dtList[oldLength].getAsFile(), file3, "It should be stored in the last index as a file");
       is(dtList[oldLength].type, "random/string", "It should have the correct type");
       is(dtList[oldLength].kind, "file", "It should have the correct kind");
+
+      todo(files.length == 3, "This test has chrome privileges, so the FileList objects aren't updated live");
+      files = e.dataTransfer.files;
       is(files[files.length - 1], file3, "It should also be in the files list");
 
       oldLength = dtList.length;
       var nonstring = {};
       e.dataTransfer.mozSetDataAt("jsobject", nonstring, 0);
       is(oldLength + 1, dtList.length,
          "Adding a non-string object using the mozAPIs to index 0 should add an item to the dataTransfer");
       is(dtList[oldLength].type, "jsobject", "It should have the correct type");
--- a/dom/svg/test/test_tabindex.html
+++ b/dom/svg/test/test_tabindex.html
@@ -9,17 +9,17 @@
 </head>
 <body>
 <svg xmlns="http://www.w3.org/2000/svg" overflow="visible">
   <foreignObject id="f" x="0" y="0" width="200" height="60" tabindex="0">
     <body xmlns="http://www.w3.org/1999/xhtml">
       <p>Here is a paragraph that requires word wrap</p>
     </body>
   </foreignObject>
-  <rect id="r" x="0" y="70" width="100" height="100" fill="yellow" tabIndex="1"/>
+  <rect id="r" x="0" y="70" width="100" height="100" fill="yellow" tabindex="1"/>
   <text id="t" x="0" y="200" tabindex="2">
         This is SVG text
   </text>
 </svg>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
--- a/dom/tests/mochitest/bugs/mochitest.ini
+++ b/dom/tests/mochitest/bugs/mochitest.ini
@@ -71,17 +71,16 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_bug393974.html]
 [test_bug394769.html]
 [test_bug396843.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug))
 [test_bug400204.html]
 [test_bug404748.html]
 [test_bug406375.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android'
-[test_bug411103.html]
 [test_bug414291.html]
 tags = openwindow
 [test_bug427744.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android'
 [test_bug42976.html]
 [test_bug430276.html]
 [test_bug437361.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(dom.disable_open_during_load not implemented in b2g, showmodaldialog) b2g-debug(dom.disable_open_during_load not implemented in b2g, showmodaldialog) b2g-desktop(dom.disable_open_during_load not implemented in b2g, showmodaldialog)
deleted file mode 100644
--- a/dom/tests/mochitest/bugs/test_bug411103.html
+++ /dev/null
@@ -1,188 +0,0 @@
-<!DOCTYPE html>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=411103
--->
-<head>
-  <title>Test for Bug 411103</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=411103">Mozilla Bug 411103</a>
-<p id="display"></p>
-<div id="content" style="display: none"></div>
-
-<!-- XML's createElement and createElementNS aren't HTML's, of course -->
-<iframe src="data:application/xml,%3Cfoo%3EXML%3C/foo%3E" name="xmlWindow"></iframe>
-
-<!-- for good measure... -->
-<iframe src="data:application/xhtml+xml,%3Chtml%20xmlns=%22http://www.w3.org/1999/xhtml%22%3E%3Cbody%3E%3Cp%3EXHTML%3C/p%3E%3C/body%3E%3C/html%3E"
-        name="xhtmlWindow"></iframe>
-
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-SimpleTest.waitForExplicitFinish();
-
-/** Test for Bug 411103 **/
-var allNSTests =
-  [
-   { args: [undefined, undefined] },
-   { args: [null, undefined] },
-   { args: [undefined, null] },
-   { args: [null, null] },
-   { args: [null, ""], code: 5 },
-   { args: ["", null] },
-   { args: ["", ""], code: 5 },
-   { args: [null, "<div>"], code: 5 },
-   { args: [null, "0div"], code: 5 },
-   { args: [null, "di v"], code: 5 },
-   { args: [null, "di<v"], code: 5 },
-   { args: [null, "-div"], code: 5 },
-   { args: [null, ".div"], code: 5 },
-   { args: ["http://example.com/", "<div>"], code: 5 },
-   { args: ["http://example.com/", "0div"], code: 5 },
-   { args: ["http://example.com/", "di<v"], code: 5 },
-   { args: ["http://example.com/", "-div"], code: 5 },
-   { args: ["http://example.com/", ".div"], code: 5 },
-   { args: [null, ":div"], code: 14 },
-   { args: [null, "div:"], code: 14 },
-   { args: ["http://example.com/", ":div"], code: 14 },
-   { args: ["http://example.com/", "div:"], code: 14 },
-   { args: [null, "d:iv"], code: 14 },
-   { args: [null, "a:b:c"], code: 14, message: "valid XML name, invalid QName" },
-   { args: ["http://example.com/", "a:b:c"], code: 14, message: "valid XML name, invalid QName" },
-   { args: [null, "a::c"], code: 14, message: "valid XML name, invalid QName" },
-   { args: ["http://example.com/", "a::c"], code: 14, message: "valid XML name, invalid QName" },
-   { args: ["http://example.com/", "a:0"], code: 5, message: "valid XML name, not a valid QName" },
-   { args: ["http://example.com/", "0:a"], code: 5, message: "0 at start makes it not a valid XML name" },
-   { args: ["http://example.com/", "a:_"] },
-   { args: ["http://example.com/", "a:\u0BC6"], code: 14,
-     message: "non-ASCII character after colon is CombiningChar, which is " +
-              "NCNameChar but not (Letter | \"_\") so invalid at start of " +
-              "NCName (but still a valid XML name, hence not 5)" },
-   { args: ["http://example.com/", "\u0BC6:a"], code: 14,
-     message: "non-ASCII character after colon is CombiningChar, which is " +
-              "NCNameChar but not (Letter | \"_\") so invalid at start of " +
-              "NCName (Gecko chooses to throw 14 here, but either is valid " +
-              "as this is both an invalid XML name and an invalid QName)" },
-   { args: ["http://example.com/", "a:a\u0BC6"] },
-   { args: ["http://example.com/", "a\u0BC6:a"] },
-   { args: ["http://example.com/", "xml:test"], code: 14, message: "binding xml prefix wrong" },
-   { args: ["http://example.com/", "xmlns:test"], code: 14, message: "binding xmlns prefix wrong" },
-   { args: ["http://www.w3.org/2000/xmlns/", "x:test"], code: 14, message: "binding namespace namespace to wrong prefix" },
-   { args: ["http://www.w3.org/2000/xmlns/", "xmlns:test"] },
-   { args: ["http://www.w3.org/XML/1998/namespace", "xml:test"] },
-   { args: ["http://www.w3.org/XML/1998/namespace", "x:test"] },
-  ];
-
-var allNoNSTests =
-  [
-   { args: [undefined] },
-   { args: [null] },
-   { args: [""], code: 5 },
-   { args: ["<div>"], code: 5 },
-   { args: ["0div"], code: 5 },
-   { args: ["di v"], code: 5 },
-   { args: ["di<v"], code: 5 },
-   { args: ["-div"], code: 5 },
-   { args: [".div"], code: 5 },
-   { args: [":"], message: "valid XML name, invalid QName" },
-   { args: [":div"], message: "valid XML name, invalid QName" },
-   { args: ["div:"], message: "valid XML name, invalid QName" },
-   { args: ["d:iv"] },
-   { args: ["a:b:c"], message: "valid XML name, invalid QName" },
-   { args: ["a::c"], message: "valid XML name, invalid QName" },
-   { args: ["a::c:"], message: "valid XML name, invalid QName" },
-   { args: ["a:0"], message: "valid XML name, not a valid QName" },
-   { args: ["0:a"], code: 5, message: "0 at start makes it not a valid XML name" },
-   { args: ["a:_"] },
-   { args: ["a:\u0BC6"],
-     message: "non-ASCII character after colon is CombiningChar, which is " +
-              "valid in pre-namespace XML" },
-   { args: ["\u0BC6:a"], code: 5, message: "not a valid start character" },
-   { args: ["a:a\u0BC6"] },
-   { args: ["a\u0BC6:a"] },
-   { args: ["xml:test"] },
-   { args: ["xmlns:test"] },
-   { args: ["x:test"] },
-   { args: ["xmlns:test"] },
-  ];
-
-function sourceify(v)
-{
-  switch (typeof v)
-  {
-    case "undefined":
-      return v;
-
-    case "string":
-      return '"' + v.replace('"', '\\"') + '"';
-
-    default:
-      return String(v);
-  }
-}
-
-function sourceifyArgs(args)
-{
-  var copy = new Array(args.length);
-  for (var i = 0, sz = args.length; i < sz; i++)
-    copy[i] = sourceify(args[i]);
-
-  return copy.join(", ");
-}
-
-function runTests(tests, methodName, document)
-{
-  for (var i = 0, sz = tests.length; i < sz; i++)
-  {
-    var test = tests[i];
-  
-    var argStr = sourceifyArgs(test.args);
-    try
-    {
-      document[methodName].apply(document, test.args);
-      var msg = "expected no exception for " +
-                "document." + methodName + "(" + argStr + ")";
-      if ("message" in test)
-        msg += "; " + test.message;
-      ok(!("code" in test), msg);
-    }
-    catch (e)
-    {
-      msg = "exception code for document." + methodName + "(" + argStr + ")";
-      if ("message" in test)
-        msg += "; " + test.message;
-      is(e.code, test.code || "no exception", msg);
-    }
-  }
-}
-
-
-function run()
-{
-  // HTML document
-  runTests(allNSTests, "createElementNS", document);
-  runTests(allNoNSTests, "createElement", document);
-
-  // XML document
-  var xmlDocument = window.frames.xmlWindow.document;
-  runTests(allNSTests, "createElementNS", xmlDocument);
-  runTests(allNoNSTests, "createElement", xmlDocument);
-
-  // XHTML document, for good measure
-  var xhtmlDocument = window.frames.xhtmlWindow.document;
-  runTests(allNSTests, "createElementNS", xhtmlDocument);
-  runTests(allNoNSTests, "createElement", xhtmlDocument);
-
-  SimpleTest.finish();
-}
-
-window.addEventListener("load", run, false);
-
-</script>
-</pre>
-</body>
-</html>
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -54,16 +54,17 @@ static StaticRefPtr<CompositorBridgeChil
 
 Atomic<int32_t> CompositableForwarder::sSerialCounter(0);
 
 CompositorBridgeChild::CompositorBridgeChild(ClientLayerManager *aLayerManager)
   : mLayerManager(aLayerManager)
   , mCanSend(false)
   , mFwdTransactionId(0)
   , mMessageLoop(MessageLoop::current())
+  , mSectionAllocator(nullptr)
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 CompositorBridgeChild::~CompositorBridgeChild()
 {
   if (mCanSend) {
     gfxCriticalError() << "CompositorBridgeChild was not deinitialized";
@@ -95,16 +96,21 @@ CompositorBridgeChild::Destroy()
   if (!mCanSend) {
     return;
   }
 
   for (size_t i = 0; i < mTexturePools.Length(); i++) {
     mTexturePools[i]->Destroy();
   }
 
+  if (mSectionAllocator) {
+    delete mSectionAllocator;
+    mSectionAllocator = nullptr;
+  }
+
   // Destroying the layer manager may cause all sorts of things to happen, so
   // let's make sure there is still a reference to keep this alive whatever
   // happens.
   RefPtr<CompositorBridgeChild> selfRef = this;
 
   if (mLayerManager) {
     mLayerManager->Destroy();
     mLayerManager = nullptr;
@@ -955,16 +961,31 @@ CompositorBridgeChild::HandleMemoryPress
 void
 CompositorBridgeChild::ClearTexturePool()
 {
   for (size_t i = 0; i < mTexturePools.Length(); i++) {
     mTexturePools[i]->Clear();
   }
 }
 
+FixedSizeSmallShmemSectionAllocator*
+CompositorBridgeChild::GetTileLockAllocator()
+{
+  MOZ_ASSERT(IPCOpen());
+  if (!IPCOpen()) {
+    return nullptr;
+  }
+
+  if (!mSectionAllocator) {
+    mSectionAllocator = new FixedSizeSmallShmemSectionAllocator(this);
+  }
+  return mSectionAllocator;
+}
+
+
 PTextureChild*
 CompositorBridgeChild::CreateTexture(const SurfaceDescriptor& aSharedData,
                                      LayersBackend aLayersBackend,
                                      TextureFlags aFlags,
                                      uint64_t aSerial)
 {
   return PCompositorBridgeChild::SendPTextureConstructor(aSharedData, aLayersBackend, aFlags, 0 /* FIXME? */, aSerial);
 }
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -189,16 +189,18 @@ public:
 
   virtual void CancelWaitForRecycle(uint64_t aTextureId) override;
 
   TextureClientPool* GetTexturePool(LayersBackend aBackend,
                                     gfx::SurfaceFormat aFormat,
                                     TextureFlags aFlags);
   void ClearTexturePool();
 
+  virtual FixedSizeSmallShmemSectionAllocator* GetTileLockAllocator() override;
+
   void HandleMemoryPressure();
 
   virtual MessageLoop* GetMessageLoop() const override { return mMessageLoop; }
 
   virtual base::ProcessId GetParentPid() const override { return OtherPid(); }
 
   virtual bool AllocUnsafeShmem(size_t aSize,
                                 mozilla::ipc::SharedMemory::SharedMemoryType aShmType,
@@ -307,14 +309,16 @@ private:
    */
   nsDataHashtable<nsUint64HashKey, RefPtr<TextureClient> > mTexturesWaitingRecycled;
 
   MessageLoop* mMessageLoop;
 
   AutoTArray<RefPtr<TextureClientPool>,2> mTexturePools;
 
   uint64_t mProcessToken;
+
+  FixedSizeSmallShmemSectionAllocator* mSectionAllocator;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_CompositorBrigedChild_h
--- a/gfx/layers/ipc/ISurfaceAllocator.h
+++ b/gfx/layers/ipc/ISurfaceAllocator.h
@@ -311,36 +311,17 @@ public:
 
   // can be called on the compositor process.
   static void FreeShmemSection(ShmemSection& aShmemSection);
 
   void ShrinkShmemSectionHeap();
 
   ShmemAllocator* GetShmAllocator() { return mShmProvider->AsShmemAllocator(); }
 
-  /**
-    * In order to avoid shutdown crashes, we need to test for mShmProvider->AsShmemAllocator()
-    * here. Basically, there's a case where we have the following class hierarchy:
-    *
-    * ClientIPCAllocator -> TextureForwarder -> CompositableForwarder -> ShadowLayerForwarder
-    *
-    * In ShadowLayerForwarder's dtor, we tear down the actor and close the IPC channel.
-    * In TextureForwarder's dtor, we destroy the FixedSizeSmallShmemAllocator and that in turn calls
-    * ClientIPCAllocator::IPCOpen() to determine whether we can dealloc some shmem regions.
-    *
-    * This does not work. In the above class diagram, as the ShadowLayerForwarder's dtor has run
-    * its course, the ClientIPCAllocator object we're holding on to is now just a plain
-    * ClientIPCAllocator and so we call ClientIPCAllocator's IPCOpen() which unconditionally
-    * returns true. We therefore have to rely on AsShmemAllocator() to determine whether we can
-    * do these deallocs as ClientIPCAllocator::AsShmemAllocator() returns nullptr.
-    *
-    * Ideally, we should move a lot of this destruction work into non-destructor Destroy() methods
-    * which do cleanup before we destroy the objects.
-    */
-  bool IPCOpen() const { return mShmProvider->AsShmemAllocator() && mShmProvider->IPCOpen(); }
+  bool IPCOpen() const { return mShmProvider->IPCOpen(); }
 
 protected:
   std::vector<mozilla::ipc::Shmem> mUsedShmems;
   ClientIPCAllocator* mShmProvider;
 };
 
 } // namespace layers
 } // namespace mozilla
deleted file mode 100644
--- a/gfx/layers/ipc/TextureForwarder.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * vim: sw=2 ts=8 et :
- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "mozilla/layers/TextureForwarder.h"
-
-namespace mozilla {
-namespace layers {
-
-TextureForwarder::TextureForwarder()
-  : mSectionAllocator(nullptr)
-{
-}
-
-TextureForwarder::~TextureForwarder()
-{
-  if (mSectionAllocator) {
-    delete mSectionAllocator;
-  }
-}
-
-FixedSizeSmallShmemSectionAllocator*
-TextureForwarder::GetTileLockAllocator()
-{
-  MOZ_ASSERT(IPCOpen());
-  if (!IPCOpen()) {
-    return nullptr;
-  }
-
-  if (!mSectionAllocator) {
-    mSectionAllocator = new FixedSizeSmallShmemSectionAllocator(this);
-  }
-  return mSectionAllocator;
-}
-
-} // namespace layers
-} // namespace mozilla
--- a/gfx/layers/ipc/TextureForwarder.h
+++ b/gfx/layers/ipc/TextureForwarder.h
@@ -17,33 +17,26 @@
 #include "mozilla/gfx/Rect.h"
 
 namespace mozilla {
 namespace layers {
 
 class TextureForwarder : public ClientIPCAllocator
 {
 public:
-  TextureForwarder();
-
-  virtual ~TextureForwarder();
-
   /**
    * Create a TextureChild/Parent pair as as well as the TextureHost on the parent side.
    */
   virtual PTextureChild* CreateTexture(
     const SurfaceDescriptor& aSharedData,
     LayersBackend aLayersBackend,
     TextureFlags aFlags,
     uint64_t aSerial) = 0;
 
   virtual TextureForwarder* AsTextureForwarder() override { return this; }
 
-  virtual FixedSizeSmallShmemSectionAllocator* GetTileLockAllocator();
-
-private:
-  FixedSizeSmallShmemSectionAllocator* mSectionAllocator;
+  virtual FixedSizeSmallShmemSectionAllocator* GetTileLockAllocator() { return nullptr; }
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -369,17 +369,16 @@ UNIFIED_SOURCES += [
     'ipc/RemoteContentController.cpp',
     'ipc/ShadowLayerChild.cpp',
     'ipc/ShadowLayerParent.cpp',
     'ipc/ShadowLayers.cpp',
     'ipc/SharedBufferManagerChild.cpp',
     'ipc/SharedBufferManagerParent.cpp',
     'ipc/SharedPlanarYCbCrImage.cpp',
     'ipc/SharedRGBImage.cpp',
-    'ipc/TextureForwarder.cpp',
     'LayerScope.cpp',
     'LayersLogging.cpp',
     'LayerSorter.cpp',
     'LayersTypes.cpp',
     'opengl/CompositingRenderTargetOGL.cpp',
     'opengl/CompositorOGL.cpp',
     'opengl/GLBlitTextureImageHelper.cpp',
     'opengl/OGLShaderProgram.cpp',
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -211,20 +211,16 @@ nsBlockFrame::InitDebugFlags()
         ShowDebugFlags();
       }
     }
   }
 }
 
 #endif
 
-// add in a sanity check for absurdly deep frame trees.  See bug 42138
-// can't just use IsFrameTreeTooDeep() because that method has side effects we don't want
-#define MAX_DEPTH_FOR_LIST_RENUMBERING 200  // 200 open displayable tags is pretty unrealistic
-
 //----------------------------------------------------------------------
 
 // Debugging support code
 
 #ifdef DEBUG
 const char* nsBlockFrame::kReflowCommandType[] = {
   "ContentChanged",
   "StyleChanged",
@@ -673,17 +669,17 @@ nsBlockFrame::GetMinISize(nsRenderingCon
   AutoNoisyIndenter indenter(gNoisyIntrinsic);
 #endif
 
   for (nsBlockFrame* curFrame = this; curFrame;
        curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
     curFrame->LazyMarkLinesDirty();
   }
 
-  if (RenumberLists(PresContext())) {
+  if (RenumberList()) {
     AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
   }
   if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION)
     ResolveBidi();
   InlineMinISizeData data;
   for (nsBlockFrame* curFrame = this; curFrame;
        curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
     for (line_iterator line = curFrame->begin_lines(), line_end = curFrame->end_lines();
@@ -761,17 +757,17 @@ nsBlockFrame::GetPrefISize(nsRenderingCo
   AutoNoisyIndenter indenter(gNoisyIntrinsic);
 #endif
 
   for (nsBlockFrame* curFrame = this; curFrame;
        curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
     curFrame->LazyMarkLinesDirty();
   }
 
-  if (RenumberLists(PresContext())) {
+  if (RenumberList()) {
     AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
   }
   if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION)
     ResolveBidi();
   InlinePrefISizeData data;
   for (nsBlockFrame* curFrame = this; curFrame;
        curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
     for (line_iterator line = curFrame->begin_lines(), line_end = curFrame->end_lines();
@@ -1107,17 +1103,17 @@ nsBlockFrame::Reflow(nsPresContext*     
   // to continually recompute it.
   BlockReflowInput state(*reflowInput, aPresContext, this,
                            blockStartMarginRoot, blockEndMarginRoot,
                            needFloatManager, consumedBSize);
 
   if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION)
     static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
 
-  if (RenumberLists(aPresContext)) {
+  if (RenumberList()) {
     AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
   }
 
 #ifdef DEBUG
   // Between when we drain pushed floats and when we complete reflow,
   // we're allowed to have multiple continuations of the same float on
   // our floats list, since a first-in-flow might get pushed to a later
   // continuation of its containing block.  But it's not permitted
@@ -2924,55 +2920,46 @@ nsBlockFrame::MoveChildFramesOfLine(nsLi
 
 nsresult 
 nsBlockFrame::AttributeChanged(int32_t         aNameSpaceID,
                                nsIAtom*        aAttribute,
                                int32_t         aModType)
 {
   nsresult rv = nsContainerFrame::AttributeChanged(aNameSpaceID,
                                                    aAttribute, aModType);
-
   if (NS_FAILED(rv)) {
     return rv;
   }
-  if (nsGkAtoms::start == aAttribute ||
-      (nsGkAtoms::reversed == aAttribute &&
-       mContent->IsHTMLElement(nsGkAtoms::ol))) {
-    nsPresContext* presContext = PresContext();
-
-    // XXX Not sure if this is necessary anymore
-    if (RenumberLists(presContext)) {
-      presContext->PresShell()->
-        FrameNeedsReflow(this, nsIPresShell::eStyleChange,
-                         NS_FRAME_HAS_DIRTY_CHILDREN);
-    }
-  }
-  else if (nsGkAtoms::value == aAttribute) {
+  if (nsGkAtoms::value == aAttribute) {
     const nsStyleDisplay* styleDisplay = StyleDisplay();
     if (NS_STYLE_DISPLAY_LIST_ITEM == styleDisplay->mDisplay) {
-      // Search for the closest ancestor that's a block frame. We
-      // make the assumption that all related list items share a
-      // common block parent.
+      // Search for the closest ancestor that's a block/grid/flex frame.
+      // We make the assumption that all related list items share a
+      // common block/grid/flex ancestor.
       // XXXldb I think that's a bad assumption.
-      nsBlockFrame* blockParent = nsLayoutUtils::FindNearestBlockAncestor(this);
-
-      // Tell the enclosing block frame to renumber list items within
-      // itself
-      if (nullptr != blockParent) {
-        nsPresContext* presContext = PresContext();
+      nsContainerFrame* ancestor = GetParent();
+      for (; ancestor; ancestor = ancestor->GetParent()) {
+        auto frameType = ancestor->GetType();
+        if (frameType == nsGkAtoms::blockFrame ||
+            frameType == nsGkAtoms::flexContainerFrame ||
+            frameType == nsGkAtoms::gridContainerFrame) {
+          break;
+        }
+      }
+      // Tell the ancestor to renumber list items within itself.
+      if (ancestor) {
         // XXX Not sure if this is necessary anymore
-        if (blockParent->RenumberLists(presContext)) {
-          presContext->PresShell()->
-            FrameNeedsReflow(blockParent, nsIPresShell::eStyleChange,
+        if (ancestor->RenumberList()) {
+          PresContext()->PresShell()->
+            FrameNeedsReflow(ancestor, nsIPresShell::eStyleChange,
                              NS_FRAME_HAS_DIRTY_CHILDREN);
         }
       }
     }
   }
-
   return rv;
 }
 
 static inline bool
 IsNonAutoNonZeroBSize(const nsStyleCoord& aCoord)
 {
   nsStyleUnit unit = aCoord.GetUnit();
   if (unit == eStyleUnit_Auto ||
@@ -6965,225 +6952,57 @@ nsBlockFrame::GetSpokenBulletText(nsAStr
     if (bullet) {
       bullet->GetSpokenText(aText);
     } else {
       aText.Truncate();
     }
   }
 }
 
-// static
 bool
-nsBlockFrame::FrameStartsCounterScope(nsIFrame* aFrame)
-{
-  nsIContent* content = aFrame->GetContent();
-  if (!content || !content->IsHTMLElement())
-    return false;
-
-  nsIAtom *localName = content->NodeInfo()->NameAtom();
-  return localName == nsGkAtoms::ol ||
-         localName == nsGkAtoms::ul ||
-         localName == nsGkAtoms::dir ||
-         localName == nsGkAtoms::menu;
-}
-
-bool
-nsBlockFrame::RenumberLists(nsPresContext* aPresContext)
-{
-  if (!FrameStartsCounterScope(this)) {
-    // If this frame doesn't start a counter scope then we don't need
-    // to renumber child list items.
-    return false;
-  }
-
-  MOZ_ASSERT(mContent->IsHTMLElement(),
-             "FrameStartsCounterScope should only return true for HTML elements");
-
-  // Setup initial list ordinal value
-  // XXX Map html's start property to counter-reset style
-  int32_t ordinal = 1;
-  int32_t increment;
-  if (mContent->IsHTMLElement(nsGkAtoms::ol) &&
-      mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::reversed)) {
-    increment = -1;
-  } else {
-    increment = 1;
-  }
-
-  nsGenericHTMLElement *hc = nsGenericHTMLElement::FromContent(mContent);
-  // Must be non-null, since FrameStartsCounterScope only returns true
-  // for HTML elements.
-  MOZ_ASSERT(hc, "How is mContent not HTML?");
-  const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::start);
-  if (attr && attr->Type() == nsAttrValue::eInteger) {
-    ordinal = attr->GetIntegerValue();
-  } else if (increment < 0) {
-    // <ol reversed> case, or some other case with a negative increment: count
-    // up the child list
-    ordinal = 0;
-    nsBlockFrame* block = static_cast<nsBlockFrame*>(FirstInFlow());
-    RenumberListsInBlock(aPresContext, block, &ordinal, 0, -increment, true);
-  }
-
-  // Get to first-in-flow
-  nsBlockFrame* block = static_cast<nsBlockFrame*>(FirstInFlow());
-  return RenumberListsInBlock(aPresContext, block, &ordinal, 0, increment, false);
-}
-
-bool
-nsBlockFrame::RenumberListsInBlock(nsPresContext* aPresContext,
-                                   nsBlockFrame* aBlockFrame,
-                                   int32_t* aOrdinal,
-                                   int32_t aDepth,
-                                   int32_t aIncrement,
-                                   bool aForCounting)
+nsBlockFrame::RenumberChildFrames(int32_t* aOrdinal,
+                                  int32_t aDepth,
+                                  int32_t aIncrement,
+                                  bool aForCounting)
 {
   // Examine each line in the block
   bool foundValidLine;
-  nsBlockInFlowLineIterator bifLineIter(aBlockFrame, &foundValidLine);
-  
-  if (!foundValidLine)
+  nsBlockInFlowLineIterator bifLineIter(this, &foundValidLine);
+  if (!foundValidLine) {
     return false;
+  }
 
   bool renumberedABullet = false;
-
   do {
     nsLineList::iterator line = bifLineIter.GetLine();
     nsIFrame* kid = line->mFirstChild;
     int32_t n = line->GetChildCount();
     while (--n >= 0) {
-      bool kidRenumberedABullet = RenumberListsFor(aPresContext, kid, aOrdinal,
-                                                   aDepth, aIncrement,
-                                                   aForCounting);
+      bool kidRenumberedABullet =
+        kid->RenumberFrameAndDescendants(aOrdinal, aDepth, aIncrement, aForCounting);
       if (!aForCounting && kidRenumberedABullet) {
         line->MarkDirty();
         renumberedABullet = true;
       }
       kid = kid->GetNextSibling();
     }
   } while (bifLineIter.Next());
 
   // We need to set NS_FRAME_HAS_DIRTY_CHILDREN bits up the tree between
   // the bullet and the caller of RenumberLists.  But the caller itself
   // has to be responsible for setting the bit itself, since that caller
   // might be making a FrameNeedsReflow call, which requires that the
   // bit not be set yet.
   if (renumberedABullet && aDepth != 0) {
-    aBlockFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+    AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
   }
 
   return renumberedABullet;
 }
 
-bool
-nsBlockFrame::RenumberListsFor(nsPresContext* aPresContext,
-                               nsIFrame* aKid,
-                               int32_t* aOrdinal,
-                               int32_t aDepth,
-                               int32_t aIncrement,
-                               bool aForCounting)
-{
-  NS_PRECONDITION(aPresContext && aKid && aOrdinal, "null params are immoral!");
-
-  // add in a sanity check for absurdly deep frame trees.  See bug 42138
-  if (MAX_DEPTH_FOR_LIST_RENUMBERING < aDepth)
-    return false;
-
-  // if the frame is a placeholder, then get the out of flow frame
-  nsIFrame* kid = nsPlaceholderFrame::GetRealFrameFor(aKid);
-  const nsStyleDisplay* display = kid->StyleDisplay();
-
-  // drill down through any wrappers to the real frame
-  kid = kid->GetContentInsertionFrame();
-
-  // possible there is no content insertion frame
-  if (!kid)
-    return false;
-
-  // Do not renumber list for summary elements.
-  if (HTMLDetailsElement::IsDetailsEnabled()) {
-    HTMLSummaryElement* summary =
-      HTMLSummaryElement::FromContent(kid->GetContent());
-    if (summary && summary->IsMainSummary()) {
-      return false;
-    }
-  }
-
-  bool kidRenumberedABullet = false;
-
-  // If the frame is a list-item and the frame implements our
-  // block frame API then get its bullet and set the list item
-  // ordinal.
-  if (NS_STYLE_DISPLAY_LIST_ITEM == display->mDisplay) {
-    // Make certain that the frame is a block frame in case
-    // something foreign has crept in.
-    nsBlockFrame* listItem = nsLayoutUtils::GetAsBlock(kid);
-    if (listItem) {
-      nsBulletFrame* bullet = listItem->GetBullet();
-      if (bullet) {
-        if (!aForCounting) {
-          bool changed;
-          *aOrdinal = bullet->SetListItemOrdinal(*aOrdinal, &changed, aIncrement);
-          if (changed) {
-            kidRenumberedABullet = true;
-
-            // The ordinal changed - mark the bullet frame, and any
-            // intermediate frames between it and the block (are there
-            // ever any?), dirty.
-            // The calling code will make the necessary FrameNeedsReflow
-            // call for the list ancestor.
-            bullet->AddStateBits(NS_FRAME_IS_DIRTY);
-            nsIFrame *f = bullet;
-            do {
-              nsIFrame *parent = f->GetParent();
-              parent->ChildIsDirty(f);
-              f = parent;
-            } while (f != listItem);
-          }
-        } else {
-          // We're only counting the number of children,
-          // not restyling them. Don't take |value|
-          // into account when incrementing the ordinal
-          // or dirty the bullet.
-          *aOrdinal += aIncrement;
-        }
-      }
-
-      // XXX temporary? if the list-item has child list-items they
-      // should be numbered too; especially since the list-item is
-      // itself (ASSUMED!) not to be a counter-resetter.
-      bool meToo = RenumberListsInBlock(aPresContext, listItem, aOrdinal,
-                                        aDepth + 1, aIncrement,
-                                        aForCounting);
-      if (meToo) {
-        kidRenumberedABullet = true;
-      }
-    }
-  }
-  else if (NS_STYLE_DISPLAY_BLOCK == display->mDisplay) {
-    if (FrameStartsCounterScope(kid)) {
-      // Don't bother recursing into a block frame that is a new
-      // counter scope. Any list-items in there will be handled by
-      // it.
-    }
-    else {
-      // If the display=block element is a block frame then go ahead
-      // and recurse into it, as it might have child list-items.
-      nsBlockFrame* kidBlock = nsLayoutUtils::GetAsBlock(kid);
-      if (kidBlock) {
-        kidRenumberedABullet = RenumberListsInBlock(aPresContext, kidBlock,
-                                                    aOrdinal, aDepth + 1,
-                                                    aIncrement,
-                                                    aForCounting);
-      }
-    }
-  }
-  return kidRenumberedABullet;
-}
-
 void
 nsBlockFrame::ReflowBullet(nsIFrame* aBulletFrame,
                            BlockReflowInput& aState,
                            ReflowOutput& aMetrics,
                            nscoord aLineTop)
 {
   const ReflowInput &rs = aState.mReflowInput;
 
--- a/layout/generic/nsBlockFrame.h
+++ b/layout/generic/nsBlockFrame.h
@@ -514,16 +514,20 @@ public:
       nsIFrame* f = e.get();
       NS_ASSERTION(!(f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT),
         "pushed floats must be at the beginning of the float list");
     }
 #endif
     return false;
   }
 
+  virtual bool RenumberChildFrames(int32_t* aOrdinal,
+                                   int32_t aDepth,
+                                   int32_t aIncrement,
+                                   bool aForCounting) override;
 protected:
 
   /** grab overflow lines from this block's prevInFlow, and make them
     * part of this block's mLines list.
     * @return true if any lines were drained.
     */
   bool DrainOverflowLines();
 
@@ -776,63 +780,16 @@ protected:
                             nsLineBox* aLine,
                             nscoord aDeltaBCoord);
 
   void CheckFloats(BlockReflowInput& aState);
 
   //----------------------------------------
   // List handling kludge
 
-  // If this returns true, the block it's called on should get the
-  // NS_FRAME_HAS_DIRTY_CHILDREN bit set on it by the caller; either directly
-  // if it's already in reflow, or via calling FrameNeedsReflow() to schedule a
-  // reflow.
-  bool RenumberLists(nsPresContext* aPresContext);
-
-  /**
-   * Renumber lists for a single block frame
-   * @param aOrdinal Ordinal number to start counting at.
-   *        Modifies this number for each associated list
-   *        item. Changes in the numbering due to setting
-   *        the |value| attribute are included if |aForCounting|
-   *        is false. This value is both an input and output
-   *        of this function, with the output value being the
-   *        next ordinal number to be used.
-   * @param aIncrement Amount to increase by after visiting each associated
-   *        list item, unless overridden by |value|.
-   * @param aForCounting Whether we are counting the elements or actually
-   *        restyling them. When true, this simply visits all children,
-   *        ignoring |<li value="..">| changes, effectively counting them
-   *        and storing the result in |aOrdinal|. This is useful for
-   *        |<ol reversed>|, where we need to count the number of
-   *        applicable child list elements before numbering. When false,
-   *        this will restyle all applicable descendants, and the next
-   *        ordinal value will be stored in |aOrdinal|, taking into account
-   *        any changes from |<li value="..">|.
-   * @param aDepth Current depth in frame tree from root list element.
-   */
-  static bool RenumberListsInBlock(nsPresContext* aPresContext,
-                                   nsBlockFrame* aBlockFrame,
-                                   int32_t* aOrdinal,
-                                   int32_t aDepth,
-                                   int32_t aIncrement,
-                                   bool aForCounting);
-
-  /**
-   * Renumber the lists for a single frame.
-   * May recurse into RenumberListsInBlock.
-   * See RenumberListsInBlock for description of parameters.
-   */
-  static bool RenumberListsFor(nsPresContext* aPresContext, nsIFrame* aKid,
-                               int32_t* aOrdinal, int32_t aDepth,
-                               int32_t aIncrement,
-                               bool aForCounting);
-
-  static bool FrameStartsCounterScope(nsIFrame* aFrame);
-
   void ReflowBullet(nsIFrame* aBulletFrame,
                     BlockReflowInput& aState,
                     ReflowOutput& aMetrics,
                     nscoord aLineTop);
 
   //----------------------------------------
 
   virtual nsILineIterator* GetLineIterator() override;
--- a/layout/generic/nsContainerFrame.cpp
+++ b/layout/generic/nsContainerFrame.cpp
@@ -2,16 +2,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* base class #1 for rendering objects that have child lists */
 
 #include "nsContainerFrame.h"
 
+#include "mozilla/dom/HTMLDetailsElement.h"
+#include "mozilla/dom/HTMLSummaryElement.h"
 #include "nsAbsoluteContainingBlock.h"
 #include "nsIDocument.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 #include "nsRect.h"
 #include "nsPoint.h"
 #include "nsStyleConsts.h"
 #include "nsView.h"
@@ -1791,16 +1793,223 @@ nsContainerFrame::ResolvedOrientationIsV
       return GetWritingMode().IsVertical();
     case NS_STYLE_ORIENT_BLOCK:
       return !GetWritingMode().IsVertical();
   }
   NS_NOTREACHED("unexpected -moz-orient value");
   return false;
 }
 
+// static
+bool
+nsContainerFrame::FrameStartsCounterScope(nsIFrame* aFrame)
+{
+  nsIContent* content = aFrame->GetContent();
+  if (!content || !content->IsHTMLElement())
+    return false;
+
+  nsIAtom* localName = content->NodeInfo()->NameAtom();
+  return localName == nsGkAtoms::ol ||
+         localName == nsGkAtoms::ul ||
+         localName == nsGkAtoms::dir ||
+         localName == nsGkAtoms::menu;
+}
+
+bool
+nsContainerFrame::RenumberList()
+{
+  if (!FrameStartsCounterScope(this)) {
+    // If this frame doesn't start a counter scope then we don't need
+    // to renumber child list items.
+    return false;
+  }
+
+  MOZ_ASSERT(mContent->IsHTMLElement(),
+             "FrameStartsCounterScope should only return true for HTML elements");
+
+  // Setup initial list ordinal value
+  // XXX Map html's start property to counter-reset style
+  int32_t ordinal = 1;
+  int32_t increment;
+  if (mContent->IsHTMLElement(nsGkAtoms::ol) &&
+      mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::reversed)) {
+    increment = -1;
+  } else {
+    increment = 1;
+  }
+
+  nsGenericHTMLElement* hc = nsGenericHTMLElement::FromContent(mContent);
+  // Must be non-null, since FrameStartsCounterScope only returns true
+  // for HTML elements.
+  MOZ_ASSERT(hc, "How is mContent not HTML?");
+  const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::start);
+  nsContainerFrame* fif = static_cast<nsContainerFrame*>(FirstInFlow());
+  if (attr && attr->Type() == nsAttrValue::eInteger) {
+    ordinal = attr->GetIntegerValue();
+  } else if (increment < 0) {
+    // <ol reversed> case, or some other case with a negative increment: count
+    // up the child list
+    ordinal = 0;
+    fif->RenumberChildFrames(&ordinal, 0, -increment, true);
+  }
+
+  return fif->RenumberChildFrames(&ordinal, 0, increment, false);
+}
+
+// add in a sanity check for absurdly deep frame trees.  See bug 42138
+// can't just use IsFrameTreeTooDeep() because that method has side effects we don't want
+#define MAX_DEPTH_FOR_LIST_RENUMBERING 200  // 200 open displayable tags is pretty unrealistic
+
+bool
+nsContainerFrame::RenumberFrameAndDescendants(int32_t* aOrdinal,
+                                              int32_t aDepth,
+                                              int32_t aIncrement,
+                                              bool aForCounting)
+{
+  NS_PRECONDITION(aOrdinal, "null params are immoral!");
+
+  // add in a sanity check for absurdly deep frame trees.  See bug 42138
+  if (MAX_DEPTH_FOR_LIST_RENUMBERING < aDepth) {
+    return false;
+  }
+  const nsStyleDisplay* display = StyleDisplay();
+
+  // drill down through any wrappers to the real frame
+  nsIFrame* kid = GetContentInsertionFrame();
+  if (!kid) {
+    return false;
+  }
+
+  // Do not renumber list for summary elements.
+  if (HTMLDetailsElement::IsDetailsEnabled()) {
+    HTMLSummaryElement* summary =
+      HTMLSummaryElement::FromContent(kid->GetContent());
+    if (summary && summary->IsMainSummary()) {
+      return false;
+    }
+  }
+
+  bool kidRenumberedABullet = false;
+
+  // If the frame is a list-item and the frame implements our
+  // block frame API then get its bullet and set the list item
+  // ordinal.
+  if (NS_STYLE_DISPLAY_LIST_ITEM == display->mDisplay) {
+    // Make certain that the frame is a block frame in case
+    // something foreign has crept in.
+    nsBlockFrame* listItem = nsLayoutUtils::GetAsBlock(kid);
+    if (listItem) {
+      nsBulletFrame* bullet = listItem->GetBullet();
+      if (bullet) {
+        if (!aForCounting) {
+          bool changed;
+          *aOrdinal = bullet->SetListItemOrdinal(*aOrdinal, &changed, aIncrement);
+          if (changed) {
+            kidRenumberedABullet = true;
+
+            // The ordinal changed - mark the bullet frame, and any
+            // intermediate frames between it and the block (are there
+            // ever any?), dirty.
+            // The calling code will make the necessary FrameNeedsReflow
+            // call for the list ancestor.
+            bullet->AddStateBits(NS_FRAME_IS_DIRTY);
+            nsIFrame *f = bullet;
+            do {
+              nsIFrame *parent = f->GetParent();
+              parent->ChildIsDirty(f);
+              f = parent;
+            } while (f != listItem);
+          }
+        } else {
+          // We're only counting the number of children,
+          // not restyling them. Don't take |value|
+          // into account when incrementing the ordinal
+          // or dirty the bullet.
+          *aOrdinal += aIncrement;
+        }
+      }
+
+      // XXX temporary? if the list-item has child list-items they
+      // should be numbered too; especially since the list-item is
+      // itself (ASSUMED!) not to be a counter-resetter.
+      bool meToo = listItem->RenumberChildFrames(aOrdinal, aDepth + 1,
+                                                 aIncrement, aForCounting);
+      if (meToo) {
+        kidRenumberedABullet = true;
+      }
+    }
+  } else if (display->mDisplay == NS_STYLE_DISPLAY_BLOCK ||
+             display->mDisplay == NS_STYLE_DISPLAY_FLEX ||
+             display->mDisplay == NS_STYLE_DISPLAY_GRID) {
+    if (FrameStartsCounterScope(kid)) {
+      // Don't bother recursing into a frame that is a new counter scope.
+      // Any list-items in there will be handled by it.
+    } else {
+      nsContainerFrame* container = do_QueryFrame(kid);
+      if (container) {
+        kidRenumberedABullet =
+          container->RenumberChildFrames(aOrdinal, aDepth + 1,
+                                         aIncrement, aForCounting);
+      }
+    }
+  }
+  return kidRenumberedABullet;
+}
+
+bool
+nsContainerFrame::RenumberChildFrames(int32_t* aOrdinal,
+                                      int32_t aDepth,
+                                      int32_t aIncrement,
+                                      bool aForCounting)
+{
+  bool renumbered = false;
+  for (auto kid : mFrames) {
+    bool kidRenumbered =
+      kid->RenumberFrameAndDescendants(aOrdinal, aDepth, aIncrement, aForCounting);
+    if (!aForCounting && kidRenumbered) {
+      renumbered = true;
+    }
+  }
+
+  // We need to set NS_FRAME_HAS_DIRTY_CHILDREN bits up the tree between
+  // the bullet and the caller of RenumberLists.  But the caller itself
+  // has to be responsible for setting the bit itself, since that caller
+  // might be making a FrameNeedsReflow call, which requires that the
+  // bit not be set yet.
+  if (renumbered && aDepth != 0) {
+    AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+  }
+
+  return renumbered;
+}
+
+nsresult
+nsContainerFrame::AttributeChanged(int32_t         aNameSpaceID,
+                                   nsIAtom*        aAttribute,
+                                   int32_t         aModType)
+{
+  nsresult rv = nsSplittableFrame::AttributeChanged(aNameSpaceID,
+                                                    aAttribute, aModType);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (nsGkAtoms::start == aAttribute ||
+      (nsGkAtoms::reversed == aAttribute &&
+       mContent->IsHTMLElement(nsGkAtoms::ol))) {
+
+    // XXX Not sure if this is necessary anymore
+    if (RenumberList()) {
+      PresContext()->PresShell()->
+        FrameNeedsReflow(this, nsIPresShell::eStyleChange,
+                         NS_FRAME_HAS_DIRTY_CHILDREN);
+    }
+  }
+  return rv;
+}
+
 nsOverflowContinuationTracker::nsOverflowContinuationTracker(nsContainerFrame* aFrame,
                                                              bool              aWalkOOFFrames,
                                                              bool              aSkipOverflowContainerChildren)
   : mOverflowContList(nullptr),
     mPrevOverflowCont(nullptr),
     mSentry(nullptr),
     mParent(aFrame),
     mSkipOverflowContainerChildren(aSkipOverflowContainerChildren),
--- a/layout/generic/nsContainerFrame.h
+++ b/layout/generic/nsContainerFrame.h
@@ -64,17 +64,21 @@ public:
   virtual void GetChildLists(nsTArray<ChildList>* aLists) const override;
   virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
   virtual void ChildIsDirty(nsIFrame* aChild) override;
 
   virtual bool IsLeaf() const override;
   virtual FrameSearchResult PeekOffsetNoAmount(bool aForward, int32_t* aOffset) override;
   virtual FrameSearchResult PeekOffsetCharacter(bool aForward, int32_t* aOffset,
                                      bool aRespectClusters = true) override;
-  
+
+  virtual nsresult AttributeChanged(int32_t         aNameSpaceID,
+                                    nsIAtom*        aAttribute,
+                                    int32_t         aModType) override;
+
 #ifdef DEBUG_FRAME_DUMP
   void List(FILE* out = stderr, const char* aPrefix = "", uint32_t aFlags = 0) const override;
 #endif  
 
   // nsContainerFrame methods
 
   /**
    * Called to set the initial list of frames. This happens after the frame
@@ -455,16 +459,62 @@ public:
   static void PlaceFrameView(nsIFrame* aFrame)
   {
     if (aFrame->HasView())
       nsContainerFrame::PositionFrameView(aFrame);
     else
       nsContainerFrame::PositionChildViews(aFrame);
   }
 
+  static bool FrameStartsCounterScope(nsIFrame* aFrame);
+
+  /**
+   * Renumber the list of the counter scope started by this frame, if any.
+   * If this returns true, the frame it's called on should get the
+   * NS_FRAME_HAS_DIRTY_CHILDREN bit set on it by the caller; either directly
+   * if it's already in reflow, or via calling FrameNeedsReflow() to schedule
+   * a reflow.
+   */
+  bool RenumberList();
+
+  /**
+   * Renumber this frame if it's a list-item, then call RenumberChildFrames.
+   * @param aOrdinal Ordinal number to start counting at.
+   *        Modifies this number for each associated list
+   *        item. Changes in the numbering due to setting
+   *        the |value| attribute are included if |aForCounting|
+   *        is false. This value is both an input and output
+   *        of this function, with the output value being the
+   *        next ordinal number to be used.
+   * @param aDepth Current depth in frame tree from root list element.
+   * @param aIncrement Amount to increase by after visiting each associated
+   *        list item, unless overridden by |value|.
+   * @param aForCounting Whether we are counting the elements or actually
+   *        restyling them. When true, this simply visits all children,
+   *        ignoring |<li value="..">| changes, effectively counting them
+   *        and storing the result in |aOrdinal|. This is useful for
+   *        |<ol reversed>|, where we need to count the number of
+   *        applicable child list elements before numbering. When false,
+   *        this will restyle all applicable descendants, and the next
+   *        ordinal value will be stored in |aOrdinal|, taking into account
+   *        any changes from |<li value="..">|.
+   */
+  bool RenumberFrameAndDescendants(int32_t* aOrdinal,
+                                   int32_t aDepth,
+                                   int32_t aIncrement,
+                                   bool aForCounting) override;
+  /**
+   * Renumber the child frames using RenumberFrameAndDescendants.
+   * See RenumberFrameAndDescendants for description of parameters.
+   */
+  virtual bool RenumberChildFrames(int32_t* aOrdinal,
+                                   int32_t aDepth,
+                                   int32_t aIncrement,
+                                   bool aForCounting);
+
 #define NS_DECLARE_FRAME_PROPERTY_FRAMELIST(prop) \
   NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(prop, nsFrameList)
 
   typedef PropertyDescriptor<nsFrameList> FrameListPropertyDescriptor;
 
   NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowProperty)
   NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowContainersProperty)
   NS_DECLARE_FRAME_PROPERTY_FRAMELIST(ExcessOverflowContainersProperty)
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -3845,16 +3845,18 @@ nsFlexContainerFrame::Reflow(nsPresConte
   if (!HasAnyStateBits(NS_STATE_FLEX_CHILDREN_REORDERED)) {
     if (SortChildrenIfNeeded<IsOrderLEQ>()) {
       AddStateBits(NS_STATE_FLEX_CHILDREN_REORDERED);
     }
   } else {
     SortChildrenIfNeeded<IsOrderLEQWithDOMFallback>();
   }
 
+  RenumberList();
+
   const FlexboxAxisTracker axisTracker(this, aReflowInput.GetWritingMode());
 
   // If we're being fragmented into a constrained BSize, then subtract off
   // borderpadding BStart from that constrained BSize, to get the available
   // BSize for our content box. (No need to subtract the borderpadding BStart
   // if we're already skipping it via GetLogicalSkipSides, though.)
   nscoord availableBSizeForContent = aReflowInput.AvailableBSize();
   if (availableBSizeForContent != NS_UNCONSTRAINEDSIZE &&
@@ -4384,16 +4386,18 @@ nsFlexContainerFrame::ReflowFlexItem(nsP
 }
 
 /* virtual */ nscoord
 nsFlexContainerFrame::GetMinISize(nsRenderingContext* aRenderingContext)
 {
   nscoord minWidth = 0;
   DISPLAY_MIN_WIDTH(this, minWidth);
 
+  RenumberList();
+
   const nsStylePosition* stylePos = StylePosition();
   const FlexboxAxisTracker axisTracker(this, GetWritingMode());
 
   for (nsIFrame* childFrame : mFrames) {
     nscoord childMinWidth =
       nsLayoutUtils::IntrinsicForContainer(aRenderingContext, childFrame,
                                            nsLayoutUtils::MIN_ISIZE);
     // For a horizontal single-line flex container, the intrinsic min width is
@@ -4411,16 +4415,18 @@ nsFlexContainerFrame::GetMinISize(nsRend
 }
 
 /* virtual */ nscoord
 nsFlexContainerFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
 {
   nscoord prefWidth = 0;
   DISPLAY_PREF_WIDTH(this, prefWidth);
 
+  RenumberList();
+
   // XXXdholbert Optimization: We could cache our intrinsic widths like
   // nsBlockFrame does (and return it early from this function if it's set).
   // Whenever anything happens that might change it, set it to
   // NS_INTRINSIC_WIDTH_UNKNOWN (like nsBlockFrame::MarkIntrinsicISizesDirty
   // does)
   const FlexboxAxisTracker axisTracker(this, GetWritingMode());
 
   for (nsIFrame* childFrame : mFrames) {
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -5594,16 +5594,18 @@ nsGridContainerFrame::Reflow(nsPresConte
       firstNIF->MergeSortedExcessOverflowContainers(childOCNIFs);
     }
 
     MOZ_ASSERT(foundOwnPushedChild || !items.IsEmpty() || mDidPushItemsBitMayLie,
                "NS_STATE_GRID_DID_PUSH_ITEMS lied");
     ::MergeSortedFrameLists(mFrames, items, GetContent());
   }
 
+  RenumberList();
+
 #ifdef DEBUG
   mDidPushItemsBitMayLie = false;
   SanityCheckGridItemsBeforeReflow();
 #endif // DEBUG
 
   const nsStylePosition* stylePos = aReflowInput.mStylePosition;
   if (!prevInFlow) {
     InitImplicitNamedAreas(stylePos);
@@ -5897,16 +5899,18 @@ nsGridContainerFrame::Reflow(nsPresConte
   FinishAndStoreOverflow(&aDesiredSize);
   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
 }
 
 nscoord
 nsGridContainerFrame::IntrinsicISize(nsRenderingContext* aRenderingContext,
                                      IntrinsicISizeType  aConstraint)
 {
+  RenumberList();
+
   // Calculate the sum of column sizes under aConstraint.
   // http://dev.w3.org/csswg/css-grid/#intrinsic-sizes
   GridReflowInput state(this, *aRenderingContext);
   InitImplicitNamedAreas(state.mGridStyle); // XXX optimize
   LogicalSize indefinite(state.mWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
   Grid grid;
   grid.PlaceGridItems(state, indefinite, indefinite, indefinite);  // XXX optimize
   if (grid.mGridColEnd == 0) {
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -3269,16 +3269,27 @@ public:
     return StyleDisplay()->BackfaceIsHidden();
   }
 
   /**
    * Returns true if the frame is scrolled out of view.
    */
   bool IsScrolledOutOfView();
 
+  /**
+   * If this returns true, the frame it's called on should get the
+   * NS_FRAME_HAS_DIRTY_CHILDREN bit set on it by the caller; either directly
+   * if it's already in reflow, or via calling FrameNeedsReflow() to schedule a
+   * reflow.
+   */
+  virtual bool RenumberFrameAndDescendants(int32_t* aOrdinal,
+                                           int32_t aDepth,
+                                           int32_t aIncrement,
+                                           bool aForCounting) { return false; }
+
 protected:
   // Members
   nsRect           mRect;
   nsIContent*      mContent;
   nsStyleContext*  mStyleContext;
 private:
   nsContainerFrame* mParent;
   nsIFrame*        mNextSibling;  // doubly-linked list of frames
--- a/layout/generic/nsPlaceholderFrame.h
+++ b/layout/generic/nsPlaceholderFrame.h
@@ -134,16 +134,25 @@ public:
     nsIFrame* realFrame = GetRealFrameForPlaceholder(this);
     return realFrame ? realFrame->AccessibleType() :
                        nsFrame::AccessibleType();
   }
 #endif
 
   virtual nsStyleContext* GetParentStyleContext(nsIFrame** aProviderFrame) const override;
 
+  bool RenumberFrameAndDescendants(int32_t* aOrdinal,
+                                   int32_t aDepth,
+                                   int32_t aIncrement,
+                                   bool aForCounting) override
+  {
+    return mOutOfFlowFrame->
+      RenumberFrameAndDescendants(aOrdinal, aDepth, aIncrement, aForCounting);
+  }
+
   /**
    * @return the out-of-flow for aFrame if aFrame is a placeholder; otherwise
    * aFrame
    */
   static nsIFrame* GetRealFrameFor(nsIFrame* aFrame) {
     NS_PRECONDITION(aFrame, "Must have a frame to work with");
     if (aFrame->GetType() == nsGkAtoms::placeholderFrame) {
       return GetRealFrameForPlaceholder(aFrame);
new file mode 100644
--- /dev/null
+++ b/layout/reftests/list-item/numbering-3-ref.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html><head>
+    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+    <title>Reference for testcase #1 for bug 1171419</title>
+<style>
+l { display:block; list-style: decimal inside; }
+</style>
+</head>
+<body>
+
+<ol style="display:grid; list-style-type:none">
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+    <l start="4"><li style="overflow: hidden">List item 4</li></l>
+    <l start="5"><li style="overflow: hidden">List item 5</li></l>
+    <l start="6"><li>List item 6</li></l>
+    <l start="7"><li>List item 7</li></l>
+</ol>
+
+<ol style="display:grid; list-style-type:none">
+<div>
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+</div>
+</ol>
+
+<ol style="display:grid; list-style-type:none">
+<div style="display:grid">
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+</div>
+</ol>
+
+<ol style="display:inline-grid; list-style-type:none">
+<div style="display:grid">
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+</div>
+</ol>
+
+<ol style="display:grid; list-style-type:none">
+    <l start="1"><li style="-moz-column-width:1em; column-width:1em;">item1</li></l>
+    <l start="2"><li style="-moz-column-width:1em; column-width:1em;">item2</li></l>
+    <l start="3"><li style="-moz-column-width:1em; column-width:1em;">item3</li></l>
+</ol>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/list-item/numbering-3.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html><head>
+    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+    <title>Testcase #1 for bug 1171419</title>
+</head>
+<body>
+
+<ol style="list-style: decimal inside; display:grid">
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+    <li style="overflow: hidden">List item 4</li>
+    <li style="overflow: hidden">List item 5</li>
+    <li>List item 6</li>
+    <div style="overflow: hidden"><li>List item 7</li></div>
+</ol>
+
+<ol style="list-style: decimal inside; display:grid">
+<div>
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+</div>
+</ol>
+
+<ol style="list-style: decimal inside; display:grid">
+<div style="display:grid">
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+</div>
+</ol>
+
+<ol style="list-style: decimal inside; display:inline-grid">
+<div style="display:grid">
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+</div>
+</ol>
+
+<ol style="list-style: decimal inside; display:grid">
+    <li style="-moz-column-width:1em; column-width:1em;">item1</li>
+    <li style="-moz-column-width:1em; column-width:1em;">item2</li>
+    <li style="-moz-column-width:1em; column-width:1em;">item3</li>
+</ol>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/list-item/numbering-4-ref.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html><head>
+    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+    <title>Reference for testcase #2 for bug 1171419</title>
+<style>
+l { display:block; list-style: decimal inside; }
+</style>
+</head>
+<body>
+
+<ol style="display:flex; list-style-type:none">
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+    <l start="4"><li style="overflow: hidden">List item 4</li></l>
+    <l start="5"><li style="overflow: hidden">List item 5</li></l>
+    <l start="6"><li>List item 6</li></l>
+    <l start="7"><li>List item 7</li></l>
+</ol>
+
+<ol style="display:flex; list-style-type:none">
+<div>
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+</div>
+</ol>
+
+<ol style="display:flex; list-style-type:none">
+<div style="display:flex">
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+</div>
+</ol>
+
+<ol style="display:inline-flex; list-style-type:none">
+<div style="display:flex">
+    <l start="1"><li style="overflow: hidden">List item 1</li></l>
+    <l start="2"><li style="overflow: hidden">List item 2</li></l>
+    <l start="3"><li>List item 3</li></l>
+</div>
+</ol>
+
+<ol style="display:flex; list-style-type:none">
+    <l start="1"><li style="-moz-column-width:1em; column-width:1em;">item1</li></l>
+    <l start="2"><li style="-moz-column-width:1em; column-width:1em;">item2</li></l>
+    <l start="3"><li style="-moz-column-width:1em; column-width:1em;">item3</li></l>
+</ol>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/list-item/numbering-4.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html><head>
+    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+    <title>Testcase #2 for bug 1171419</title>
+</head>
+<body>
+
+<ol style="list-style: decimal inside; display:flex">
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+    <li style="overflow: hidden">List item 4</li>
+    <li style="overflow: hidden">List item 5</li>
+    <li>List item 6</li>
+    <div style="overflow: hidden"><li>List item 7</li></div>
+</ol>
+
+<ol style="list-style: decimal inside; display:flex">
+<div>
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+</div>
+</ol>
+
+<ol style="list-style: decimal inside; display:flex">
+<div style="display:flex">
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+</div>
+</ol>
+
+<ol style="list-style: decimal inside; display:inline-flex">
+<div style="display:flex">
+    <li style="overflow: hidden">List item 1</li>
+    <li style="overflow: hidden">List item 2</li>
+    <li>List item 3</li>
+</div>
+</ol>
+
+<ol style="list-style: decimal inside; display:flex">
+    <li style="-moz-column-width:1em; column-width:1em;">item1</li>
+    <li style="-moz-column-width:1em; column-width:1em;">item2</li>
+    <li style="-moz-column-width:1em; column-width:1em;">item3</li>
+</ol>
+
+</body>
+</html>
--- a/layout/reftests/list-item/reftest.list
+++ b/layout/reftests/list-item/reftest.list
@@ -1,10 +1,12 @@
 fuzzy-if(OSX>=1008,55,4) == numbering-1.html numbering-1-ref.html
 == numbering-2.html numbering-2-ref.html
+pref(layout.css.grid.enabled,true) fuzzy-if(OSX>=1008,8,1) == numbering-3.html numbering-3-ref.html
+fuzzy-if(OSX>=1008,72,2) == numbering-4.html numbering-4-ref.html
 == ol-reversed-1a.html ol-reversed-1-ref.html
 asserts(1) == ol-reversed-1b.html ol-reversed-1-ref.html # bug 478135
 == ol-reversed-1c.html ol-reversed-1-ref.html
 == ol-reversed-2.html ol-reversed-2-ref.html
 == ol-reversed-3.html ol-reversed-3-ref.html
 == bullet-space-1.html bullet-space-1-ref.html
 == bullet-space-2.html bullet-space-2-ref.html
 == bullet-intrinsic-isize-1.html bullet-intrinsic-isize-1-ref.html
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -221,16 +221,17 @@ geckoview_java_files = [
     'GeckoScreenOrientation.java',
     'GeckoService.java',
     'GeckoSharedPrefs.java',
     'GeckoSmsManager.java',
     'GeckoThread.java',
     'GeckoView.java',
     'GeckoViewChrome.java',
     'GeckoViewContent.java',
+    'GeckoViewFragment.java',
     'gfx/Axis.java',
     'gfx/BitmapUtils.java',
     'gfx/BufferedImage.java',
     'gfx/BufferedImageGLInfo.java',
     'gfx/DisplayPortCalculator.java',
     'gfx/DisplayPortMetrics.java',
     'gfx/DrawTimingQueue.java',
     'gfx/DynamicToolbarAnimator.java',
--- a/mobile/android/base/resources/layout/customtabs_activity.xml
+++ b/mobile/android/base/resources/layout/customtabs_activity.xml
@@ -21,17 +21,18 @@
         android:background="@android:color/transparent">
 
         <RelativeLayout android:id="@+id/gecko_layout"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_below="@+id/tablet_tab_strip"
             android:layout_above="@+id/find_in_page">
 
-            <org.mozilla.gecko.GeckoView android:id="@+id/layer_view"
+            <fragment class="org.mozilla.gecko.GeckoViewFragment"
+                android:id="@+id/layer_view"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:scrollbars="none"/>
 
         </RelativeLayout>
 
     </view>
 
--- a/mobile/android/base/resources/layout/gecko_app.xml
+++ b/mobile/android/base/resources/layout/gecko_app.xml
@@ -21,20 +21,21 @@
          android:background="@android:color/transparent">
 
         <RelativeLayout android:id="@+id/gecko_layout"
                         android:layout_width="match_parent"
                         android:layout_height="match_parent"
                         android:layout_below="@+id/tablet_tab_strip"
                         android:layout_above="@+id/find_in_page">
 
-            <org.mozilla.gecko.GeckoView android:id="@+id/layer_view"
-                                         android:layout_width="match_parent"
-                                         android:layout_height="match_parent"
-                                         android:scrollbars="none"/>
+            <fragment class="org.mozilla.gecko.GeckoViewFragment"
+                      android:id="@+id/layer_view"
+                      android:layout_width="match_parent"
+                      android:layout_height="match_parent"
+                      android:scrollbars="none"/>
 
             <AbsoluteLayout android:id="@+id/plugin_container"
                             android:background="@android:color/transparent"
                             android:layout_width="match_parent"
                             android:layout_height="match_parent"/>
 
             <org.mozilla.gecko.FormAssistPopup android:id="@+id/form_assist_popup"
                                                android:layout_width="match_parent"
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java
@@ -42,16 +42,18 @@ public class GeckoView extends LayerView
     private static final String DEFAULT_SHARED_PREFERENCES_FILE = "GeckoView";
     private static final String LOGTAG = "GeckoView";
 
     private ChromeDelegate mChromeDelegate;
     private ContentDelegate mContentDelegate;
 
     private InputConnectionListener mInputConnectionListener;
 
+    private boolean onAttachedToWindowCalled;
+
     @Override
     public void handleMessage(final String event, final JSONObject message) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 try {
                     if (event.equals("Gecko:Ready")) {
                         handleReady(message);
@@ -202,54 +204,71 @@ public class GeckoView extends LayerView
         stateSaved = true;
         return new StateBinder(superState, this.window);
     }
 
     @Override
     protected void onRestoreInstanceState(final Parcelable state)
     {
         final StateBinder stateBinder = (StateBinder) state;
-        // We have to always call super.onRestoreInstanceState because View keeps
-        // track of these calls and throws an exception when we don't call it.
-        super.onRestoreInstanceState(stateBinder.superState);
 
         if (stateBinder.window != null) {
             this.window = stateBinder.window;
         }
         stateSaved = false;
+
+        if (onAttachedToWindowCalled) {
+            reattachWindow();
+        }
+
+        // We have to always call super.onRestoreInstanceState because View keeps
+        // track of these calls and throws an exception when we don't call it.
+        super.onRestoreInstanceState(stateBinder.superState);
+    }
+
+    private void openWindow() {
+        final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+
+        final String chromeURI = getGeckoInterface().getDefaultChromeURI();
+
+        if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
+            Window.open(window, this, getCompositor(),
+                        chromeURI, metrics.widthPixels, metrics.heightPixels);
+        } else {
+            GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, Window.class,
+                    "open", window, GeckoView.class, this, Object.class, getCompositor(),
+                    String.class, chromeURI, metrics.widthPixels, metrics.heightPixels);
+        }
+    }
+
+    private void reattachWindow() {
+        if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
+            window.reattach(this, getCompositor());
+        } else {
+            GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
+                    window, "reattach", GeckoView.class, this, Object.class, getCompositor());
+        }
     }
 
     @Override
     public void onAttachedToWindow()
     {
         final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
 
         if (window == null) {
             // Open a new nsWindow if we didn't have one from before.
             window = new Window();
-            final String chromeURI = getGeckoInterface().getDefaultChromeURI();
-
-            if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
-                Window.open(window, this, getCompositor(),
-                            chromeURI, metrics.widthPixels, metrics.heightPixels);
-            } else {
-                GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, Window.class,
-                        "open", window, GeckoView.class, this, Object.class, getCompositor(),
-                        String.class, chromeURI, metrics.widthPixels, metrics.heightPixels);
-            }
+            openWindow();
         } else {
-            if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
-                window.reattach(this, getCompositor());
-            } else {
-                GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
-                        window, "reattach", GeckoView.class, this, Object.class, getCompositor());
-            }
+            reattachWindow();
         }
 
         super.onAttachedToWindow();
+
+        onAttachedToWindowCalled = true;
     }
 
     @Override
     public void onDetachedFromWindow()
     {
         super.onDetachedFromWindow();
         super.destroy();
 
@@ -262,16 +281,18 @@ public class GeckoView extends LayerView
             window.close();
             window.disposeNative();
         } else {
             GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
                     window, "close");
             GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
                     window, "disposeNative");
         }
+
+        onAttachedToWindowCalled = false;
     }
 
     @WrapForJNI public static final int LOAD_DEFAULT = 0;
     @WrapForJNI public static final int LOAD_NEW_TAB = 1;
     @WrapForJNI public static final int LOAD_SWITCH_TAB = 2;
 
     public void loadUri(String uri, int flags) {
         if (window == null) {
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoViewFragment.java
@@ -0,0 +1,52 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko;
+
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class GeckoViewFragment extends android.support.v4.app.Fragment {
+    private static final String LOGTAG = "GeckoViewFragment";
+
+    private static Parcelable state = null;
+    private static GeckoViewFragment lastUsed = null;
+    private GeckoView geckoView = null;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        setRetainInstance(true);
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        geckoView = new GeckoView(getContext());
+        return geckoView;
+    }
+
+    @Override
+    public void onResume() {
+        if (state != null && lastUsed != this) {
+            // "Restore" the window from the previously used GeckoView to this GeckoView and attach it
+            geckoView.onRestoreInstanceState(state);
+            state = null;
+        }
+        super.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        state = geckoView.onSaveInstanceState();
+        lastUsed = this;
+        super.onPause();
+    }
+}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -86,19 +86,19 @@ class GeckoLayerClient implements LayerV
      * 3) whenever reading multiple fields from mViewportMetrics without synchronization (i.e. in
      *    case 1 above) you should always first grab a local copy of the reference, and then use
      *    that because mViewportMetrics might get reassigned in between reading the different
      *    fields. */
     private volatile ImmutableViewportMetrics mViewportMetrics;
 
     private volatile boolean mGeckoIsReady;
 
-    private final PanZoomController mPanZoomController;
+    /* package */ final PanZoomController mPanZoomController;
     private final DynamicToolbarAnimator mToolbarAnimator;
-    private final LayerView mView;
+    /* package */ final LayerView mView;
 
     /* This flag is true from the time that browser.js detects a first-paint is about to start,
      * to the time that we receive the first-paint composite notification from the compositor.
      * Note that there is a small race condition with this; if there are two paints that both
      * have the first-paint flag set, and the second paint happens concurrently with the
      * composite for the first paint, then this flag may be set to true prematurely. Fixing this
      * is possible but risky; see https://bugzilla.mozilla.org/show_bug.cgi?id=797615#c751
      */
@@ -137,16 +137,20 @@ class GeckoLayerClient implements LayerV
         mView.setListener(this);
         mContentDocumentIsDisplayed = true;
     }
 
     public void setOverscrollHandler(final Overscroll listener) {
         mPanZoomController.setOverscrollHandler(listener);
     }
 
+    public void setGeckoReady(boolean ready) {
+        mGeckoIsReady = ready;
+    }
+
     @Override // PanZoomTarget
     public boolean isGeckoReady() {
         return mGeckoIsReady;
     }
 
     /** Attaches to root layer so that Gecko appears. */
     @WrapForJNI(calledFrom = "gecko")
     private void onGeckoReady() {
@@ -161,16 +165,17 @@ class GeckoLayerClient implements LayerV
         // Gecko being ready is one of the two conditions (along with having an available
         // surface) that cause us to create the compositor. So here, now that we know gecko
         // is ready, call updateCompositor() to see if we can actually do the creation.
         // This needs to run on the UI thread so that the surface validity can't change on
         // us while we're in the middle of creating the compositor.
         mView.post(new Runnable() {
             @Override
             public void run() {
+                mPanZoomController.attach();
                 mView.updateCompositor();
             }
         });
     }
 
     public void destroy() {
         mPanZoomController.destroy();
         mToolbarAnimator.destroy();
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
@@ -26,16 +26,17 @@ import org.mozilla.gecko.ZoomConstraints
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
+import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.TextureView;
 import android.view.View;
 import android.view.ViewGroup;
@@ -66,16 +67,18 @@ public class LayerView extends ScrollVie
     private float mSurfaceTranslation;
 
     /* This should only be modified on the Java UI thread. */
     private final Overscroll mOverscroll;
 
     private boolean mServerSurfaceValid;
     private int mWidth, mHeight;
 
+    private boolean onAttachedToWindowCalled;
+
     /* This is written by the Gecko thread and the UI thread, and read by the UI thread. */
     @WrapForJNI(stubName = "CompositorCreated", calledFrom = "ui")
     /* package */ volatile boolean mCompositorCreated;
 
     private class Compositor extends JNIObject {
         public Compositor() {
         }
 
@@ -111,16 +114,18 @@ public class LayerView extends ScrollVie
             mCompositorCreated = true;
         }
 
         @WrapForJNI(calledFrom = "gecko")
         private void destroy() {
             // The nsWindow has been closed. First mark our compositor as destroyed.
             LayerView.this.mCompositorCreated = false;
 
+            LayerView.this.mLayerClient.setGeckoReady(false);
+
             // Then clear out any pending calls on the UI thread by disposing on the UI thread.
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     disposeNative();
                 }
             });
         }
@@ -294,16 +299,24 @@ public class LayerView extends ScrollVie
         }
         if (mPanZoomController != null && mPanZoomController.onMotionEvent(event)) {
             return true;
         }
         return false;
     }
 
     @Override
+    protected void onRestoreInstanceState(final Parcelable state) {
+        if (onAttachedToWindowCalled) {
+            attachCompositor();
+        }
+        super.onRestoreInstanceState(state);
+    }
+
+    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
         // We are adding descendants to this LayerView, but we don't want the
         // descendants to affect the way LayerView retains its focus.
         setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
 
         // This check should not be done before the view is attached to a window
@@ -347,16 +360,25 @@ public class LayerView extends ScrollVie
             container.addView(mSurfaceView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
             addView(container, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
 
             SurfaceHolder holder = mSurfaceView.getHolder();
             holder.addCallback(new SurfaceListener());
         }
 
         attachCompositor();
+
+        onAttachedToWindowCalled = true;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        onAttachedToWindowCalled = false;
     }
 
     // Don't expose GeckoLayerClient to things outside this package; only expose it as an Object
     GeckoLayerClient getLayerClient() { return mLayerClient; }
 
     public PanZoomController getPanZoomController() { return mPanZoomController; }
     public DynamicToolbarAnimator getDynamicToolbarAnimator() { return mToolbarAnimator; }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
@@ -138,16 +138,17 @@ class NativePanZoomController extends JN
 
         return handleMouseEvent(event.getActionMasked(), event.getEventTime(), event.getMetaState(), x, y, event.getButtonState());
     }
 
 
     NativePanZoomController(PanZoomTarget target, View view) {
         mTarget = target;
         mView = (LayerView) view;
+        mDestroyed = true;
 
         String[] prefs = { "ui.scrolling.negate_wheel_scroll" };
         mPrefsObserver = new PrefsHelper.PrefHandlerBase() {
             @Override public void prefValue(String pref, boolean value) {
                 if (pref.equals("ui.scrolling.negate_wheel_scroll")) {
                     mNegateWheelScroll = value;
                 }
             }
@@ -203,16 +204,21 @@ class NativePanZoomController extends JN
         }
         if (mDestroyed || !mTarget.isGeckoReady()) {
             return;
         }
         mDestroyed = true;
         disposeNative();
     }
 
+    @Override
+    public void attach() {
+        mDestroyed = false;
+    }
+
     @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") @Override // JNIObject
     protected native void disposeNative();
 
     @Override
     public void setOverscrollHandler(final Overscroll handler) {
         mOverscroll = handler;
     }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomController.java
@@ -19,16 +19,17 @@ public interface PanZoomController {
 
     static class Factory {
         static PanZoomController create(PanZoomTarget target, View view, EventDispatcher dispatcher) {
             return new NativePanZoomController(target, view);
         }
     }
 
     public void destroy();
+    public void attach();
 
     public boolean onTouchEvent(MotionEvent event);
     public boolean onMotionEvent(MotionEvent event);
     public void onMotionEventVelocity(final long aEventTime, final float aSpeedY);
 
     public void setOverscrollHandler(final Overscroll controller);
 
     public void setIsLongpressEnabled(boolean isLongpressEnabled);
--- a/parser/expat/lib/moz_extensions.c
+++ b/parser/expat/lib/moz_extensions.c
@@ -40,27 +40,24 @@ int MOZ_XMLCheckQName(const char* ptr, c
           or we've already seen a colon. */
       if (ns_aware && (nmstrt || *colon || ptr + 2 == end)) {
         return MOZ_EXPAT_MALFORMED;
       }
       *colon = ptr;
       nmstrt = ns_aware; /* e.g. "a:0" should be valid if !ns_aware */
       break;
     case BT_NONASCII:
-      if (nmstrt && !IS_NMSTRT_CHAR_MINBPC(ptr)) {
-        /* If this is a valid name character and we're namespace-aware, the
-           QName is malformed.  Otherwise, this character's invalid at the
-           start of a name (or, if we're namespace-aware, at the start of a
-           localpart). */
-        return (IS_NAME_CHAR_MINBPC(ptr) && ns_aware) ?
-               MOZ_EXPAT_MALFORMED :
-               MOZ_EXPAT_INVALID_CHARACTER;
+      if (!IS_NAME_CHAR_MINBPC(ptr) ||
+          (nmstrt && !*colon && !IS_NMSTRT_CHAR_MINBPC(ptr))) {
+        return MOZ_EXPAT_INVALID_CHARACTER;
       }
-      if (!IS_NAME_CHAR_MINBPC(ptr)) {
-        return MOZ_EXPAT_INVALID_CHARACTER;
+      if (nmstrt && *colon && !IS_NMSTRT_CHAR_MINBPC(ptr)) {
+        /* If a non-starting character like a number is right after the colon,
+           this is a namespace error, not invalid character */
+        return MOZ_EXPAT_MALFORMED;
       }
       nmstrt = 0;
       break;
     case BT_NMSTRT:
     case BT_HEX:
       nmstrt = 0;
       break;
     case BT_DIGIT:
--- a/services/common/kinto-offline-client.js
+++ b/services/common/kinto-offline-client.js
@@ -15,17 +15,17 @@
 
 /*
  * This file is generated from kinto.js - do not modify directly.
  */
 
 this.EXPORTED_SYMBOLS = ["loadKinto"];
 
 /*
- * Version 4.0.2 - ef8a96f
+ * Version 4.0.3 - 8100433
  */
 
 (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.loadKinto = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
 "use strict";
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
@@ -2639,17 +2639,17 @@ function pFinally(promise, fn) {
  */
 function deepEqual(a, b) {
   if (a === b) {
     return true;
   }
   if (typeof a !== typeof b) {
     return false;
   }
-  if (!(a instanceof Object) || !(b instanceof Object)) {
+  if (!(a && typeof a == "object") || !(b && typeof b == "object")) {
     return false;
   }
   if (Object.keys(a).length !== Object.keys(b).length) {
     return false;
   }
   for (let k in a) {
     if (!deepEqual(a[k], b[k])) {
       return false;
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -1,12 +1,20 @@
 {
   "items": {
     "manual": [
       {
+        "path": "dummy.xml",
+        "url": "/dummy.xml"
+      },
+      {
+        "path": "dummy.xhtml",
+        "url": "/dummy.xhtml"
+      },
+      {
         "path": "2dcontext/conformance-requirements/2d.coordinatespace-manual.html",
         "url": "/2dcontext/conformance-requirements/2d.coordinatespace-manual.html"
       },
       {
         "path": "2dcontext/drawing-paths-to-the-canvas/canvas_focus_drawFocusIfNeeded_AAPI_001-manual.html",
         "url": "/2dcontext/drawing-paths-to-the-canvas/canvas_focus_drawFocusIfNeeded_AAPI_001-manual.html"
       },
       {
--- a/testing/web-platform/meta/dom/historical.html.ini
+++ b/testing/web-platform/meta/dom/historical.html.ini
@@ -1,14 +1,18 @@
 [historical.html]
   type: testharness
   [Historical DOM features must be removed: CDATASection]
     expected: FAIL
+    bug: 660660
 
   [Historical DOM features must be removed: DOMError]
     expected: FAIL
+    bug: 1120178
 
   [Historical DOM features must be removed: createCDATASection]
     expected: FAIL
+    bug: 660660
 
   [Node member must be nuked: rootNode]
     expected: FAIL
+    bug: 1269155
 
--- a/testing/web-platform/meta/dom/interfaces.html.ini
+++ b/testing/web-platform/meta/dom/interfaces.html.ini
@@ -1,25 +1,28 @@
 [interfaces.html]
   type: testharness
   prefs: [dom.node.rootNode.enabled:true]
   [MutationObserver interface: operation observe(Node,MutationObserverInit)]
     expected: FAIL
+    bug: https://github.com/whatwg/dom/issues/316
 
   [Document interface: attribute origin]
     expected: FAIL
+    bug: 931884
 
   [Document interface: operation query(DOMString)]
     expected: FAIL
 
   [Document interface: operation queryAll(DOMString)]
     expected: FAIL
 
   [Document interface: xmlDoc must inherit property "origin" with the proper type (3)]
     expected: FAIL
+    bug: 931884
 
   [Document interface: xmlDoc must inherit property "query" with the proper type (34)]
     expected: FAIL
 
   [Document interface: calling query(DOMString) on xmlDoc with too few arguments must throw TypeError]
     expected: FAIL
 
   [Document interface: xmlDoc must inherit property "queryAll" with the proper type (35)]
@@ -73,31 +76,33 @@
   [Attr interface: attribute nodeValue]
     expected: FAIL
 
   [Attr interface: attribute textContent]
     expected: FAIL
 
   [NodeFilter interface: existence and properties of interface object]
     expected: FAIL
+    bug: https://github.com/heycam/webidl/issues/96
 
   [Document interface: xmlDoc must inherit property "query" with the proper type (35)]
     expected: FAIL
 
   [Document interface: xmlDoc must inherit property "queryAll" with the proper type (36)]
     expected: FAIL
 
   [Element interface: element must inherit property "query" with the proper type (36)]
     expected: FAIL
 
   [Element interface: element must inherit property "queryAll" with the proper type (37)]
     expected: FAIL
 
   [Document interface: new Document() must inherit property "origin" with the proper type (3)]
     expected: FAIL
+    bug: 931884
 
   [Document interface: new Document() must inherit property "query" with the proper type (35)]
     expected: FAIL
 
   [Document interface: calling query(DOMString) on new Document() with too few arguments must throw TypeError]
     expected: FAIL
 
   [Document interface: new Document() must inherit property "queryAll" with the proper type (36)]
--- a/testing/web-platform/meta/dom/nodes/DOMImplementation-createDocument.html.ini
+++ b/testing/web-platform/meta/dom/nodes/DOMImplementation-createDocument.html.ini
@@ -1,17 +1,22 @@
 [DOMImplementation-createDocument.html]
   type: testharness
-  [createDocument test 6: null,"̀foo",null,"INVALID_CHARACTER_ERR"]
+  [createDocument test 67: "http://example.com/","a:0",null,"NAMESPACE_ERR"]
     expected: FAIL
+    bug: https://github.com/whatwg/dom/issues/319
 
-  [createDocument test 172: metadata for null,null,DocumentType node]
+  [createDocument test 197: metadata for null,null,DocumentType node]
     expected: FAIL
+    bug: 520969
 
-  [createDocument test 172: null,null,DocumentType node,null]
+  [createDocument test 197: null,null,DocumentType node,null]
     expected: FAIL
-
-  [createDocument test 185: null,"",DocumentType node]
-    expected: FAIL
+    bug: 520969
 
-  [createDocument test 186: null,"",DocumentType node]
+  [createDocument test 210: null,"",DocumentType node]
     expected: FAIL
+    bug: 520969
 
+  [createDocument test 211: null,"",DocumentType node]
+    expected: FAIL
+    bug: 520969
+
--- a/testing/web-platform/meta/dom/nodes/Document-createElementNS.html.ini
+++ b/testing/web-platform/meta/dom/nodes/Document-createElementNS.html.ini
@@ -1,5 +1,14 @@
 [Document-createElementNS.html]
   type: testharness
-  [createElementNS test 6: null,"̀foo","INVALID_CHARACTER_ERR"]
+  [createElementNS test 67 in HTML document: "http://example.com/","a:0","NAMESPACE_ERR"]
+    bug: https://github.com/whatwg/dom/issues/319
     expected: FAIL
 
+  [createElementNS test 67 in XML document: "http://example.com/","a:0","NAMESPACE_ERR"]
+    bug: https://github.com/whatwg/dom/issues/319
+    expected: FAIL
+
+  [createElementNS test 67 in XHTML document: "http://example.com/","a:0","NAMESPACE_ERR"]
+    bug: https://github.com/whatwg/dom/issues/319
+    expected: FAIL
+
--- a/testing/web-platform/meta/dom/nodes/Element-classlist.html.ini
+++ b/testing/web-platform/meta/dom/nodes/Element-classlist.html.ini
@@ -1,28 +1,36 @@
 [Element-classlist.html]
   type: testharness
   [classList must be correct for an element that has classes]
     expected: FAIL
+    bug: https://github.com/whatwg/dom/issues/105
+    bug: 869788
 
   [empty classList should return the empty string since the ordered set parser skip the whitespaces]
     expected: FAIL
 
   [classList.remove must collapse whitespaces around each token and remove duplicates]
     expected: FAIL
+    bug: https://github.com/whatwg/dom/issues/105
+    bug: 869788
 
   [classList.add must collapse whitespaces and remove duplicates when adding a token that already exists]
     expected: FAIL
+    bug: https://github.com/whatwg/dom/issues/105
+    bug: 869788
 
   [classList.add should treat \\t as a space]
     expected: FAIL
 
   [classList.add should treat \\r as a space]
     expected: FAIL
 
   [classList.add should treat \\n as a space]
     expected: FAIL
 
   [classList.add should treat \\f as a space]
     expected: FAIL
 
   [classList.replace must collapse whitespaces around each token and remove duplicates]
     expected: FAIL
+    bug: https://github.com/whatwg/dom/issues/105
+    bug: 869788
--- a/testing/web-platform/tests/dom/nodes/Document-createElement.html
+++ b/testing/web-platform/tests/dom/nodes/Document-createElement.html
@@ -4,82 +4,154 @@
 <link rel=help href="https://dom.spec.whatwg.org/#dom-document-createelement">
 <link rel=help href="https://dom.spec.whatwg.org/#dom-element-localname">
 <link rel=help href="https://dom.spec.whatwg.org/#dom-element-tagname">
 <link rel=help href="https://dom.spec.whatwg.org/#dom-element-prefix">
 <link rel=help href="https://dom.spec.whatwg.org/#dom-element-namespaceuri">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
+<iframe src="/dummy.xml"></iframe>
+<iframe src="/dummy.xhtml"></iframe>
 <script>
 function toASCIIUppercase(str) {
   var diff = "a".charCodeAt(0) - "A".charCodeAt(0);
   var res = "";
   for (var i = 0; i < str.length; ++i) {
     if ("a" <= str[i] && str[i] <= "z") {
       res += String.fromCharCode(str.charCodeAt(i) - diff);
     } else {
       res += str[i];
     }
   }
   return res;
 }
-test(function() {
-  var HTMLNS = "http://www.w3.org/1999/xhtml",
-      valid = [
-        //[input, localName],
-        [undefined, "undefined"],
-        [null, "null"],
-        ["foo", "foo"],
-        ["f1oo", "f1oo"],
-        ["foo1", "foo1"],
-        ["f\u0300oo", "f\u0300oo"],
-        ["foo\u0300", "foo\u0300"],
-        [":foo", ":foo"],
-        ["f:oo", "f:oo"],
-        ["foo:", "foo:"],
-        ["xml", "xml"],
-        ["xmlns", "xmlns"],
-        ["xmlfoo", "xmlfoo"],
-        ["xml:foo", "xml:foo"],
-        ["xmlns:foo", "xmlns:foo"],
-        ["xmlfoo:bar", "xmlfoo:bar"],
-        ["svg", "svg"],
-        ["math", "math"],
-        ["FOO", "foo"],
-        ["mar\u212a", "mar\u212a"],
-        ["\u0130nput", "\u0130nput"],
-        ["\u0131nput", "\u0131nput"]
-     ],
-     invalid = [
-       "",
-       "1foo",
-       "\u0300foo",
-       "}foo",
-       "f}oo",
-       "foo}",
-       "\ufffffoo",
-       "f\uffffoo",
-       "foo\uffff",
-       "<foo",
-       "foo>",
-       "<foo>",
-       "f<oo"
-     ]
+function toASCIILowercase(str) {
+  var diff = "a".charCodeAt(0) - "A".charCodeAt(0);
+  var res = "";
+  for (var i = 0; i < str.length; ++i) {
+    if ("A" <= str[i] && str[i] <= "Z") {
+      res += String.fromCharCode(str.charCodeAt(i) + diff);
+    } else {
+      res += str[i];
+    }
+  }
+  return res;
+}
+var HTMLNS = "http://www.w3.org/1999/xhtml",
+    valid = [
+      undefined,
+      null,
+      "foo",
+      "f1oo",
+      "foo1",
+      "f\u0BC6",
+      "foo\u0BC6",
+      ":",
+      ":foo",
+      "f:oo",
+      "foo:",
+      "f:o:o",
+      "f::oo",
+      "f::oo:",
+      "foo:0",
+      "foo:_",
+      // combining char after :, invalid QName but valid Name
+      "foo:\u0BC6",
+      "foo:foo\u0BC6",
+      "foo\u0BC6:foo",
+      "xml",
+      "xmlns",
+      "xmlfoo",
+      "xml:foo",
+      "xmlns:foo",
+      "xmlfoo:bar",
+      "svg",
+      "math",
+      "FOO",
+      // Test that non-ASCII chars don't get uppercased/lowercased
+      "mar\u212a",
+      "\u0130nput",
+      "\u0131nput",
+    ],
+    invalid = [
+      "",
+      "1foo",
+      "1:foo",
+      "fo o",
+      "\u0BC6foo",
+      "}foo",
+      "f}oo",
+      "foo}",
+      "\ufffffoo",
+      "f\uffffoo",
+      "foo\uffff",
+      "<foo",
+      "foo>",
+      "<foo>",
+      "f<oo",
+      "-foo",
+      ".foo",
+      "\u0BC6",
+    ]
 
-  valid.forEach(function(t) {
-    test(function() {
-      var elt = document.createElement(t[0])
-      assert_true(elt instanceof Element)
-      assert_true(elt instanceof Node)
-      assert_equals(elt.localName, t[1])
-      assert_equals(elt.tagName, toASCIIUppercase(t[1]))
-      assert_equals(elt.prefix, null)
-      assert_equals(elt.namespaceURI, HTMLNS)
-    }, "createElement(" + format_value(t[0]) + ")");
+var xmlIframe = document.querySelector('[src="/dummy.xml"]');
+var xhtmlIframe = document.querySelector('[src="/dummy.xhtml"]');
+
+function getWin(desc) {
+  if (desc == "HTML document") {
+    return window;
+  }
+  if (desc == "XML document") {
+    assert_equals(xmlIframe.contentDocument.documentElement.textContent,
+                  "Dummy XML document", "XML document didn't load");
+    return xmlIframe.contentWindow;
+  }
+  if (desc == "XHTML document") {
+    assert_equals(xhtmlIframe.contentDocument.documentElement.textContent,
+                  "Dummy XHTML document", "XHTML document didn't load");
+    return xhtmlIframe.contentWindow;
+  }
+}
+
+
+valid.forEach(function(t) {
+  ["HTML document", "XML document", "XHTML document"].forEach(function(desc) {
+    async_test(function(testObj) {
+      window.addEventListener("load", function() {
+        testObj.step(function() {
+          var win = getWin(desc);
+          var doc = win.document;
+          var elt = doc.createElement(t)
+          assert_true(elt instanceof win.Element, "instanceof Element")
+          assert_true(elt instanceof win.Node, "instanceof Node")
+          assert_equals(elt.localName,
+                        desc == "HTML document" ? toASCIILowercase(String(t))
+                                                : String(t),
+                        "localName")
+          assert_equals(elt.tagName,
+                        desc == "HTML document" ? toASCIIUppercase(String(t))
+                                                : String(t),
+                        "tagName")
+          assert_equals(elt.prefix, null, "prefix")
+          assert_equals(elt.namespaceURI,
+                        desc == "XML document" ? null : HTMLNS, "namespaceURI")
+        });
+        testObj.done();
+      });
+    }, "createElement(" + format_value(t) + ") in " + desc);
   });
-  invalid.forEach(function(arg) {
-    test(function() {
-      assert_throws("INVALID_CHARACTER_ERR", function() { document.createElement(arg) })
-    }, "createElement(" + format_value(arg) + ")");
+});
+invalid.forEach(function(arg) {
+  ["HTML document", "XML document", "XHTML document"].forEach(function(desc) {
+    async_test(function(testObj) {
+      window.addEventListener("load", function() {
+        testObj.step(function() {
+          var doc = getWin(desc).document;
+          assert_throws("InvalidCharacterError",
+                        function() { doc.createElement(arg) })
+        });
+        testObj.done();
+      });
+    }, "createElement(" + format_value(arg) + ") in " + desc);
   });
-})
+});
 </script>
--- a/testing/web-platform/tests/dom/nodes/Document-createElementNS.html
+++ b/testing/web-platform/tests/dom/nodes/Document-createElementNS.html
@@ -1,70 +1,98 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Document.createElementNS</title>
 <link rel=help href="https://dom.spec.whatwg.org/#dom-document-createelementns">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="Document-createElementNS.js"></script>
 <div id="log"></div>
+<iframe src="/dummy.xml"></iframe>
+<iframe src="/dummy.xhtml"></iframe>
 <script>
-test(function() {
-  var tests = createElementNS_tests.concat([
-    /* Arrays with three elements:
-     *   the namespace argument
-     *   the qualifiedName argument
-     *   the expected exception, or null if none
-     */
-    ["", "", "INVALID_CHARACTER_ERR"],
-    [null, null, null],
-    [null, "", "INVALID_CHARACTER_ERR"],
-    [undefined, null, null],
-    [undefined, undefined, null],
-    [undefined, "", "INVALID_CHARACTER_ERR"],
-    ["http://example.com/", null, null],
-    ["http://example.com/", "", "INVALID_CHARACTER_ERR"],
-    ["/", null, null],
-    ["/", "", "INVALID_CHARACTER_ERR"],
-    ["http://www.w3.org/XML/1998/namespace", null, null],
-    ["http://www.w3.org/XML/1998/namespace", "", "INVALID_CHARACTER_ERR"],
-    ["http://www.w3.org/2000/xmlns/", null, "NAMESPACE_ERR"],
-    ["http://www.w3.org/2000/xmlns/", "", "INVALID_CHARACTER_ERR"],
-    ["foo:", null, null],
-    ["foo:", "", "INVALID_CHARACTER_ERR"],
-  ])
+var tests = createElementNS_tests.concat([
+  /* Arrays with three elements:
+   *   the namespace argument
+   *   the qualifiedName argument
+   *   the expected exception, or null if none
+   */
+  ["", "", "INVALID_CHARACTER_ERR"],
+  [null, null, null],
+  [null, "", "INVALID_CHARACTER_ERR"],
+  [undefined, null, null],
+  [undefined, undefined, null],
+  [undefined, "", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", null, null],
+  ["http://example.com/", "", "INVALID_CHARACTER_ERR"],
+  ["/", null, null],
+  ["/", "", "INVALID_CHARACTER_ERR"],
+  ["http://www.w3.org/XML/1998/namespace", null, null],
+  ["http://www.w3.org/XML/1998/namespace", "", "INVALID_CHARACTER_ERR"],
+  ["http://www.w3.org/2000/xmlns/", null, "NAMESPACE_ERR"],
+  ["http://www.w3.org/2000/xmlns/", "", "INVALID_CHARACTER_ERR"],
+  ["foo:", null, null],
+  ["foo:", "", "INVALID_CHARACTER_ERR"],
+])
+
+var xmlIframe = document.querySelector('[src="/dummy.xml"]');
+var xhtmlIframe = document.querySelector('[src="/dummy.xhtml"]');
 
-  tests.forEach(function(t, i) {
-    test(function() {
-      var namespace = t[0], qualifiedName = t[1], expected = t[2]
-      if (expected != null) {
-        assert_throws(expected, function() { document.createElementNS(namespace, qualifiedName) })
-      } else {
-        var element = document.createElementNS(namespace, qualifiedName)
-        assert_not_equals(element, null)
-        assert_equals(element.nodeType, Node.ELEMENT_NODE)
-        assert_equals(element.nodeType, element.ELEMENT_NODE)
-        assert_equals(element.nodeValue, null)
-        assert_equals(element.ownerDocument, document)
-        var qualified = String(qualifiedName), names = []
-        if (qualified.indexOf(":") >= 0) {
-          names = qualified.split(":", 2)
+function runTest(t, i, desc) {
+  async_test(function(testObj) {
+    window.addEventListener("load", function() {
+      testObj.step(function() {
+        var doc;
+        if (desc == "HTML document") {
+          doc = document;
+        } else if (desc == "XML document") {
+          doc = xmlIframe.contentDocument;
+          // Make sure we're testing the right document
+          assert_equals(doc.documentElement.textContent, "Dummy XML document");
+        } else if (desc == "XHTML document") {
+          doc = xhtmlIframe.contentDocument;
+          assert_equals(doc.documentElement.textContent, "Dummy XHTML document");
+        }
+        var namespace = t[0], qualifiedName = t[1], expected = t[2]
+        if (expected != null) {
+          assert_throws(expected, function() { doc.createElementNS(namespace, qualifiedName) })
         } else {
-          names = [null, qualified]
+          var element = doc.createElementNS(namespace, qualifiedName)
+          assert_not_equals(element, null)
+          assert_equals(element.nodeType, Node.ELEMENT_NODE)
+          assert_equals(element.nodeType, element.ELEMENT_NODE)
+          assert_equals(element.nodeValue, null)
+          assert_equals(element.ownerDocument, doc)
+          var qualified = String(qualifiedName), names = []
+          if (qualified.indexOf(":") >= 0) {
+            names = qualified.split(":", 2)
+          } else {
+            names = [null, qualified]
+          }
+          assert_equals(element.prefix, names[0])
+          assert_equals(element.localName, names[1])
+          assert_equals(element.tagName, qualified)
+          assert_equals(element.nodeName, qualified)
+          assert_equals(element.namespaceURI,
+                        namespace === undefined || namespace === "" ? null
+                                                                    : namespace)
         }
-        assert_equals(element.prefix, names[0])
-        assert_equals(element.localName, names[1])
-        assert_equals(element.tagName, qualified)
-        assert_equals(element.nodeName, qualified)
-        assert_equals(element.namespaceURI, namespace === undefined ? null : namespace)
-      }
-    }, "createElementNS test " + i + ": " + t.map(function(el) { return format_value(el) }))
-  })
+      });
+      testObj.done();
+    });
+  }, "createElementNS test " + i + " in " + desc + ": " + t.map(format_value))
+}
+
+tests.forEach(function(t, i) {
+  runTest(t, i, "HTML document")
+  runTest(t, i, "XML document")
+  runTest(t, i, "XHTML document")
 })
 
+
 test(function() {
   var HTMLNS = "http://www.w3.org/1999/xhtml";
   var element = document.createElementNS(HTMLNS, "span");
   assert_equals(element.namespaceURI, HTMLNS);
   assert_equals(element.prefix, null);
   assert_equals(element.localName, "span");
   assert_equals(element.tagName, "SPAN");
   assert_true(element instanceof Node, "Should be a Node");
--- a/testing/web-platform/tests/dom/nodes/Document-createElementNS.js
+++ b/testing/web-platform/tests/dom/nodes/Document-createElementNS.js
@@ -1,67 +1,92 @@
 var createElementNS_tests = [
   /* Arrays with three elements:
    *   the namespace argument
    *   the qualifiedName argument
    *   the expected exception, or null if none
    */
+  [null, null, null],
   [null, undefined, null],
   [null, "foo", null],
   [null, "1foo", "INVALID_CHARACTER_ERR"],
   [null, "f1oo", null],
   [null, "foo1", null],
   [null, "1foo", "INVALID_CHARACTER_ERR"],
-  [null, "\u0300foo", "INVALID_CHARACTER_ERR"],
+  [null, "\u0BC6foo", "INVALID_CHARACTER_ERR"],
   [null, "}foo", "INVALID_CHARACTER_ERR"],
   [null, "f}oo", "INVALID_CHARACTER_ERR"],
   [null, "foo}", "INVALID_CHARACTER_ERR"],
   [null, "\uFFFFfoo", "INVALID_CHARACTER_ERR"],
   [null, "f\uFFFFoo", "INVALID_CHARACTER_ERR"],
   [null, "foo\uFFFF", "INVALID_CHARACTER_ERR"],
   [null, "<foo", "INVALID_CHARACTER_ERR"],
   [null, "foo>", "INVALID_CHARACTER_ERR"],
   [null, "<foo>", "INVALID_CHARACTER_ERR"],
   [null, "f<oo", "INVALID_CHARACTER_ERR"],
   [null, "^^", "INVALID_CHARACTER_ERR"],
+  [null, "fo o", "INVALID_CHARACTER_ERR"],
+  [null, "-foo", "INVALID_CHARACTER_ERR"],
+  [null, ".foo", "INVALID_CHARACTER_ERR"],
   [null, ":foo", "NAMESPACE_ERR"],
   [null, "f:oo", "NAMESPACE_ERR"],
   [null, "foo:", "NAMESPACE_ERR"],
+  [null, "f:o:o", "NAMESPACE_ERR"],
   [null, ":", "NAMESPACE_ERR"],
   [null, "xml", null],
   [null, "xmlns", "NAMESPACE_ERR"],
   [null, "xmlfoo", null],
   [null, "xml:foo", "NAMESPACE_ERR"],
   [null, "xmlns:foo", "NAMESPACE_ERR"],
   [null, "xmlfoo:bar", "NAMESPACE_ERR"],
   [null, "null:xml", "NAMESPACE_ERR"],
+  ["", null, null],
   ["", ":foo", "NAMESPACE_ERR"],
   ["", "f:oo", "NAMESPACE_ERR"],
   ["", "foo:", "NAMESPACE_ERR"],
+  [undefined, null, null],
   [undefined, undefined, null],
   [undefined, "foo", null],
   [undefined, "1foo", "INVALID_CHARACTER_ERR"],
   [undefined, "f1oo", null],
   [undefined, "foo1", null],
   [undefined, ":foo", "NAMESPACE_ERR"],
   [undefined, "f:oo", "NAMESPACE_ERR"],
   [undefined, "foo:", "NAMESPACE_ERR"],
+  [undefined, "f::oo", "NAMESPACE_ERR"],
   [undefined, "xml", null],
   [undefined, "xmlns", "NAMESPACE_ERR"],
   [undefined, "xmlfoo", null],
   [undefined, "xml:foo", "NAMESPACE_ERR"],
   [undefined, "xmlns:foo", "NAMESPACE_ERR"],
   [undefined, "xmlfoo:bar", "NAMESPACE_ERR"],
   ["http://example.com/", "foo", null],
   ["http://example.com/", "1foo", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", "<foo>", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", "fo<o", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", "-foo", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", ".foo", "INVALID_CHARACTER_ERR"],
   ["http://example.com/", "f1oo", null],
   ["http://example.com/", "foo1", null],
   ["http://example.com/", ":foo", "NAMESPACE_ERR"],
   ["http://example.com/", "f:oo", null],
+  ["http://example.com/", "f:o:o", "NAMESPACE_ERR"],
   ["http://example.com/", "foo:", "NAMESPACE_ERR"],
+  ["http://example.com/", "f::oo", "NAMESPACE_ERR"],
+  ["http://example.com/", "a:0", "NAMESPACE_ERR"],
+  ["http://example.com/", "0:a", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", "a:_", null],
+  ["http://example.com/", "a:\u0BC6", "NAMESPACE_ERR"],
+  ["http://example.com/", "\u0BC6:a", "INVALID_CHARACTER_ERR"],
+  ["http://example.com/", "a:a\u0BC6", null],
+  ["http://example.com/", "a\u0BC6:a", null],
+  ["http://example.com/", "xml:test", "NAMESPACE_ERR"],
+  ["http://example.com/", "xmlns:test", "NAMESPACE_ERR"],
+  ["http://example.com/", "test:xmlns", null],
+  ["http://example.com/", "xmlns", "NAMESPACE_ERR"],
   ["http://example.com/", "_:_", null],
   ["http://example.com/", "_:h0", null],
   ["http://example.com/", "_:test", null],
   ["http://example.com/", "l_:_", null],
   ["http://example.com/", "ns:_0", null],
   ["http://example.com/", "ns:a0", null],
   ["http://example.com/", "ns0:test", null],
   ["http://example.com/", "a.b:c", null],
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/dummy.xhtml
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><head><title>Dummy XHTML document</title></head><body /></html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/dummy.xml
@@ -0,0 +1,1 @@
+<foo>Dummy XML document</foo>
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -707,17 +707,17 @@ add_old_configure_assignment('NECKO_PROT
 
 # Graphics
 # ==============================================================
 option('--disable-skia', help='Disable use of Skia')
 
 @depends('--disable-skia', target)
 def skia(value, target):
     if value.origin == 'default' and target.endianness == 'big':
-        return False
+        return None
     if value:
         return True
 
 set_config('MOZ_ENABLE_SKIA', skia)
 set_define('MOZ_ENABLE_SKIA', skia)
 set_define('USE_SKIA', skia)
 
 @depends(skia, target)
@@ -728,20 +728,20 @@ def skia_android(skia, target):
 set_define('SK_BUILD_FOR_ANDROID_NDK', skia_android)
 
 option('--disable-skia-gpu', help='Disable use of Skia-GPU')
 
 @depends('--disable-skia-gpu', skia, target)
 def skia_gpu(value, skia, target):
     if value.origin == 'default':
         if not skia:
-            return False
+            return None
         # Skia GPU support may not reliably build on certain *BSDs (see bug 1234494)
         if target.os in ('NetBSD', 'OpenBSD'):
-            return False
+            return None
     elif value and not skia:
         die('Cannot enable Skia-GPU without enabling Skia')
     if skia and value:
         return True
 
 set_config('MOZ_ENABLE_SKIA_GPU', skia_gpu)
 set_define('USE_SKIA_GPU', skia_gpu)
 
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -924,16 +924,21 @@ var loadManifestFromWebManifest = Task.a
     throw new Error("Extension is invalid");
 
   let bss = (manifest.browser_specific_settings && manifest.browser_specific_settings.gecko)
       || (manifest.applications && manifest.applications.gecko) || {};
   if (manifest.browser_specific_settings && manifest.applications) {
     logger.warn("Ignoring applications property in manifest");
   }
 
+  // A * is illegal in strict_min_version
+  if (bss.strict_min_version && bss.strict_min_version.split(".").some(part => part == "*")) {
+    logger.warn("The use of '*' in strict_min_version is deprecated");
+  }
+
   let addon = new AddonInternal();
   addon.id = bss.id;
   addon.version = manifest.version;
   addon.type = "webextension";
   addon.unpack = false;
   addon.strictCompatibility = true;
   addon.bootstrap = true;
   addon.hasBinaryComponents = false;
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
@@ -358,9 +358,40 @@ add_task(function* test_strict_min_max()
   yield AddonManager.installTemporaryAddon(addonDir);
   addon = yield promiseAddonByID(newId);
 
   do_check_neq(addon, null);
   do_check_eq(addon.id, newId);
 
   addon.uninstall();
   flushAndRemove(addonDir);
+
+  // * in min will generate a warning
+  for (let version of ["0.*", "0.*.0"]) {
+    newId = "strict_min_star@tests.mozilla.org";
+    let apps = {
+      applications: {
+        gecko: {
+          id: newId,
+          strict_min_version: version,
+        },
+      },
+    }
+    let testManifest = Object.assign(apps, MANIFEST);
+
+    let addonDir = yield promiseWriteWebManifestForExtension(testManifest, gTmpD,
+                                            "strict_min_star");
+
+    let { messages } = yield promiseConsoleOutput(function* () {
+      yield AddonManager.installTemporaryAddon(addonDir);
+    });
+    ok(messages.some(msg => msg.message.includes("The use of '*' in strict_min_version is deprecated")),
+       "Deprecation warning for strict_min_version with '*' was generated");
+
+    let addon = yield promiseAddonByID(newId);
+
+    notEqual(addon, null, "Add-on is installed");
+    equal(addon.id, newId, "Add-on has the expected id");
+
+    addon.uninstall();
+    flushAndRemove(addonDir);
+  }
 });