Merge inbound to mozilla-central. a=merge
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Fri, 19 Oct 2018 16:11:27 +0300
changeset 500578 18859d2fec94f35e924e9093a99169623e0b2d78
parent 500541 1030155f208e08a4a4673655c8b6b343c1b920a1 (current diff)
parent 500577 f35ac5e55bb4ea3a74c53b964bec3627c984961b (diff)
child 500579 4888629028695c78b394cef273c00e70ae4b87df
child 500640 0cb99356070d3f79a1ff20c64970d092b7d908c5
child 500692 16864944bab1d02697d9d08b7fdeaebaa62d8f59
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
browser/components/payments/res/paymentRequest.js
browser/components/payments/test/browser/browser_payments_onboarding_wizard.js
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
servo/components/style/properties/helpers/animated_properties.mako.rs
--- 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
+      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\