Merge mozilla-central to autoland. a=merge CLOSED TREE
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Fri, 19 Oct 2018 16:30:39 +0300
changeset 490496 0cb99356070d3f79a1ff20c64970d092b7d908c5
parent 490495 1ac84aa579d69572a6569e70b73c1e17d206361e (current diff)
parent 490438 18859d2fec94f35e924e9093a99169623e0b2d78 (diff)
child 490497 8b9c8d77185a1eafcf79046782d4ee6c47d84f1d
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersmerge
milestone64.0a1
Merge mozilla-central to autoland. a=merge CLOSED TREE
dom/tests/html/jshandlers.html
dom/tests/js/DumpHTML.js
dom/tests/js/DumpTree.js
dom/tests/js/HTMLString.js
dom/tests/js/attributes.html
dom/tests/js/class.html
dom/tests/js/clone.html
dom/tests/js/docfrag.html
dom/tests/js/flamer.gif
dom/tests/js/id.html
dom/tests/js/lists.html
dom/tests/js/simple.js
dom/tests/js/ssheets.js
dom/tests/js/style1.html
dom/tests/js/style2.html
dom/tests/js/tables/changeCaption.js
dom/tests/js/tables/changeCell.js
dom/tests/js/timer.js
dom/tests/js/write.html
dom/tests/js/write2.html
mobile/android/modules/geckoview/GeckoViewNavigation.jsm
mobile/android/modules/geckoview/LoadURIDelegate.jsm
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
@@ -29,213 +29,210 @@ var ControlCenter = {
   init(libDir) {
     // Disable the FTU tours.
     Services.prefs.setIntPref("privacy.trackingprotection.introCount", 20);
     Services.prefs.setIntPref("browser.contentblocking.introCount", 20);
   },
 
   configurations: {
     about: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage("about:rights");
         await openIdentityPopup();
       },
     },
 
     localFile: {
-      selectors: ["#identity-popup"],
+      // This selector is different so we can exclude the changing file: path
+      selectors: ["#identity-popup-security"],
       async applyConfig() {
         let channel = NetUtil.newChannel({
             uri: "resource://mozscreenshots/lib/mozscreenshots.html",
             loadUsingSystemPrincipal: true,
         });
         channel = channel.QueryInterface(Ci.nsIFileChannel);
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
         let gBrowser = browserWindow.gBrowser;
         BrowserTestUtils.loadURI(gBrowser.selectedBrowser, channel.file.path);
         await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
         await openIdentityPopup();
       },
-
-      async verifyConfig() {
-        return { todo: "Bug 1373563: intermittent controlCenter_localFile on Taskcluster" };
-      },
     },
 
     http: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage(HTTP_PAGE);
         await openIdentityPopup();
       },
     },
 
     httpSubView: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage(HTTP_PAGE);
         await openIdentityPopup(true);
       },
     },
 
     https: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage(HTTPS_PAGE);
         await openIdentityPopup();
       },
     },
 
     httpsSubView: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage(HTTPS_PAGE);
         await openIdentityPopup(true);
       },
     },
 
     singlePermission: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         let uri = Services.io.newURI(PERMISSIONS_PAGE);
         SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
 
         await loadPage(PERMISSIONS_PAGE);
         await openIdentityPopup();
       },
     },
 
     allPermissions: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         // TODO: (Bug 1330601) Rewrite this to consider temporary (TAB) permission states.
         // There are 2 possible non-default permission states, so we alternate between them.
         let states = [SitePermissions.ALLOW, SitePermissions.BLOCK];
         let uri = Services.io.newURI(PERMISSIONS_PAGE);
         SitePermissions.listPermissions().forEach(function(permission, index) {
           SitePermissions.set(uri, permission, states[index % 2]);
         });
 
         await loadPage(PERMISSIONS_PAGE);
         await openIdentityPopup();
       },
     },
 
     mixed: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage(MIXED_CONTENT_URL);
         await openIdentityPopup();
       },
     },
 
     mixedSubView: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage(MIXED_CONTENT_URL);
         await openIdentityPopup(true);
       },
     },
 
     mixedPassive: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage(MIXED_PASSIVE_CONTENT_URL);
         await openIdentityPopup();
       },
     },
 
     mixedPassiveSubView: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage(MIXED_PASSIVE_CONTENT_URL);
         await openIdentityPopup(true);
       },
     },
 
     mixedActive: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage(MIXED_ACTIVE_CONTENT_URL);
         await openIdentityPopup();
       },
     },
 
     mixedActiveSubView: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage(MIXED_ACTIVE_CONTENT_URL);
         await openIdentityPopup(true);
       },
     },
 
     mixedActiveUnblocked: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
         let gBrowser = browserWindow.gBrowser;
         await loadPage(MIXED_ACTIVE_CONTENT_URL);
         gBrowser.ownerGlobal.gIdentityHandler.disableMixedContentProtection();
         await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, MIXED_ACTIVE_CONTENT_URL);
         await openIdentityPopup();
       },
     },
 
     mixedActiveUnblockedSubView: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
         let gBrowser = browserWindow.gBrowser;
         await loadPage(MIXED_ACTIVE_CONTENT_URL);
         gBrowser.ownerGlobal.gIdentityHandler.disableMixedContentProtection();
         await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, MIXED_ACTIVE_CONTENT_URL);
         await openIdentityPopup(true);
       },
     },
 
     httpPassword: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage(HTTP_PASSWORD_PAGE);
         await openIdentityPopup();
       },
     },
 
     httpPasswordSubView: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         await loadPage(HTTP_PASSWORD_PAGE);
         await openIdentityPopup(true);
       },
     },
 
     trackingProtectionNoElements: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
 
         await loadPage(HTTP_PAGE);
         await openIdentityPopup();
       },
     },
 
     trackingProtectionEnabled: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
         await UrlClassifierTestUtils.addTestTrackers();
 
         await loadPage(TRACKING_PAGE);
         await openIdentityPopup();
       },
     },
 
     trackingProtectionDisabled: {
-      selectors: ["#identity-popup"],
+      selectors: ["#navigator-toolbox", "#identity-popup"],
       async applyConfig() {
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
         let gBrowser = browserWindow.gBrowser;
         Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
         await UrlClassifierTestUtils.addTestTrackers();
 
         await loadPage(TRACKING_PAGE);
         await openIdentityPopup();
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/LightweightThemes.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/LightweightThemes.jsm
@@ -20,68 +20,68 @@ var LightweightThemes = {
     // convert -size 3000x200 canvas:#eee white_theme.png
     let whiteImage = libDir.clone();
     whiteImage.append("white_theme.png");
     this._whiteImageURL = Services.io.newFileURI(whiteImage).spec;
   },
 
   configurations: {
     noLWT: {
-      selectors: ["#navigator-toolbox"],
+      selectors: [],
       async applyConfig() {
         LightweightThemeManager.currentTheme = null;
       },
     },
 
     darkLWT: {
-      selectors: ["#navigator-toolbox"],
+      selectors: [],
       applyConfig() {
         LightweightThemeManager.setLocalTheme({
           id:          "black",
           name:        "black",
           headerURL:   LightweightThemes._blackImageURL,
           textcolor:   "#eeeeee",
           accentcolor: "#111111",
         });
 
         // Wait for LWT listener
         return new Promise(resolve => {
           setTimeout(() => {
-            resolve("darkLWT");
+            resolve();
           }, 500);
         });
       },
     },
 
     lightLWT: {
-      selectors: ["#navigator-toolbox"],
+      selectors: [],
       applyConfig() {
         LightweightThemeManager.setLocalTheme({
           id:          "white",
           name:        "white",
           headerURL:   LightweightThemes._whiteImageURL,
           textcolor:   "#111111",
           accentcolor: "#eeeeee",
         });
         // Wait for LWT listener
         return new Promise(resolve => {
           setTimeout(() => {
-            resolve("lightLWT");
+            resolve();
           }, 500);
         });
       },
     },
 
     compactLight: {
-      selectors: ["#navigator-toolbox"],
+      selectors: [],
       applyConfig() {
         LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-compact-light@mozilla.org");
       },
     },
 
     compactDark: {
-      selectors: ["#navigator-toolbox"],
+      selectors: [],
       applyConfig() {
         LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-compact-dark@mozilla.org");
       },
     },
   },
 };
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm
@@ -18,91 +18,91 @@ var PermissionPrompts = {
   init(libDir) {
     Services.prefs.setBoolPref("media.navigator.permission.fake", true);
     Services.prefs.setBoolPref("extensions.install.requireBuiltInCerts", false);
     Services.prefs.setBoolPref("signon.rememberSignons", true);
   },
 
   configurations: {
     shareDevices: {
-      selectors: ["#notification-popup"],
+      selectors: ["#notification-popup", "#identity-box"],
       async applyConfig() {
         await closeLastTab();
         await clickOn("#webRTC-shareDevices");
       },
     },
 
     shareMicrophone: {
-      selectors: ["#notification-popup"],
+      selectors: ["#notification-popup", "#identity-box"],
       async applyConfig() {
         await closeLastTab();
         await clickOn("#webRTC-shareMicrophone");
       },
     },
 
     shareVideoAndMicrophone: {
-      selectors: ["#notification-popup"],
+      selectors: ["#notification-popup", "#identity-box"],
       async applyConfig() {
         await closeLastTab();
         await clickOn("#webRTC-shareDevices2");
       },
     },
 
     shareScreen: {
-      selectors: ["#notification-popup"],
+      selectors: ["#notification-popup", "#identity-box"],
       async applyConfig() {
         await closeLastTab();
         await clickOn("#webRTC-shareScreen");
       },
     },
 
     geo: {
-      selectors: ["#notification-popup"],
+      selectors: ["#notification-popup", "#identity-box"],
       async applyConfig() {
         await closeLastTab();
         await clickOn("#geo");
       },
     },
 
     persistentStorage: {
-      selectors: ["#notification-popup"],
+      selectors: ["#notification-popup", "#identity-box"],
       async applyConfig() {
         await closeLastTab();
         await clickOn("#persistent-storage");
       },
     },
 
     loginCapture: {
-      selectors: ["#notification-popup"],
+      selectors: ["#notification-popup", "#identity-box"],
       async applyConfig() {
         await closeLastTab();
         await clickOn("#login-capture");
       },
     },
 
     notifications: {
-      selectors: ["#notification-popup"],
+      selectors: ["#notification-popup", "#identity-box"],
       async applyConfig() {
         await closeLastTab();
         await clickOn("#web-notifications");
       },
     },
 
     addons: {
-      selectors: ["#notification-popup"],
+      selectors: ["#notification-popup", "#identity-box"],
       async applyConfig() {
         Services.prefs.setBoolPref("xpinstall.whitelist.required", true);
 
         await closeLastTab();
         await clickOn("#addons");
       },
     },
 
     addonsNoWhitelist: {
-      selectors: ["#notification-popup"],
+      selectors: ["#notification-popup", "#identity-box"],
       async applyConfig() {
         Services.prefs.setBoolPref("xpinstall.whitelist.required", false);
 
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
         let notification = browserWindow.document.getElementById("addon-install-confirmation-notification");
 
         await closeLastTab();
         await clickOn("#addons");
--- a/dom/html/HTMLAllCollection.cpp
+++ b/dom/html/HTMLAllCollection.cpp
@@ -44,20 +44,47 @@ HTMLAllCollection::GetParentObject() con
 }
 
 uint32_t
 HTMLAllCollection::Length()
 {
   return Collection()->Length(true);
 }
 
-nsIContent*
+Element*
 HTMLAllCollection::Item(uint32_t aIndex)
 {
-  return Collection()->Item(aIndex);
+  nsIContent* item = Collection()->Item(aIndex);
+  return item ? item->AsElement() : nullptr;
+}
+
+void
+HTMLAllCollection::Item(const Optional<nsAString>& aNameOrIndex,
+                        Nullable<OwningHTMLCollectionOrElement>& aResult)
+{
+  if (!aNameOrIndex.WasPassed()) {
+    aResult.SetNull();
+    return;
+  }
+
+  const nsAString& nameOrIndex = aNameOrIndex.Value();
+  uint32_t indexVal;
+  if (js::StringIsArrayIndex(nameOrIndex.BeginReading(),
+                             nameOrIndex.Length(),
+                             &indexVal)) {
+    Element* element = Item(indexVal);
+    if (element) {
+      aResult.SetValue().SetAsElement() = element;
+    } else {
+      aResult.SetNull();
+    }
+    return;
+  }
+
+  NamedItem(nameOrIndex, aResult);
 }
 
 nsContentList*
 HTMLAllCollection::Collection()
 {
   if (!mCollection) {
     nsIDocument* document = mDocument;
     mCollection = document->GetElementsByTagName(NS_LITERAL_STRING("*"));
@@ -116,17 +143,17 @@ HTMLAllCollection::GetDocumentAllList(co
       return new nsContentList(mDocument, DocAllResultMatch, nullptr,
                                nullptr, true, id);
     });
 }
 
 void
 HTMLAllCollection::NamedGetter(const nsAString& aID,
                                bool& aFound,
-                               Nullable<OwningNodeOrHTMLCollection>& aResult)
+                               Nullable<OwningHTMLCollectionOrElement>& aResult)
 {
   if (aID.IsEmpty()) {
     aFound = false;
     aResult.SetNull();
     return;
   }
 
   nsContentList* docAllList = GetDocumentAllList(aID);
@@ -143,17 +170,17 @@ HTMLAllCollection::NamedGetter(const nsA
     aFound = true;
     aResult.SetValue().SetAsHTMLCollection() = docAllList;
     return;
   }
 
   // There's only 0 or 1 items. Return the first one or null.
   if (nsIContent* node = docAllList->Item(0, true)) {
     aFound = true;
-    aResult.SetValue().SetAsNode() = node;
+    aResult.SetValue().SetAsElement() = node->AsElement();
     return;
   }
 
   aFound = false;
   aResult.SetNull();
 }
 
 void
--- a/dom/html/HTMLAllCollection.h
+++ b/dom/html/HTMLAllCollection.h
@@ -11,76 +11,82 @@
 #include "nsISupportsImpl.h"
 #include "nsRefPtrHashtable.h"
 #include "nsWrapperCache.h"
 
 #include <stdint.h>
 
 class nsContentList;
 class nsHTMLDocument;
-class nsIContent;
 class nsINode;
 
 namespace mozilla {
 namespace dom {
 
-class OwningNodeOrHTMLCollection;
+class Element;
+class OwningHTMLCollectionOrElement;
 template<typename> struct Nullable;
+template<typename> class Optional;
 
 class HTMLAllCollection final : public nsISupports
                               , public nsWrapperCache
 {
   ~HTMLAllCollection();
 
 public:
   explicit HTMLAllCollection(nsHTMLDocument* aDocument);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(HTMLAllCollection)
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
   nsINode* GetParentObject() const;
 
   uint32_t Length();
-  nsIContent* Item(uint32_t aIndex);
-  void Item(const nsAString& aName, Nullable<OwningNodeOrHTMLCollection>& aResult)
+  Element* IndexedGetter(uint32_t aIndex, bool& aFound)
   {
-    NamedItem(aName, aResult);
-  }
-  nsIContent* IndexedGetter(uint32_t aIndex, bool& aFound)
-  {
-    nsIContent* result = Item(aIndex);
+    Element* result = Item(aIndex);
     aFound = !!result;
     return result;
   }
 
   void NamedItem(const nsAString& aName,
-                 Nullable<OwningNodeOrHTMLCollection>& aResult)
+                 Nullable<OwningHTMLCollectionOrElement>& aResult)
   {
     bool found = false;
     NamedGetter(aName, found, aResult);
   }
   void NamedGetter(const nsAString& aName,
                    bool& aFound,
-                   Nullable<OwningNodeOrHTMLCollection>& aResult);
+                   Nullable<OwningHTMLCollectionOrElement>& aResult);
   void GetSupportedNames(nsTArray<nsString>& aNames);
-  void LegacyCall(JS::Handle<JS::Value>, const nsAString& aName,
-                  Nullable<OwningNodeOrHTMLCollection>& aResult)
+
+  void Item(const Optional<nsAString>& aNameOrIndex,
+            Nullable<OwningHTMLCollectionOrElement>& aResult);
+
+  void LegacyCall(JS::Handle<JS::Value>,
+                  const Optional<nsAString>& aNameOrIndex,
+                  Nullable<OwningHTMLCollectionOrElement>& aResult)
   {
-    NamedItem(aName, aResult);
+    Item(aNameOrIndex, aResult);
   }
 
 private:
   nsContentList* Collection();
 
   /**
    * Returns the HTMLCollection for document.all[aID], or null if there isn't one.
    */
   nsContentList* GetDocumentAllList(const nsAString& aID);
 
+  /**
+   * Helper for indexed getter and spec Item() method.
+   */
+  Element* Item(uint32_t aIndex);
+
   RefPtr<nsHTMLDocument> mDocument;
   RefPtr<nsContentList> mCollection;
   nsRefPtrHashtable<nsStringHashKey, nsContentList> mNamedMap;
 };
 
 } // namespace dom
 } // namespace mozilla
 
deleted file mode 100644
--- a/dom/tests/html/jshandlers.html
+++ /dev/null
@@ -1,76 +0,0 @@
-<html>
-<body>
-The link below has an onmousedown, an onmouseup, and an onmousemove handler.
-Mouseover or click for event info in debug console.
-<a href="jshandlers.html">Link back to this page</a>
-
-<p>The link below has an event that should open www.mozilla.org when 
-   clicked
-</p>
-<!-- The link does 'return 0' - as per bug 345521 this should *not* be 
-     interpreted as false
--->
-<a href="http://www.mozilla.org" onclick="return 0">Click me</a>
-
-<p>The link below has an event that is cancelled - nothing should happen when 
-   clicked
-</p>
-<a href="http://www.mozilla.org" onclick="return false">Click me<a/>
-
-</body>
-<script>
-function findElementByTagName(start, tag)
-{
-    var type = start.nodeType;
-
-    if (type == Node.ELEMENT) {
-
-		if (tag == start.tagName) {
-			//dump ("found one\n");
-			return start;
-		}
-
-        if (start.hasChildNodes) {
-            var children = start.childNodes;
-            var length = children.length;
-            var count = 0;
-            while(count < length) {
-				var ret = findElementByTagName(children[count], tag)
-				if (null != ret) {
-					return ret;
-				}
-                count++;
-            }
-        }
-    }
-	return null;
-}
-
-function getFirstLink()
-{
-	var node = document.documentElement;
-	var ret = findElementByTagName(node, "A");
-	return ret;
-}
-
-function ondown() 
-{
-	dump("got mousedown in script\n");
-}
-
-function onup()
-{
-	dump("got mouseup in script\n");
-}
-
-function onmove(event)
-{
-	dump("got mousemove in script at "+event.clientX+", "+event.clientY+"\n");
-}
-
-var l = getFirstLink();
-l.onmousedown = ondown;
-l.onmouseup = onup;
-l.onmousemove = onmove;
-</script>
-</html>
deleted file mode 100644
--- a/dom/tests/js/DumpHTML.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-
-//
-// Dump the html content in html format
-//
-function html(node)
-{
-    var type = node.nodeType;
-    if (type == Node.ELEMENT_NODE) {
-
-        // open tag
-        dump("<" + node.tagName)
-
-        // dump the attributes if any
-        attributes = node.attributes;
-        if (null != attributes) {
-            var countAttrs = attributes.length;
-            var index = 0
-            while(index < countAttrs) {
-                att = attributes[index];
-                if (null != att) {
-                    dump(" " + att.value)
-                }
-                index++
-            }
-        }
-
-        // close tag
-        dump(">")
-
-        // recursively dump the children
-        if (node.hasChildNodes()) {
-            // get the children
-            var children = node.childNodes;
-            var length = children.length;
-            var count = 0;
-            while(count < length) {
-                child = children[count]
-                html(child)
-                count++
-            }
-            dump("</" + node.tagName + ">");
-        }
-
-        
-    }
-    // if it's a piece of text just dump the text
-    else if (type == Node.TEXT_NODE) {
-        dump(node.data)
-    }
-}
-
-html(document.documentElement)
-dump("\n")
deleted file mode 100644
--- a/dom/tests/js/DumpTree.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-
-//
-// travers the html tree and dump out the type of element
-//
-function traverse(node, indent)
-{
-    dump("\n")
-    indent += "  "
-    var type = node.nodeType;
-
-    // if it's an element dump the tag and recurse the children
-    if (type == Node.ELEMENT_NODE) {
-
-        dump(indent + node.tagName)
-
-        // go through the children
-        if (node.hasChildNodes()) {
-            var children = node.childNodes;
-            var length = children.length;
-            var count = 0;
-            while(count < length) {
-                child = children[count]
-                traverse(child, indent)
-                count++
-            }
-        }
-    }
-    // it's just text, no tag, dump "Text"
-    else if (type == Node.TEXT_NODE) {
-        dump(indent + "Text")
-    }
-}
-
-var node = document.documentElement
-
-traverse(node, "")
-dump("\n")
-
-  
\ No newline at end of file
deleted file mode 100644
--- a/dom/tests/js/HTMLString.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-
-//
-// return a string representing the html content in html format 
-//
-function htmlString(node, indent)
-{
-    var html = ""
-    indent += "  "
-
-    var type = node.nodeType
-    if (type == Node.ELEMENT) {
-
-        // open tag
-        html += "\n" + indent + "<" + node.tagName
-
-        // dump the attributes if any
-        attributes = node.attributes
-        if (null != attributes) {
-            var countAttrs = attributes.length
-            var index = 0
-            while(index < countAttrs) {
-                att = attributes[index]
-                if (null != att) {
-                    html += " "
-                    html += att.name + "=" + att.value;
-                }
-                index++
-            }
-        }
-
-        // end tag
-        html += ">"
-
-        // recursively dump the children
-        if (node.hasChildNodes) {
-            // get the children
-            var children = node.childNodes
-            var length = children.length
-            var count = 0;
-            while(count < length) {
-                child = children[count]
-                html += htmlString(child, indent)
-                count++
-            }
-        }
-
-        // close tag
-        html += "\n" + indent + "</" + node.tagName + ">"
-    }
-    // if it's a piece of text just dump the text
-    else if (type == Node.TEXT) {
-        html += node.data
-    }
-
-    return html;
-}
-
-htmlString(document.documentElement, "") 
-
-
-  
\ No newline at end of file
deleted file mode 100644
--- a/dom/tests/js/attributes.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<HTML>
-<HEAD>
-  <TITLE>Attributes test</TITLE>
-</HEAD>
-<BODY bgColor="#ffffff" text="#000000">
-<H1>Attributes test</H1>
-
-<P>You should see the following in the console:</P>
-<PRE>
-attribute.getNamedItem == getAttributeNode: true
-attribute BGCOLOR=#ffffff
-changing attribute node value changes attribute value: true
-return value of removeNamedItem is attribute node: true
-removing attribute changes attribute count: true
-changing disembodied attribute value works: true
-removing attribute node removes attribute: true
-</PRE>
-
-<P>The text should turn green and then you should see
-the following in the console:</P>
-<PRE>
-setting an existing attribute returns the old node: true
-</PRE>
-
-<SCRIPT>
-a = document.body.attributes.getNamedItem("bgcolor")
-a2 = document.body.getAttributeNode("bgcolor")
-n = document.body.attributes.length;
-dump("attribute.getNamedItem == getAttributeNode: " + (a == a2) + "\n");
-
-dump("attribute " + a.name + "=" + a.value + "\n");
-
-a.value = "#00ffff"
-dump("changing attribute node value changes attribute value: " + (document.body.getAttribute("bgcolor") == "#00ffff") + "\n");
-
-a = document.body.attributes.removeNamedItem("bgcolor")
-dump("return value of removeNamedItem is attribute node: " + (a == a2) + "\n");
-
-dump("removing attribute changes attribute count: " + (document.body.attributes.length == (n-1)) + "\n");
-
-a.value = "#ff0000"
-dump("changing disembodied attribute value works: " + (a.value == "#ff0000") + "\n");
-
-dump("removing attribute node removes attribute: " + (document.body.getAttribute("bgcolor") == "") + "\n");
-
-a = document.body.attributes.getNamedItem("TEXT");
-a2 = document.createAttribute("text");
-a2.value = "#00ff00";
-a3 = document.body.attributes.setNamedItem(a2);
-dump("setting an existing attribute returns the old node: " + (a == a3) + "\n");
-</SCRIPT>
-
-</BODY>
-</HTML>
-
deleted file mode 100644
--- a/dom/tests/js/class.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<HTML>
-<HEAD>
-<STYLE>
- .first  { display: inline; color: green; }
- .second { display: block; color: red; }
-</STYLE>
-<SCRIPT>
-var className = "first";
-function toggleClass() {
-  var node = document.getElementById("foo");
-  if (className == "first") {
-    className = "second";
-  }
-  else {
-    className = "first";
-  }
-  node.className = className;
-}
-</SCRIPT>
-</HEAD>
-<BODY>
-<H1>Changing CLASS test</H1>
-<P>Clicking on the button that follows this paragraph 
-should change the layout of <SPAN ID="foo" CLASS="first">these words</SPAN>
-with respect to the rest of the flow.</P>
-<FORM>
-<INPUT TYPE="button" VALUE="Toggle class" onClick="toggleClass(); return true;">
-</FORM>
-</BODY>
-</HTML>
deleted file mode 100644
--- a/dom/tests/js/clone.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<HTML>
-<HEAD>
-<TITLE>Clone test</TITLE>
-<SCRIPT>
-function clonePara()
-{
-  var p = document.getElementsByTagName("P")[0];
-  var newp = p.cloneNode(true);
-  
-  document.body.appendChild(newp);
-}
-</SCRIPT>
-</HEAD>
-<BODY>
-<H1>Clone test</H1>
-
-<P>If you press the button <B>below</B>, this paragraph and the
-image <IMG SRC="flamer.gif"> will be <FONT SIZE=+3>cloned</FONT>.
-If you see an <I>exact</I> copy of this paragraph, the test
-succeeded.</P>
-
-<FORM>
-<INPUT TYPE="button" NAME="clone" VALUE="Clone" onClick="clonePara();">
-</FORM>
-</BODY>
-</HTML>
\ No newline at end of file
deleted file mode 100644
--- a/dom/tests/js/docfrag.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<HTML>
-<HEAD>
-<SCRIPT>
-function docFragAppendTest() {
-  var d = document.createDocumentFragment();
-  d.appendChild(document.createTextNode(" Hello"));
-  var b = document.createElement("B")
-  b.appendChild(document.createTextNode(" there"));
-  d.appendChild(b);
-  p = document.getElementById("appendTest");
-  p.appendChild(d);
-  alert("This number should be 0: " + d.childNodes.length);
-}
-function docFragReplaceTest() {
-  var d = document.createDocumentFragment();
-  d.appendChild(document.createTextNode(" new"));
-  var b = document.createElement("B")
-  b.appendChild(document.createTextNode(" ones"));
-  d.appendChild(b);
-  p = document.getElementById("replaceTest");
-  s = document.getElementById("replaceSpan");
-  if (null != s) {
-    p.replaceChild(d, s);
-  }
-  alert("This number should be 0: " + d.childNodes.length);
-}
-</SCRIPT>
-</HEAD>
-<BODY>
-<H1>Document Fragment test</H1>
-
-<P ID="appendTest">If this test works, clicking on this <A href="" onclick="docFragAppendTest(); return false;">link</A> will add two words to the end of this paragraph.</P>
-
-<P ID="replaceTest">
-Clicking on this <A href="" onclick="docFragReplaceTest(); return false;">link</A> will replace the following two words with new ones: <span id="replaceSpan">two words</span>.
-</P>
-
-</BODY>
-</HTML>
\ No newline at end of file
deleted file mode 100644
index 5a05df583f1f690b913469e3d5ff1aa2371384f8..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/dom/tests/js/id.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<HTML>
-<HEAD>
-<STYLE>
- #first  { display: inline; color: green; }
- #second { display: block; color: red; }
-</STYLE>
-<SCRIPT>
-var id = "first";
-function toggleId() {
-  var node = document.getElementById(id);
-  if (id == "first") {
-    id = "second";
-  }
-  else {
-    id = "first";
-  }
-  node.id = id;
-}
-</SCRIPT>
-</HEAD>
-<BODY>
-<H1>Changing ID test</H1>
-<P>Clicking on the button that follows this paragraph 
-should change the layout of <SPAN ID="first">these words</SPAN>
-with respect to the rest of the flow.</P>
-<FORM>
-<INPUT TYPE="button" VALUE="Toggle ID" onClick="toggleId(); return true;">
-</FORM>
-</BODY>
-</HTML>
deleted file mode 100644
--- a/dom/tests/js/lists.html
+++ /dev/null
@@ -1,68 +0,0 @@
-<html>
-<body>
-<p>This test does a few things:
-<ul>
-  <li>It has a couple of:
-  <ul>
-      <li>Images: <IMG SRC="http://zabadubop/layers/tests/mzcolor.gif" ID="foo"> and
-          <IMG SRC="http://peoplestage.netscape.com/kipp/nerdly_int.gif" NAME="kipp">.
-      <li>Links to <a href="http://home.netscape.com">Netscape</a> and
-          <A HREF="http://peoplestage.netscape.com/kipp">Kippy's Home Page</A>.
-      <li>and Anchors to <a NAME="anchor1">here</A> and 
-          <A name="anchor2">here</a>.
-  </ul>
-  <li>It dumps (check the JS console) the images, links and anchors using
-      the document.images, document.links and document.anchors arrays.
-  <li>Then it removes one of the images.
-  <li>Dumps the images array again. This is to prove that the images array
-      is live.
-  <li>Adds back the image.
-  <li>And the dumps the images array again. The image arrays order should
-      now be different.
-  <li>It gets a list of LIs (using getElementsByTagName()) and prints
-      out all their tagNames. There should be 10.
-</ul>
-<script>
-var x;
-dump("Images:\n");
-for (x=0; x < document.images.length; x++) {
-  dump("Image#" + x + ": " + document.images[x].getDOMAttribute("SRC") + "\n");
-}
-dump("\nLinks:\n");
-for (x=0; x < document.links.length; x++) {
-  dump("Link#" + x + ": " + document.links[x].getDOMAttribute("HREF") + "\n");
-}
-dump("\nAnchors:\n");
-for (x=0; x < document.anchors.length; x++) {
-  dump("Anchors#" + x + ": " + document.anchors[x].getDOMAttribute("NAME") + "\n");
-}
-
-dump("\nRemoving image\n");
-var img=document.images[1];
-var parent=img.parentNode;
-parent.removeChild(img);
-dump("Images:\n");
-for (x=0; x < document.images.length; x++) {
-  dump("Image#" + x + ": " + document.images[x].getDOMAttribute("SRC") + "\n");
-}
-
-dump("\nInserting image back into list\n");
-var sib=parent.childNodes[0];
-parent.insertBefore(img, sib);
-dump("Images:\n");
-for (x=0; x < document.images.length; x++) {
-  dump("Image#" + x + ": " + document.images[x].getDOMAttribute("SRC") + "\n");
-}
-
-var lis = document.getElementsByTagName("LI");
-dump("Lists:\n");
-for (x=0; x < lis.length; x++) {
-  dump(lis[x].tagName + "\n");
-}
-
-dump("Named elements:\n");
-dump(document.kipp.tagName + " with NAME=" + document.kipp.getDOMAttribute("NAME") + "\n");
-
-</script>
-</body>
-</html>
\ No newline at end of file
deleted file mode 100644
--- a/dom/tests/js/simple.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-var node = document.documentElement
-
-node.nodeType
-
-node.tagName
-
-var attrList = node.attributes
-
-attrList.length
-
-var attr = attrList.item(0)
-
-attr.name
-
-attr.value
-
-node.hasChildNodes
-
-var children = node.childNodes
-
-children.length
-
-node = children.item(1);
-node.nodeType
-
-node.tagName
-
-node = node.firstChild
-
-node = node.nextSibling
-
-node = node.parentNode
-
- 
\ No newline at end of file
deleted file mode 100644
--- a/dom/tests/js/ssheets.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-var s;
-for (s = 0; s < document.styleSheets.length; s++) {
-   var sheet = document.styleSheets[s];
-   dump("Style sheet #" + (s+1) + ": " + sheet.title + "\n");
-   var i, r;
-   dump("\n");
-   for (i = 0; i < sheet.imports.length; i++) {
-      dump("@import url(" + sheet.imports[i].href + ");\n");
-   }
-   dump("\n");
-   for (r = 0; r < sheet.rules.length; r++) {
-     var rule = sheet.rules[r];
-     dump(rule.selectorText + "  {" + "\n");
-     var style = rule.style;
-     var p;
-     for (p = 0; p < style.length; p++) {
-	dump("    " + style[p] + ":" + style.getPropertyValue(style[p]) + ";\n");
-     }
-     dump("    }\n");
-   }
-   dump("\n");
-} 
\ No newline at end of file
deleted file mode 100644
--- a/dom/tests/js/style1.html
+++ /dev/null
@@ -1,111 +0,0 @@
-<html>
-<head>
-<title>Example 0</title>
-<style title="hello" media="screen, print">
- :first-letter { color: green; }
- a#id.foo:visited:first-line { color: red; }
- a#id.foo:first-line { color: red; }
- a#id.foo:visited { color: red; }
- a#id:first-line { color: red; }
- a#id:visited { color: red; }
- a:first-line { color: red; }
- a:visited { color: red; }
- a:visited:first-line { color: red; }
- a:visited:visited { color: red; }
- a:first-line:visited { color: red; }
- :active { color: blue; }
- P.first:first-line { color: blue; }
- P.first:first-letter { color: yellow; }
-</style>
-</head>
-
-<body bgcolor="#FFFFFF" text="#000000">
-<h1>Example 0: Basic HTML Text Styles</h1>
-<p><br></p>
-<h2>Formatted Text</h2>
-<p>This is a basic paragraph with <b>bold</b>, <i>italic</i> and <i>bold-italic 
-  </i> text. It also includes <font color="#FF0000">red</font>, <font color="#00FF00">green</font> 
-  and <font color="#0000FF">blue</font> text. It has <s>strikethru</s> and <u>underline</u>. 
-  <u> </u></p>
-<p>This is a paragraph with font variations: <b><font face="Arial, Helvetica, sans-serif">Arial,</font></b><font face="Arial, Helvetica, sans-serif"> 
-  <i><font face="Verdana, Arial, Helvetica, sans-serif">Verdana</font>,</i> <font face="co">COURIER, 
-  <font face="Times New Roman, Times, serif">Times New Roman.</font></font></font></p>
-<p class="first"><font size=7>Font size=7, </font><font size=6>Font size=6, </font><font size=5>Font 
-  size=5, </font><font size=4>Font size=4, </font><font size=3>Font size=3, </font><font size=2>Font 
-  size=2, </font><font size=1>Font size=1, </font><font point-size=24 font-weight=700>Font 
-  point-size=24 font-weight=700</font></p>
-<p><THREED>3D Text. 3D Text. 3D Text. 3D Text. 3D Text. </THREED><br>
-<h2><br>
-</h2>
-<h2>Listings</h2>
-<h3>Bulleted List </h3>
-<ul>
-  <li>One</li>
-  <li>Two
-    <ul>
-      <li>Apples</li>
-      <li>Oranges</li>
-      <li>Bananas</li>
-    </ul>
-  </li>
-  <li>Three</li>
-</ul>
-<br>
-<h3>Numbered List </h3>
-<ol>
-  <li>One</li>
-  <li>Two 
-    <ol>
-      <li>Apples</li>
-      <li>Oranges</li>
-      <li>Bananas</li>
-    </ol>
-  </li>
-  <li>Three</li>
-</ol>
-<h2>Justified Text</h2>
-<p>This paragraph is aligned <b>left</b>. This paragraph is aligned <b>left</b>. 
-  This paragraph is aligned <b>left</b>. This paragraph is aligned <b>left</b>. 
-  This paragraph is aligned <b>left</b>. This paragraph is aligned <b>left</b>. 
-  This paragraph is aligned <b>left</b>. </p>
-<p align="RIGHT">This paragarph is aligned <b>right. </b>This paragarph is aligned 
-  <b>right. </b>This paragarph is aligned <b>right. </b>This paragarph is aligned 
-  <b>right. </b>This paragarph is aligned <b>right. </b>This paragarph is aligned 
-  <b>right. </b>This paragarph is aligned <b>right. </b><b> </b>This paragarph 
-  is aligned <b>right. </b></p>
-<p align="CENTER">This paragraph is aligned <b>center</b>. This paragraph is aligned 
-  <b>center</b>.This paragraph is aligned <b>center</b>.This paragraph is aligned 
-  <b>center</b>.This paragraph is aligned <b>center</b>.This paragraph is aligned 
-  <b>center</b>.This paragraph is aligned <b>center</b>.This paragraph is aligned 
-  <b>center</b>.This paragraph is aligned <b>center</b>.This paragraph is aligned 
-  <b>center</b>.</p>
-<p></p>
-<p>&nbsp;</p>
-<script>
-var r = 0, g = 0, b = 0;
-var h = document.documentElement.childNodes[1].childNodes[1];
-var sheet = document.styleSheets[0];
-var rule = sheet.cssRules[0];
-var size = 10;
-
-function changeColor() {
-  r += 5;
-  g += 2;
-  b += 3;
-  r %= 255;
-  g %= 255;
-  b %= 255;
-  size += 1;
-  if (size > 48) {
-    size = 10;
-  }
-
-  h.style.backgroundColor = "rgb(" + r + "," + g + "," + b + ")";
-  rule.style.color = "rgb(" + r + "," + g + "," + b + ")";
-  rule.style.fontSize = size + "pt";
-}
-
-setInterval(changeColor, 40);
-</script>
-</body>
-</html>
deleted file mode 100644
--- a/dom/tests/js/style2.html
+++ /dev/null
@@ -1,118 +0,0 @@
-<html>
-<head>
-<title>Example 0</title>
-<style>
- a#id.foo:visited:first-line { color: red; }
- a#id.foo:first-line { color: red; }
- a#id.foo:visited { color: red; }
- a#id:first-line { color: red; }
- a#id:visited { color: red; }
- a:first-line { color: red; }
- a:visited { color: red; }
- a:visited:first-line { color: red; }
- a:visited:visited { color: red; }
- a:first-line:visited { color: red; }
- :active { color: blue; }
- :first-letter { color: green; }
-</style>
-</head>
-
-<body bgcolor="#FFFFFF" text="#000000">
-<table border=4>
-<tr>
-<td>
-<h1>Example 0: Basic HTML Text Styles</h1>
-</td>
-<td>
-more table cell
-</td>
-</tr>
-<tr>
-<td>second row</td>
-<td>second row</td>
-</tr>
-</table>
-<p><br></p>
-<h2>Formatted Text</h2>
-<p>This is a basic paragraph with <b>bold</b>, <i>italic</i> and <i>bold-italic 
-  </i> text. It also includes <font color="#FF0000">red</font>, <font color="#00FF00">green</font> 
-  and <font color="#0000FF">blue</font> text. It has <s>strikethru</s> and <u>underline</u>. 
-  <u> </u></p>
-<p>This is a paragraph with font variations: <b><font face="Arial, Helvetica, sans-serif">Arial,</font></b><font face="Arial, Helvetica, sans-serif"> 
-  <i><font face="Verdana, Arial, Helvetica, sans-serif">Verdana</font>,</i> <font face="co">COURIER, 
-  <font face="Times New Roman, Times, serif">Times New Roman.</font></font></font></p>
-<p><font size=7>Font size=7, </font><font size=6>Font size=6, </font><font size=5>Font 
-  size=5, </font><font size=4>Font size=4, </font><font size=3>Font size=3, </font><font size=2>Font 
-  size=2, </font><font size=1>Font size=1, </font><font point-size=24 font-weight=700>Font 
-  point-size=24 font-weight=700</font></p>
-<p><THREED>3D Text. 3D Text. 3D Text. 3D Text. 3D Text. </THREED><br>
-<h2><br>
-</h2>
-<h2>Listings</h2>
-<h3>Bulleted List </h3>
-<ul>
-  <li>One</li>
-  <li>Two
-    <ul>
-      <li>Apples</li>
-      <li>Oranges</li>
-      <li>Bananas</li>
-    </ul>
-  </li>
-  <li>Three</li>
-</ul>
-<br>
-<h3>Numbered List </h3>
-<ol>
-  <li>One</li>
-  <li>Two 
-    <ol>
-      <li>Apples</li>
-      <li>Oranges</li>
-      <li>Bananas</li>
-    </ol>
-  </li>
-  <li>Three</li>
-</ol>
-<h2>Justified Text</h2>
-<p>This paragraph is aligned <b>left</b>. This paragraph is aligned <b>left</b>. 
-  This paragraph is aligned <b>left</b>. This paragraph is aligned <b>left</b>. 
-  This paragraph is aligned <b>left</b>. This paragraph is aligned <b>left</b>. 
-  This paragraph is aligned <b>left</b>. </p>
-<p align="RIGHT">This paragarph is aligned <b>right. </b>This paragarph is aligned 
-  <b>right. </b>This paragarph is aligned <b>right. </b>This paragarph is aligned 
-  <b>right. </b>This paragarph is aligned <b>right. </b>This paragarph is aligned 
-  <b>right. </b>This paragarph is aligned <b>right. </b><b> </b>This paragarph 
-  is aligned <b>right. </b></p>
-<p align="CENTER">This paragraph is aligned <b>center</b>. This paragraph is aligned 
-  <b>center</b>.This paragraph is aligned <b>center</b>.This paragraph is aligned 
-  <b>center</b>.This paragraph is aligned <b>center</b>.This paragraph is aligned 
-  <b>center</b>.This paragraph is aligned <b>center</b>.This paragraph is aligned 
-  <b>center</b>.This paragraph is aligned <b>center</b>.This paragraph is aligned 
-  <b>center</b>.</p>
-<p></p>
-<p>&nbsp;</p>
-<script>
-var r = 0, g = 0, b = 0;
-var h = document.documentElement.childNodes[1].childNodes[1].childNodes[0].childNodes[0].childNodes[0].childNodes[1];
-h.style.borderStyle = "groove";
-
-function changeColor() {
-  r += 5;
-  g += 2;
-  b += 3;
-  r %= 255;
-  g %= 255;
-  b %= 255;
-
-  h.style.backgroundColor = "rgb(" + r + "," + g + "," + b + ")";
-  h.style.color = "rgb(" + (255-r) + "," + (255-g) + "," + (255-b) + ")";
-  h.style.fontSize = "" + (r/3) + "pt";
-  h.style.borderWidth = "" + (r/5) + "px";
-  h.style.borderColor = h.style.color;
-}
-
-setInterval(changeColor, 40);
-</script>
-</body>
-</html>
deleted file mode 100644
--- a/dom/tests/js/tables/changeCaption.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-function findBody(node)
-{
-  if (node.nodeType != Node.ELEMENT_NODE) {
-    return null;
-  }
-  var children = node.childNodes;
-  if (children == null) {
-    return null;
-  }
-  var length = children.length;
-  var child = null;
-  var count = 0;
-  while (count < length) {
-    child = children[count];
-    if (child.tagName == "BODY") {
-      dump("BODY found");
-      return child;
-    }
-    var body = findBody(child);
-    if (null != body) {
-      return body;
-    }
-    count++;
-  }
-  return null;
-}
-
-// Given the body element, find the first table element
-function findTable(body)
-{
-  // XXX A better way to do this would be to use getElementsByTagName(), but
-  // it isn't implemented yet...
-  var children = body.childNodes
-  if (children == null) {
-    return null;
-  }
-  var length = children.length;
-  var child = null;
-  var count = 0;
-  while (count < length) {
-    child = children[count];
-    if (child.nodeType == Node.ELEMENT_NODE) {
-      if (child.tagName == "TABLE") {
-        dump("TABLE found");
-        break;
-      }
-    }
-    count++;
-  }
-
-  return child;
-}
-
-// Change the table's caption
-function changeCaption(table)
-{
-  // Get the first element. This is the caption (maybe). We really should
-  // check...
-  var caption = table.firstChild
-
-  // Get the caption text
-  var text = caption.firstChild
-
-  // Append some text
-  text.append(" NEW TEXT")
-}
-
-changeCaption(findTable(findBody(document.documentElement)))
-
deleted file mode 100644
--- a/dom/tests/js/tables/changeCell.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-function findBody(node)
-{
-  if (node.nodeType != Node.ELEMENT_NODE) {
-    return null;
-  }
-  var children = node.childNodes;
-  if (children == null) {
-    return null;
-  }
-  var length = children.length;
-  var child = null;
-  var count = 0;
-  while (count < length) {
-    child = children[count];
-    if (child.tagName == "BODY") {
-      dump("BODY found");
-      return child;
-    }
-    var body = findBody(child);
-    if (null != body) {
-      return body;
-    }
-    count++;
-  }
-  return null;
-}
-
-// Given the body element, find the first table element
-function findTable(body)
-{
-  // XXX A better way to do this would be to use getElementsByTagName(), but
-  // it isn't implemented yet...
-  var children = body.childNodes
-  if (children == null) {
-    return null;
-  }
-  var length = children.length;
-  var child = null;
-  var count = 0;
-  while (count < length) {
-    child = children[count];
-    if (child.nodeType == Node.ELEMENT_NODE) {
-      if (child.tagName == "TABLE") {
-        dump("TABLE found");
-        break;
-      }
-    }
-    count++;
-  }
-
-  return child;
-}
-
-// Given a table element, find the first table body
-function findTableBody(table)
-{
-  // XXX A better way to do this would be to use getElementsByTagName(), but
-  // it isn't implemented yet...
-  var children = table.childNodes
-  if (children == null) {
-    return null;
-  }
-  var length = children.length;
-  var child = null;
-  var count = 0;
-  while (count < length) {
-    child = children[count];
-    if (child.nodeType == Node.ELEMENT_NODE) {
-      if (child.tagName == "TBODY") {
-        break;
-      }
-    }
-    count++;
-  }
-
-  return child;
-}
-
-// Change the text of the first table cell
-function changeCell(table)
-{
-  // Get a table body
-  var body = findTableBody(table)
-
-  // The table body's first child is a table row
-  var row = body.firstChild
-
-  // The row's first child is a table cell
-  var cell = row.firstChild
-
-  // Get the cell's text
-  var text = cell.firstChild
-
-  // Append some text
-  text.append(" NEW TEXT")
-}
-
-changeCell(findTable(findBody(document.documentElement)))
-
deleted file mode 100644
--- a/dom/tests/js/timer.js
+++ /dev/null
@@ -1,71 +0,0 @@
-
-function oneShot(testNum, str)
-{
-  dump("Test #" + testNum + " successful:" + str + "\n");
-}
-
-setTimeout(oneShot, 1000, 1, "one shot timer with function argument");
-setTimeout("oneShot(2, 'one shot timer with string argument');", 2000);
-
-function reschedule(testNum, numTimes)
-{
-  if (numTimes == 4) { 
-    dump("Test #" + testNum + " successful: Creating a timeout in a timeout\n");
-    kickoff4();
-  }
-  else {
-    dump("Test #" + testNum + " in progress: " + numTimes + "\n");
-    setTimeout(reschedule, 500, 3, numTimes+1);
-  }
-}
-
-setTimeout(reschedule, 3000, 3, 0); 
-
-var count = 0;
-var repeat_timer = null;
-function repeat(testNum, numTimes, str, func, delay)
-{
-  dump("Test #" + testNum + " in progress: interval delayed by " + delay + " milliseconds\n");
-  if (count++ > numTimes) {
-    clearInterval(repeat_timer);
-    dump("Test #" + testNum + " successful: " + str + "\n");
-    if (func != null) {
-      func();
-    }
-  }
-}
-
-function kickoff4()
-{
-  repeat_timer = setInterval(repeat, 500, 4, 5, "interval test", kickoff5);
-}
-
-//setTimeout(kickoff4, 5000);
-
-function oneShot2(testNum)
-{
-  dump("Test #" + testNum + " in progress: one shot timer\n");
-  if (count++ == 4) {
-    dump("Test #" + testNum + " in progress: end of one shots\n");
-  }
-  else {
-    setTimeout(oneShot2, 500, 5);
-  }
-}
-
-function kickoff5()
-{
-  count = 0;
-  setTimeout(oneShot2, 500, 5);
-  repeat_timer = setInterval("repeat(5, 8, 'multiple timer test', kickoff6)", 600);
-}
-
-//setTimeout(kickoff5, 12000);
-
-function kickoff6()
-{
-  dump("Test #6: Interval timeout should end when you go to a new page\n");
-  setInterval(repeat, 1000, 6, 1000, "endless timer test", null);
-}
-
-//setTimeout(kickoff6, 18000);
\ No newline at end of file
deleted file mode 100644
--- a/dom/tests/js/write.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<HTML>
-<BODY>
-<P>This is text in the document.</P>
-<SCRIPT>
-document.writeln("<P>This is text generated by a document.write.");
-document.writeln("And this is an image: <IMG SRC='http://zabadubop/layers/tests/mzcolor.gif'></P>");
-</SCRIPT>
-And now some more text in the document.
-<SCRIPT>
-document.writeln("<SCRIPT>document.writeln('<P>Text from a recursive document.write.</P>');<\/SCRIPT>");
-</SCRIPT>
-</BODY>
\ No newline at end of file
deleted file mode 100644
--- a/dom/tests/js/write2.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<HTML>
-<HEAD>
-<SCRIPT>
-var w = null;
-var count = 0;
-var isOpen = false;
-
-function newWin() {
-  if ((w == null) || (w.closed == true)) {
-    w = window.open("about:blank", "writetest");
-  }
-}
-
-function incrWrite() {
-  if (w != null) {
-    if (!isOpen) {
-      count = 0;
-      isOpen = true;
-      w.document.write("<p>Opening document and counting up....</p>");
-    }
-
-    w.document.write("<p>Counter at: " + count++ + "</p>");
-  }
-}
-
-function closeDoc() {
-  if ((w != null) && isOpen) {
-    w.document.write("<p>Closing document!</p>");
-    w.document.close();
-    isOpen = false;
-  }
-}
-</SCRIPT>
-</HEAD>
-<BODY>
-<h1>document.write (out-of-line) test</h1>
-<p>This test uses the open, write and close methods of a
-document to construct a document. It tests the "out-of-line"
-capabilities of document.write i.e. the ability to use 
-document.write to create an entirely new document.</p>
-
-<form>
-<p>Use this button to create a new window. If one already
-exists, we'll use it.
-<INPUT TYPE="button" NAME="newwin" VALUE="New Window" onClick="newWin(); return true;">
-</p>
-
-<p>Use this button to write the new value of a counter into
-the document. If the document was previously closed, it will be
-reopened (and the old contents will be destroyed.
-<INPUT TYPE="button" NAME="incrwrite" VALUE="Write" onClick="incrWrite(); return true;">
-</p>
-
-<p>Use this button to close the document. Subsequent writes will rewrite
-the document.
-<INPUT TYPE="button" NAME="closedoc" VALUE="Close Document" onClick="closeDoc(); return true;">
-<p>
-</FORM>
-</BODY>
-</HTML>
\ No newline at end of file
--- a/dom/webidl/HTMLAllCollection.webidl
+++ b/dom/webidl/HTMLAllCollection.webidl
@@ -1,14 +1,13 @@
 /* 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/. */
 
 /* Emulates undefined through Codegen.py. */
 [LegacyUnenumerableNamedProperties]
 interface HTMLAllCollection {
   readonly attribute unsigned long length;
-  getter Node? (unsigned long index);
-  Node? item(unsigned long index);
-  (Node or HTMLCollection)? item(DOMString name);
-  legacycaller (Node or HTMLCollection)? (DOMString name);
-  getter (Node or HTMLCollection)? namedItem(DOMString name);
+  getter Element (unsigned long index);
+  getter (HTMLCollection or Element)? namedItem(DOMString name);
+  (HTMLCollection or Element)? item(optional DOMString nameOrIndex);
+  legacycaller (HTMLCollection or Element)? (optional DOMString nameOrIndex);
 };
--- a/js/src/builtin/Array.cpp
+++ b/js/src/builtin/Array.cpp
@@ -218,17 +218,17 @@ GetLengthProperty(JSContext* cx, HandleO
  * except that by using signed 31-bit integers we miss the top half of the
  * valid range. This function checks the string representation itself; note
  * that calling a standard conversion routine might allow strings such as
  * "08" or "4.0" as array indices, which they are not.
  *
  */
 template <typename CharT>
 static bool
-StringIsArrayIndex(const CharT* s, uint32_t length, uint32_t* indexp)
+StringIsArrayIndexHelper(const CharT* s, uint32_t length, uint32_t* indexp)
 {
     const CharT* end = s + length;
 
     if (length == 0 || length > (sizeof("4294967294") - 1) || !IsAsciiDigit(*s)) {
         return false;
     }
 
     uint32_t c = 0, previous = 0;
@@ -260,18 +260,30 @@ StringIsArrayIndex(const CharT* s, uint3
     return false;
 }
 
 JS_FRIEND_API(bool)
 js::StringIsArrayIndex(JSLinearString* str, uint32_t* indexp)
 {
     AutoCheckCannotGC nogc;
     return str->hasLatin1Chars()
-           ? ::StringIsArrayIndex(str->latin1Chars(nogc), str->length(), indexp)
-           : ::StringIsArrayIndex(str->twoByteChars(nogc), str->length(), indexp);
+           ? StringIsArrayIndexHelper(str->latin1Chars(nogc), str->length(), indexp)
+           : StringIsArrayIndexHelper(str->twoByteChars(nogc), str->length(), indexp);
+}
+
+JS_FRIEND_API(bool)
+js::StringIsArrayIndex(const char16_t* str, uint32_t length, uint32_t* indexp)
+{
+    return StringIsArrayIndexHelper(str, length, indexp);
+}
+
+JS_FRIEND_API(bool)
+js::StringIsArrayIndex(const char* str, uint32_t length, uint32_t* indexp)
+{
+    return StringIsArrayIndexHelper(str, length, indexp);
 }
 
 template <typename T>
 static bool
 ToId(JSContext* cx, T index, MutableHandleId id);
 
 template <>
 bool
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -92,16 +92,17 @@ UNIFIED_SOURCES += [
     'testSavedStacks.cpp',
     'testScriptInfo.cpp',
     'testScriptObject.cpp',
     'testSetProperty.cpp',
     'testSetPropertyIgnoringNamedGetter.cpp',
     'testSharedImmutableStringsCache.cpp',
     'testSourcePolicy.cpp',
     'testStringBuffer.cpp',
+    'testStringIsArrayIndex.cpp',
     'testStructuredClone.cpp',
     'testSymbol.cpp',
     'testThreadingConditionVariable.cpp',
     'testThreadingExclusiveData.cpp',
     'testThreadingMutex.cpp',
     'testThreadingThread.cpp',
     'testToSignedOrUnsignedInteger.cpp',
     'testTypedArrays.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testStringIsArrayIndex.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+/* 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 "jsfriendapi.h"
+
+#include "jsapi-tests/tests.h"
+
+using mozilla::ArrayLength;
+
+// Need to account for string literals including the \0 at the end.
+#define STR(x) x, (ArrayLength(x)-1)
+
+static const struct TestTuple {
+    /* The string being tested. */
+    const char* string;
+    /* The number of characters from the string to use. */
+    size_t length;
+    /* Whether this string is an index. */
+    bool isindex;
+    /* If it's an index, what index it is.  Ignored if not an index. */
+    uint32_t index;
+} tests[] = {
+    { STR("0"), true, 0 },
+    { STR("1"), true, 1 },
+    { STR("2"), true, 2 },
+    { STR("9"), true, 9 },
+    { STR("10"), true, 10 },
+    { STR("15"), true, 15 },
+    { STR("16"), true, 16 },
+    { STR("17"), true, 17 },
+    { STR("99"), true, 99 },
+    { STR("100"), true, 100 },
+    { STR("255"), true, 255 },
+    { STR("256"), true, 256 },
+    { STR("257"), true, 257 },
+    { STR("999"), true, 999 },
+    { STR("1000"), true, 1000 },
+    { STR("4095"), true, 4095 },
+    { STR("4096"), true, 4096 },
+    { STR("9999"), true, 9999 },
+    { STR("1073741823"), true, 1073741823 },
+    { STR("1073741824"), true, 1073741824 },
+    { STR("1073741825"), true, 1073741825 },
+    { STR("2147483647"), true, 2147483647 },
+    { STR("2147483648"), true, 2147483648u },
+    { STR("2147483649"), true, 2147483649u },
+    { STR("4294967294"), true, 4294967294u },
+    { STR("4294967295"), false, 0 },  // Not an array index because need to be able to represent length
+    { STR("-1"), false, 0 },
+    { STR("abc"), false, 0 },
+    { STR(" 0"), false, 0 },
+    { STR("0 "), false, 0 },
+    // Tests to make sure the passed-in length is taken into account
+    { "0 ", 1, true, 0 },
+    { "123abc", 3, true, 123 },
+    { "123abc", 2, true, 12 },
+};
+
+BEGIN_TEST(testStringIsArrayIndex)
+{
+    for (size_t i = 0, sz = ArrayLength(tests); i < sz; i++) {
+        uint32_t index;
+        bool isindex = js::StringIsArrayIndex(tests[i].string,
+                                              tests[i].length,
+                                              &index);
+        CHECK_EQUAL(isindex, tests[i].isindex);
+        if (isindex) {
+            CHECK_EQUAL(index, tests[i].index);
+        }
+    }
+
+    return true;
+}
+END_TEST(testStringIsArrayIndex)
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1002,19 +1002,36 @@ CopyFlatStringChars(char16_t* dest, JSFl
  * results that match the output of Reflect.ownKeys.
  */
 JS_FRIEND_API(bool)
 GetPropertyKeys(JSContext* cx, JS::HandleObject obj, unsigned flags, JS::AutoIdVector* props);
 
 JS_FRIEND_API(bool)
 AppendUnique(JSContext* cx, JS::AutoIdVector& base, JS::AutoIdVector& others);
 
+/**
+ * Determine whether the given string is an array index in the sense of <https://tc39.github.io/ecma262/#array-index>.
+ *
+ * If it isn't, returns false.
+ *
+ * If it is, returns true and outputs the index in *indexp.
+ */
 JS_FRIEND_API(bool)
 StringIsArrayIndex(JSLinearString* str, uint32_t* indexp);
 
+/**
+ * Overloads of StringIsArrayIndex taking (char*,length) pairs.  These
+ * behave the same as the JSLinearString version.
+ */
+JS_FRIEND_API(bool)
+StringIsArrayIndex(const char* str, uint32_t length, uint32_t* indexp);
+
+JS_FRIEND_API(bool)
+StringIsArrayIndex(const char16_t* str, uint32_t length, uint32_t* indexp);
+
 JS_FRIEND_API(void)
 SetPreserveWrapperCallback(JSContext* cx, PreserveWrapperCallback callback);
 
 JS_FRIEND_API(bool)
 IsObjectInContextCompartment(JSObject* obj, const JSContext* cx);
 
 /*
  * NB: keep these in sync with the copy in builtin/SelfHostingDefines.h.
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -579,49 +579,48 @@ public class CustomTabsActivity extends 
 
     @Override
     public void onCanGoForward(GeckoSession session, boolean canGoForward) {
         mCanGoForward = canGoForward;
         updateMenuItemForward();
     }
 
     @Override
-    public GeckoResult<AllowOrDeny> onLoadRequest(final GeckoSession session, final String urlStr,
-                                                  final int target,
-                                                  final int flags) {
-        if (target != GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW) {
+    public GeckoResult<AllowOrDeny> onLoadRequest(final GeckoSession session,
+                                                  final LoadRequest request) {
+        if (request.target != GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW) {
             return GeckoResult.fromValue(AllowOrDeny.ALLOW);
         }
 
-        final Uri uri = Uri.parse(urlStr);
+        final Uri uri = Uri.parse(request.uri);
         if (uri == null) {
             // We can't handle this, so deny it.
-            Log.w(LOGTAG, "Failed to parse URL for navigation: " + urlStr);
+            Log.w(LOGTAG, "Failed to parse URL for navigation: " + request.uri);
             return GeckoResult.fromValue(AllowOrDeny.DENY);
         }
 
         // Always use Fennec for these schemes.
         if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme()) ||
             "data".equals(uri.getScheme()) || "blob".equals(uri.getScheme())) {
             final Intent intent = new Intent(this, BrowserApp.class);
             intent.setAction(Intent.ACTION_VIEW);
             intent.setData(uri);
             intent.setPackage(getPackageName());
             try {
                 startActivity(intent);
             } catch (ActivityNotFoundException e) {
-                Log.w(LOGTAG, "No activity handler found for: " + urlStr);
+                Log.w(LOGTAG, "No activity handler found for: " + request.uri);
             }
         } else {
             final Intent intent = new Intent(Intent.ACTION_VIEW);
             intent.setData(uri);
             try {
                 startActivity(intent);
             } catch (ActivityNotFoundException e) {
-                Log.w(LOGTAG, "No activity handler found for: " + urlStr);
+                Log.w(LOGTAG, "No activity handler found for: " + request.uri);
             }
         }
 
         return GeckoResult.fromValue(AllowOrDeny.DENY);
     }
 
     @Override
     public GeckoResult<GeckoSession> onNewSession(final GeckoSession session, final String uri) {
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -373,27 +373,26 @@ public class WebAppActivity extends AppC
     }
 
     @Override // GeckoSession.ContentDelegate
     public void onFullScreen(GeckoSession session, boolean fullScreen) {
         updateFullScreenContent(fullScreen);
     }
 
     @Override
-    public GeckoResult<AllowOrDeny> onLoadRequest(final GeckoSession session, final String urlStr,
-                                                  final int target,
-                                                  final int flags) {
-        final Uri uri = Uri.parse(urlStr);
+    public GeckoResult<AllowOrDeny> onLoadRequest(final GeckoSession session,
+                                                  final LoadRequest request) {
+        final Uri uri = Uri.parse(request.uri);
         if (uri == null) {
             // We can't really handle this, so deny it?
-            Log.w(LOGTAG, "Failed to parse URL for navigation: " + urlStr);
+            Log.w(LOGTAG, "Failed to parse URL for navigation: " + request.uri);
             return GeckoResult.fromValue(AllowOrDeny.DENY);
         }
 
-        if (mManifest.isInScope(uri) && target != TARGET_WINDOW_NEW) {
+        if (mManifest.isInScope(uri) && request.target != TARGET_WINDOW_NEW) {
             // This is in scope and wants to load in the same frame, so
             // let Gecko handle it.
             return GeckoResult.fromValue(AllowOrDeny.ALLOW);
         }
 
         if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme()) ||
             "data".equals(uri.getScheme()) || "blob".equals(uri.getScheme())) {
             final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder()
@@ -411,17 +410,17 @@ public class WebAppActivity extends AppC
             tab.launchUrl(this, uri);
         } else {
             final Intent intent = new Intent();
             intent.setAction(Intent.ACTION_VIEW);
             intent.setData(uri);
             try {
                 startActivity(intent);
             } catch (ActivityNotFoundException e) {
-                Log.w(LOGTAG, "No activity handler found for: " + urlStr);
+                Log.w(LOGTAG, "No activity handler found for: " + request.uri);
             }
         }
 
         return GeckoResult.fromValue(AllowOrDeny.DENY);
     }
 
     @Override
     public GeckoResult<GeckoSession> onNewSession(final GeckoSession session, final String uri) {
--- a/mobile/android/chrome/geckoview/GeckoViewNavigationContent.js
+++ b/mobile/android/chrome/geckoview/GeckoViewNavigationContent.js
@@ -15,29 +15,31 @@ XPCOMUtils.defineLazyModuleGetters(this,
 class GeckoViewNavigationContent extends GeckoViewContentModule {
   onInit() {
     docShell.loadURIDelegate = this;
   }
 
   // nsILoadURIDelegate.
   loadURI(aUri, aWhere, aFlags, aTriggeringPrincipal) {
     debug `loadURI: uri=${aUri && aUri.spec}
-                    where=${aWhere} flags=${aFlags}`;
+                    where=${aWhere} flags=${aFlags}
+                    tp=${aTriggeringPrincipal && aTriggeringPrincipal.URI &&
+                         aTriggeringPrincipal.URI.spec}`;
 
     if (!this.enabled) {
       return false;
     }
 
     // TODO: Remove this when we have a sensible error API.
     if (aUri && aUri.displaySpec.startsWith("about:certerror")) {
       addEventListener("click", ErrorPageEventHandler, true);
     }
 
     return LoadURIDelegate.load(content, this.eventDispatcher,
-                                aUri, aWhere, aFlags);
+                                aUri, aWhere, aFlags, aTriggeringPrincipal);
   }
 
   // nsILoadURIDelegate.
   handleLoadError(aUri, aError, aErrorModule) {
     debug `handleLoadError: uri=${aUri && aUri.spec}
                              uri2=${aUri && aUri.displaySpec}
                              error=${aError}`;
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
@@ -4,16 +4,17 @@
 
 package org.mozilla.geckoview.test
 
 import android.app.assist.AssistStructure
 import android.os.Build
 import org.mozilla.geckoview.AllowOrDeny
 import org.mozilla.geckoview.GeckoResult
 import org.mozilla.geckoview.GeckoSession
+import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.IgnoreCrash
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
 import org.mozilla.geckoview.test.util.Callbacks
 import org.mozilla.geckoview.test.util.UiThreadUtils
 
@@ -50,18 +51,19 @@ class ContentDelegateTest : BaseSessionT
     }
 
     @Test fun download() {
         sessionRule.session.loadTestPath(DOWNLOAD_HTML_PATH)
 
         sessionRule.waitUntilCalled(object : Callbacks.NavigationDelegate, Callbacks.ContentDelegate {
 
             @AssertCalled(count = 2)
-            override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, flags: Int): GeckoResult<AllowOrDeny>? {
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest):
+                                       GeckoResult<AllowOrDeny>? {
                 return null
             }
 
             @AssertCalled(false)
             override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
                 return null
             }
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -4,16 +4,17 @@
 
 package org.mozilla.geckoview.test
 
 import org.mozilla.gecko.util.GeckoBundle
 import org.mozilla.geckoview.AllowOrDeny
 import org.mozilla.geckoview.GeckoResult
 import org.mozilla.geckoview.GeckoSession
 import org.mozilla.geckoview.GeckoSessionSettings
+import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
 import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate;
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.NullDelegate
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
 import org.mozilla.geckoview.test.util.Callbacks
 
@@ -31,19 +32,20 @@ import org.junit.runner.RunWith
 class NavigationDelegateTest : BaseSessionTest() {
 
     fun testLoadErrorWithErrorPage(testUri: String, expectedCategory: Int,
                                    expectedError: Int,
                                    errorPageUrl: String?) {
         sessionRule.delegateDuringNextWait(
                 object : Callbacks.ProgressDelegate, Callbacks.NavigationDelegate, Callbacks.ContentDelegate {
                     @AssertCalled(count = 1, order = [1])
-                    override fun onLoadRequest(session: GeckoSession, uri: String,
-                                               where: Int, flags: Int): GeckoResult<AllowOrDeny>? {
-                        assertThat("URI should be " + testUri, uri, equalTo(testUri))
+                    override fun onLoadRequest(session: GeckoSession,
+                                               request: LoadRequest):
+                                               GeckoResult<AllowOrDeny>? {
+                        assertThat("URI should be " + testUri, request.uri, equalTo(testUri))
                         return null
                     }
 
                     @AssertCalled(count = 1, order = [2])
                     override fun onPageStart(session: GeckoSession, url: String) {
                         assertThat("URI should be " + testUri, url, equalTo(testUri))
                     }
 
@@ -216,24 +218,27 @@ class NavigationDelegateTest : BaseSessi
             "http://jigsaw.w3.org/HTTP/300/301.html"
         }
 
         sessionRule.session.loadUri(uri)
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 2, order = [1, 2])
-            override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, flags: Int): GeckoResult<AllowOrDeny>? {
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest):
+                                       GeckoResult<AllowOrDeny>? {
                 assertThat("Session should not be null", session, notNullValue())
-                assertThat("URI should not be null", uri, notNullValue())
-                assertThat("URL should match", uri,
-                        equalTo(forEachCall(uri, redirectUri)))
-                assertThat("Where should not be null", where, notNullValue())
-                assertThat("Where should match", where,
+                assertThat("URI should not be null", request.uri, notNullValue())
+                assertThat("URL should match", request.uri,
+                        equalTo(forEachCall(request.uri, redirectUri)))
+                assertThat("Trigger URL should be null", request.triggerUri,
+                           nullValue())
+                assertThat("Target should not be null", request.target, notNullValue())
+                assertThat("Target should match", request.target,
                         equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
                 return null
             }
         })
     }
 
     @Test fun safebrowsingPhishing() {
         val phishingUri = "https://www.itisatrap.org/firefox/its-a-trap.html"
@@ -395,23 +400,26 @@ class NavigationDelegateTest : BaseSessi
     }
 
     @Test fun load() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
-            override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, flags: Int): GeckoResult<AllowOrDeny>? {
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest):
+                                       GeckoResult<AllowOrDeny>? {
                 assertThat("Session should not be null", session, notNullValue())
-                assertThat("URI should not be null", uri, notNullValue())
-                assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
-                assertThat("Where should not be null", where, notNullValue())
-                assertThat("Where should match", where,
+                assertThat("URI should not be null", request.uri, notNullValue())
+                assertThat("URI should match", request.uri, endsWith(HELLO_HTML_PATH))
+                assertThat("Trigger URL should be null", request.triggerUri,
+                           nullValue())
+                assertThat("Target should not be null", request.target, notNullValue())
+                assertThat("Target should match", request.target,
                            equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
                 return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("Session should not be null", session, notNullValue())
                 assertThat("URL should not be null", url, notNullValue())
@@ -588,20 +596,23 @@ class NavigationDelegateTest : BaseSessi
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         sessionRule.session.reload()
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
-            override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, flags: Int): GeckoResult<AllowOrDeny>? {
-                assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
-                assertThat("Where should match", where,
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest):
+                                       GeckoResult<AllowOrDeny>? {
+                assertThat("URI should match", request.uri, endsWith(HELLO_HTML_PATH))
+                assertThat("Trigger URL should be null", request.triggerUri,
+                           nullValue())
+                assertThat("Target should match", request.target,
                            equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
                 return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
             }
@@ -637,20 +648,23 @@ class NavigationDelegateTest : BaseSessi
             }
         })
 
         sessionRule.session.goBack()
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
-            override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, flags: Int): GeckoResult<AllowOrDeny>? {
-                assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
-                assertThat("Where should match", where,
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest):
+                                       GeckoResult<AllowOrDeny>? {
+                assertThat("URI should match", request.uri, endsWith(HELLO_HTML_PATH))
+                assertThat("Trigger URL should be null", request.triggerUri,
+                           nullValue())
+                assertThat("Target should match", request.target,
                            equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
                 return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
             }
@@ -671,20 +685,23 @@ class NavigationDelegateTest : BaseSessi
             }
         })
 
         sessionRule.session.goForward()
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
-            override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, flags: Int): GeckoResult<AllowOrDeny>? {
-                assertThat("URI should match", uri, endsWith(HELLO2_HTML_PATH))
-                assertThat("Where should match", where,
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest):
+                                       GeckoResult<AllowOrDeny>? {
+                assertThat("URI should match", request.uri, endsWith(HELLO2_HTML_PATH))
+                assertThat("Trigger URL should be null", request.triggerUri,
+                           nullValue())
+                assertThat("Target should match", request.target,
                            equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
                 return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH))
             }
@@ -704,20 +721,21 @@ class NavigationDelegateTest : BaseSessi
                 return null
             }
         })
     }
 
     @Test fun onLoadUri_returnTrueCancelsLoad() {
         sessionRule.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 2)
-            override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, flags: Int): GeckoResult<AllowOrDeny> {
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest):
+                                       GeckoResult<AllowOrDeny>? {
                 val res : AllowOrDeny
-                if (uri.endsWith(HELLO_HTML_PATH)) {
+                if (request.uri.endsWith(HELLO_HTML_PATH)) {
                     res = AllowOrDeny.DENY
                 } else {
                     res = AllowOrDeny.ALLOW
                 }
                 return GeckoResult.fromValue(res)
             }
         })
 
@@ -745,20 +763,23 @@ class NavigationDelegateTest : BaseSessi
 
         sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
         sessionRule.session.evaluateJS("window.open('newSession_child.html', '_blank')")
 
         sessionRule.session.waitUntilCalled(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
-            override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, flags: Int): GeckoResult<AllowOrDeny>? {
-                assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
-                assertThat("Where should be correct", where,
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest):
+                                       GeckoResult<AllowOrDeny>? {
+                assertThat("URI should be correct", request.uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
+                assertThat("Trigger URL should match", request.triggerUri,
+                           endsWith(NEW_SESSION_HTML_PATH))
+                assertThat("Target should be correct", request.target,
                            equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW))
                 return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
                 assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
                 return null
@@ -775,20 +796,23 @@ class NavigationDelegateTest : BaseSessi
         sessionRule.session.waitForPageStop()
 
         sessionRule.session.evaluateJS("$('#targetBlankLink').click()")
 
         sessionRule.session.waitUntilCalled(object : Callbacks.NavigationDelegate {
             // We get two onLoadRequest calls for the link click,
             // one when loading the URL and one when opening a new window.
             @AssertCalled(count = 2, order = [1])
-            override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, flags: Int): GeckoResult<AllowOrDeny>? {
-                assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
-                assertThat("Where should be correct", where,
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest):
+                                       GeckoResult<AllowOrDeny>? {
+                assertThat("URI should be correct", request.uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
+                assertThat("Trigger URL should be null", request.triggerUri,
+                           endsWith(NEW_SESSION_HTML_PATH))
+                assertThat("Target should be correct", request.target,
                            equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW))
                 return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
                 assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
                 return null
@@ -871,40 +895,42 @@ class NavigationDelegateTest : BaseSessi
     @Test fun onNewSession_notCalledForHandledLoads() {
         // Disable popup blocker.
         sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false))
 
         sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
         sessionRule.session.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
-            override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, flags: Int): GeckoResult<AllowOrDeny> {
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest):
+                                       GeckoResult<AllowOrDeny>? {
                 // Pretend we handled the target="_blank" link click.
                 val res : AllowOrDeny
-                if (uri.endsWith(NEW_SESSION_CHILD_HTML_PATH)) {
+                if (request.uri.endsWith(NEW_SESSION_CHILD_HTML_PATH)) {
                     res = AllowOrDeny.DENY
                 } else {
                     res = AllowOrDeny.ALLOW
                 }
                 return GeckoResult.fromValue(res)
             }
         })
 
         sessionRule.session.evaluateJS("$('#targetBlankLink').click()")
 
         sessionRule.session.reload()
         sessionRule.session.waitForPageStop()
 
         // Assert that onNewSession was not called for the link click.
         sessionRule.session.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 2)
-            override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, flags: Int): GeckoResult<AllowOrDeny>? {
-                assertThat("URI must match", uri,
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest):
+                                       GeckoResult<AllowOrDeny>? {
+                assertThat("URI must match", request.uri,
                            endsWith(forEachCall(NEW_SESSION_CHILD_HTML_PATH, NEW_SESSION_HTML_PATH)))
                 return null
             }
 
             @AssertCalled(count = 0)
             override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
                 return null
             }
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PromptDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PromptDelegateTest.kt
@@ -1,13 +1,14 @@
 package org.mozilla.geckoview.test
 
 import org.mozilla.geckoview.AllowOrDeny
 import org.mozilla.geckoview.GeckoResult
 import org.mozilla.geckoview.GeckoSession
+import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
 import org.mozilla.geckoview.test.util.Callbacks
 
 import android.support.test.filters.MediumTest
 import android.support.test.runner.AndroidJUnit4
 import org.hamcrest.Matchers.*
@@ -44,20 +45,21 @@ class PromptDelegateTest : BaseSessionTe
             override fun onPopupRequest(session: GeckoSession, targetUri: String): GeckoResult<AllowOrDeny>? {
                 assertThat("Session should not be null", session, notNullValue())
                 assertThat("URL should not be null", targetUri, notNullValue())
                 assertThat("URL should match", targetUri, endsWith(HELLO_HTML_PATH))
                 return GeckoResult.fromValue(AllowOrDeny.ALLOW)
             }
 
             @AssertCalled(count = 2)
-            override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, flags: Int): GeckoResult<AllowOrDeny>? {
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest): GeckoResult<AllowOrDeny>? {
                 assertThat("Session should not be null", session, notNullValue())
-                assertThat("URL should not be null", uri, notNullValue())
-                assertThat("URL should match", uri, endsWith(forEachCall(POPUP_HTML_PATH, HELLO_HTML_PATH)))
+                assertThat("URL should not be null", request.uri, notNullValue())
+                assertThat("URL should match", request.uri, endsWith(forEachCall(POPUP_HTML_PATH, HELLO_HTML_PATH)))
                 return null
             }
         })
 
         sessionRule.session.loadTestPath(POPUP_HTML_PATH)
         sessionRule.waitUntilCalled(Callbacks.NavigationDelegate::class, "onNewSession")
     }
 
@@ -70,20 +72,21 @@ class PromptDelegateTest : BaseSessionTe
             override fun onPopupRequest(session: GeckoSession, targetUri: String): GeckoResult<AllowOrDeny>? {
                 assertThat("Session should not be null", session, notNullValue())
                 assertThat("URL should not be null", targetUri, notNullValue())
                 assertThat("URL should match", targetUri, endsWith(HELLO_HTML_PATH))
                 return GeckoResult.fromValue(AllowOrDeny.DENY)
             }
 
             @AssertCalled(count = 1)
-            override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, flags: Int): GeckoResult<AllowOrDeny>? {
+            override fun onLoadRequest(session: GeckoSession,
+                                       request: LoadRequest): GeckoResult<AllowOrDeny>? {
                 assertThat("Session should not be null", session, notNullValue())
-                assertThat("URL should not be null", uri, notNullValue())
-                assertThat("URL should match", uri, endsWith(POPUP_HTML_PATH))
+                assertThat("URL should not be null", request.uri, notNullValue())
+                assertThat("URL should match", request.uri, endsWith(POPUP_HTML_PATH))
                 return null
             }
 
             @AssertCalled(count = 0)
             override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
                 return null
             }
         })
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
@@ -46,18 +46,18 @@ public class TestRunnerActivity extends 
         }
 
         @Override
         public void onCanGoForward(GeckoSession session, boolean canGoForward) {
 
         }
 
         @Override
-        public GeckoResult<AllowOrDeny> onLoadRequest(GeckoSession session, String uri,
-                                                      int target, int flags) {
+        public GeckoResult<AllowOrDeny> onLoadRequest(GeckoSession session,
+                                                  LoadRequest request) {
             // Allow Gecko to load all URIs
             return GeckoResult.fromValue(AllowOrDeny.ALLOW);
         }
 
         @Override
         public GeckoResult<GeckoSession> onNewSession(GeckoSession session, String uri) {
             return GeckoResult.fromValue(createBackgroundSession(session.getSettings()));
         }
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.geckoview.test.util
 
 import org.mozilla.geckoview.AllowOrDeny
 import org.mozilla.geckoview.GeckoResponse
 import org.mozilla.geckoview.GeckoResult
 import org.mozilla.geckoview.GeckoSession
+import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
 
 import android.view.inputmethod.CursorAnchorInfo
 import android.view.inputmethod.ExtractedText
 import android.view.inputmethod.ExtractedTextRequest
 
 class Callbacks private constructor() {
     object Default : All
 
@@ -49,18 +50,18 @@ class Callbacks private constructor() {
         }
 
         override fun onCanGoBack(session: GeckoSession, canGoBack: Boolean) {
         }
 
         override fun onCanGoForward(session: GeckoSession, canGoForward: Boolean) {
         }
 
-        override fun onLoadRequest(session: GeckoSession, uri: String, where: Int,
-                                   flags: Int): GeckoResult<AllowOrDeny>? {
+        override fun onLoadRequest(session: GeckoSession,
+                                   request: LoadRequest): GeckoResult<AllowOrDeny>? {
             return null
         }
 
         override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
             return null
         }
 
         override fun onLoadError(session: GeckoSession, uri: String?,
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -169,32 +169,16 @@ public class GeckoSession extends LayerS
             "GeckoViewNavigation", this,
             new String[]{
                 "GeckoView:LocationChange",
                 "GeckoView:OnLoadError",
                 "GeckoView:OnLoadRequest",
                 "GeckoView:OnNewSession"
             }
         ) {
-            // This needs to match nsIBrowserDOMWindow.idl
-            private int convertGeckoTarget(int geckoTarget) {
-                switch (geckoTarget) {
-                    case 0: // OPEN_DEFAULTWINDOW
-                    case 1: // OPEN_CURRENTWINDOW
-                        return NavigationDelegate.TARGET_WINDOW_CURRENT;
-                    default: // OPEN_NEWWINDOW, OPEN_NEWTAB, OPEN_SWITCHTAB
-                        return NavigationDelegate.TARGET_WINDOW_NEW;
-                }
-            }
-
-            // The flags are already matched with nsIDocShell.idl.
-            private int filterFlags(int flags) {
-                return flags & NavigationDelegate.LOAD_REQUEST_IS_USER_TRIGGERED;
-            }
-
             private @NavigationDelegate.LoadErrorCategory int getErrorCategory(
                     long errorModule, @NavigationDelegate.LoadError int error) {
                 // Match flags with XPCOM ErrorList.h.
                 if (errorModule == 21) {
                     return NavigationDelegate.ERROR_CATEGORY_SECURITY;
                 }
                 return error & 0xF;
             }
@@ -299,32 +283,35 @@ public class GeckoSession extends LayerS
                         delegate.onLocationChange(GeckoSession.this,
                                                   message.getString("uri"));
                     }
                     delegate.onCanGoBack(GeckoSession.this,
                                          message.getBoolean("canGoBack"));
                     delegate.onCanGoForward(GeckoSession.this,
                                             message.getBoolean("canGoForward"));
                 } else if ("GeckoView:OnLoadRequest".equals(event)) {
-                    final String uri = message.getString("uri");
-                    final int where = convertGeckoTarget(message.getInt("where"));
-                    final int flags = filterFlags(message.getInt("flags"));
-
-                    if (!IntentUtils.isUriSafeForScheme(uri)) {
+                    final NavigationDelegate.LoadRequest request =
+                        new NavigationDelegate.LoadRequest(
+                              message.getString("uri"),
+                              message.getString("triggerUri"),
+                              message.getInt("where"),
+                              message.getInt("flags"));
+
+                    if (!IntentUtils.isUriSafeForScheme(request.uri)) {
                         callback.sendError("Blocked unsafe intent URI");
 
-                        delegate.onLoadError(GeckoSession.this, uri,
+                        delegate.onLoadError(GeckoSession.this, request.uri,
                                              NavigationDelegate.ERROR_CATEGORY_URI,
                                              NavigationDelegate.ERROR_MALFORMED_URI);
 
                         return;
                     }
 
                     final GeckoResult<AllowOrDeny> result =
-                        delegate.onLoadRequest(GeckoSession.this, uri, where, flags);
+                        delegate.onLoadRequest(GeckoSession.this, request);
 
                     if (result == null) {
                         callback.sendSuccess(null);
                         return;
                     }
 
                     result.then(new GeckoResult.OnValueListener<AllowOrDeny, Void>() {
                         @Override
@@ -2551,50 +2538,83 @@ public class GeckoSession extends LayerS
         void onCanGoForward(GeckoSession session, boolean canGoForward);
 
         @IntDef({TARGET_WINDOW_NONE, TARGET_WINDOW_CURRENT, TARGET_WINDOW_NEW})
         /* package */ @interface TargetWindow {}
         public static final int TARGET_WINDOW_NONE = 0;
         public static final int TARGET_WINDOW_CURRENT = 1;
         public static final int TARGET_WINDOW_NEW = 2;
 
-        @IntDef(flag = true,
-                value = {LOAD_REQUEST_IS_USER_TRIGGERED})
-        /* package */ @interface LoadRequestFlags {}
-
-        // Match with nsIDocShell.idl.
         /**
-         * The load request was triggered by user input.
+         * Load request details.
          */
-        public static final int LOAD_REQUEST_IS_USER_TRIGGERED = 0x1000;
+        public static class LoadRequest {
+            /* package */ LoadRequest(@NonNull final String uri,
+                                      @Nullable final String triggerUri,
+                                      int geckoTarget,
+                                      int flags) {
+                this.uri = uri;
+                this.triggerUri = triggerUri;
+                this.target = convertGeckoTarget(geckoTarget);
+
+                // Match with nsIDocShell.idl.
+                this.isUserTriggered = (flags & 0x1000) != 0;
+            }
+
+            // This needs to match nsIBrowserDOMWindow.idl
+            private @TargetWindow int convertGeckoTarget(int geckoTarget) {
+                switch (geckoTarget) {
+                    case 0: // OPEN_DEFAULTWINDOW
+                    case 1: // OPEN_CURRENTWINDOW
+                        return TARGET_WINDOW_CURRENT;
+                    default: // OPEN_NEWWINDOW, OPEN_NEWTAB, OPEN_SWITCHTAB
+                        return TARGET_WINDOW_NEW;
+                }
+            }
+
+            /**
+             * The URI to be loaded.
+             */
+            public final @NonNull String uri;
+
+            /**
+             * The URI of the origin page that triggered the load request.
+             * null for initial loads and loads originating from data: URIs.
+             */
+            public final @Nullable String triggerUri;
+
+            /**
+             * The target where the window has requested to open.
+             * One of {@link #TARGET_WINDOW_NONE TARGET_WINDOW_*}.
+             */
+            public final @TargetWindow int target;
+
+            /**
+             * True if and only if the request was triggered by user interaction.
+             */
+            public final boolean isUserTriggered;
+        }
 
         /**
          * A request to open an URI. This is called before each page load to
          * allow custom behavior implementation.
          * For example, this can be used to override the behavior of
          * TAGET_WINDOW_NEW requests, which defaults to requesting a new
          * GeckoSession via onNewSession.
          *
          * @param session The GeckoSession that initiated the callback.
-         * @param uri The URI to be loaded.
-         * @param target The target where the window has requested to open.
-         *               One of {@link #TARGET_WINDOW_NONE TARGET_WINDOW_*}.
-         * @param flags The load request flags.
-         *              One or more of {@link #LOAD_REQUEST_IS_USER_TRIGGERED
-         *              LOAD_REQUEST_*}.
+         * @param request The {@link LoadRequest} containing the request details.
          *
          * @return A {@link GeckoResult} with a AllowOrDeny value which indicates whether
          *         or not the load was handled. If unhandled, Gecko will continue the
          *         load as normal. If handled (true value), Gecko will abandon the load.
          *         A null return value is interpreted as false (unhandled).
          */
         @Nullable GeckoResult<AllowOrDeny> onLoadRequest(@NonNull GeckoSession session,
-                                                         @NonNull String uri,
-                                                         @TargetWindow int target,
-                                                         @LoadRequestFlags int flags);
+                                                         @NonNull LoadRequest request);
 
         /**
         * A request has been made to open a new session. The URI is provided only for
         * informational purposes. Do not call GeckoSession.loadUri() here. Additionally, the
         * returned GeckoSession must be a newly-created one.
         *
         * @param session The GeckoSession that initiated the callback.
         * @param uri The URI to be loaded.
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -627,20 +627,23 @@ public class GeckoViewActivity extends A
         }
 
         @Override
         public void onCanGoForward(GeckoSession session, boolean canGoForward) {
             mCanGoForward = canGoForward;
         }
 
         @Override
-        public GeckoResult<AllowOrDeny> onLoadRequest(final GeckoSession session, final String uri,
-                                                       final int target, final int flags) {
-            Log.d(LOGTAG, "onLoadRequest=" + uri + " where=" + target +
-                  " flags=" + flags);
+        public GeckoResult<AllowOrDeny> onLoadRequest(final GeckoSession session,
+                                                      final LoadRequest request) {
+            Log.d(LOGTAG, "onLoadRequest=" + request.uri +
+                  " triggerUri=" + request.triggerUri +
+                  " where=" + request.target +
+                  " isUserTriggered=" + request.isUserTriggered);
+
             return GeckoResult.fromValue(AllowOrDeny.ALLOW);
         }
 
         @Override
         public GeckoResult<GeckoSession> onNewSession(final GeckoSession session, final String uri) {
             GeckoSession newSession = new GeckoSession(session.getSettings());
 
             Intent intent = new Intent(GeckoViewActivity.this, SessionActivity.class);
--- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
@@ -151,17 +151,17 @@ class GeckoViewNavigation extends GeckoV
   }
 
   // nsIBrowserDOMWindow.
   createContentWindow(aUri, aOpener, aWhere, aFlags, aTriggeringPrincipal) {
     debug `createContentWindow: uri=${aUri && aUri.spec}
                                 where=${aWhere} flags=${aFlags}`;
 
     if (LoadURIDelegate.load(this.window, this.eventDispatcher,
-                             aUri, aWhere, aFlags)) {
+                             aUri, aWhere, aFlags, aTriggeringPrincipal)) {
       // The app has handled the load, abort open-window handling.
       Components.returnCode = Cr.NS_ERROR_ABORT;
       return null;
     }
 
     const browser = this.handleNewSession(aUri, aOpener, aWhere, aFlags, null);
     if (!browser) {
       Components.returnCode = Cr.NS_ERROR_ABORT;
@@ -175,17 +175,18 @@ class GeckoViewNavigation extends GeckoV
   createContentWindowInFrame(aUri, aParams, aWhere, aFlags, aNextTabParentId,
                              aName) {
     debug `createContentWindowInFrame: uri=${aUri && aUri.spec}
                                        where=${aWhere} flags=${aFlags}
                                        nextTabParentId=${aNextTabParentId}
                                        name=${aName}`;
 
     if (LoadURIDelegate.load(this.window, this.eventDispatcher,
-                             aUri, aWhere, aFlags)) {
+                             aUri, aWhere, aFlags,
+                             aParams.triggeringPrincipal)) {
       // The app has handled the load, abort open-window handling.
       Components.returnCode = Cr.NS_ERROR_ABORT;
       return null;
     }
 
     const browser = this.handleNewSession(aUri, null, aWhere, aFlags, aNextTabParentId);
     if (!browser) {
       Components.returnCode = Cr.NS_ERROR_ABORT;
@@ -196,17 +197,17 @@ class GeckoViewNavigation extends GeckoV
   }
 
   handleOpenUri(aUri, aOpener, aWhere, aFlags, aTriggeringPrincipal,
                 aNextTabParentId) {
     debug `handleOpenUri: uri=${aUri && aUri.spec}
                           where=${aWhere} flags=${aFlags}`;
 
     if (LoadURIDelegate.load(this.window, this.eventDispatcher,
-                             aUri, aWhere, aFlags)) {
+                             aUri, aWhere, aFlags, aTriggeringPrincipal)) {
       return null;
     }
 
     let browser = this.browser;
 
     if (aWhere === Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW ||
         aWhere === Ci.nsIBrowserDOMWindow.OPEN_NEWTAB ||
         aWhere === Ci.nsIBrowserDOMWindow.OPEN_SWITCHTAB) {
@@ -226,17 +227,18 @@ class GeckoViewNavigation extends GeckoV
   openURI(aUri, aOpener, aWhere, aFlags, aTriggeringPrincipal) {
     const browser = this.handleOpenUri(aUri, aOpener, aWhere, aFlags,
                                        aTriggeringPrincipal, null);
     return browser && browser.contentWindow;
   }
 
   // nsIBrowserDOMWindow.
   openURIInFrame(aUri, aParams, aWhere, aFlags, aNextTabParentId, aName) {
-    const browser = this.handleOpenUri(aUri, null, aWhere, aFlags, null,
+    const browser = this.handleOpenUri(aUri, null, aWhere, aFlags,
+                                       aParams.triggeringPrincipal,
                                        aNextTabParentId);
     return browser;
   }
 
   // nsIBrowserDOMWindow.
   isTabContentWindow(aWindow) {
     return this.browser.contentWindow === aWindow;
   }
--- a/mobile/android/modules/geckoview/LoadURIDelegate.jsm
+++ b/mobile/android/modules/geckoview/LoadURIDelegate.jsm
@@ -13,26 +13,33 @@ XPCOMUtils.defineLazyModuleGetters(this,
   Services: "resource://gre/modules/Services.jsm",
 });
 
 GeckoViewUtils.initLogging("LoadURIDelegate", this);
 
 const LoadURIDelegate = {
   // Delegate URI loading to the app.
   // Return whether the loading has been handled.
-  load: function(aWindow, aEventDispatcher, aUri, aWhere, aFlags) {
+  load: function(aWindow, aEventDispatcher, aUri, aWhere, aFlags,
+                 aTriggeringPrincipal) {
     if (!aWindow) {
       return false;
     }
 
+    const triggerUri = aTriggeringPrincipal &&
+                       (aTriggeringPrincipal.isNullPrincipal
+                        ? null
+                        : aTriggeringPrincipal.URI);
+
     const message = {
       type: "GeckoView:OnLoadRequest",
       uri: aUri ? aUri.displaySpec : "",
       where: aWhere,
       flags: aFlags,
+      triggerUri: triggerUri && triggerUri.displaySpec,
     };
 
     let handled = undefined;
     aEventDispatcher.sendRequestForResult(message).then(response => {
       handled = response;
     }, () => {
       // There was an error or listener was not registered in GeckoSession,
       // treat as unhandled.
--- a/servo/components/malloc_size_of/Cargo.toml
+++ b/servo/components/malloc_size_of/Cargo.toml
@@ -7,16 +7,17 @@ publish = false
 
 [lib]
 path = "lib.rs"
 
 [features]
 servo = [
     "hyper",
     "hyper_serde",
+    "keyboard-types",
     "mozjs",
     "serde",
     "serde_bytes",
     "servo_channel",
     "string_cache",
     "time",
     "url",
     "webrender_api",
@@ -25,16 +26,17 @@ servo = [
 
 [dependencies]
 app_units = "0.7"
 cssparser = "0.24.0"
 euclid = "0.19"
 hashglobe = { path = "../hashglobe" }
 hyper = { version = "0.10", optional = true }
 hyper_serde = { version = "0.8", optional = true }
+keyboard-types = {version = "0.4.2-servo", features = ["serde"], optional = true}
 mozjs = { version = "0.9.0", optional = true }
 selectors = { path = "../selectors" }
 serde = { version = "1.0.27", optional = true }
 serde_bytes = { version = "0.10", optional = true }
 servo_arc = { path = "../servo_arc" }
 servo_channel = { path = "../channel", optional = true }
 smallbitvec = "2.1.0"
 smallvec = "0.6"
--- a/servo/components/malloc_size_of/lib.rs
+++ b/servo/components/malloc_size_of/lib.rs
@@ -47,16 +47,18 @@ extern crate app_units;
 extern crate cssparser;
 extern crate euclid;
 extern crate hashglobe;
 #[cfg(feature = "servo")]
 extern crate hyper;
 #[cfg(feature = "servo")]
 extern crate hyper_serde;
 #[cfg(feature = "servo")]
+extern crate keyboard_types;
+#[cfg(feature = "servo")]
 extern crate mozjs as js;
 extern crate selectors;
 #[cfg(feature = "servo")]
 extern crate serde;
 #[cfg(feature = "servo")]
 extern crate serde_bytes;
 extern crate servo_arc;
 #[cfg(feature = "servo")]
@@ -910,18 +912,16 @@ impl MallocSizeOf for url::Host {
         }
     }
 }
 #[cfg(feature = "webrender_api")]
 malloc_size_of_is_0!(webrender_api::BorderRadius);
 #[cfg(feature = "webrender_api")]
 malloc_size_of_is_0!(webrender_api::BorderStyle);
 #[cfg(feature = "webrender_api")]
-malloc_size_of_is_0!(webrender_api::BorderWidths);
-#[cfg(feature = "webrender_api")]
 malloc_size_of_is_0!(webrender_api::BoxShadowClipMode);
 #[cfg(feature = "webrender_api")]
 malloc_size_of_is_0!(webrender_api::ClipAndScrollInfo);
 #[cfg(feature = "webrender_api")]
 malloc_size_of_is_0!(webrender_api::ColorF);
 #[cfg(feature = "webrender_api")]
 malloc_size_of_is_0!(webrender_api::ComplexClipRegion);
 #[cfg(feature = "webrender_api")]
@@ -953,16 +953,29 @@ malloc_size_of_is_0!(webrender_api::Repe
 #[cfg(feature = "webrender_api")]
 malloc_size_of_is_0!(webrender_api::ScrollSensitivity);
 #[cfg(feature = "webrender_api")]
 malloc_size_of_is_0!(webrender_api::StickyOffsetBounds);
 #[cfg(feature = "webrender_api")]
 malloc_size_of_is_0!(webrender_api::TransformStyle);
 
 #[cfg(feature = "servo")]
+impl MallocSizeOf for keyboard_types::Key {
+    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+        match self {
+            keyboard_types::Key::Character(ref s) => s.size_of(ops),
+            _ => 0,
+        }
+    }
+}
+
+#[cfg(feature = "servo")]
+malloc_size_of_is_0!(keyboard_types::Modifiers);
+
+#[cfg(feature = "servo")]
 impl MallocSizeOf for xml5ever::QualName {
     fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
         self.prefix.size_of(ops) + self.ns.size_of(ops) + self.local.size_of(ops)
     }
 }
 
 #[cfg(feature = "servo")]
 impl MallocSizeOf for hyper::header::Headers {
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -209,56 +209,44 @@ impl fmt::Debug for KeyframesAnimationSt
 }
 
 /// State relating to an animation.
 #[derive(Clone, Debug)]
 pub enum Animation {
     /// A transition is just a single frame triggered at a time, with a reflow.
     ///
     /// the f64 field is the start time as returned by `time::precise_time_s()`.
-    ///
-    /// The `bool` field is werther this animation should no longer run.
-    Transition(OpaqueNode, f64, AnimationFrame, bool),
+    Transition(OpaqueNode, f64, AnimationFrame),
     /// A keyframes animation is identified by a name, and can have a
     /// node-dependent state (i.e. iteration count, etc.).
     ///
     /// TODO(emilio): The animation object could be refcounted.
     Keyframes(
         OpaqueNode,
         KeyframesAnimation,
         Atom,
         KeyframesAnimationState,
     ),
 }
 
 impl Animation {
-    /// Mark this animation as expired.
-    #[inline]
-    pub fn mark_as_expired(&mut self) {
-        debug_assert!(!self.is_expired());
-        match *self {
-            Animation::Transition(_, _, _, ref mut expired) => *expired = true,
-            Animation::Keyframes(_, _, _, ref mut state) => state.expired = true,
-        }
-    }
-
     /// Whether this animation is expired.
     #[inline]
     pub fn is_expired(&self) -> bool {
         match *self {
-            Animation::Transition(_, _, _, expired) => expired,
+            Animation::Transition(..) => false,
             Animation::Keyframes(_, _, _, ref state) => state.expired,
         }
     }
 
     /// The opaque node that owns the animation.
     #[inline]
     pub fn node(&self) -> &OpaqueNode {
         match *self {
-            Animation::Transition(ref node, _, _, _) => node,
+            Animation::Transition(ref node, _, _) => node,
             Animation::Keyframes(ref node, _, _, _) => node,
         }
     }
 
     /// Whether this animation is paused. A transition can never be paused.
     #[inline]
     pub fn is_paused(&self) -> bool {
         match *self {
@@ -445,32 +433,38 @@ pub fn start_transitions_if_applicable(
             // transition is the same as that of a transition that's already
             // running on the same node.
             //
             // [1]: https://drafts.csswg.org/css-transitions/#starting
             if possibly_expired_animations
                 .iter()
                 .any(|animation| animation.has_the_same_end_value_as(&property_animation))
             {
+                debug!(
+                    "Not initiating transition for {}, other transition \
+                     found with the same end value",
+                    property_animation.property_name()
+                );
                 continue;
             }
 
+            debug!("Kicking off transition of {:?}", property_animation);
+
             // Kick off the animation.
             let box_style = new_style.get_box();
             let now = timer.seconds();
             let start_time = now + (box_style.transition_delay_mod(i).seconds() as f64);
             new_animations_sender
                 .send(Animation::Transition(
                     opaque_node,
                     start_time,
                     AnimationFrame {
                         duration: box_style.transition_duration_mod(i).seconds() as f64,
-                        property_animation: property_animation,
+                        property_animation,
                     },
-                    /* is_expired = */ false,
                 )).unwrap();
 
             had_animations = true;
         }
     }
 
     had_animations
 }
@@ -539,83 +533,86 @@ pub fn maybe_start_animations<E>(
 ) -> bool
 where
     E: TElement,
 {
     let mut had_animations = false;
 
     let box_style = new_style.get_box();
     for (i, name) in box_style.animation_name_iter().enumerate() {
-        let name = if let Some(atom) = name.as_atom() {
-            atom
-        } else {
-            continue;
+        let name = match name.as_atom() {
+            Some(atom) => atom,
+            None => continue,
         };
 
         debug!("maybe_start_animations: name={}", name);
         let total_duration = box_style.animation_duration_mod(i).seconds();
         if total_duration == 0. {
             continue;
         }
 
-        if let Some(anim) = context.stylist.get_animation(name, element) {
-            debug!("maybe_start_animations: animation {} found", name);
+        let anim = match context.stylist.get_animation(name, element) {
+            Some(animation) => animation,
+            None => continue,
+        };
+
+        debug!("maybe_start_animations: animation {} found", name);
 
-            // If this animation doesn't have any keyframe, we can just continue
-            // without submitting it to the compositor, since both the first and
-            // the second keyframes would be synthetised from the computed
-            // values.
-            if anim.steps.is_empty() {
-                continue;
-            }
+        // If this animation doesn't have any keyframe, we can just continue
+        // without submitting it to the compositor, since both the first and
+        // the second keyframes would be synthetised from the computed
+        // values.
+        if anim.steps.is_empty() {
+            continue;
+        }
+
+        let delay = box_style.animation_delay_mod(i).seconds();
+        let now = context.timer.seconds();
+        let animation_start = now + delay as f64;
+        let duration = box_style.animation_duration_mod(i).seconds();
+        let iteration_state = match box_style.animation_iteration_count_mod(i) {
+            AnimationIterationCount::Infinite => KeyframesIterationState::Infinite,
+            AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n),
+        };
+
+        let animation_direction = box_style.animation_direction_mod(i);
 
-            let delay = box_style.animation_delay_mod(i).seconds();
-            let now = context.timer.seconds();
-            let animation_start = now + delay as f64;
-            let duration = box_style.animation_duration_mod(i).seconds();
-            let iteration_state = match box_style.animation_iteration_count_mod(i) {
-                AnimationIterationCount::Infinite => KeyframesIterationState::Infinite,
-                AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n),
-            };
+        let initial_direction = match animation_direction {
+            AnimationDirection::Normal | AnimationDirection::Alternate => {
+                AnimationDirection::Normal
+            },
+            AnimationDirection::Reverse | AnimationDirection::AlternateReverse => {
+                AnimationDirection::Reverse
+            },
+        };
+
+        let running_state = match box_style.animation_play_state_mod(i) {
+            AnimationPlayState::Paused => KeyframesRunningState::Paused(0.),
+            AnimationPlayState::Running => KeyframesRunningState::Running,
+        };
 
-            let animation_direction = box_style.animation_direction_mod(i);
-
-            let initial_direction = match animation_direction {
-                AnimationDirection::Normal | AnimationDirection::Alternate => {
-                    AnimationDirection::Normal
+        new_animations_sender
+            .send(Animation::Keyframes(
+                node,
+                anim.clone(),
+                name.clone(),
+                KeyframesAnimationState {
+                    started_at: animation_start,
+                    duration: duration as f64,
+                    delay: delay as f64,
+                    iteration_state,
+                    running_state,
+                    direction: animation_direction,
+                    current_direction: initial_direction,
+                    expired: false,
+                    cascade_style: new_style.clone(),
                 },
-                AnimationDirection::Reverse | AnimationDirection::AlternateReverse => {
-                    AnimationDirection::Reverse
-                },
-            };
-
-            let running_state = match box_style.animation_play_state_mod(i) {
-                AnimationPlayState::Paused => KeyframesRunningState::Paused(0.),
-                AnimationPlayState::Running => KeyframesRunningState::Running,
-            };
-
-            new_animations_sender
-                .send(Animation::Keyframes(
-                    node,
-                    anim.clone(),
-                    name.clone(),
-                    KeyframesAnimationState {
-                        started_at: animation_start,
-                        duration: duration as f64,
-                        delay: delay as f64,
-                        iteration_state: iteration_state,
-                        running_state: running_state,
-                        direction: animation_direction,
-                        current_direction: initial_direction,
-                        expired: false,
-                        cascade_style: new_style.clone(),
-                    },
-                )).unwrap();
-            had_animations = true;
-        }
+            ))
+            .unwrap();
+        had_animations = true;
     }
 
     had_animations
 }
 
 /// Updates a given computed style for a given animation frame. Returns a bool
 /// representing if the style was indeed updated.
 pub fn update_style_for_animation_frame(
@@ -635,44 +632,63 @@ pub fn update_style_for_animation_frame(
 
     frame
         .property_animation
         .update(Arc::make_mut(&mut new_style), progress);
 
     true
 }
 
+/// Returns the kind of animation update that happened.
+pub enum AnimationUpdate {
+    /// The style was successfully updated, the animation is still running.
+    Regular,
+    /// A style change canceled this animation.
+    AnimationCanceled,
+}
+
 /// Updates a single animation and associated style based on the current time.
+///
+/// FIXME(emilio): This doesn't handle any kind of dynamic change to the
+/// animation or transition properties in any reasonable way.
+///
+/// This should probably be split in two, one from updating animations and
+/// transitions in response to a style change (that is,
+/// consider_starting_transitions + maybe_start_animations, but handling
+/// canceled animations, duration changes, etc, there instead of here), and this
+/// function should be only about the style update in response of a transition.
 pub fn update_style_for_animation<E>(
     context: &SharedStyleContext,
     animation: &Animation,
     style: &mut Arc<ComputedValues>,
     font_metrics_provider: &FontMetricsProvider,
-) where
+) -> AnimationUpdate
+where
     E: TElement,
 {
-    debug!("update_style_for_animation: entering");
+    debug!("update_style_for_animation: {:?}", animation);
     debug_assert!(!animation.is_expired());
 
     match *animation {
-        Animation::Transition(_, start_time, ref frame, _) => {
-            debug!("update_style_for_animation: transition found");
+        Animation::Transition(_, start_time, ref frame) => {
             let now = context.timer.seconds();
             let mut new_style = (*style).clone();
             let updated_style =
                 update_style_for_animation_frame(&mut new_style, now, start_time, frame);
             if updated_style {
                 *style = new_style
             }
+            // FIXME(emilio): Should check before updating the style that the
+            // transition_property still transitions this, or bail out if not.
+            //
+            // Or doing it in process_animations, only if transition_property
+            // changed somehow (even better).
+            AnimationUpdate::Regular
         },
         Animation::Keyframes(_, ref animation, ref name, ref state) => {
-            debug!(
-                "update_style_for_animation: animation found: \"{}\", {:?}",
-                name, state
-            );
             let duration = state.duration;
             let started_at = state.started_at;
 
             let now = match state.running_state {
                 KeyframesRunningState::Running => context.timer.seconds(),
                 KeyframesRunningState::Paused(progress) => started_at + duration * progress,
             };
 
@@ -680,48 +696,33 @@ pub fn update_style_for_animation<E>(
 
             let maybe_index = style
                 .get_box()
                 .animation_name_iter()
                 .position(|animation_name| Some(name) == animation_name.as_atom());
 
             let index = match maybe_index {
                 Some(index) => index,
-                None => {
-                    warn!(
-                        "update_style_for_animation: Animation {:?} not found in style",
-                        name
-                    );
-                    return;
-                },
+                None => return AnimationUpdate::AnimationCanceled,
             };
 
             let total_duration = style.get_box().animation_duration_mod(index).seconds() as f64;
             if total_duration == 0. {
-                debug!(
-                    "update_style_for_animation: zero duration for animation {:?}",
-                    name
-                );
-                return;
+                return AnimationUpdate::AnimationCanceled;
             }
 
             let mut total_progress = (now - started_at) / total_duration;
             if total_progress < 0. {
                 warn!("Negative progress found for animation {:?}", name);
-                return;
+                return AnimationUpdate::Regular;
             }
             if total_progress > 1. {
                 total_progress = 1.;
             }
 
-            debug!(
-                "update_style_for_animation: anim \"{}\", steps: {:?}, state: {:?}, progress: {}",
-                name, animation.steps, state, total_progress
-            );
-
             // Get the target and the last keyframe position.
             let last_keyframe_position;
             let target_keyframe_position;
             match state.current_direction {
                 AnimationDirection::Normal => {
                     target_keyframe_position = animation
                         .steps
                         .iter()
@@ -753,21 +754,17 @@ pub fn update_style_for_animation<E>(
 
             debug!(
                 "update_style_for_animation: keyframe from {:?} to {:?}",
                 last_keyframe_position, target_keyframe_position
             );
 
             let target_keyframe = match target_keyframe_position {
                 Some(target) => &animation.steps[target],
-                None => {
-                    warn!("update_style_for_animation: No current keyframe found for animation \"{}\" at progress {}",
-                          name, total_progress);
-                    return;
-                },
+                None => return AnimationUpdate::Regular,
             };
 
             let last_keyframe = &animation.steps[last_keyframe_position];
 
             let relative_timespan =
                 (target_keyframe.start_percentage.0 - last_keyframe.start_percentage.0).abs();
             let relative_duration = relative_timespan as f64 * duration;
             let last_keyframe_ended_at = match state.current_direction {
@@ -841,16 +838,17 @@ pub fn update_style_for_animation<E>(
                 }
             }
 
             debug!(
                 "update_style_for_animation: got style change in animation \"{}\"",
                 name
             );
             *style = new_style;
+            AnimationUpdate::Regular
         },
     }
 }
 
 /// Update the style in the node when it finishes.
 #[cfg(feature = "servo")]
 pub fn complete_expired_transitions(
     node: OpaqueNode,
@@ -859,18 +857,19 @@ pub fn complete_expired_transitions(
 ) -> bool {
     let had_animations_to_expire;
     {
         let all_expired_animations = context.expired_animations.read();
         let animations_to_expire = all_expired_animations.get(&node);
         had_animations_to_expire = animations_to_expire.is_some();
         if let Some(ref animations) = animations_to_expire {
             for animation in *animations {
+                debug!("Updating expired animation {:?}", animation);
                 // TODO: support animation-fill-mode
-                if let Animation::Transition(_, _, ref frame, _) = *animation {
+                if let Animation::Transition(_, _, ref frame) = *animation {
                     frame.property_animation.update(Arc::make_mut(style), 1.0);
                 }
             }
         }
     }
 
     if had_animations_to_expire {
         context.expired_animations.write().remove(&node);
--- a/servo/components/style/gecko/arc_types.rs
+++ b/servo/components/style/gecko/arc_types.rs
@@ -18,22 +18,22 @@ use gecko_bindings::bindings::RawServoMe
 use gecko_bindings::bindings::RawServoMozDocumentRule;
 use gecko_bindings::bindings::RawServoNamespaceRule;
 use gecko_bindings::bindings::RawServoPageRule;
 use gecko_bindings::bindings::RawServoRuleNode;
 use gecko_bindings::bindings::RawServoRuleNodeStrong;
 use gecko_bindings::bindings::RawServoSupportsRule;
 use gecko_bindings::bindings::ServoCssRules;
 use gecko_bindings::structs::RawServoAnimationValue;
+use gecko_bindings::structs::RawServoCssUrlData;
 use gecko_bindings::structs::RawServoDeclarationBlock;
 use gecko_bindings::structs::RawServoFontFaceRule;
 use gecko_bindings::structs::RawServoMediaList;
 use gecko_bindings::structs::RawServoStyleRule;
 use gecko_bindings::structs::RawServoStyleSheetContents;
-use gecko_bindings::structs::RawServoCssUrlData;
 use gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong};
 use media_queries::MediaList;
 use properties::{ComputedValues, PropertyDeclarationBlock};
 use properties::animated_properties::AnimationValue;
 use rule_tree::StrongRuleNode;
 use servo_arc::{Arc, ArcBorrow};
 use shared_lock::Locked;
 use std::{mem, ptr};
--- a/servo/components/style/gecko/url.rs
+++ b/servo/components/style/gecko/url.rs
@@ -1,19 +1,19 @@
 /* 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/. */
 
 //! Common handling for the specified value CSS url() values.
 
 use cssparser::Parser;
 use gecko_bindings::bindings;
-use gecko_bindings::structs::root::nsStyleImageRequest;
 use gecko_bindings::structs::root::mozilla::CORSMode;
 use gecko_bindings::structs::root::mozilla::css::URLValue;
+use gecko_bindings::structs::root::nsStyleImageRequest;
 use gecko_bindings::sugar::ownership::{HasArcFFI, FFIArcHelpers};
 use gecko_bindings::sugar::refptr::RefPtr;
 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
 use nsstring::nsCString;
 use parser::{Parse, ParserContext};
 use servo_arc::Arc;
 use std::fmt::{self, Write};
 use style_traits::{CssWriter, ParseError, ToCss};
@@ -230,17 +230,17 @@ impl ToComputedValue for SpecifiedImageU
     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
         computed.0.clone()
     }
 }
 
 fn serialize_computed_url<W>(
     url_value: &URLValue,
     dest: &mut CssWriter<W>,
-    get_url: unsafe extern "C" fn(*const URLValue, *mut nsCString) -> (),
+    get_url: unsafe extern "C" fn(*const URLValue, *mut nsCString),
 ) -> fmt::Result
 where
     W: Write,
 {
     dest.write_str("url(")?;
     unsafe {
         let mut string = nsCString::new();
         get_url(url_value, &mut string);
--- a/servo/components/style/gecko/values.rs
+++ b/servo/components/style/gecko/values.rs
@@ -22,19 +22,19 @@ use values::computed::{LengthOrPercentag
 use values::computed::{MaxLength as ComputedMaxLength, MozLength as ComputedMozLength, Percentage};
 use values::computed::{NonNegativeLength, NonNegativeLengthOrPercentage, NonNegativeNumber};
 use values::computed::FlexBasis as ComputedFlexBasis;
 use values::computed::basic_shape::ShapeRadius as ComputedShapeRadius;
 use values::generics::{CounterStyleOrNone, NonNegative};
 use values::generics::basic_shape::ShapeRadius;
 use values::generics::box_::Perspective;
 use values::generics::flex::FlexBasis;
-use values::generics::length::{MaxLength, MozLength};
 use values::generics::gecko::ScrollSnapPoint;
 use values::generics::grid::{TrackBreadth, TrackKeyword};
+use values::generics::length::{MaxLength, MozLength};
 
 /// A trait that defines an interface to convert from and to `nsStyleCoord`s.
 pub trait GeckoStyleCoordConvertible: Sized {
     /// Convert this to a `nsStyleCoord`.
     fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T);
     /// Given a `nsStyleCoord`, try to get a value of this type..
     fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self>;
 }
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -580,17 +580,17 @@ trait PrivateMatchMethods: TElement {
     #[cfg(feature = "servo")]
     fn update_animations_for_cascade(
         &self,
         context: &SharedStyleContext,
         style: &mut Arc<ComputedValues>,
         possibly_expired_animations: &mut Vec<::animation::PropertyAnimation>,
         font_metrics: &::font_metrics::FontMetricsProvider,
     ) {
-        use animation::{self, Animation};
+        use animation::{self, Animation, AnimationUpdate};
         use dom::TNode;
 
         // Finish any expired transitions.
         let this_opaque = self.as_node().opaque();
         animation::complete_expired_transitions(this_opaque, style, context);
 
         // Merge any running animations into the current style, and cancel them.
         let had_running_animations = context
@@ -598,40 +598,39 @@ trait PrivateMatchMethods: TElement {
             .read()
             .get(&this_opaque)
             .is_some();
         if !had_running_animations {
             return;
         }
 
         let mut all_running_animations = context.running_animations.write();
-        for running_animation in all_running_animations.get_mut(&this_opaque).unwrap() {
-            // This shouldn't happen frequently, but under some circumstances
-            // mainly huge load or debug builds, the constellation might be
-            // delayed in sending the `TickAllAnimations` message to layout.
-            //
-            // Thus, we can't assume all the animations have been already
-            // updated by layout, because other restyle due to script might be
-            // triggered by layout before the animation tick.
-            //
-            // See #12171 and the associated PR for an example where this
-            // happened while debugging other release panic.
-            if running_animation.is_expired() {
+        for mut running_animation in all_running_animations.get_mut(&this_opaque).unwrap() {
+            if let Animation::Transition(_, _, ref frame) = *running_animation {
+                possibly_expired_animations.push(frame.property_animation.clone());
                 continue;
             }
 
-            animation::update_style_for_animation::<Self>(
+            let update = animation::update_style_for_animation::<Self>(
                 context,
-                running_animation,
+                &mut running_animation,
                 style,
                 font_metrics,
             );
 
-            if let Animation::Transition(_, _, ref frame, _) = *running_animation {
-                possibly_expired_animations.push(frame.property_animation.clone())
+            match *running_animation {
+                Animation::Transition(..) => unreachable!(),
+                Animation::Keyframes(_, _, _, ref mut state) => {
+                    match update {
+                        AnimationUpdate::Regular => {},
+                        AnimationUpdate::AnimationCanceled => {
+                            state.expired = true;
+                        }
+                    }
+                }
             }
         }
     }
 }
 
 impl<E: TElement> PrivateMatchMethods for E {}
 
 /// The public API that elements expose for selector matching.
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -122,27 +122,32 @@ pub enum AnimatedProperty {
             %>
             /// ${prop.name}
             ${prop.camel_case}(${value_type}, ${value_type}),
         % endif
     % endfor
 }
 
 impl AnimatedProperty {
-    /// Get the name of this property.
-    pub fn name(&self) -> &'static str {
+    /// Get the id of the property we're animating.
+    pub fn id(&self) -> LonghandId {
         match *self {
             % for prop in data.longhands:
-                % if prop.animatable and not prop.logical:
-                    AnimatedProperty::${prop.camel_case}(..) => "${prop.name}",
-                % endif
+            % if prop.animatable and not prop.logical:
+            AnimatedProperty::${prop.camel_case}(..) => LonghandId::${prop.camel_case},
+            % endif
             % endfor
         }
     }
 
+    /// Get the name of this property.
+    pub fn name(&self) -> &'static str {
+        self.id().name()
+    }
+
     /// Whether this interpolation does animate, that is, whether the start and
     /// end values are different.
     pub fn does_animate(&self) -> bool {
         match *self {
             % for prop in data.longhands:
                 % if prop.animatable and not prop.logical:
                     AnimatedProperty::${prop.camel_case}(ref from, ref to) => from != to,
                 % endif
--- a/servo/components/style/properties/longhands/border.mako.rs
+++ b/servo/components/style/properties/longhands/border.mako.rs
@@ -106,16 +106,17 @@
     "border-image-source",
     "ImageLayer",
     initial_value="Either::First(None_)",
     initial_specified_value="Either::First(None_)",
     spec="https://drafts.csswg.org/css-backgrounds/#the-background-image",
     vector=False,
     animation_value_type="discrete",
     flags="APPLIES_TO_FIRST_LETTER",
+    boxed=product == "servo",
 )}
 
 ${helpers.predefined_type(
     "border-image-outset",
     "LengthOrNumberRect",
     parse_method="parse_non_negative",
     initial_value="computed::LengthOrNumberRect::all(computed::LengthOrNumber::zero())",
     initial_specified_value="specified::LengthOrNumberRect::all(specified::LengthOrNumber::zero())",
--- a/servo/components/style/servo/selector_parser.rs
+++ b/servo/components/style/servo/selector_parser.rs
@@ -543,17 +543,17 @@ impl<'a, 'i> ::selectors::Parser<'i> for
                 ServoAnonymousBlock
             },
             "-servo-inline-block-wrapper" => {
                 if !self.in_user_agent_stylesheet() {
                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
                 }
                 ServoInlineBlockWrapper
             },
-            "-servo-input-absolute" => {
+            "-servo-inline-absolute" => {
                 if !self.in_user_agent_stylesheet() {
                     return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
                 }
                 ServoInlineAbsolute
             },
             _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
 
         };
--- a/servo/components/style/servo/url.rs
+++ b/servo/components/style/servo/url.rs
@@ -18,16 +18,19 @@ use values::computed::{Context, ToComput
 /// A CSS url() value for servo.
 ///
 /// Servo eagerly resolves SpecifiedUrls, which it can then take advantage of
 /// when computing values. In contrast, Gecko uses a different URL backend, so
 /// eagerly resolving with rust-url would be duplicated work.
 ///
 /// However, this approach is still not necessarily optimal: See
 /// <https://bugzilla.mozilla.org/show_bug.cgi?id=1347435#c6>
+///
+/// TODO(emilio): This should be shrunk by making CssUrl a wrapper type of an
+/// arc, and keep the serialization in that Arc. See gecko/url.rs for example.
 #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, SpecifiedValueInfo)]
 pub struct CssUrl {
     /// The original URI. This might be optional since we may insert computed
     /// values of images into the cascade directly, and we don't bother to
     /// convert their serialization.
     ///
     /// Refcounted since cloning this should be cheap and data: uris can be
     /// really large.
--- a/servo/components/style/stylesheets/supports_rule.rs
+++ b/servo/components/style/stylesheets/supports_rule.rs
@@ -6,19 +6,18 @@
 
 use cssparser::{Delimiter, Parser, SourceLocation, Token};
 use cssparser::{ParseError as CssParseError, ParserInput};
 use cssparser::parse_important;
 #[cfg(feature = "gecko")]
 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
 use parser::ParserContext;
 use properties::{PropertyDeclaration, PropertyId, SourcePropertyDeclaration};
-use selectors::parser::SelectorParseErrorKind;
 use selector_parser::{SelectorImpl, SelectorParser};
-use selectors::parser::Selector;
+use selectors::parser::{Selector, SelectorParseErrorKind};
 use servo_arc::Arc;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
 use shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
 use std::ffi::{CStr, CString};
 use std::fmt::{self, Write};
 use std::str;
 use str::CssStringWriter;
 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
@@ -345,23 +344,24 @@ impl RawSelector {
         let mut input = Parser::new(&mut input);
         input.parse_entirely(|input| -> Result<(), CssParseError<()>> {
             let parser = SelectorParser {
                 namespaces,
                 stylesheet_origin: context.stylesheet_origin,
                 url_data: Some(context.url_data),
             };
 
+            #[allow(unused_variables)]
             let selector = Selector::<SelectorImpl>::parse(&parser, input)
                 .map_err(|_| input.new_custom_error(()))?;
 
             #[cfg(feature = "gecko")]
             {
+                use selector_parser::PseudoElement;
                 use selectors::parser::Component;
-                use selector_parser::PseudoElement;
 
                 let has_any_unknown_webkit_pseudo =
                     selector.has_pseudo_element() &&
                     selector.iter_raw_match_order().any(|component| {
                         matches!(
                             *component,
                             Component::PseudoElement(PseudoElement::UnknownWebkit(..))
                         )
--- a/servo/components/style/values/computed/angle.rs
+++ b/servo/components/style/values/computed/angle.rs
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Computed angles.
 
 use num_traits::Zero;
-use std::fmt::{self, Write};
 use std::{f32, f64};
 use std::f64::consts::PI;
+use std::fmt::{self, Write};
 use std::ops::Add;
 use style_traits::{CssWriter, ToCss};
 use values::CSSFloat;
 use values::distance::{ComputeSquaredDistance, SquaredDistance};
 
 /// A computed angle in degrees.
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[derive(Animate, Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToAnimatedZero)]
--- a/servo/components/style/values/computed/length.rs
+++ b/servo/components/style/values/computed/length.rs
@@ -9,18 +9,18 @@ use ordered_float::NotNan;
 use std::fmt::{self, Write};
 use std::ops::{Add, Neg};
 use style_traits::{CssWriter, ToCss};
 use style_traits::values::specified::AllowedNumericType;
 use super::{Context, Number, Percentage, ToComputedValue};
 use values::{specified, Auto, CSSFloat, Either, Normal};
 use values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
 use values::distance::{ComputeSquaredDistance, SquaredDistance};
+use values::generics::NonNegative;
 use values::generics::length::{MaxLength as GenericMaxLength, MozLength as GenericMozLength};
-use values::generics::NonNegative;
 use values::specified::length::{AbsoluteLength, FontBaseSize, FontRelativeLength};
 use values::specified::length::ViewportPercentageLength;
 
 pub use super::image::Image;
 pub use values::specified::url::UrlOrNone;
 pub use values::specified::{Angle, BorderStyle, Time};
 
 impl ToComputedValue for specified::NoCalcLength {
--- a/servo/components/style/values/specified/angle.rs
+++ b/servo/components/style/values/specified/angle.rs
@@ -1,25 +1,26 @@
 /* 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/. */
 
 //! Specified angles.
 
 use cssparser::{Parser, Token};
 use parser::{Parse, ParserContext};
+use std::f32::consts::PI;
 use std::fmt::{self, Write};
-use std::f32::consts::PI;
 use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, ToCss};
 use values::CSSFloat;
 use values::computed::{Context, ToComputedValue};
 use values::computed::angle::Angle as ComputedAngle;
 use values::specified::calc::CalcNode;
 
 /// A specified angle dimension.
+#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToCss)]
 pub enum AngleDimension {
     /// An angle with degree unit.
     #[css(dimension)]
     Deg(CSSFloat),
     /// An angle with gradian unit.
     #[css(dimension)]
     Grad(CSSFloat),
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -13,18 +13,18 @@ use font_metrics::FontMetricsQueryResult
 use parser::{Parse, ParserContext};
 use std::cmp;
 use std::ops::{Add, Mul};
 use style_traits::{ParseError, SpecifiedValueInfo, StyleParseErrorKind};
 use style_traits::values::specified::AllowedNumericType;
 use super::{AllowQuirks, Number, Percentage, ToComputedValue};
 use values::{Auto, CSSFloat, Either, Normal};
 use values::computed::{self, CSSPixelLength, Context, ExtremumLength};
+use values::generics::NonNegative;
 use values::generics::length::{MaxLength as GenericMaxLength, MozLength as GenericMozLength};
-use values::generics::NonNegative;
 use values::specified::calc::CalcNode;
 
 pub use values::specified::calc::CalcLengthOrPercentage;
 pub use super::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use super::image::{GradientKind, Image};
 
 /// Number of app units per pixel
 pub const AU_PER_PX: CSSFloat = 60.;
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -606,70 +606,16 @@ mod shorthand_serialization {
                 background-position-x: center;\
                 background-position-y: 20px;";
             let block = parse(|c, i| Ok(parse_property_declaration_list(c, i)), block_text).unwrap();
             let serialization = block.to_css_string();
             assert_eq!(serialization, "background-position: center 20px;");
         }
     }
 
-    mod transform {
-        pub use super::*;
-        use style::values::generics::transform::TransformOperation;
-        use style::values::specified::{Angle, Number};
-        use style::values::specified::transform::TransformOperation as SpecifiedOperation;
-
-        #[test]
-        fn should_serialize_none_correctly() {
-            use style::properties::longhands::transform;
-
-            assert_roundtrip_with_context!(transform::parse, "none");
-        }
-
-        #[inline(always)]
-        fn validate_serialization(op: &SpecifiedOperation, expected_string: &'static str) {
-            let css_string = op.to_css_string();
-            assert_eq!(css_string, expected_string);
-        }
-
-        #[test]
-        fn transform_scale() {
-            validate_serialization(&TransformOperation::Scale(Number::new(1.3), None), "scale(1.3)");
-            validate_serialization(
-                &TransformOperation::Scale(Number::new(2.0), Some(Number::new(2.0))),
-                "scale(2, 2)");
-            validate_serialization(&TransformOperation::ScaleX(Number::new(42.0)), "scaleX(42)");
-            validate_serialization(&TransformOperation::ScaleY(Number::new(0.3)), "scaleY(0.3)");
-            validate_serialization(&TransformOperation::ScaleZ(Number::new(1.0)), "scaleZ(1)");
-            validate_serialization(
-                &TransformOperation::Scale3D(Number::new(4.0), Number::new(5.0), Number::new(6.0)),
-                "scale3d(4, 5, 6)");
-        }
-
-        #[test]
-        fn transform_skew() {
-            validate_serialization(
-                &TransformOperation::Skew(Angle::from_degrees(42.3, false), None),
-                "skew(42.3deg)");
-            validate_serialization(
-                &TransformOperation::Skew(Angle::from_gradians(-50.0, false), Some(Angle::from_turns(0.73, false))),
-                "skew(-50grad, 0.73turn)");
-            validate_serialization(
-                &TransformOperation::SkewX(Angle::from_radians(0.31, false)), "skewX(0.31rad)");
-        }
-
-        #[test]
-        fn transform_rotate() {
-            validate_serialization(
-                &TransformOperation::Rotate(Angle::from_turns(35.0, false)),
-                "rotate(35turn)"
-            )
-        }
-    }
-
     mod quotes {
         pub use super::*;
 
         #[test]
         fn should_serialize_none_correctly() {
             use style::properties::longhands::quotes;
 
             assert_roundtrip_with_context!(quotes::parse, "none");
--- a/taskcluster/ci/toolchain/windows.yml
+++ b/taskcluster/ci/toolchain/windows.yml
@@ -112,18 +112,17 @@ win64-rust-1.29:
         docker-image: {in-tree: toolchain-build}
         max-run-time: 7200
         env:
             UPLOAD_DIR: artifacts
     run:
         using: toolchain-script
         script: repack_rust.py
         arguments: [
-            # Rustc 1.30 beta for rust-lang/rust#52847
-            '--channel', 'beta-2018-09-28',
+            '--channel', '1.29.0',
             '--host', 'x86_64-pc-windows-msvc',
             '--target', 'x86_64-pc-windows-msvc',
             '--target', 'i686-pc-windows-msvc',
         ]
         toolchain-alias: win64-rust
         toolchain-artifact: public/build/rustc.tar.bz2
 
 win64-rust-nightly:
--- a/testing/web-platform/meta/html/dom/interfaces.https.html.ini
+++ b/testing/web-platform/meta/html/dom/interfaces.https.html.ini
@@ -353,19 +353,16 @@
   [Window interface: operation postMessage(any, [object Object\], WindowPostMessageOptions)]
     expected: FAIL
 
   [html interfaces]
     expected: FAIL
 
 
 [interfaces.https.html?include=HTML.*]
-  [HTMLAllCollection interface: operation item(DOMString)]
-    expected: FAIL
-
   [HTMLAllCollection must be primary interface of document.all]
     expected: FAIL
 
   [Stringification of document.all]
     expected: FAIL
 
   [HTMLAllCollection interface: document.all must inherit property "length" with the proper type]
     expected: FAIL
--- a/testing/web-platform/meta/html/infrastructure/common-dom-interfaces/collections/htmlallcollection.html.ini
+++ b/testing/web-platform/meta/html/infrastructure/common-dom-interfaces/collections/htmlallcollection.html.ini
@@ -1,31 +1,3 @@
 [htmlallcollection.html]
-  [legacy caller with "array index property name"]
-    expected: FAIL
-
-  [legacy caller with "array index property name" as number]
-    expected: FAIL
-
-  [legacy caller with invalid "array index property name"]
-    expected: FAIL
-
-  [legacy caller with no argument]
-    expected: FAIL
-
-  [item method with "array index property name"]
-    expected: FAIL
-
-  [item method with invalid "array index property name"]
-    expected: FAIL
-
-  [item method with no argument]
-    expected: FAIL
-
   [collections are new live HTMLCollection instances]
     expected: FAIL
-
-  [legacy caller with undefined]
-    expected: FAIL
-
-  [item method with undefined]
-    expected: FAIL
-
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -13,17 +13,17 @@
 // "file=" argument, and obviously the filename is case-sensitive iff you're on
 // a case-sensitive filesystem.  If you specify more than one "file=" argument,
 // only the first one is used.
 
 "use strict";
 
 // ---------------------------------------------------------------------------
 
-var CC = Components.Constructor;
+let CC = Components.Constructor;
 
 const KIND_NONHEAP           = Ci.nsIMemoryReporter.KIND_NONHEAP;
 const KIND_HEAP              = Ci.nsIMemoryReporter.KIND_HEAP;
 const KIND_OTHER             = Ci.nsIMemoryReporter.KIND_OTHER;
 
 const UNITS_BYTES            = Ci.nsIMemoryReporter.UNITS_BYTES;
 const UNITS_COUNT            = Ci.nsIMemoryReporter.UNITS_COUNT;
 const UNITS_COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
@@ -43,25 +43,25 @@ XPCOMUtils.defineLazyGetter(this, "nsBin
                                      "setInputStream"));
 XPCOMUtils.defineLazyGetter(this, "nsFile",
                             () => CC("@mozilla.org/file/local;1",
                                      "nsIFile", "initWithPath"));
 XPCOMUtils.defineLazyGetter(this, "nsGzipConverter",
                             () => CC("@mozilla.org/streamconv;1?from=gzip&to=uncompressed",
                                      "nsIStreamConverter"));
 
-var gMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
+let gMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
              .getService(Ci.nsIMemoryReporterManager);
 
 const gPageName = "about:memory";
 document.title = gPageName;
 
 const gUnnamedProcessStr = "Main Process";
 
-var gIsDiff = false;
+let gIsDiff = false;
 
 // ---------------------------------------------------------------------------
 
 // Forward slashes in URLs in paths are represented with backslashes to avoid
 // being mistaken for path separators.  Paths/names where this hasn't been
 // undone are prefixed with "unsafe"; the rest are prefixed with "safe".
 function flipBackslashes(aUnsafeStr) {
   // Save memory by only doing the replacement if it's necessary.
@@ -79,76 +79,76 @@ function assert(aCond, aMsg) {
     reportAssertionFailure(aMsg);
     throw new Error(gAssertionFailureMsgPrefix + aMsg);
   }
 }
 
 // This is used for malformed input from memory reporters.
 function assertInput(aCond, aMsg) {
   if (!aCond) {
-    throw new Error("Invalid memory report(s): " + aMsg);
+    throw new Error(`Invalid memory report(s): ${aMsg}`);
   }
 }
 
-function handleException(ex) {
-  let str = "" + ex;
+function handleException(aEx) {
+  let str = "" + aEx;
   if (str.startsWith(gAssertionFailureMsgPrefix)) {
     // Argh, assertion failure within this file!  Give up.
-    throw ex;
+    throw aEx;
   } else {
     // File or memory reporter problem.  Print a message.
     updateMainAndFooter(str, NO_TIMESTAMP, HIDE_FOOTER, "badInputWarning");
   }
 }
 
 function reportAssertionFailure(aMsg) {
   let debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
   if (debug.isDebugBuild) {
     debug.assertion(aMsg, "false", "aboutMemory.js", 0);
   }
 }
 
-function debug(x) {
+function debug(aVal) {
   let section = appendElement(document.body, "div", "section");
-  appendElementWithText(section, "div", "debug", JSON.stringify(x));
+  appendElementWithText(section, "div", "debug", JSON.stringify(aVal));
 }
 
 // ---------------------------------------------------------------------------
 
 function onUnload() {
 }
 
 // ---------------------------------------------------------------------------
 
 // The <div> holding everything but the header and footer (if they're present).
 // It's what is updated each time the page changes.
-var gMain;
+let gMain;
 
 // The <div> holding the footer.
-var gFooter;
+let gFooter;
 
 // The "verbose" checkbox.
-var gVerbose;
+let gVerbose;
 
 // The "anonymize" checkbox.
-var gAnonymize;
+let gAnonymize;
 
 // Values for the |aFooterAction| argument to updateTitleMainAndFooter.
 const HIDE_FOOTER = 0;
 const SHOW_FOOTER = 1;
 
 // Values for the |aShowTimestamp| argument to updateTitleMainAndFooter.
 const NO_TIMESTAMP = 0;
 const SHOW_TIMESTAMP = 1;
 
 function updateTitleMainAndFooter(aTitleNote, aMsg, aShowTimestamp,
                                   aFooterAction, aClassName) {
   document.title = gPageName;
   if (aTitleNote) {
-    document.title += " (" + aTitleNote + ")";
+    document.title += ` (${aTitleNote})`;
   }
 
   // Clear gMain by replacing it with an empty node.
   let tmp = gMain.cloneNode(false);
   gMain.parentNode.replaceChild(tmp, gMain);
   gMain = tmp;
 
   gMain.classList.remove("hidden");
@@ -163,17 +163,17 @@ function updateTitleMainAndFooter(aTitle
     let className = "section";
     if (aClassName) {
       className = className + " " + aClassName;
     }
     if (aShowTimestamp == SHOW_TIMESTAMP) {
       // JS has many options for pretty-printing timestamps. We use
       // toISOString() because it has sub-second granularity, which is useful
       // if you quickly and repeatedly click one of the buttons.
-      aMsg += " (" + (new Date()).toISOString() + ")";
+      aMsg += ` (${(new Date()).toISOString()})`;
     }
     msgElement = appendElementWithText(gMain, "div", className, aMsg);
   }
 
   switch (aFooterAction) {
    case HIDE_FOOTER: gFooter.classList.add("hidden"); break;
    case SHOW_FOOTER: gFooter.classList.remove("hidden"); break;
    default: assert(false, "bad footer action in updateTitleMainAndFooter");
@@ -259,28 +259,27 @@ function onLoad() {
   // A hidden file input element that can be invoked when necessary.
   let fileInput1 = appendHiddenFileInput(header, "fileInput1", function() {
     let file = this.files[0];
     let filename = file.mozFullPath;
     updateAboutMemoryFromFile(filename);
   });
 
   // Ditto.
-  let fileInput2 =
-      appendHiddenFileInput(header, "fileInput2", function(e) {
+  let fileInput2 = appendHiddenFileInput(header, "fileInput2", function(aElem) {
     let file = this.files[0];
     // First time around, we stash a copy of the filename and reinvoke.  Second
     // time around we do the diff and display.
     if (!this.filename1) {
       this.filename1 = file.mozFullPath;
 
-      // e.skipClick is only true when testing -- it allows fileInput2's
+      // aElem.skipClick is only true when testing -- it allows fileInput2's
       // onchange handler to be re-called without having to go via the file
       // picker.
-      if (!e.skipClick) {
+      if (!aElem.skipClick) {
         this.click();
       }
     } else {
       let filename1 = this.filename1;
       delete this.filename1;
       updateAboutMemoryFromTwoFiles(filename1, file.mozFullPath);
     }
   });
@@ -320,29 +319,26 @@ function onLoad() {
   let labelDiv1 =
    appendElementWithText(row1, "div", "opsRowLabel", "Show memory reports");
   let label1 = appendElementWithText(labelDiv1, "label", "");
   gVerbose = appendElement(label1, "input", "");
   gVerbose.type = "checkbox";
   gVerbose.id = "verbose"; // used for testing
   appendTextNode(label1, "verbose");
 
-  const kEllipsis = "\u2026";
-
   // The "measureButton" id is used for testing.
   appendButton(row1, CuDesc, doMeasure, "Measure", "measureButton");
-  appendButton(row1, LdDesc, () => fileInput1.click(), "Load" + kEllipsis);
-  appendButton(row1, DfDesc, () => fileInput2.click(),
-               "Load and diff" + kEllipsis);
+  appendButton(row1, LdDesc, () => fileInput1.click(), "Load…");
+  appendButton(row1, DfDesc, () => fileInput2.click(), "Load and diff…");
 
   let row2 = appendElement(ops, "div", "opsRow");
 
   let labelDiv2 =
     appendElementWithText(row2, "div", "opsRowLabel", "Save memory reports");
-  appendButton(row2, SvDesc, saveReportsToFile, "Measure and save" + kEllipsis);
+  appendButton(row2, SvDesc, saveReportsToFile, "Measure and save…");
 
   // XXX: this isn't a great place for this checkbox, but I can't think of
   // anywhere better.
   let label2 = appendElementWithText(labelDiv2, "label", "");
   gAnonymize = appendElement(label2, "input", "");
   gAnonymize.type = "checkbox";
   appendTextNode(label2, "anonymize");
 
@@ -400,19 +396,19 @@ function onLoad() {
   appendElementWithText(gFooter, "div", "legend hiddenOnMobile", legendText2);
 
   // See if we're loading from a file.  (Because about:memory is a non-standard
   // URL, location.search is undefined, so we have to use location.href
   // instead.)
   let search = location.href.split("?")[1];
   if (search) {
     let searchSplit = search.split("&");
-    for (let i = 0; i < searchSplit.length; i++) {
-      if (searchSplit[i].toLowerCase().startsWith("file=")) {
-        let filename = searchSplit[i].substring("file=".length);
+    for (let s of searchSplit) {
+      if (s.toLowerCase().startsWith("file=")) {
+        let filename = s.substring("file=".length);
         updateAboutMemoryFromFile(decodeURIComponent(filename));
         return;
       }
     }
   }
 }
 
 // ---------------------------------------------------------------------------
@@ -471,23 +467,23 @@ function doDMD() {
 function dumpGCLogAndCCLog(aVerbose) {
   let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
                 .getService(Ci.nsIMemoryInfoDumper);
 
   let inProgress = updateMainAndFooter("Saving logs...",
                                        NO_TIMESTAMP, HIDE_FOOTER);
   let section = appendElement(gMain, "div", "section");
 
-  function displayInfo(gcLog, ccLog, isParent) {
+  function displayInfo(aGCLog, aCCLog, aIsParent) {
     appendElementWithText(section, "div", "",
-                          "Saved GC log to " + gcLog.path);
+                          "Saved GC log to " + aGCLog.path);
 
     let ccLogType = aVerbose ? "verbose" : "concise";
     appendElementWithText(section, "div", "",
-                          "Saved " + ccLogType + " CC log to " + ccLog.path);
+                          "Saved " + ccLogType + " CC log to " + aCCLog.path);
   }
 
   dumper.dumpGCAndCCLogsToFile("", aVerbose, /* dumpChildProcesses = */ true,
                                { onDump: displayInfo,
                                  onFinish() {
                                    inProgress.remove();
                                  },
                                });
@@ -525,17 +521,17 @@ function updateAboutMemoryFromReporters(
 
   } catch (ex) {
     handleException(ex);
   }
 }
 
 // Increment this if the JSON format changes.
 //
-var gCurrentFileFormatVersion = 1;
+let gCurrentFileFormatVersion = 1;
 
 
 /**
  * Parse a string as JSON and extract the |memory_report| property if it has
  * one, which indicates the string is from a crash dump.
  *
  * @param aStr
  *        The string.
@@ -564,18 +560,17 @@ function updateAboutMemoryFromJSONObject
                 "data version number missing or doesn't match");
     assertInput(aObj.hasMozMallocUsableSize !== undefined,
                 "missing 'hasMozMallocUsableSize' property");
     assertInput(aObj.reports && aObj.reports instanceof Array,
                 "missing or non-array 'reports' property");
 
     let processMemoryReportsFromFile =
         function(aHandleReport, aDisplayReports) {
-      for (let i = 0; i < aObj.reports.length; i++) {
-        let r = aObj.reports[i];
+      for (let r of aObj.reports) {
         aHandleReport(r.process, r.path, r.kind, r.units, r.amount,
                       r.description, r._presence);
       }
       aDisplayReports();
     };
     appendAboutMemoryMain(processMemoryReportsFromFile,
                           aObj.hasMozMallocUsableSize);
   } catch (ex) {
@@ -681,17 +676,17 @@ function updateAboutMemoryFromFile(aFile
  * diffs them.
  *
  * @param aFilename1
  *        The name of the first file being read from.
  * @param aFilename2
  *        The name of the first file being read from.
  */
 function updateAboutMemoryFromTwoFiles(aFilename1, aFilename2) {
-  let titleNote = "diff of " + aFilename1 + " and " + aFilename2;
+  let titleNote = `diff of ${aFilename1} and ${aFilename2}`;
   loadMemoryReportsFromFile(aFilename1, titleNote, function(aStr1) {
     loadMemoryReportsFromFile(aFilename2, titleNote, function(aStr2) {
       try {
         let obj1 = parseAndUnwrapIfCrashDump(aStr1);
         let obj2 = parseAndUnwrapIfCrashDump(aStr2);
         gIsDiff = true;
         updateAboutMemoryFromJSONObject(diffJSONObjects(obj1, obj2));
         gIsDiff = false;
@@ -700,17 +695,17 @@ function updateAboutMemoryFromTwoFiles(a
       }
     });
   });
 }
 
 // ---------------------------------------------------------------------------
 
 // Something unlikely to appear in a process name.
-var kProcessPathSep = "^:^:^";
+let kProcessPathSep = "^:^:^";
 
 // Short for "diff report".
 function DReport(aKind, aUnits, aAmount, aDescription, aNMerged, aPresence) {
   this._kind = aKind;
   this._units = aUnits;
   this._amount = aAmount;
   this._description = aDescription;
   this._nMerged = aNMerged;
@@ -770,19 +765,17 @@ DReport.ADDED_FOR_BALANCE = 3;
  * DReport objects for values.
  *
  * @param aJSONReports
  *        The |reports| field of a JSON object.
  * @return The constructed report map.
  */
 function makeDReportMap(aJSONReports) {
   let dreportMap = {};
-  for (let i = 0; i < aJSONReports.length; i++) {
-    let jr = aJSONReports[i];
-
+  for (let jr of aJSONReports) {
     assert(jr.process !== undefined, "Missing process");
     assert(jr.path !== undefined, "Missing path");
     assert(jr.kind !== undefined, "Missing kind");
     assert(jr.units !== undefined, "Missing units");
     assert(jr.amount !== undefined, "Missing amount");
     assert(jr.description !== undefined, "Missing description");
 
     // Strip out some non-deterministic stuff that prevents clean diffs.
@@ -1007,18 +1000,18 @@ function appendAboutMemoryMain(aProcessR
     }
   }
 
   function displayReports() {
     // Sort the processes.
     let processes = Object.keys(pcollsByProcess);
     processes.sort(function(aProcessA, aProcessB) {
       assert(aProcessA != aProcessB,
-             "Elements of Object.keys() should be unique, but " +
-             "saw duplicate '" + aProcessA + "' elem.");
+             `Elements of Object.keys() should be unique, but ` +
+             `saw duplicate '${aProcessA}' elem.`);
 
       // Always put the main process first.
       if (aProcessA == gUnnamedProcessStr) {
         return -1;
       }
       if (aProcessB == gUnnamedProcessStr) {
         return 1;
       }
@@ -1043,18 +1036,17 @@ function appendAboutMemoryMain(aProcessR
       if (aProcessA > aProcessB) {
         return 1;
       }
 
       return 0;
     });
 
     // Generate output for each process.
-    for (let i = 0; i < processes.length; i++) {
-      let process = processes[i];
+    for (let [i, process] of processes.entries()) {
       let section = appendElement(gMain, "div", "section");
 
       appendProcessAboutMemoryElements(section, i, process,
                                        pcollsByProcess[process]._trees,
                                        pcollsByProcess[process]._degenerates,
                                        pcollsByProcess[process]._heapTotal,
                                        aHasMozMallocUsableSize);
     }
@@ -1090,19 +1082,19 @@ function TreeNode(aUnsafeName, aUnits, a
   // - _description
   // - _hideKids (only defined if true)
   // - _maxAbsDescendant (on-demand, only when gIsDiff is set)
 }
 
 TreeNode.prototype = {
   findKid(aUnsafeName) {
     if (this._kids) {
-      for (let i = 0; i < this._kids.length; i++) {
-        if (this._kids[i]._unsafeName === aUnsafeName) {
-          return this._kids[i];
+      for (let kid of this._kids) {
+        if (kid._unsafeName === aUnsafeName) {
+          return kid;
         }
       }
     }
     return undefined;
   },
 
   // When gIsDiff is false, tree operations -- sorting and determining if a
   // sub-tree is significant -- are straightforward. But when gIsDiff is true,
@@ -1118,28 +1110,28 @@ TreeNode.prototype = {
 
     if ("_maxAbsDescendant" in this) {
       // We've computed this before? Return the saved value.
       return this._maxAbsDescendant;
     }
 
     // Compute the maximum absolute value of all descendants.
     let max = Math.abs(this._amount);
-    for (let i = 0; i < this._kids.length; i++) {
-      max = Math.max(max, this._kids[i].maxAbsDescendant());
+    for (let kid of this._kids) {
+      max = Math.max(max, kid.maxAbsDescendant());
     }
     this._maxAbsDescendant = max;
     return max;
   },
 
   toString() {
     switch (this._units) {
       case UNITS_BYTES: return formatBytes(this._amount);
       case UNITS_COUNT:
-      case UNITS_COUNT_CUMULATIVE: return formatInt(this._amount);
+      case UNITS_COUNT_CUMULATIVE: return formatNum(this._amount);
       case UNITS_PERCENTAGE: return formatPercentage(this._amount);
       default:
         throw "Invalid memory report(s): bad units in TreeNode.toString";
     }
   },
 };
 
 // Sort TreeNodes first by size, then by name.  The latter is important for the
@@ -1198,18 +1190,18 @@ function fillInTree(aRoot) {
         aT._nMerged = kid._nMerged;
       }
       assert(!aT._hideKids && !kid._hideKids, "_hideKids set when merging");
 
     } else {
       // Non-leaf node with multiple children.  Derive its _amount and
       // _description entirely from its children...
       let kidsBytes = 0;
-      for (let i = 0; i < aT._kids.length; i++) {
-        kidsBytes += fillInNonLeafNodes(aT._kids[i]);
+      for (let kid of aT._kids) {
+        kidsBytes += fillInNonLeafNodes(kid);
       }
 
       // ... except in one special case. When diffing two memory report sets,
       // if one set has a node with children and the other has the same node
       // but without children -- e.g. the first has "a/b/c" and "a/b/d", but
       // the second only has "a/b" -- we need to add a fake node "a/b/(fake)"
       // to the second to make the trees comparable. It's ugly, but it works.
       if (aT._amount !== undefined &&
@@ -1298,67 +1290,67 @@ function sortTreeAndInsertAggregateNodes
 
   aT._kids.sort(TreeNode.compareAmounts);
 
   // If the first child is insignificant, they all are, and there's no point
   // creating an aggregate node that lacks siblings.  Just set the parent's
   // _hideKids property and process all children.
   if (isInsignificant(aT._kids[0])) {
     aT._hideKids = true;
-    for (let i = 0; i < aT._kids.length; i++) {
-      sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
+    for (let kid of aT._kids) {
+      sortTreeAndInsertAggregateNodes(aTotalBytes, kid);
     }
     return;
   }
 
   // Look at all children except the last one.
   let i;
   for (i = 0; i < aT._kids.length - 1; i++) {
     if (isInsignificant(aT._kids[i])) {
       // This child is below the significance threshold.  If there are other
       // (smaller) children remaining, move them under an aggregate node.
       let i0 = i;
       let nAgg = aT._kids.length - i0;
       // Create an aggregate node.  Inherit units from the parent;  everything
       // in the tree should have the same units anyway (we test this later).
-      let aggT = new TreeNode("(" + nAgg + " tiny)", aT._units);
+      let aggT = new TreeNode(`(${nAgg} tiny)`, aT._units);
       aggT._kids = [];
       let aggBytes = 0;
       for ( ; i < aT._kids.length; i++) {
         aggBytes += aT._kids[i]._amount;
         aggT._kids.push(aT._kids[i]);
       }
       aggT._hideKids = true;
       aggT._amount = aggBytes;
       aggT._description =
         nAgg + " sub-trees that are below the " + kSignificanceThresholdPerc +
         "% significance threshold.";
       aT._kids.splice(i0, nAgg, aggT);
       aT._kids.sort(TreeNode.compareAmounts);
 
       // Process the moved children.
-      for (i = 0; i < aggT._kids.length; i++) {
-        sortTreeAndInsertAggregateNodes(aTotalBytes, aggT._kids[i]);
+      for (let kid of aggT._kids) {
+        sortTreeAndInsertAggregateNodes(aTotalBytes, kid);
       }
       return;
     }
 
     sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
   }
 
   // The first n-1 children were significant.  Don't consider if the last child
   // is significant;  there's no point creating an aggregate node that only has
   // one child.  Just process it.
   sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
 }
 
 // Global variable indicating if we've seen any invalid values for this
 // process;  it holds the unsafePaths of any such reports.  It is reset for
 // each new process.
-var gUnsafePathsWithInvalidValuesForThisProcess = [];
+let gUnsafePathsWithInvalidValuesForThisProcess = [];
 
 function appendWarningElements(aP, aHasKnownHeapAllocated,
                                aHasMozMallocUsableSize) {
   if (!aHasKnownHeapAllocated && !aHasMozMallocUsableSize) {
     appendElementWithText(aP, "p", "",
       "WARNING: the 'heap-allocated' memory reporter and the " +
       "moz_malloc_usable_size() function do not work for this platform " +
       "and/or configuration.  This means that 'heap-unclassified' is not " +
@@ -1414,32 +1406,29 @@ function appendWarningElements(aP, aHasK
  *        The table of degenerate trees for this process.
  * @param aHasMozMallocUsableSize
  *        Boolean indicating if moz_malloc_usable_size works.
  * @return The generated text.
  */
 function appendProcessAboutMemoryElements(aP, aN, aProcess, aTrees,
                                           aDegenerates, aHeapTotal,
                                           aHasMozMallocUsableSize) {
-  const kUpwardsArrow   = "\u2191",
-        kDownwardsArrow = "\u2193";
-
   let appendLink = function(aHere, aThere, aArrow) {
     let link = appendElementWithText(aP, "a", "upDownArrow", aArrow);
     link.href = "#" + aThere + aN;
     link.id = aHere + aN;
-    link.title = "Go to the " + aThere + " of " + aProcess;
+    link.title = `Go to the ${aThere} of ${aProcess}`;
     link.style = "text-decoration: none";
 
     // This gives nice spacing when we copy and paste.
     appendElementWithText(aP, "span", "", "\n");
   };
 
   appendElementWithText(aP, "h1", "", aProcess);
-  appendLink("start", "end", kDownwardsArrow);
+  appendLink("start", "end", "↓");
 
   // We'll fill this in later.
   let warningsDiv = appendElement(aP, "div", "accuracyWarning");
 
   // The explicit tree.
   let hasExplicitTree;
   let hasKnownHeapAllocated;
   {
@@ -1488,168 +1477,130 @@ function appendProcessAboutMemoryElement
       maxStringLength = length;
     }
     otherDegenerates.push(t);
   }
   otherDegenerates.sort(TreeNode.compareUnsafeNames);
 
   // Now generate the elements, putting non-degenerate trees first.
   let pre = appendSectionHeader(aP, "Other Measurements");
-  for (let i = 0; i < otherTrees.length; i++) {
-    let t = otherTrees[i];
+  for (let t of otherTrees) {
     appendTreeElements(pre, t, aProcess, "");
     appendTextNode(pre, "\n"); // blank lines after non-degenerate trees
   }
-  for (let i = 0; i < otherDegenerates.length; i++) {
-    let t = otherDegenerates[i];
-    let padText = pad("", maxStringLength - t.toString().length, " ");
+  for (let t of otherDegenerates) {
+    let padText = "".padStart(maxStringLength - t.toString().length, " ");
     appendTreeElements(pre, t, aProcess, padText);
   }
   appendTextNode(aP, "\n"); // gives nice spacing when we copy and paste
 
   // Add any warnings about inaccuracies in the "explicit" tree due to platform
   // limitations.  These must be computed after generating all the text.  The
   // newlines give nice spacing if we copy+paste into a text buffer.
   if (hasExplicitTree) {
     appendWarningElements(warningsDiv, hasKnownHeapAllocated,
                           aHasMozMallocUsableSize);
   }
 
   appendElementWithText(aP, "h3", "", "End of " + aProcess);
-  appendLink("end", "start", kUpwardsArrow);
+  appendLink("end", "start", "↑");
 }
 
-/**
- * Determines if a number has a negative sign when converted to a string.
- * Works even for -0.
- *
- * @param aN
- *        The number.
- * @return A boolean.
- */
-function hasNegativeSign(aN) {
-  if (aN === 0) { // this succeeds for 0 and -0
-    return 1 / aN === -Infinity; // this succeeds for -0
-  }
-  return aN < 0;
-}
+// Used for UNITS_BYTES values that are printed as MiB.
+const kMBStyle = {
+  minimumFractionDigits: 2,
+  maximumFractionDigits: 2,
+};
+
+// Used for UNITS_PERCENTAGE values.
+const kPercStyle = {
+  style: "percent",
+  minimumFractionDigits: 2,
+  maximumFractionDigits: 2,
+};
+
+// Used for fractions within the tree.
+const kFracStyle = {
+  style: "percent",
+  minimumIntegerDigits: 2,
+  minimumFractionDigits: 2,
+  maximumFractionDigits: 2,
+};
+
+// Used for special-casing 100% fractions within the tree.
+const kFrac1Style = {
+  style: "percent",
+  minimumIntegerDigits: 3,
+  minimumFractionDigits: 1,
+  maximumFractionDigits: 1,
+};
 
 /**
  * Formats an int as a human-readable string.
  *
  * @param aN
  *        The integer to format.
- * @param aExtra
- *        An extra string to tack onto the end.
+ * @param aOptions
+ *        Optional options object.
  * @return A human-readable string representing the int.
- *
- * Note: building an array of chars and converting that to a string with
- * Array.join at the end is more memory efficient than using string
- * concatenation.  See bug 722972 for details.
  */
-function formatInt(aN, aExtra) {
-  let neg = false;
-  if (hasNegativeSign(aN)) {
-    neg = true;
-    aN = -aN;
-  }
-  let s = [];
-  while (true) {
-    let k = aN % 1000;
-    aN = Math.floor(aN / 1000);
-    if (aN > 0) {
-      if (k < 10) {
-        s.unshift(",00", k);
-      } else if (k < 100) {
-        s.unshift(",0", k);
-      } else {
-        s.unshift(",", k);
-      }
-    } else {
-      s.unshift(k);
-      break;
-    }
-  }
-  if (neg) {
-    s.unshift("-");
-  }
-  if (aExtra) {
-    s.push(aExtra);
-  }
-  return s.join("");
+function formatNum(aN, aOptions) {
+  return aN.toLocaleString("en-US", aOptions);
 }
 
 /**
  * Converts a byte count to an appropriate string representation.
  *
  * @param aBytes
  *        The byte count.
  * @return The string representation.
  */
 function formatBytes(aBytes) {
-  let unit = gVerbose.checked ? " B" : " MB";
-
-  let s;
-  if (gVerbose.checked) {
-    s = formatInt(aBytes, unit);
-  } else {
-    let mbytes = (aBytes / (1024 * 1024)).toFixed(2);
-    let a = String(mbytes).split(".");
-    // If the argument to formatInt() is -0, it will print the negative sign.
-    s = formatInt(Number(a[0])) + "." + a[1] + unit;
-  }
-  return s;
+  return gVerbose.checked
+       ? `${formatNum(aBytes)} B`
+       : `${formatNum(aBytes / (1024 * 1024), kMBStyle)} MB`;
 }
 
 /**
- * Converts a percentage to an appropriate string representation.
+ * Converts a UNITS_PERCENTAGE value to an appropriate string representation.
  *
  * @param aPerc100x
  *        The percentage, multiplied by 100 (see nsIMemoryReporter).
  * @return The string representation
  */
 function formatPercentage(aPerc100x) {
-  return (aPerc100x / 100).toFixed(2) + "%";
+  // A percentage like 12.34% will have an aPerc100x value of 1234, and we need
+  // to divide that by 10,000 to get the 0.1234 that toLocaleString() wants.
+  return formatNum(aPerc100x / 10000, kPercStyle);
 }
 
-/**
- * Right-justifies a string in a field of a given width, padding as necessary.
+/*
+ * Converts a tree fraction to an appropriate string representation.
  *
- * @param aS
- *        The string.
- * @param aN
- *        The field width.
- * @param aC
- *        The char used to pad.
- * @return The string representation.
+ * @param aNum
+ *        The numerator.
+ * @param aDenom
+ *        The denominator.
+ * @return The string representation
  */
-function pad(aS, aN, aC) {
-  let padding = "";
-  let n2 = aN - aS.length;
-  for (let i = 0; i < n2; i++) {
-    padding += aC;
-  }
-  return padding + aS;
+function formatTreeFrac(aNum, aDenom) {
+  // Two special behaviours here:
+  // - We treat 0 / 0 as 100%.
+  // - We want 4 digits, as much as possible, because it gives good vertical
+  //   alignment. For positive numbers, 00.00%--99.99% works straighforwardly,
+  //   but 100.0% needs special handling.
+  let num = aDenom === 0 ? 1 : (aNum / aDenom);
+  return (0.99995 <= num && num <= 1)
+         ? formatNum(1, kFrac1Style)
+         : formatNum(num, kFracStyle);
 }
 
-// There's a subset of the Unicode "light" box-drawing chars that is widely
-// implemented in terminals, and this code sticks to that subset to maximize
-// the chance that copying and pasting about:memory output to a terminal will
-// work correctly.
-const kHorizontal                   = "\u2500",
-      kVertical                     = "\u2502",
-      kUpAndRight                   = "\u2514",
-      kUpAndRight_Right_Right       = "\u2514\u2500\u2500",
-      kVerticalAndRight             = "\u251c",
-      kVerticalAndRight_Right_Right = "\u251c\u2500\u2500",
-      kVertical_Space_Space         = "\u2502  ";
-
-const kNoKidsSep                    = " \u2500\u2500 ",
-      kHideKidsSep                  = " ++ ",
-      kShowKidsSep                  = " -- ";
+const kNoKidsSep   = " ── ",
+      kHideKidsSep = " ++ ",
+      kShowKidsSep = " -- ";
 
 function appendMrNameSpan(aP, aDescription, aUnsafeName, aIsInvalid, aNMerged,
                           aPresence) {
   let safeName = flipBackslashes(aUnsafeName);
   if (!aIsInvalid && !aNMerged && !aPresence) {
     safeName += "\n";
   }
   let nameSpan = appendElementWithText(aP, "span", "mrName", safeName);
@@ -1662,17 +1613,17 @@ function appendMrNameSpan(aP, aDescripti
     }
     let noteSpan = appendElementWithText(aP, "span", "mrNote", noteText);
     noteSpan.title =
       "Warning: this value is invalid and indicates a bug in one or more " +
       "memory reporters. ";
   }
 
   if (aNMerged) {
-    let noteText = " [" + aNMerged + "]";
+    let noteText = ` [${aNMerged}]`;
     if (!aPresence) {
       noteText += "\n";
     }
     let noteSpan = appendElementWithText(aP, "span", "mrNote", noteText);
     noteSpan.title =
       "This value is the sum of " + aNMerged +
       " memory reports that all have the same path.";
   }
@@ -1692,34 +1643,33 @@ function appendMrNameSpan(aP, aDescripti
       c = "!";
       title = "One of the sets of memory reports lacked children for this " +
               "node's parent. This is a fake child node added to make the " +
               "two memory sets comparable.";
       break;
      default: assert(false, "bad presence");
       break;
     }
-    let noteSpan = appendElementWithText(aP, "span", "mrNote",
-                                         " [" + c + "]\n");
+    let noteSpan = appendElementWithText(aP, "span", "mrNote", ` [${c}]\n`);
     noteSpan.title = title;
   }
 }
 
 // This is used to record the (safe) IDs of which sub-trees have been manually
 // expanded (marked as true) and collapsed (marked as false).  It's used to
 // replicate the collapsed/expanded state when the page is updated.  It can end
 // up holding IDs of nodes that no longer exist, e.g. for compartments that
 // have been closed.  This doesn't seem like a big deal, because the number is
 // limited by the number of entries the user has changed from their original
 // state.
-var gShowSubtreesBySafeTreeId = {};
+let gShowSubtreesBySafeTreeId = {};
 
-function assertClassListContains(e, className) {
-  assert(e, "undefined " + className);
-  assert(e.classList.contains(className), "classname isn't " + className);
+function assertClassListContains(aElem, aClassName) {
+  assert(aElem, "undefined " + aClassName);
+  assert(aElem.classList.contains(aClassName), "classname isn't " + aClassName);
 }
 
 function toggle(aEvent) {
   // This relies on each line being a span that contains at least four spans:
   // mrValue, mrPerc, mrSep, mrName, and then zero or more mrNotes.  All
   // whitespace must be within one of these spans for this function to find the
   // right nodes.  And the span containing the children of this line must
   // immediately follow.  Assertions check this.
@@ -1783,85 +1733,82 @@ function expandPathToThisElement(aElemen
  *        The tree root.
  * @param aProcess
  *        The process the tree corresponds to.
  * @param aPadText
  *        A string to pad the start of each entry.
  */
 function appendTreeElements(aP, aRoot, aProcess, aPadText) {
   /**
-   * Appends the elements for a particular tree, without a heading.
+   * Appends the elements for a particular tree, without a heading. There's a
+   * subset of the Unicode "light" box-drawing chars that is widely implemented
+   * in terminals, and this code sticks to that subset to maximize the chance
+   * that copying and pasting about:memory output to a terminal will work
+   * correctly.
    *
    * @param aP
    *        The parent DOM node.
    * @param aProcess
    *        The process the tree corresponds to.
    * @param aUnsafeNames
    *        An array of the names forming the path to aT.
    * @param aRoot
    *        The root of the tree this sub-tree belongs to.
    * @param aT
    *        The tree.
-   * @param aTreelineText1
-   *        The first part of the treeline for this entry and this entry's
-   *        children.
-   * @param aTreelineText2a
-   *        The second part of the treeline for this entry.
-   * @param aTreelineText2b
-   *        The second part of the treeline for this entry's children.
+   * @param aTlThis
+   *        The treeline for this entry.
+   * @param aTlKids
+   *        The treeline for this entry's children.
    * @param aParentStringLength
    *        The length of the formatted byte count of the top node in the tree.
    */
   function appendTreeElements2(aP, aProcess, aUnsafeNames, aRoot, aT,
-                               aTreelineText1, aTreelineText2a,
-                               aTreelineText2b, aParentStringLength) {
+                               aTlThis, aTlKids, aParentStringLength) {
     function appendN(aS, aC, aN) {
       for (let i = 0; i < aN; i++) {
         aS += aC;
       }
       return aS;
     }
 
     // The tree line.  Indent more if this entry is narrower than its parent.
     let valueText = aT.toString();
-    let extraTreelineLength =
+    let extraTlLength =
       Math.max(aParentStringLength - valueText.length, 0);
-    if (extraTreelineLength > 0) {
-      aTreelineText2a =
-        appendN(aTreelineText2a, kHorizontal, extraTreelineLength);
-      aTreelineText2b =
-        appendN(aTreelineText2b, " ", extraTreelineLength);
+    if (extraTlLength > 0) {
+      aTlThis = appendN(aTlThis, "─", extraTlLength);
+      aTlKids = appendN(aTlKids, " ", extraTlLength);
     }
-    let treelineText = aTreelineText1 + aTreelineText2a;
-    appendElementWithText(aP, "span", "treeline", treelineText);
+    appendElementWithText(aP, "span", "treeline", aTlThis);
 
     // Detect and record invalid values.  But not if gIsDiff is true, because
     // we expect negative values in that case.
     assertInput(aRoot._units === aT._units,
                 "units within a tree are inconsistent");
     let tIsInvalid = false;
     if (!gIsDiff && !(0 <= aT._amount && aT._amount <= aRoot._amount)) {
       tIsInvalid = true;
       let unsafePath = aUnsafeNames.join("/");
       gUnsafePathsWithInvalidValuesForThisProcess.push(unsafePath);
-      reportAssertionFailure("Invalid value (" + aT._amount + " / " +
-                             aRoot._amount + ") for " +
-                             flipBackslashes(unsafePath));
+      reportAssertionFailure(
+        `Invalid value (${aT._amount} / ${aRoot._amount}) for ` +
+        flipBackslashes(unsafePath));
     }
 
     // For non-leaf nodes, the entire sub-tree is put within a span so it can
     // be collapsed if the node is clicked on.
     let d;
     let sep;
     let showSubtrees;
     if (aT._kids) {
       // Determine if we should show the sub-tree below this entry;  this
       // involves reinstating any previous toggling of the sub-tree.
       let unsafePath = aUnsafeNames.join("/");
-      let safeTreeId = aProcess + ":" + flipBackslashes(unsafePath);
+      let safeTreeId = `${aProcess}:${flipBackslashes(unsafePath)}`;
       showSubtrees = !aT._hideKids;
       if (gShowSubtreesBySafeTreeId[safeTreeId] !== undefined) {
         showSubtrees = gShowSubtreesBySafeTreeId[safeTreeId];
       }
       d = appendElement(aP, "span", "hasKids");
       d.id = safeTreeId;
       d.onclick = toggle;
       sep = showSubtrees ? kShowKidsSep : kHideKidsSep;
@@ -1871,25 +1818,19 @@ function appendTreeElements(aP, aRoot, a
       d = aP;
     }
 
     // The value.
     appendElementWithText(d, "span", "mrValue" + (tIsInvalid ? " invalid" : ""),
                           valueText);
 
     // The percentage (omitted for single entries).
-    let percText;
     if (!aT._isDegenerate) {
-      // Treat 0 / 0 as 100%.
-      let num = aRoot._amount === 0 ? 100 : (100 * aT._amount / aRoot._amount);
-      let numText = num.toFixed(2);
-      percText = numText === "100.00"
-               ? " (100.0%)"
-               : (0 <= num && num < 10 ? " (0" : " (") + numText + "%)";
-      appendElementWithText(d, "span", "mrPerc", percText);
+      let percText = formatTreeFrac(aT._amount, aRoot._amount);
+      appendElementWithText(d, "span", "mrPerc", ` (${percText})`);
     }
 
     // The separator.
     appendElementWithText(d, "span", "mrSep", sep);
 
     // The entry's name.
     appendMrNameSpan(d, aT._description, aT._unsafeName,
                      tIsInvalid, aT._nMerged, aT._presence);
@@ -1900,38 +1841,39 @@ function appendTreeElements(aP, aRoot, a
       expandPathToThisElement(d);
     }
 
     // Recurse over children.
     if (aT._kids) {
       // The 'kids' class is just used for sanity checking in toggle().
       d = appendElement(aP, "span", showSubtrees ? "kids" : "kids hidden");
 
-      let kidTreelineText1 = aTreelineText1 + aTreelineText2b;
-      for (let i = 0; i < aT._kids.length; i++) {
-        let kidTreelineText2a, kidTreelineText2b;
-        if (i < aT._kids.length - 1) {
-          kidTreelineText2a = kVerticalAndRight_Right_Right;
-          kidTreelineText2b = kVertical_Space_Space;
-        } else {
-          kidTreelineText2a = kUpAndRight_Right_Right;
-          kidTreelineText2b = "   ";
-        }
-        aUnsafeNames.push(aT._kids[i]._unsafeName);
-        appendTreeElements2(d, aProcess, aUnsafeNames, aRoot, aT._kids[i],
-                            kidTreelineText1, kidTreelineText2a,
-                            kidTreelineText2b, valueText.length);
+      let tlThisForMost, tlKidsForMost;
+      if (aT._kids.length > 1) {
+        tlThisForMost = aTlKids + "├──";
+        tlKidsForMost = aTlKids + "│  ";
+      }
+      let tlThisForLast = aTlKids + "└──";
+      let tlKidsForLast = aTlKids + "   ";
+
+      for (let [i, kid] of aT._kids.entries()) {
+        let isLast = i == aT._kids.length - 1;
+        aUnsafeNames.push(kid._unsafeName);
+        appendTreeElements2(d, aProcess, aUnsafeNames, aRoot, kid,
+                            !isLast ? tlThisForMost : tlThisForLast,
+                            !isLast ? tlKidsForMost : tlKidsForLast,
+                            valueText.length);
         aUnsafeNames.pop();
       }
     }
   }
 
   let rootStringLength = aRoot.toString().length;
   appendTreeElements2(aP, aProcess, [aRoot._unsafeName], aRoot, aRoot,
-                      aPadText, "", "", rootStringLength);
+                      aPadText, aPadText, rootStringLength);
 }
 
 // ---------------------------------------------------------------------------
 
 function appendSectionHeader(aP, aText) {
   appendElementWithText(aP, "h2", "", aText + "\n");
   return appendElement(aP, "pre", "entries");
 }
@@ -1941,41 +1883,41 @@ function appendSectionHeader(aP, aText) 
 function saveReportsToFile() {
   let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
   fp.appendFilter("Zipped JSON files", "*.json.gz");
   fp.appendFilters(Ci.nsIFilePicker.filterAll);
   fp.filterIndex = 0;
   fp.addToRecentDocs = true;
   fp.defaultString = "memory-report.json.gz";
 
-  let fpFinish = function(file) {
+  let fpFinish = function(aFile) {
     let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
                    .getService(Ci.nsIMemoryInfoDumper);
     let finishDumping = () => {
-      updateMainAndFooter("Saved memory reports to " + file.path,
+      updateMainAndFooter("Saved memory reports to " + aFile.path,
                           SHOW_TIMESTAMP, HIDE_FOOTER);
     };
-    dumper.dumpMemoryReportsToNamedFile(file.path, finishDumping, null,
+    dumper.dumpMemoryReportsToNamedFile(aFile.path, finishDumping, null,
                                         gAnonymize.checked);
   };
 
   let fpCallback = function(aResult) {
     if (aResult == Ci.nsIFilePicker.returnOK ||
         aResult == Ci.nsIFilePicker.returnReplace) {
       fpFinish(fp.file);
     }
   };
 
   try {
     fp.init(window, "Save Memory Reports", Ci.nsIFilePicker.modeSave);
   } catch (ex) {
     // This will fail on Android, since there is no Save as file picker there.
     // Just save to the default downloads dir if it does.
-    Downloads.getSystemDownloadsDirectory().then(function(dirPath) {
-      let file = FileUtils.File(dirPath);
+    Downloads.getSystemDownloadsDirectory().then(function(aDirPath) {
+      let file = FileUtils.File(aDirPath);
       file.append(fp.defaultString);
       fpFinish(file);
     });
 
     return;
   }
   fp.open(fpCallback);
 }
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory.xul
@@ -266,17 +266,17 @@ WARNING: the following values are negati
 \n\
 This indicates a defect in one or more memory reporters. The invalid values are highlighted.\n\
 Explicit Allocations\n\
 \n\
 98.00 MB (100.0%) -- explicit\n\
 ├──150.00 MB (153.06%) ── js/compartment(http://too-big.com/)/stuff [?!]\n\
 ├───5.00 MB (05.10%) ── ok\n\
 └──-57.00 MB (-58.16%) -- (2 tiny) [?!]\n\
-   ├───-2.00 MB (-2.04%) ── neg1 [?!]\n\
+   ├───-2.00 MB (-02.04%) ── neg1 [?!]\n\
    └──-55.00 MB (-56.12%) ── heap-unclassified [?!]\n\
 \n\
 Other Measurements\n\
 \n\
  100.00 MB ── heap-allocated\n\
   -0.00 MB ── other1 [?!]\n\
 -222.00 MB ── other2 [?!]\n\
       -333 ── other3 [?!]\n\
@@ -333,24 +333,24 @@ This indicates a defect in one or more m
 Explicit Allocations\n\
 \n\
 99.95 MB (100.0%) -- explicit\n\
 ├──99.00 MB (99.05%) ── big\n\
 └───0.95 MB (00.95%) -- (3 tiny)\n\
     ├──0.96 MB (00.96%) ── heap-unclassified\n\
     ├──0.01 MB (00.01%) -- a\n\
     │  ├──0.04 MB (00.04%) ── pos\n\
-    │  ├──-0.01 MB (-0.01%) ── neg2 [?!]\n\
-    │  └──-0.02 MB (-0.02%) ── neg1 [?!]\n\
-    └──-0.02 MB (-0.02%) -- b/c [?!]\n\
+    │  ├──-0.01 MB (-00.01%) ── neg2 [?!]\n\
+    │  └──-0.02 MB (-00.02%) ── neg1 [?!]\n\
+    └──-0.02 MB (-00.02%) -- b/c [?!]\n\
        ├───0.01 MB (00.01%) ── g/h\n\
        ├───0.00 MB (00.00%) ── i/j\n\
-       └──-0.04 MB (-0.04%) -- d [?!]\n\
+       └──-0.04 MB (-00.04%) -- d [?!]\n\
           ├───0.02 MB (00.02%) ── e\n\
-          └──-0.06 MB (-0.06%) ── f [?!]\n\
+          └──-0.06 MB (-00.06%) ── f [?!]\n\
 \n\
 Other Measurements\n\
 \n\
 100.00 MB ── heap-allocated\n\
 \n\
 End of 5th\n\
 ";
 
@@ -436,17 +436,17 @@ WARNING: the following values are negati
     other5 \n\
 \n\
 This indicates a defect in one or more memory reporters. The invalid values are highlighted.\n\
 Explicit Allocations\n\
 \n\
 102,760,448 B (100.0%) -- explicit\n\
 ├──157,286,400 B (153.06%) ── js/compartment(http://too-big.com/)/stuff [?!]\n\
 ├────5,242,880 B (05.10%) ── ok\n\
-├───-2,097,152 B (-2.04%) ── neg1 [?!]\n\
+├───-2,097,152 B (-02.04%) ── neg1 [?!]\n\
 └──-57,671,680 B (-56.12%) ── heap-unclassified [?!]\n\
 \n\
 Other Measurements\n\
 \n\
  104,857,600 B ── heap-allocated\n\
         -111 B ── other1 [?!]\n\
 -232,783,872 B ── other2 [?!]\n\
           -333 ── other3 [?!]\n\
@@ -502,24 +502,24 @@ WARNING: the following values are negati
 This indicates a defect in one or more memory reporters. The invalid values are highlighted.\n\
 Explicit Allocations\n\
 \n\
 104,801,280 B (100.0%) -- explicit\n\
 ├──103,809,024 B (99.05%) ── big\n\
 ├────1,007,616 B (00.96%) ── heap-unclassified\n\
 ├───────10,240 B (00.01%) -- a\n\
 │       ├──40,960 B (00.04%) ── pos\n\
-│       ├──-10,240 B (-0.01%) ── neg2 [?!]\n\
-│       └──-20,480 B (-0.02%) ── neg1 [?!]\n\
-└──────-25,600 B (-0.02%) -- b/c [?!]\n\
+│       ├──-10,240 B (-00.01%) ── neg2 [?!]\n\
+│       └──-20,480 B (-00.02%) ── neg1 [?!]\n\
+└──────-25,600 B (-00.02%) -- b/c [?!]\n\
        ├───10,240 B (00.01%) ── g/h\n\
        ├────5,120 B (00.00%) ── i/j\n\
-       └──-40,960 B (-0.04%) -- d [?!]\n\
+       └──-40,960 B (-00.04%) -- d [?!]\n\
           ├───20,480 B (00.02%) ── e\n\
-          └──-61,440 B (-0.06%) ── f [?!]\n\
+          └──-61,440 B (-00.06%) ── f [?!]\n\
 \n\
 Other Measurements\n\
 \n\
 104,857,600 B ── heap-allocated\n\
 \n\
 End of 5th\n\
 ";
 
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
@@ -358,37 +358,37 @@ Other Measurements\n\
 \n\
 -0.00 MB (100.0%) -- p8\n\
 └──-0.00 MB (100.0%) -- a\n\
    ├──-0.00 MB (50.00%) -- b\n\
    │  ├──-0.00 MB (31.82%) -- c\n\
    │  │  ├──-0.00 MB (18.18%) ── e [-]\n\
    │  │  └──-0.00 MB (13.64%) ── d [-]\n\
    │  ├──-0.00 MB (22.73%) ── f [-]\n\
-   │  └───0.00 MB (-4.55%) ── (fake child) [!]\n\
+   │  └───0.00 MB (-04.55%) ── (fake child) [!]\n\
    └──-0.00 MB (50.00%) -- g\n\
       ├──-0.00 MB (31.82%) ── i [-]\n\
       ├──-0.00 MB (27.27%) ── h [-]\n\
-      └───0.00 MB (-9.09%) ── (fake child) [!]\n\
+      └───0.00 MB (-09.09%) ── (fake child) [!]\n\
 \n\
 End of P8\n\
 ";
 
   // This is the output for a verbose diff.
   let expectedDiffVerbose =
 "\
 P\n\
 \n\
 WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
 Explicit Allocations\n\
 \n\
 -10,005 B (100.0%) -- explicit\n\
 ├──-10,000 B (99.95%) ── storage/prefixset/goog-phish-shavar\n\
 ├───────-6 B (00.06%) ── spell-check [2]\n\
-└────────1 B (-0.01%) ── xpcom/category-manager\n\
+└────────1 B (-00.01%) ── xpcom/category-manager\n\
 \n\
 Other Measurements\n\
 \n\
 1,002,000 B (100.0%) -- a\n\
 ├──1,000,000 B (99.80%) ── b\n\
 ├──────1,000 B (00.10%) -- c\n\
 │      ├──-999,000 B (-99.70%) ── e\n\
 │      ├──998,000 B (99.60%) ── d\n\
@@ -456,21 +456,21 @@ Other Measurements\n\
 \n\
 -22 B (100.0%) -- p8\n\
 └──-22 B (100.0%) -- a\n\
    ├──-11 B (50.00%) -- b\n\
    │  ├───-7 B (31.82%) -- c\n\
    │  │   ├──-4 B (18.18%) ── e [-]\n\
    │  │   └──-3 B (13.64%) ── d [-]\n\
    │  ├───-5 B (22.73%) ── f [-]\n\
-   │  └────1 B (-4.55%) ── (fake child) [!]\n\
+   │  └────1 B (-04.55%) ── (fake child) [!]\n\
    └──-11 B (50.00%) -- g\n\
       ├───-7 B (31.82%) ── i [-]\n\
       ├───-6 B (27.27%) ── h [-]\n\
-      └────2 B (-9.09%) ── (fake child) [!]\n\
+      └────2 B (-09.09%) ── (fake child) [!]\n\
 \n\
 End of P8\n\
 ";
 
   // This is the output for the crash reports diff.
   let expectedDiff2 =
 "\
 Main Process (pid NNN)\n\