Merge mozilla-inbound to mozilla-central r=merge a=merge
authorAndreea Pavel <apavel@mozilla.com>
Thu, 04 Jan 2018 23:28:19 +0200
changeset 449560 b602b19745e33f26c196dc6fadb01ce9fa2b66a4
parent 449525 95704f0aec0cf50666ded74c8c504ff2917c59c0 (current diff)
parent 449559 8b100f473b7db72dbe93d28e45dd5ca33f6d3e59 (diff)
child 449561 b4ff37d647a778d139c8c7eabfaa7c666f22be1c
child 449625 e0c5e43e5690e4aab4612aea27a799086f267a3c
child 449674 ea7269f28ea02ca6e6d0aad57afe3f8e6977e8fe
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone59.0a1
first release with
nightly linux32
b602b19745e3 / 59.0a1 / 20180104220114 / files
nightly linux64
b602b19745e3 / 59.0a1 / 20180104220114 / files
nightly mac
b602b19745e3 / 59.0a1 / 20180104220114 / files
nightly win32
b602b19745e3 / 59.0a1 / 20180104220114 / files
nightly win64
b602b19745e3 / 59.0a1 / 20180104220114 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central r=merge a=merge
browser/components/preferences/in-content/main.xul
gfx/thebes/gfxPrefs.h
rdf/datasource/nsFileSystemDataSource.cpp
rdf/datasource/nsFileSystemDataSource.h
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/module-in-xhtml.xhtml.ini
toolkit/content/findUtils.js
toolkit/content/finddialog.js
toolkit/content/finddialog.xul
toolkit/content/widgets/preferences.xml
toolkit/locales/en-US/chrome/global/finddialog.dtd
toolkit/locales/en-US/chrome/global/finddialog.properties
toolkit/themes/linux/global/preferences.css
toolkit/themes/osx/global/icons/panebutton-active.png
toolkit/themes/osx/global/icons/panebutton-inactive.png
toolkit/themes/osx/global/preferences.css
toolkit/themes/shared/in-content/common.inc.css
toolkit/themes/windows/global/preferences.css
--- a/.taskcluster.yml
+++ b/.taskcluster.yml
@@ -180,8 +180,9 @@ tasks:
               parent: '${action.taskGroupId}'
               action:
                 name: '${action.name}'
                 context:
                   taskGroupId: '${action.taskGroupId}'
                   taskId: {$eval: 'taskId'}
                   input: {$eval: 'input'}
                   parameters: {$eval: 'parameters'}
+          - tasks_for: '${tasks_for}'
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -63,23 +63,29 @@ var whitelist = [
   // See bug 1339424 for why this is hard to fix.
   {file: "chrome://global/locale/fallbackMenubar.properties",
    platforms: ["linux", "win"]},
   {file: "chrome://global/locale/printPageSetup.dtd", platforms: ["macosx"]},
   {file: "chrome://global/locale/printPreviewProgress.dtd",
    platforms: ["macosx"]},
   {file: "chrome://global/locale/printProgress.dtd", platforms: ["macosx"]},
 
+  // toolkit/content/aboutRights-unbranded.xhtml doesn't use aboutRights.css
+  {file: "chrome://global/skin/aboutRights.css", skipUnofficial: true},
+
   // devtools/client/inspector/bin/dev-server.js
   {file: "chrome://devtools/content/inspector/markup/markup.xhtml",
    isFromDevTools: true},
 
   // Kept for add-on compatibility, should be removed in bug 851471.
   {file: "chrome://mozapps/skin/downloads/downloadIcon.png"},
 
+  // SpiderMonkey parser API, currently unused in browser/ and toolkit/
+  {file: "resource://gre/modules/reflect.jsm"},
+
   // extensions/pref/autoconfig/src/nsReadConfig.cpp
   {file: "resource://gre/defaults/autoconfig/prefcalls.js"},
 
   // modules/libpref/Preferences.cpp
   {file: "resource://gre/greprefs.js"},
 
   // browser/extensions/pdfjs/content/web/viewer.js
   {file: "resource://pdf.js/build/pdf.worker.js"},
@@ -127,18 +133,18 @@ var whitelist = [
   {file: "resource://shield-recipe-client-content/shield-content-process.js"},
 
   // Starting from here, files in the whitelist are bugs that need fixing.
   // Bug 1339424 (wontfix?)
   {file: "chrome://browser/locale/taskbar.properties",
    platforms: ["linux", "macosx"]},
   // Bug 1316187
   {file: "chrome://global/content/customizeToolbar.xul"},
-  // Bug 1343837
-  {file: "chrome://global/content/findUtils.js"},
+  // Bug 1356031 (only used by devtools)
+  {file: "chrome://global/skin/icons/error-16.png"},
   // Bug 1348362
   {file: "chrome://global/skin/icons/warning-64.png", platforms: ["linux"]},
   // Bug 1348525
   {file: "chrome://global/skin/splitter/grip-bottom.gif", platforms: ["linux"]},
   {file: "chrome://global/skin/splitter/grip-left.gif", platforms: ["linux"]},
   {file: "chrome://global/skin/splitter/grip-right.gif", platforms: ["linux"]},
   {file: "chrome://global/skin/splitter/grip-top.gif", platforms: ["linux"]},
   // Bug 1348526
@@ -159,21 +165,30 @@ var whitelist = [
   // Bug 1348559
   {file: "chrome://pippki/content/resetpassword.xul"},
   // Bug 1351078
   {file: "resource://gre/modules/Battery.jsm"},
   // Bug 1337345
   {file: "resource://gre/modules/Manifest.jsm"},
   // Bug 1351097
   {file: "resource://gre/modules/accessibility/AccessFu.jsm"},
+  // Bug 1356043
+  {file: "resource://gre/modules/PerfMeasurement.jsm"},
+  // Bug 1356045
+  {file: "chrome://global/content/test-ipc.xul"},
+  // Bug 1356036
+  {file: "resource://gre/modules/PerformanceWatcher-content.js"},
+  {file: "resource://gre/modules/PerformanceWatcher.jsm"},
+  // Bug 1378173 (warning: still used by devtools)
+  {file: "resource://gre/modules/Promise.jsm"},
 ];
 
 whitelist = new Set(whitelist.filter(item =>
   ("isFromDevTools" in item) == isDevtools &&
-  (!item.skipNightly || !AppConstants.NIGHTLY_BUILD) &&
+  (!item.skipUnofficial || !AppConstants.MOZILLA_OFFICIAL) &&
   (!item.platforms || item.platforms.includes(AppConstants.platform))
 ).map(item => item.file));
 
 const ignorableWhitelist = new Set([
   // The communicator.css file is kept for add-on backward compat.
   "chrome://communicator/skin/communicator.css",
 
   // These 2 files are unreferenced only when building without the crash
@@ -193,16 +208,18 @@ const ignorableWhitelist = new Set([
   // Bug 1351669 - obsolete test file
   "resource://gre/res/test.properties",
 ]);
 for (let entry of ignorableWhitelist) {
   whitelist.add(entry);
 }
 
 if (!isDevtools) {
+  // services/sync/modules/main.js
+  whitelist.add("resource://services-sync/service.js");
   // services/sync/modules/service.js
   for (let module of ["addons.js", "bookmarks.js", "forms.js", "history.js",
                       "passwords.js", "prefs.js", "tabs.js",
                       "extension-storage.js"]) {
     whitelist.add("resource://services-sync/engines/" + module);
   }
 
 }
@@ -212,19 +229,27 @@ const gInterestingCategories = new Set([
   "webextension-scripts", "webextension-schemas", "webextension-scripts-addon",
   "webextension-scripts-content", "webextension-scripts-devtools"
 ]);
 
 var gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]
                  .getService(Ci.nsIChromeRegistry);
 var gChromeMap = new Map();
 var gOverrideMap = new Map();
-var gReferencesFromCode = new Set();
 var gComponentsSet = new Set();
 
+// In this map when the value is a Set of URLs, the file is referenced if any
+// of the files in the Set is referenced.
+// When the value is null, the file is referenced unconditionally.
+// When the value is a string, "whitelist-direct" means that we have not found
+// any reference in the code, but have a matching whitelist entry for this file.
+// "whitelist" means that the file is indirectly whitelisted, ie. a whitelisted
+// file causes this file to be referenced.
+var gReferencesFromCode = new Map();
+
 var resHandler = Services.io.getProtocolHandler("resource")
                          .QueryInterface(Ci.nsIResProtocolHandler);
 var gResourceMap = [];
 function trackResourcePrefix(prefix) {
   let uri = Services.io.newURI("resource://" + prefix + "/");
   gResourceMap.unshift([prefix, resHandler.resolveURI(uri)]);
 }
 trackResourcePrefix("gre");
@@ -249,80 +274,116 @@ function parseManifest(manifestUri) {
         // that the overlay is only referenced if the original xul
         // file is referenced somewhere.
         let os = "os=" + Services.appinfo.OS;
         if (!argv.some(s => s.startsWith("os=") && s != os)) {
           gOverrideMap.set(Services.io.newURI(argv[1]).specIgnoringRef,
                            Services.io.newURI(argv[0]).specIgnoringRef);
         }
       } else if (type == "category" && gInterestingCategories.has(argv[0])) {
-        gReferencesFromCode.add(argv[2]);
+        gReferencesFromCode.set(argv[2], null);
       } else if (type == "resource") {
         trackResourcePrefix(argv[0]);
       } else if (type == "component") {
         gComponentsSet.add(argv[1]);
       }
     }
   });
 }
 
+function addCodeReference(url, fromURI) {
+  let from = convertToCodeURI(fromURI.spec);
+
+  // Ignore self references.
+  if (url == from)
+    return;
+
+  let ref;
+  if (gReferencesFromCode.has(url)) {
+    ref = gReferencesFromCode.get(url);
+    if (ref === null)
+      return;
+  } else {
+    // Mark any file referenced by a 'features' bootstrap.js file as
+    // unconditionally referenced. The features folder is only in
+    // resource://app/ for non-packaged builds.
+    if (/resource:\/\/app\/features\/[^/]+\/bootstrap\.js/.test(from)) {
+      gReferencesFromCode.set(url, null);
+      return;
+    }
+    ref = new Set();
+    gReferencesFromCode.set(url, ref);
+  }
+  ref.add(from);
+}
+
+function listCodeReferences(refs) {
+  let refList = [];
+  if (refs) {
+    for (let ref of refs) {
+      refList.push(ref);
+    }
+  }
+  return refList.join(",");
+}
+
 function parseCSSFile(fileUri) {
   return fetchFile(fileUri.spec).then(data => {
     for (let line of data.split("\n")) {
       let urls = line.match(/url\([^()]+\)/g);
       if (!urls) {
         // @import rules can take a string instead of a url.
         let importMatch = line.match(/@import ['"]?([^'"]*)['"]?/);
         if (importMatch && importMatch[1]) {
           let url = Services.io.newURI(importMatch[1], null, fileUri).spec;
-          gReferencesFromCode.add(convertToCodeURI(url));
+          addCodeReference(convertToCodeURI(url), fileUri);
         }
         continue;
       }
 
       for (let url of urls) {
         // Remove the url(" prefix and the ") suffix.
         url = url.replace(/url\(([^)]*)\)/, "$1")
                  .replace(/^"(.*)"$/, "$1")
                  .replace(/^'(.*)'$/, "$1");
         if (url.startsWith("data:"))
           continue;
 
         try {
           url = Services.io.newURI(url, null, fileUri).specIgnoringRef;
-          gReferencesFromCode.add(convertToCodeURI(url));
+          addCodeReference(convertToCodeURI(url), fileUri);
         } catch (e) {
           ok(false, "unexpected error while resolving this URI: " + url);
         }
       }
     }
   });
 }
 
 function parseCodeFile(fileUri) {
   return fetchFile(fileUri.spec).then(data => {
     let baseUri;
     for (let line of data.split("\n")) {
       let urls =
-        line.match(/["'`]chrome:\/\/[a-zA-Z0-9 -]+\/(content|skin|locale)\/[^"'` ]*["'`]/g);
+        line.match(/["'`]chrome:\/\/[a-zA-Z0-9-]+\/(content|skin|locale)\/[^"'` ]*["'`]/g);
       if (!urls) {
         urls = line.match(/["']resource:\/\/[^"']+["']/g);
         if (urls && isDevtools &&
             /baseURI: "resource:\/\/devtools\//.test(line)) {
           baseUri = Services.io.newURI(urls[0].slice(1, -1));
           continue;
         }
       }
       if (!urls) {
         // If there's no absolute chrome URL, look for relative ones in
         // src and href attributes.
         let match = line.match("(?:src|href)=[\"']([^$&\"']+)");
         if (match && match[1]) {
           let url = Services.io.newURI(match[1], null, fileUri).spec;
-          gReferencesFromCode.add(convertToCodeURI(url));
+          addCodeReference(convertToCodeURI(url), fileUri);
         }
 
         if (isDevtools) {
           let rules = [
             ["gcli", "resource://devtools/shared/gcli/source/lib/gcli"],
             ["devtools/client/locales", "chrome://devtools/locale"],
             ["devtools/shared/locales", "chrome://devtools-shared/locale"],
             ["devtools/shared/platform", "resource://devtools/shared/platform/chrome"],
@@ -332,31 +393,31 @@ function parseCodeFile(fileUri) {
           match = line.match(/["']((?:devtools|gcli)\/[^\\#"']+)["']/);
           if (match && match[1]) {
             let path = match[1];
             for (let rule of rules) {
               if (path.startsWith(rule[0] + "/")) {
                 path = path.replace(rule[0], rule[1]);
                 if (!/\.(properties|js|jsm|json|css)$/.test(path))
                   path += ".js";
-                gReferencesFromCode.add(path);
+                addCodeReference(path, fileUri);
                 break;
               }
             }
           }
 
           match = line.match(/require\(['"](\.[^'"]+)['"]\)/);
           if (match && match[1]) {
             let url = match[1];
             url = Services.io.newURI(url, null, baseUri || fileUri).spec;
             url = convertToCodeURI(url);
             if (!/\.(properties|js|jsm|json|css)$/.test(url))
               url += ".js";
             if (url.startsWith("resource://")) {
-              gReferencesFromCode.add(url);
+              addCodeReference(url, fileUri);
             } else {
               // if we end up with a chrome:// url here, it's likely because
               // a baseURI to a resource:// path has been defined in another
               // .js file that is loaded in the same scope, we can't detect it.
             }
           }
         }
         continue;
@@ -378,17 +439,17 @@ function parseCodeFile(fileUri) {
         // Make urls like chrome://browser/skin/ point to an actual file,
         // and remove the ref if any.
         url = Services.io.newURI(url).specIgnoringRef;
 
         if (isDevtools && line.includes("require(") &&
             !/\.(properties|js|jsm|json|css)$/.test(url))
           url += ".js";
 
-        gReferencesFromCode.add(url);
+        addCodeReference(url, fileUri);
       }
     }
   });
 }
 
 function convertToCodeURI(fileUri) {
   let baseUri = fileUri;
   let path = "";
@@ -453,20 +514,19 @@ function findChromeUrlsFromArray(array, 
                        array.indexOf(")".charCodeAt(0), index),
                        array.indexOf("#".charCodeAt(0), index));
     let string = "";
     for ( ; index < end; ++index) {
       string += String.fromCharCode(array[index]);
     }
 
     // Only keep strings that look like real chrome or resource urls.
-    if (/chrome:\/\/[a-zA-Z09 -]+\/(content|skin|locale)\//.test(string) ||
-        /resource:\/\/gre.*\.[a-z]+/.test(string) ||
-        string.startsWith("resource://content-accessible/"))
-      gReferencesFromCode.add(string);
+    if (/chrome:\/\/[a-zA-Z09-]+\/(content|skin|locale)\//.test(string) ||
+        /resource:\/\/[a-zA-Z09-]*\/.*\.[a-z]+/.test(string))
+      gReferencesFromCode.set(string, null);
   }
 }
 
 add_task(async function checkAllTheFiles() {
   let libxulPath = OS.Constants.Path.libxul;
   if (AppConstants.platform != "macosx")
     libxulPath = OS.Constants.Path.libDir + "/" + libxulPath;
   let libxul = await OS.File.read(libxulPath);
@@ -521,44 +581,104 @@ add_task(async function checkAllTheFiles
   // the non-devtools paths:
   let devtoolsPrefixes = ["chrome://webide/",
                           "chrome://devtools",
                           "resource://devtools/",
                           "resource://devtools-client-jsonview/",
                           "resource://devtools-client-shared/",
                           "resource://app/modules/devtools",
                           "resource://gre/modules/devtools"];
+  let hasDevtoolsPrefix =
+    uri => devtoolsPrefixes.some(prefix => uri.startsWith(prefix));
   let chromeFiles = [];
   for (let uri of uris) {
     uri = convertToCodeURI(uri.spec);
     if ((uri.startsWith("chrome://") || uri.startsWith("resource://")) &&
-        isDevtools == devtoolsPrefixes.some(prefix => uri.startsWith(prefix)))
+        isDevtools == hasDevtoolsPrefix(uri))
       chromeFiles.push(uri);
   }
 
-  let isUnreferenced =
-    file => !gReferencesFromCode.has(file) &&
-            !gExceptionPaths.some(e => file.startsWith(e)) &&
-            (!gOverrideMap.has(file) || isUnreferenced(gOverrideMap.get(file)));
+  if (isDevtools) {
+    // chrome://devtools/skin/devtools-browser.css is included from browser.xul
+    gReferencesFromCode.set("chrome://browser/content/browser.xul", null);
+    // devtools' css is currently included from browser.css, see bug 1204810.
+    gReferencesFromCode.set("chrome://browser/skin/browser.css", null);
+  }
 
-  let notWhitelisted = file => {
-    if (!whitelist.has(file))
-      return true;
-    whitelist.delete(file);
-    return false;
+  let isUnreferenced = file => {
+    if (gExceptionPaths.some(e => file.startsWith(e)))
+      return false;
+    if (gReferencesFromCode.has(file)) {
+      let refs = gReferencesFromCode.get(file);
+      if (refs === null)
+        return false;
+      for (let ref of refs) {
+        if (ref.endsWith("!/bootstrap.js"))
+          return false;
+
+        if (isDevtools) {
+          if (ref.startsWith("resource://app/components/") ||
+              (file.startsWith("chrome://") && ref.startsWith("resource://")))
+            return false;
+        }
+
+        if (gReferencesFromCode.has(ref)) {
+          let refType = gReferencesFromCode.get(ref);
+          if (refType === null || // unconditionally referenced
+              refType == "whitelist" || refType == "whitelist-direct")
+            return false;
+        }
+      }
+    }
+    return !gOverrideMap.has(file) || isUnreferenced(gOverrideMap.get(file));
   };
 
-  let unreferencedFiles = chromeFiles.filter(f => {
-    let rv = isUnreferenced(f);
-    if (rv && f.startsWith("resource://app/"))
-      rv = isUnreferenced(f.replace("resource://app/", "resource:///"));
-    if (rv && /^resource:\/\/(?:app|gre)\/components\/[^/]+\.js$/.test(f))
-      rv = !gComponentsSet.has(f.replace(/.*\//, ""));
-    return rv;
-  }).filter(notWhitelisted).sort();
+  let unreferencedFiles = chromeFiles;
+
+  let removeReferenced = useWhitelist => {
+    let foundReference = false;
+    unreferencedFiles = unreferencedFiles.filter(f => {
+      let rv = isUnreferenced(f);
+      if (rv && f.startsWith("resource://app/"))
+        rv = isUnreferenced(f.replace("resource://app/", "resource:///"));
+      if (rv && /^resource:\/\/(?:app|gre)\/components\/[^/]+\.js$/.test(f))
+        rv = !gComponentsSet.has(f.replace(/.*\//, ""));
+      if (!rv) {
+        foundReference = true;
+        if (useWhitelist) {
+          info("indirectly whitelisted file: " + f + " used from " +
+               listCodeReferences(gReferencesFromCode.get(f)));
+        }
+        gReferencesFromCode.set(f, useWhitelist ? "whitelist" : null);
+      }
+      return rv;
+    });
+    return foundReference;
+  };
+  // First filter out the files that are referenced.
+  while (removeReferenced(false)) {
+    // As long as removeReferenced returns true, some files have been marked
+    // as referenced, so we need to run it again.
+  }
+  // Marked as referenced the files that have been explicitly whitelisted.
+  unreferencedFiles = unreferencedFiles.filter(file => {
+    if (whitelist.has(file)) {
+      whitelist.delete(file);
+      gReferencesFromCode.set(file, "whitelist-direct");
+      return false;
+    }
+    return true;
+  });
+  // Run the process again, this time when more files are marked as referenced,
+  // it's a consequence of the whitelist.
+  while (removeReferenced(true)) {
+    // As long as removeReferenced returns true, we need to run it again.
+  }
+
+  unreferencedFiles.sort();
 
   if (isDevtools) {
     // Bug 1351878 - handle devtools resource files
     unreferencedFiles = unreferencedFiles.filter(file => {
       if (file.startsWith("resource://")) {
         info("unreferenced devtools resource file: " + file);
         return false;
       }
@@ -580,36 +700,50 @@ add_task(async function checkAllTheFiles
       info("not reporting feature skin file that may be for another platform: " + file);
       return false;
     }
     return true;
   });
 
   is(unreferencedFiles.length, 0, "there should be no unreferenced files");
   for (let file of unreferencedFiles) {
-    ok(false, "unreferenced file: " + file);
+    let refs = gReferencesFromCode.get(file);
+    if (refs === undefined) {
+      ok(false, "unreferenced file: " + file);
+    } else {
+      let refList = listCodeReferences(refs);
+      let msg = "file only referenced from unreferenced files: " + file;
+      if (refList)
+        msg += " referenced from " + refList;
+      ok(false, msg);
+    }
   }
 
   for (let file of whitelist) {
     if (ignorableWhitelist.has(file))
       info("ignored unused whitelist entry: " + file);
     else
       ok(false, "unused whitelist entry: " + file);
   }
 
-  for (let file of gReferencesFromCode) {
+  for (let [file, refs] of gReferencesFromCode) {
     if (isDevtools != devtoolsPrefixes.some(prefix => file.startsWith(prefix)))
       continue;
 
     if ((file.startsWith("chrome://") || file.startsWith("resource://")) &&
         !chromeFileExists(file)) {
       // Ignore chrome prefixes that have been automatically expanded.
       let pathParts =
         file.match("chrome://([^/]+)/content/([^/.]+)\.xul") ||
         file.match("chrome://([^/]+)/skin/([^/.]+)\.css");
-      if (!pathParts || pathParts[1] != pathParts[2]) {
-        // TODO: bug 1349010 - add a whitelist and make this reliable enough
-        // that we could make the test fail when this catches something new.
-        info("missing file with code reference: " + file);
-      }
+      if (pathParts && pathParts[1] == pathParts[2])
+        continue;
+
+      // TODO: bug 1349010 - add a whitelist and make this reliable enough
+      // that we could make the test fail when this catches something new.
+      let refList = listCodeReferences(refs);
+      let msg = "missing file: " + file;
+      if (refList)
+        msg += " referenced from " + refList;
+      info(msg);
     }
   }
 });
--- a/browser/base/content/test/tabs/browser_tabswitch_updatecommands.js
+++ b/browser/base/content/test/tabs/browser_tabswitch_updatecommands.js
@@ -2,20 +2,25 @@
 
 "use strict";
 
 add_task(async function() {
   const uri = "data:text/html,<body><input>";
   let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri);
   let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri);
 
-  let updates = 0;
-  function countUpdates(event) { updates++; }
+  let updates = [];
+  function countUpdates(event) { updates.push((new Error()).stack); }
   let updater = document.getElementById("editMenuCommandSetAll");
   updater.addEventListener("commandupdate", countUpdates, true);
   await BrowserTestUtils.switchTab(gBrowser, tab1);
 
-  is(updates, 1, "only one command update per tab switch");
+  is(updates.length, 1, "only one command update per tab switch");
+  if (updates.length > 1) {
+    for (let stack of updates) {
+      info("Update stack:\n" + stack);
+    }
+  }
 
   updater.removeEventListener("commandupdate", countUpdates, true);
   await BrowserTestUtils.removeTab(tab1);
   await BrowserTestUtils.removeTab(tab2);
 });
--- a/build/unix/build-gcc/build-gcc.sh
+++ b/build/unix/build-gcc/build-gcc.sh
@@ -100,23 +100,24 @@ build_binutils() {
     binutils_configure_flags="--disable-gold --enable-plugins --disable-nls"
   fi
 
   mkdir $root_dir/binutils-objdir
   pushd $root_dir/binutils-objdir
   ../binutils-$binutils_version/configure --prefix=${prefix-/tools/gcc}/ $binutils_configure_flags
   make $make_flags
   make install $make_flags DESTDIR=$root_dir
+  export PATH=$root_dir/${prefix-/tools/gcc}/bin:$PATH
   popd
 }
 
 build_gcc() {
   mkdir $root_dir/gcc-objdir
   pushd $root_dir/gcc-objdir
-  ../gcc-$gcc_version/configure --prefix=${prefix-/tools/gcc} --enable-languages=c,c++  --disable-nls --disable-gnu-unique-object --enable-__cxa_atexit --with-arch-32=pentiumpro
+  ../gcc-$gcc_version/configure --prefix=${prefix-/tools/gcc} --enable-languages=c,c++  --disable-nls --disable-gnu-unique-object --enable-__cxa_atexit --with-arch-32=pentiumpro --disable-initfini-array
   make $make_flags
   make $make_flags install DESTDIR=$root_dir
 
   cd $root_dir/tools
   ln -s gcc gcc/bin/cc
   tar caf $root_dir/gcc.tar.xz gcc/
   popd
 }
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -148,16 +148,20 @@ DOMInterfaces = {
     'headerFile': 'mozilla/dom/WorkerPrivate.h',
     'nativeType': 'mozilla::dom::workers::ChromeWorkerPrivate',
 },
 
 'console': {
     'nativeType': 'mozilla::dom::Console',
 },
 
+'ConsoleInstance': {
+    'implicitJSContext': ['clear', 'count', 'groupEnd', 'time', 'timeEnd'],
+},
+
 'ConvolverNode': {
     'implicitJSContext': [ 'buffer' ],
 },
 
 'Coordinates': {
     'headerFile': 'nsGeoPosition.h'
 },
 
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1145,22 +1145,22 @@ WebGLContext::ClearBackbufferIfNeeded()
     ClearScreen();
 
     mBackbufferNeedsClear = false;
 }
 
 void
 WebGLContext::LoseOldestWebGLContextIfLimitExceeded()
 {
-    const size_t maxWebGLContexts = gfxPrefs::WebGLMaxContexts();
-    size_t maxWebGLContextsPerPrincipal = gfxPrefs::WebGLMaxContextsPerPrincipal();
+    const auto maxWebGLContexts = gfxPrefs::WebGLMaxContexts();
+    auto maxWebGLContextsPerPrincipal = gfxPrefs::WebGLMaxContextsPerPrincipal();
 
     // maxWebGLContextsPerPrincipal must be less than maxWebGLContexts
-    MOZ_ASSERT(maxWebGLContextsPerPrincipal < maxWebGLContexts);
-    maxWebGLContextsPerPrincipal = std::min(maxWebGLContextsPerPrincipal, maxWebGLContexts - 1);
+    MOZ_ASSERT(maxWebGLContextsPerPrincipal <= maxWebGLContexts);
+    maxWebGLContextsPerPrincipal = std::min(maxWebGLContextsPerPrincipal, maxWebGLContexts);
 
     if (!NS_IsMainThread()) {
         // XXX mtseng: bug 709490, WebGLMemoryTracker is not thread safe.
         return;
     }
 
     // it's important to update the index on a new context before losing old contexts,
     // otherwise new unused contexts would all have index 0 and we couldn't distinguish older ones
@@ -1215,22 +1215,22 @@ WebGLContext::LoseOldestWebGLContextIfLi
             if (contexts[i]->mLastUseIndex < oldestIndexThisPrincipal) {
                 oldestIndexThisPrincipal = contexts[i]->mLastUseIndex;
                 oldestContextThisPrincipal = contexts[i];
             }
         }
     }
 
     if (numContextsThisPrincipal > maxWebGLContextsPerPrincipal) {
-        GenerateWarning("Exceeded %zu live WebGL contexts for this principal, losing the "
+        GenerateWarning("Exceeded %u live WebGL contexts for this principal, losing the "
                         "least recently used one.", maxWebGLContextsPerPrincipal);
         MOZ_ASSERT(oldestContextThisPrincipal); // if we reach this point, this can't be null
         const_cast<WebGLContext*>(oldestContextThisPrincipal)->LoseContext();
     } else if (numContexts > maxWebGLContexts) {
-        GenerateWarning("Exceeded %zu live WebGL contexts, losing the least "
+        GenerateWarning("Exceeded %u live WebGL contexts, losing the least "
                         "recently used one.", maxWebGLContexts);
         MOZ_ASSERT(oldestContext); // if we reach this point, this can't be null
         const_cast<WebGLContext*>(oldestContext)->LoseContext();
     }
 }
 
 UniquePtr<uint8_t[]>
 WebGLContext::GetImageBuffer(int32_t* out_format)
--- a/dom/console/Console.cpp
+++ b/dom/console/Console.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/Console.h"
+#include "mozilla/dom/ConsoleInstance.h"
 #include "mozilla/dom/ConsoleBinding.h"
 
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/Performance.h"
 #include "mozilla/dom/ScriptSettings.h"
@@ -658,19 +659,21 @@ private:
 
   RefPtr<ConsoleCallData> mCallData;
 };
 
 // This runnable calls ProfileMethod() on the console on the main-thread.
 class ConsoleProfileRunnable final : public ConsoleRunnable
 {
 public:
-  ConsoleProfileRunnable(Console* aConsole, const nsAString& aAction,
+  ConsoleProfileRunnable(Console* aConsole, Console::MethodName aName,
+                         const nsAString& aAction,
                          const Sequence<JS::Value>& aArguments)
     : ConsoleRunnable(aConsole)
+    , mName(aName)
     , mAction(aAction)
     , mArguments(aArguments)
   {
     MOZ_ASSERT(aConsole);
   }
 
 private:
   bool
@@ -741,23 +744,24 @@ private:
         return;
       }
 
       if (!arguments.AppendElement(value, fallible)) {
         return;
       }
     }
 
-    mConsole->ProfileMethodInternal(aCx, mAction, arguments);
+    mConsole->ProfileMethodInternal(aCx, mName, mAction, arguments);
   }
 
   virtual void
   ReleaseData() override
   {}
 
+  Console::MethodName mName;
   nsString mAction;
 
   // This is a reference of the sequence of arguments we receive from the DOM
   // bindings and it's rooted by them. It's only used on the owning thread in
   // PreDispatch().
   const Sequence<JS::Value>& mArguments;
 };
 
@@ -765,22 +769,24 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
 
 // We don't need to traverse/unlink mStorage and mSandbox because they are not
 // CCed objects and they are only used on the main thread, even when this
 // Console object is used on workers.
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDumpFunction)
   tmp->Shutdown();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDumpFunction)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
   for (uint32_t i = 0; i < tmp->mCallDataStorage.Length(); ++i) {
     tmp->mCallDataStorage[i]->Trace(aCallbacks, aClosure);
   }
 
   for (uint32_t i = 0; i < tmp->mCallDataStoragePending.Length(); ++i) {
@@ -811,16 +817,19 @@ Console::Create(nsPIDOMWindowInner* aWin
 
   return console.forget();
 }
 
 Console::Console(nsPIDOMWindowInner* aWindow)
   : mWindow(aWindow)
   , mOuterID(0)
   , mInnerID(0)
+  , mDumpToStdout(false)
+  , mChromeInstance(false)
+  , mMaxLogLevel(ConsoleLogLevel::All)
   , mStatus(eUnknown)
   , mCreationTimeStamp(TimeStamp::Now())
 {
   if (mWindow) {
     mInnerID = mWindow->WindowID();
 
     // Without outerwindow any console message coming from this object will not
     // shown in the devtools webconsole. But this should be fine because
@@ -945,30 +954,32 @@ METHOD(Log, "log")
 METHOD(Info, "info")
 METHOD(Warn, "warn")
 METHOD(Error, "error")
 METHOD(Exception, "exception")
 METHOD(Debug, "debug")
 METHOD(Table, "table")
 METHOD(Trace, "trace")
 
+// Displays an interactive listing of all the properties of an object.
+METHOD(Dir, "dir");
+METHOD(Dirxml, "dirxml");
+
+METHOD(Group, "group")
+METHOD(GroupCollapsed, "groupCollapsed")
+
+#undef METHOD
+
 /* static */ void
 Console::Clear(const GlobalObject& aGlobal)
 {
   const Sequence<JS::Value> data;
   Method(aGlobal, MethodClear, NS_LITERAL_STRING("clear"), data);
 }
 
-// Displays an interactive listing of all the properties of an object.
-METHOD(Dir, "dir");
-METHOD(Dirxml, "dirxml");
-
-METHOD(Group, "group")
-METHOD(GroupCollapsed, "groupCollapsed")
-
 /* static */ void
 Console::GroupEnd(const GlobalObject& aGlobal)
 {
   const Sequence<JS::Value> data;
   Method(aGlobal, MethodGroupEnd, NS_LITERAL_STRING("groupEnd"), data);
 }
 
 /* static */ void
@@ -982,33 +993,45 @@ Console::TimeEnd(const GlobalObject& aGl
 {
   StringMethod(aGlobal, aLabel, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"));
 }
 
 /* static */ void
 Console::StringMethod(const GlobalObject& aGlobal, const nsAString& aLabel,
                       MethodName aMethodName, const nsAString& aMethodString)
 {
-  JSContext* cx = aGlobal.Context();
-
-  ClearException ce(cx);
+  RefPtr<Console> console = GetConsole(aGlobal);
+  if (!console) {
+    return;
+  }
+
+  console->StringMethodInternal(aGlobal.Context(), aLabel, aMethodName,
+                                aMethodString);
+}
+
+void
+Console::StringMethodInternal(JSContext* aCx, const nsAString& aLabel,
+                              MethodName aMethodName,
+                              const nsAString& aMethodString)
+{
+  ClearException ce(aCx);
 
   Sequence<JS::Value> data;
-  SequenceRooter<JS::Value> rooter(cx, &data);
-
-  JS::Rooted<JS::Value> value(cx);
-  if (!dom::ToJSValue(cx, aLabel, &value)) {
+  SequenceRooter<JS::Value> rooter(aCx, &data);
+
+  JS::Rooted<JS::Value> value(aCx);
+  if (!dom::ToJSValue(aCx, aLabel, &value)) {
     return;
   }
 
   if (!data.AppendElement(value, fallible)) {
     return;
   }
 
-  Method(aGlobal, aMethodName, aMethodString, data);
+  MethodInternal(aCx, aMethodName, aMethodString, data);
 }
 
 /* static */ void
 Console::TimeStamp(const GlobalObject& aGlobal,
                    const JS::Handle<JS::Value> aData)
 {
   JSContext* cx = aGlobal.Context();
 
@@ -1022,52 +1045,72 @@ Console::TimeStamp(const GlobalObject& a
   }
 
   Method(aGlobal, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data);
 }
 
 /* static */ void
 Console::Profile(const GlobalObject& aGlobal, const Sequence<JS::Value>& aData)
 {
-  ProfileMethod(aGlobal, NS_LITERAL_STRING("profile"), aData);
+  ProfileMethod(aGlobal, MethodProfile, NS_LITERAL_STRING("profile"), aData);
 }
 
 /* static */ void
 Console::ProfileEnd(const GlobalObject& aGlobal,
                     const Sequence<JS::Value>& aData)
 {
-  ProfileMethod(aGlobal, NS_LITERAL_STRING("profileEnd"), aData);
+  ProfileMethod(aGlobal, MethodProfileEnd, NS_LITERAL_STRING("profileEnd"),
+                aData);
 }
 
 /* static */ void
-Console::ProfileMethod(const GlobalObject& aGlobal, const nsAString& aAction,
+Console::ProfileMethod(const GlobalObject& aGlobal, MethodName aName,
+                       const nsAString& aAction,
                        const Sequence<JS::Value>& aData)
 {
   RefPtr<Console> console = GetConsole(aGlobal);
   if (!console) {
     return;
   }
 
   JSContext* cx = aGlobal.Context();
-  console->ProfileMethodInternal(cx, aAction, aData);
+  console->ProfileMethodInternal(cx, aName, aAction, aData);
+}
+
+bool
+Console::IsEnabled(JSContext* aCx) const
+{
+  // Console is always enabled if it is a custom Chrome-Only instance.
+  if (mChromeInstance) {
+    return true;
+  }
+
+  // Make all Console API no-op if DevTools aren't enabled.
+  return nsContentUtils::DevToolsEnabled(aCx);
 }
 
 void
-Console::ProfileMethodInternal(JSContext* aCx, const nsAString& aAction,
+Console::ProfileMethodInternal(JSContext* aCx, MethodName aMethodName,
+                               const nsAString& aAction,
                                const Sequence<JS::Value>& aData)
 {
-  // Make all Console API no-op if DevTools aren't enabled.
-  if (!nsContentUtils::DevToolsEnabled(aCx)) {
+  if (!IsEnabled(aCx)) {
     return;
   }
 
+  if (!ShouldProceed(aMethodName)) {
+    return;
+  }
+
+  MaybeExecuteDumpFunction(aCx, aAction, aData);
+
   if (!NS_IsMainThread()) {
     // Here we are in a worker thread.
     RefPtr<ConsoleProfileRunnable> runnable =
-      new ConsoleProfileRunnable(this, aAction, aData);
+      new ConsoleProfileRunnable(this, aMethodName, aAction, aData);
 
     runnable->Dispatch(aCx);
     return;
   }
 
   ClearException ce(aCx);
 
   RootedDictionary<ConsoleProfileEvent> event(aCx);
@@ -1202,20 +1245,24 @@ Console::Method(const GlobalObject& aGlo
                           aData);
 }
 
 void
 Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
                         const nsAString& aMethodString,
                         const Sequence<JS::Value>& aData)
 {
-  // Make all Console API no-op if DevTools aren't enabled.
-  if (!nsContentUtils::DevToolsEnabled(aCx)) {
+  if (!IsEnabled(aCx)) {
     return;
   }
+
+  if (!ShouldProceed(aMethodName)) {
+    return;
+  }
+
   AssertIsOnOwningThread();
 
   RefPtr<ConsoleCallData> callData(new ConsoleCallData());
 
   ClearException ce(aCx);
 
   if (NS_WARN_IF(!callData->Initialize(aCx, aMethodName, aMethodString,
                                        aData, this))) {
@@ -1316,19 +1363,29 @@ Console::MethodInternal(JSContext* aCx, 
 
   else if (aMethodName == MethodCount) {
     callData->mCountValue = IncreaseCounter(aCx, aData, callData->mCountLabel);
     if (!callData->mCountValue) {
       return;
     }
   }
 
+  // Before processing this CallData differently, it's time to call the dump
+  // function.
+  if (aMethodName == MethodTrace) {
+    MaybeExecuteDumpFunctionForTrace(aCx, stack);
+  } else {
+    MaybeExecuteDumpFunction(aCx, aMethodString, aData);
+  }
+
   if (NS_IsMainThread()) {
     if (mWindow) {
       callData->SetIDs(mOuterID, mInnerID);
+    } else if (!mPassedInnerID.IsEmpty()) {
+      callData->SetIDs(NS_LITERAL_STRING("jsm"), mPassedInnerID);
     } else {
       nsAutoString filename;
       if (callData->mTopStackFrame.isSome()) {
         filename = callData->mTopStackFrame->mFilename;
       }
 
       callData->SetIDs(NS_LITERAL_STRING("jsm"), filename);
     }
@@ -1487,16 +1544,17 @@ Console::PopulateConsoleNotificationInTh
     event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
   } else {
     // aData->mIDType can be eUnknown when we dispatch notifications via
     // mConsoleEventNotifier.
     event.mID.Value().SetAsUnsignedLongLong() = 0;
     event.mInnerID.Value().SetAsUnsignedLongLong() = 0;
   }
 
+  event.mConsoleID = mConsoleID;
   event.mLevel = aData->mMethodString;
   event.mFilename = frame.mFilename;
 
   nsCOMPtr<nsIURI> filenameURI;
   nsAutoCString pass;
   if (NS_IsMainThread() &&
       NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
       NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
@@ -2532,10 +2590,202 @@ Console::MonotonicTimer(JSContext* aCx, 
 
   WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(workerPrivate);
 
   *aTimeStamp = workerPrivate->TimeStampToDOMHighRes(TimeStamp::Now());
   return true;
 }
 
+/* static */ already_AddRefed<ConsoleInstance>
+Console::CreateInstance(const GlobalObject& aGlobal,
+                        const ConsoleInstanceOptions& aOptions)
+{
+  RefPtr<ConsoleInstance> console = new ConsoleInstance(aOptions);
+  return console.forget();
+}
+
+void
+Console::MaybeExecuteDumpFunction(JSContext* aCx,
+                                  const nsAString& aMethodName,
+                                  const Sequence<JS::Value>& aData)
+{
+  if (!mDumpFunction && !mDumpToStdout) {
+    return;
+  }
+
+  nsAutoString message;
+  message.AssignLiteral("console.");
+  message.Append(aMethodName);
+  message.AppendLiteral(": ");
+
+  if (!mDumpPrefix.IsEmpty()) {
+    message.Append(mDumpPrefix);
+    message.AppendLiteral(": ");
+  }
+
+  for (uint32_t i = 0; i < aData.Length(); ++i) {
+    JS::Rooted<JS::Value> v(aCx, aData[i]);
+    JS::Rooted<JSString*> jsString(aCx, JS_ValueToSource(aCx, v));
+    if (!jsString) {
+      continue;
+    }
+
+    nsAutoJSString string;
+    if (NS_WARN_IF(!string.init(aCx, jsString))) {
+      return;
+    }
+
+    if (i != 0) {
+      message.AppendLiteral(" ");
+    }
+
+    message.Append(string);
+  }
+
+  message.AppendLiteral("\n");
+  ExecuteDumpFunction(message);
+}
+
+void
+Console::MaybeExecuteDumpFunctionForTrace(JSContext* aCx, nsIStackFrame* aStack)
+{
+  if (!aStack || (!mDumpFunction && !mDumpToStdout)) {
+    return;
+  }
+
+  nsAutoString message;
+  message.AssignLiteral("console.trace:\n");
+
+  if (!mDumpPrefix.IsEmpty()) {
+    message.Append(mDumpPrefix);
+    message.AppendLiteral(": ");
+  }
+
+  nsCOMPtr<nsIStackFrame> stack(aStack);
+
+  while (stack) {
+    nsAutoString filename;
+    nsresult rv = stack->GetFilename(aCx, filename);
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    message.Append(filename);
+    message.AppendLiteral(" ");
+
+    int32_t lineNumber;
+    rv = stack->GetLineNumber(aCx, &lineNumber);
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    message.AppendInt(lineNumber);
+    message.AppendLiteral(" ");
+
+    nsAutoString functionName;
+    rv = stack->GetName(aCx, functionName);
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    message.Append(filename);
+    message.AppendLiteral("\n");
+
+    nsCOMPtr<nsIStackFrame> caller;
+    rv = stack->GetCaller(aCx, getter_AddRefs(caller));
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    if (!caller) {
+      rv = stack->GetAsyncCaller(aCx, getter_AddRefs(caller));
+      NS_ENSURE_SUCCESS_VOID(rv);
+    }
+
+    stack.swap(caller);
+  }
+
+  message.AppendLiteral("\n");
+  ExecuteDumpFunction(message);
+}
+
+void
+Console::ExecuteDumpFunction(const nsAString& aMessage)
+{
+  if (mDumpFunction) {
+    IgnoredErrorResult rv;
+    mDumpFunction->Call(aMessage, rv);
+    return;
+  }
+
+  NS_ConvertUTF16toUTF8 str(aMessage);
+  MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("%s", str.get()));
+#ifdef ANDROID
+  __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", str.get());
+#endif
+  fputs(str.get(), stdout);
+  fflush(stdout);
+}
+
+bool
+Console::ShouldProceed(MethodName aName) const
+{
+  return WebIDLLogLevelToInteger(mMaxLogLevel) <=
+           InternalLogLevelToInteger(aName);
+}
+
+uint32_t
+Console::WebIDLLogLevelToInteger(ConsoleLogLevel aLevel) const
+{
+  switch (aLevel) {
+    case ConsoleLogLevel::All: return 0;
+    case ConsoleLogLevel::Debug: return 2;
+    case ConsoleLogLevel::Log: return 3;
+    case ConsoleLogLevel::Info: return 3;
+    case ConsoleLogLevel::Clear: return 3;
+    case ConsoleLogLevel::Trace: return 3;
+    case ConsoleLogLevel::TimeEnd: return 3;
+    case ConsoleLogLevel::Time: return 3;
+    case ConsoleLogLevel::Group: return 3;
+    case ConsoleLogLevel::GroupEnd: return 3;
+    case ConsoleLogLevel::Profile: return 3;
+    case ConsoleLogLevel::ProfileEnd: return 3;
+    case ConsoleLogLevel::Dir: return 3;
+    case ConsoleLogLevel::Dirxml: return 3;
+    case ConsoleLogLevel::Warn: return 4;
+    case ConsoleLogLevel::Error: return 5;
+    case ConsoleLogLevel::Off: return UINT32_MAX;
+    default:
+      MOZ_CRASH("ConsoleLogLevel is out of sync with the Console implementation!");
+      return 0;
+  }
+
+  return 0;
+}
+
+uint32_t
+Console::InternalLogLevelToInteger(MethodName aName) const
+{
+  switch (aName) {
+    case MethodLog: return 3;
+    case MethodInfo: return 3;
+    case MethodWarn: return 4;
+    case MethodError: return 5;
+    case MethodException: return 5;
+    case MethodDebug: return 2;
+    case MethodTable: return 3;
+    case MethodTrace: return 3;
+    case MethodDir: return 3;
+    case MethodDirxml: return 3;
+    case MethodGroup: return 3;
+    case MethodGroupCollapsed: return 3;
+    case MethodGroupEnd: return 3;
+    case MethodTime: return 3;
+    case MethodTimeEnd: return 3;
+    case MethodTimeStamp: return 3;
+    case MethodAssert: return 3;
+    case MethodCount: return 3;
+    case MethodClear: return 3;
+    case MethodProfile: return 3;
+    case MethodProfileEnd: return 3;
+    default:
+      MOZ_CRASH("MethodName is out of sync with the Console implementation!");
+      return 0;
+  }
+
+  return 0;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/console/Console.h
+++ b/dom/console/Console.h
@@ -2,41 +2,41 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef mozilla_dom_Console_h
 #define mozilla_dom_Console_h
 
-#include "mozilla/dom/BindingDeclarations.h"
-#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/ConsoleBinding.h"
 #include "mozilla/JSObjectHolder.h"
 #include "mozilla/TimeStamp.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsPIDOMWindow.h"
 
 class nsIConsoleAPIStorage;
 class nsIPrincipal;
+class nsIStackFrame;
 
 namespace mozilla {
 namespace dom {
 
 class AnyCallback;
 class ConsoleCallData;
+class ConsoleInstance;
+class ConsoleInstanceDumpCallback;
 class ConsoleRunnable;
 class ConsoleCallDataRunnable;
 class ConsoleProfileRunnable;
-struct ConsoleTimerError;
-struct ConsoleStackEntry;
 
 class Console final : public nsIObserver
                     , public nsSupportsWeakReference
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Console, nsIObserver)
   NS_DECL_NSIOBSERVER
@@ -109,16 +109,20 @@ public:
          const Sequence<JS::Value>& aData);
 
   static void
   Count(const GlobalObject& aGlobal, const nsAString& aLabel);
 
   static void
   Clear(const GlobalObject& aGlobal);
 
+  static already_AddRefed<ConsoleInstance>
+  CreateInstance(const GlobalObject& aGlobal,
+                 const ConsoleInstanceOptions& aOptions);
+
   void
   ClearStorage();
 
   void
   RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
                         ErrorResult& aRv);
 
   void
@@ -149,45 +153,52 @@ private:
     MethodGroup,
     MethodGroupCollapsed,
     MethodGroupEnd,
     MethodTime,
     MethodTimeEnd,
     MethodTimeStamp,
     MethodAssert,
     MethodCount,
-    MethodClear
+    MethodClear,
+    MethodProfile,
+    MethodProfileEnd,
   };
 
   static already_AddRefed<Console>
   GetConsole(const GlobalObject& aGlobal);
 
   static already_AddRefed<Console>
   GetConsoleInternal(const GlobalObject& aGlobal, ErrorResult &aRv);
 
   static void
-  ProfileMethod(const GlobalObject& aGlobal, const nsAString& aAction,
-                const Sequence<JS::Value>& aData);
+  ProfileMethod(const GlobalObject& aGlobal, MethodName aName,
+                const nsAString& aAction, const Sequence<JS::Value>& aData);
 
   void
-  ProfileMethodInternal(JSContext* aCx, const nsAString& aAction,
+  ProfileMethodInternal(JSContext* aCx, MethodName aName,
+                        const nsAString& aAction,
                         const Sequence<JS::Value>& aData);
 
   static void
   Method(const GlobalObject& aGlobal, MethodName aName,
          const nsAString& aString, const Sequence<JS::Value>& aData);
 
   void
   MethodInternal(JSContext* aCx, MethodName aName,
                  const nsAString& aString, const Sequence<JS::Value>& aData);
 
   static void
   StringMethod(const GlobalObject& aGlobal, const nsAString& aLabel,
                MethodName aMethodName, const nsAString& aMethodString);
 
+  void
+  StringMethodInternal(JSContext* aCx, const nsAString& aLabel,
+                       MethodName aMethodName, const nsAString& aMethodString);
+
   // This method must receive aCx and aArguments in the same JSCompartment.
   void
   ProcessCallData(JSContext* aCx,
                   ConsoleCallData* aData,
                   const Sequence<JS::Value>& aArguments);
 
   void
   StoreCallData(ConsoleCallData* aData);
@@ -371,16 +382,38 @@ private:
   bool
   IsShuttingDown() const;
 
   bool
   MonotonicTimer(JSContext* aCx, MethodName aMethodName,
                  const Sequence<JS::Value>& aData,
                  DOMHighResTimeStamp* aTimeStamp);
 
+  void
+  MaybeExecuteDumpFunction(JSContext* aCx, const nsAString& aMethodName,
+                           const Sequence<JS::Value>& aData);
+
+  void
+  MaybeExecuteDumpFunctionForTrace(JSContext* aCx, nsIStackFrame* aStack);
+
+  void
+  ExecuteDumpFunction(const nsAString& aMessage);
+
+  bool
+  IsEnabled(JSContext* aCx) const;
+
+  bool
+  ShouldProceed(MethodName aName) const;
+
+  uint32_t
+  WebIDLLogLevelToInteger(ConsoleLogLevel aLevel) const;
+
+  uint32_t
+  InternalLogLevelToInteger(MethodName aName) const;
+
   // All these nsCOMPtr are touched on main thread only.
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsCOMPtr<nsIConsoleAPIStorage> mStorage;
   RefPtr<JSObjectHolder> mSandbox;
 
   // Touched on the owner thread.
   nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry;
   nsDataHashtable<nsStringHashKey, uint32_t> mCounterRegistry;
@@ -401,27 +434,37 @@ private:
   RefPtr<AnyCallback> mConsoleEventNotifier;
 
   // This is the stack for groupping.
   nsTArray<nsString> mGroupStack;
 
   uint64_t mOuterID;
   uint64_t mInnerID;
 
+  // Set only by ConsoleInstance:
+  nsString mConsoleID;
+  nsString mPassedInnerID;
+  RefPtr<ConsoleInstanceDumpCallback> mDumpFunction;
+  bool mDumpToStdout;
+  nsString mDumpPrefix;
+  bool mChromeInstance;
+  ConsoleLogLevel mMaxLogLevel;
+
   enum {
     eUnknown,
     eInitialized,
     eShuttingDown
   } mStatus;
 
   // This is used when Console is created and it's used only for JSM custom
   // console instance.
   mozilla::TimeStamp mCreationTimeStamp;
 
   friend class ConsoleCallData;
+  friend class ConsoleInstance;
   friend class ConsoleRunnable;
   friend class ConsoleCallDataRunnable;
   friend class ConsoleProfileRunnable;
 };
 
 } // namespace dom
 } // namespace mozilla
 
new file mode 100644
--- /dev/null
+++ b/dom/console/ConsoleInstance.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ConsoleInstance.h"
+#include "mozilla/dom/ConsoleBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ConsoleInstance, mConsole)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ConsoleInstance)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ConsoleInstance)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ConsoleInstance)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END
+
+namespace {
+
+ConsoleLogLevel
+PrefToValue(const nsCString& aPref)
+{
+  if (!NS_IsMainThread()) {
+    NS_WARNING("Console.maxLogLevelPref is not supported on workers!");
+    return ConsoleLogLevel::All;
+  }
+
+  nsAutoCString value;
+  nsresult rv = Preferences::GetCString(aPref.get(), value);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return ConsoleLogLevel::All;
+  }
+
+  int index = FindEnumStringIndexImpl(value.get(), value.Length(),
+                                      ConsoleLogLevelValues::strings);
+  if (NS_WARN_IF(index < 0)) {
+    return ConsoleLogLevel::All;
+  }
+
+  MOZ_ASSERT(index < (int)ConsoleLogLevel::EndGuard_);
+  return static_cast<ConsoleLogLevel>(index);
+}
+
+} // anonymous
+
+ConsoleInstance::ConsoleInstance(const ConsoleInstanceOptions& aOptions)
+  : mConsole(new Console(nullptr))
+{
+  mConsole->mConsoleID = aOptions.mConsoleID;
+  mConsole->mPassedInnerID = aOptions.mInnerID;
+
+  if (aOptions.mDump.WasPassed()) {
+    mConsole->mDumpFunction = &aOptions.mDump.Value();
+  } else {
+    // For historical reasons, ConsoleInstance prints messages on stdout.
+    mConsole->mDumpToStdout = true;
+  }
+
+  mConsole->mDumpPrefix = aOptions.mPrefix;
+
+  // Let's inform that this is a custom instance.
+  mConsole->mChromeInstance = true;
+
+  if (aOptions.mMaxLogLevel.WasPassed()) {
+    mConsole->mMaxLogLevel = aOptions.mMaxLogLevel.Value();
+  }
+
+  if (!aOptions.mMaxLogLevelPref.IsEmpty()) {
+    mConsole->mMaxLogLevel =
+      PrefToValue(NS_ConvertUTF16toUTF8(aOptions.mMaxLogLevelPref));
+  }
+}
+
+ConsoleInstance::~ConsoleInstance()
+{}
+
+JSObject*
+ConsoleInstance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return ConsoleInstanceBinding::Wrap(aCx, this, aGivenProto);
+}
+
+#define METHOD(name, string)                                               \
+  void                                                                     \
+  ConsoleInstance::name(JSContext* aCx, const Sequence<JS::Value>& aData)  \
+  {                                                                        \
+    mConsole->MethodInternal(aCx, Console::Method##name,                   \
+                             NS_LITERAL_STRING(string), aData);            \
+  }
+
+METHOD(Log, "log")
+METHOD(Info, "info")
+METHOD(Warn, "warn")
+METHOD(Error, "error")
+METHOD(Exception, "exception")
+METHOD(Debug, "debug")
+METHOD(Table, "table")
+METHOD(Trace, "trace")
+METHOD(Dir, "dir");
+METHOD(Dirxml, "dirxml");
+METHOD(Group, "group")
+METHOD(GroupCollapsed, "groupCollapsed")
+
+#undef METHOD
+
+void
+ConsoleInstance::GroupEnd(JSContext* aCx)
+{
+  const Sequence<JS::Value> data;
+  mConsole->MethodInternal(aCx, Console::MethodGroupEnd,
+                           NS_LITERAL_STRING("groupEnd"), data);
+}
+
+void
+ConsoleInstance::Time(JSContext* aCx, const nsAString& aLabel)
+{
+  mConsole->StringMethodInternal(aCx, aLabel, Console::MethodTime,
+                                 NS_LITERAL_STRING("time"));
+}
+
+void
+ConsoleInstance::TimeEnd(JSContext* aCx, const nsAString& aLabel)
+{
+  mConsole->StringMethodInternal(aCx, aLabel, Console::MethodTimeEnd,
+                                 NS_LITERAL_STRING("timeEnd"));
+}
+
+void
+ConsoleInstance::TimeStamp(JSContext* aCx, const JS::Handle<JS::Value> aData)
+{
+  ClearException ce(aCx);
+
+  Sequence<JS::Value> data;
+  SequenceRooter<JS::Value> rooter(aCx, &data);
+
+  if (aData.isString() && !data.AppendElement(aData, fallible)) {
+    return;
+  }
+
+  mConsole->MethodInternal(aCx, Console::MethodTimeStamp,
+                           NS_LITERAL_STRING("timeStamp"), data);
+}
+
+void
+ConsoleInstance::Profile(JSContext* aCx, const Sequence<JS::Value>& aData)
+{
+  mConsole->ProfileMethodInternal(aCx, Console::MethodProfile,
+                                  NS_LITERAL_STRING("profile"), aData);
+}
+
+void
+ConsoleInstance::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
+{
+  mConsole->ProfileMethodInternal(aCx, Console::MethodProfileEnd,
+                                  NS_LITERAL_STRING("profileEnd"), aData);
+}
+
+void
+ConsoleInstance::Assert(JSContext* aCx, bool aCondition,
+                        const Sequence<JS::Value>& aData)
+{
+  if (!aCondition) {
+    mConsole->MethodInternal(aCx, Console::MethodAssert,
+                             NS_LITERAL_STRING("assert"), aData);
+  }
+}
+
+void
+ConsoleInstance::Count(JSContext* aCx, const nsAString& aLabel)
+{
+  mConsole->StringMethodInternal(aCx, aLabel, Console::MethodCount,
+                                 NS_LITERAL_STRING("count"));
+}
+
+void
+ConsoleInstance::Clear(JSContext* aCx)
+{
+  const Sequence<JS::Value> data;
+  mConsole->MethodInternal(aCx, Console::MethodClear,
+                           NS_LITERAL_STRING("clear"), data);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/console/ConsoleInstance.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_ConsoleInstance_h
+#define mozilla_dom_ConsoleInstance_h
+
+#include "mozilla/dom/Console.h"
+
+
+namespace mozilla {
+namespace dom {
+
+class ConsoleInstance final : public nsISupports
+                            , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ConsoleInstance)
+
+  explicit ConsoleInstance(const ConsoleInstanceOptions& aOptions);
+
+  // WebIDL methods
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  nsPIDOMWindowInner* GetParentObject() const
+  {
+    return nullptr;
+  }
+
+  void
+  Log(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Info(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Warn(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Error(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Exception(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Debug(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Table(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Trace(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Dir(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Dirxml(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Group(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  GroupCollapsed(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  GroupEnd(JSContext* aCx);
+
+  void
+  Time(JSContext* aCx, const nsAString& aLabel);
+
+  void
+  TimeEnd(JSContext* aCx, const nsAString& aLabel);
+
+  void
+  TimeStamp(JSContext* aCx, const JS::Handle<JS::Value> aData);
+
+  void
+  Profile(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Assert(JSContext* aCx, bool aCondition, const Sequence<JS::Value>& aData);
+
+  void
+  Count(JSContext* aCx, const nsAString& aLabel);
+
+  void
+  Clear(JSContext* aCx);
+
+private:
+  ~ConsoleInstance();
+
+  RefPtr<Console> mConsole;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_ConsoleInstance_h
--- a/dom/console/moz.build
+++ b/dom/console/moz.build
@@ -18,20 +18,22 @@ EXPORTS += [
 ]
 
 EXPORTS.mozilla += [
     'ConsoleReportCollector.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'Console.h',
+    'ConsoleInstance.h',
 ]
 
 UNIFIED_SOURCES += [
     'Console.cpp',
+    'ConsoleInstance.cpp',
     'ConsoleReportCollector.cpp',
 ]
 
 EXTRA_COMPONENTS += [
     'ConsoleAPI.manifest',
     'ConsoleAPIStorage.js',
 ]
 
--- a/dom/console/tests/console.jsm
+++ b/dom/console/tests/console.jsm
@@ -1,11 +1,27 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 this.EXPORTED_SYMBOLS = [ "ConsoleTest" ];
 
 this.ConsoleTest = {
-  go: function() {
+  go: function(dumpFunction) {
     console.log("Hello world!");
+    console.createInstance().log("Hello world!");
+
+    let c = console.createInstance({
+      consoleID: "wow",
+      innerID: "CUSTOM INNER",
+      dump: dumpFunction,
+      prefix: "_PREFIX_",
+    });
+
+    c.log("Hello world!");
+    c.trace("Hello world!");
+
+    console.createInstance({ innerID: "LEVEL", maxLogLevel: "off" }).log("Invisible!");
+    console.createInstance({ innerID: "LEVEL",  maxLogLevel: "all" }).log("Hello world!");
+    console.createInstance({ innerID: "LEVEL", maxLogLevelPref: "foo.pref" }).log("Invisible!");
+    console.createInstance({ innerID: "LEVEL", maxLogLevelPref: "pref.test.console" }).log("Hello world!");
   }
 };
--- a/dom/console/tests/test_jsm.xul
+++ b/dom/console/tests/test_jsm.xul
@@ -12,40 +12,69 @@
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
 
   <script type="application/javascript">
   <![CDATA[
 
 const JSM = "chrome://mochitests/content/chrome/dom/console/tests/console.jsm";
 
+let dumpCalled = 0;
+function dumpFunction(msg) {
+  ok(msg.indexOf("_PREFIX_") != -1, "we have a prefix");
+  dump("Message received: " + msg); // Just for debugging
+  dumpCalled++;
+}
+
 function consoleListener() {
   SpecialPowers.addObserver(this, "console-api-log-event");
 }
 
 consoleListener.prototype  = {
+  count: 0,
+
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "console-api-log-event") {
       var obj = aSubject.wrappedJSObject;
       if (obj.innerID == JSM) {
         is(obj.ID, "jsm", "ID and InnerID are correctly set.");
-        is (obj.arguments[0], "Hello world!", "Message matches");
+        is(obj.arguments[0], "Hello world!", "Message matches");
+        is(obj.consoleID, "", "No consoleID for console API");
 
+        // We want to see 2 messages from this innerID, the first is generated
+        // by console.log, the second one from createInstance().log();
+        ++this.count;
+      } else if (obj.innerID == "CUSTOM INNER") {
+        is(obj.ID, "jsm", "ID and InnerID are correctly set.");
+        is(obj.arguments[0], "Hello world!", "Message matches");
+        is(obj.consoleID, "wow", "consoleID is set by consoleInstance");
+        ++this.count;
+      } else if (obj.innerID == "LEVEL") {
+        // Nothing special... just we don't want to see 'invisible' messages.
+        is(obj.ID, "jsm", "ID and InnerID are correctly set.");
+        is(obj.arguments[0], "Hello world!", "Message matches");
+        ++this.count;
+      }
+
+      if (this.count == 4) {
+        is(dumpCalled, 2, "Dump has been called!");
         SpecialPowers.removeObserver(this, "console-api-log-event");
         SimpleTest.finish();
       }
     }
   }
 }
 function test() {
   SimpleTest.waitForExplicitFinish();
 
-  var cl = new consoleListener();
-  Components.utils.import(JSM);
-  ConsoleTest.go();
+  SpecialPowers.pushPrefEnv({set: [["pref.test.console", "log"]]}).then(() => {
+    var cl = new consoleListener();
+    Components.utils.import(JSM);
+    ConsoleTest.go(dumpFunction);
+  });
 }
 
   ]]>
   </script>
 
   <body xmlns="http://www.w3.org/1999/xhtml">
   </body>
 </window>
--- a/dom/webidl/Console.webidl
+++ b/dom/webidl/Console.webidl
@@ -7,16 +7,20 @@
  * For more information on this interface, please see
  * https://console.spec.whatwg.org/#console-namespace
  */
 
 [Exposed=(Window,Worker,WorkerDebugger,Worklet,System),
  ClassString="Console",
  ProtoObjectHack]
 namespace console {
+
+  // NOTE: if you touch this namespace, remember to update the ConsoleInstance
+  // interface as well!
+
   // Logging
   void assert(optional boolean condition = false, any... data);
   void clear();
   void count(optional DOMString label = "default");
   void debug(any... data);
   void error(any... data);
   void info(any... data);
   void log(any... data);
@@ -40,22 +44,26 @@ namespace console {
   void _exception(any... data);
   void timeStamp(optional any data);
 
   void profile(any... data);
   void profileEnd(any... data);
 
   [ChromeOnly]
   const boolean IS_NATIVE_CONSOLE = true;
+
+  [ChromeOnly, NewObject]
+  ConsoleInstance createInstance(optional ConsoleInstanceOptions options);
 };
 
 // This is used to propagate console events to the observers.
 dictionary ConsoleEvent {
   (unsigned long long or DOMString) ID;
   (unsigned long long or DOMString) innerID;
+  DOMString consoleID = "";
   DOMString addonId = "";
   DOMString level = "";
   DOMString filename = "";
   unsigned long lineNumber = 0;
   unsigned long columnNumber = 0;
   DOMString functionName = "";
   double timeStamp = 0;
   sequence<any> arguments;
@@ -104,8 +112,77 @@ dictionary ConsoleTimerError {
 dictionary ConsoleCounter {
   DOMString label = "";
   unsigned long count = 0;
 };
 
 dictionary ConsoleCounterError {
   DOMString error = "maxCountersExceeded";
 };
+
+[ChromeOnly,
+ Exposed=(Window,Worker,WorkerDebugger,Worklet,System)]
+// This is basically a copy of the console namespace.
+interface ConsoleInstance {
+  // Logging
+  void assert(optional boolean condition = false, any... data);
+  void clear();
+  void count(optional DOMString label = "default");
+  void debug(any... data);
+  void error(any... data);
+  void info(any... data);
+  void log(any... data);
+  void table(any... data); // FIXME: The spec is still unclear about this.
+  void trace(any... data);
+  void warn(any... data);
+  void dir(any... data); // FIXME: This doesn't follow the spec yet.
+  void dirxml(any... data);
+
+  // Grouping
+  void group(any... data);
+  void groupCollapsed(any... data);
+  void groupEnd();
+
+  // Timing
+  void time(optional DOMString label = "default");
+  void timeEnd(optional DOMString label = "default");
+
+  // Mozilla only or Webcompat methods
+
+  void _exception(any... data);
+  void timeStamp(optional any data);
+
+  void profile(any... data);
+  void profileEnd(any... data);
+};
+
+callback ConsoleInstanceDumpCallback = void (DOMString message);
+
+enum ConsoleLogLevel {
+  "all", "debug", "log", "info", "clear", "trace", "timeEnd", "time", "group",
+  "groupEnd", "profile", "profileEnd", "dir", "dirxml", "warn", "error", "off"
+};
+
+dictionary ConsoleInstanceOptions {
+  // An optional function to intercept all strings written to stdout.
+  ConsoleInstanceDumpCallback dump;
+
+  // An optional prefix string to be printed before the actual logged message.
+  DOMString prefix = "";
+
+  // An ID representing the source of the message. Normally the inner ID of a
+  // DOM window.
+  DOMString innerID = "";
+
+  // String identified for the console, this will be passed through the console
+  // notifications.
+  DOMString consoleID = "";
+
+  // Identifier that allows to filter which messages are logged based on their
+  // log level.
+  ConsoleLogLevel maxLogLevel;
+
+  // String pref name which contains the level to use for maxLogLevel. If the
+  // pref doesn't exist, gets removed or it is used in workers, the maxLogLevel
+  // will default to the value passed to this constructor (or "all" if it wasn't
+  // specified).
+  DOMString maxLogLevelPref = "";
+};
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -741,18 +741,18 @@ private:
   DECL_GFX_PREF(Live, "webgl.enable-draft-extensions",         WebGLDraftExtensionsEnabled, bool, false);
   DECL_GFX_PREF(Live, "webgl.enable-privileged-extensions",    WebGLPrivilegedExtensionsEnabled, bool, false);
   DECL_GFX_PREF(Live, "webgl.enable-surface-texture",          WebGLSurfaceTextureEnabled, bool, false);
   DECL_GFX_PREF(Live, "webgl.enable-webgl2",                   WebGL2Enabled, bool, true);
   DECL_GFX_PREF(Live, "webgl.force-enabled",                   WebGLForceEnabled, bool, false);
   DECL_GFX_PREF(Once, "webgl.force-layers-readback",           WebGLForceLayersReadback, bool, false);
   DECL_GFX_PREF(Live, "webgl.force-index-validation",          WebGLForceIndexValidation, int32_t, 0);
   DECL_GFX_PREF(Live, "webgl.lose-context-on-memory-pressure", WebGLLoseContextOnMemoryPressure, bool, false);
-  DECL_GFX_PREF(Once, "webgl.max-contexts",                    WebGLMaxContexts, uint32_t, 32);
-  DECL_GFX_PREF(Once, "webgl.max-contexts-per-principal",      WebGLMaxContextsPerPrincipal, uint32_t, 16);
+  DECL_GFX_PREF(Live, "webgl.max-contexts",                    WebGLMaxContexts, uint32_t, 32);
+  DECL_GFX_PREF(Live, "webgl.max-contexts-per-principal",      WebGLMaxContextsPerPrincipal, uint32_t, 16);
   DECL_GFX_PREF(Live, "webgl.max-warnings-per-context",        WebGLMaxWarningsPerContext, uint32_t, 32);
   DECL_GFX_PREF(Live, "webgl.min_capability_mode",             WebGLMinCapabilityMode, bool, false);
   DECL_GFX_PREF(Live, "webgl.msaa-force",                      WebGLForceMSAA, bool, false);
   DECL_GFX_PREF(Live, "webgl.prefer-16bpp",                    WebGLPrefer16bpp, bool, false);
   DECL_GFX_PREF(Live, "webgl.restore-context-when-visible",    WebGLRestoreWhenVisible, bool, true);
   DECL_GFX_PREF(Live, "webgl.allow-immediate-queries",         WebGLImmediateQueries, bool, false);
   DECL_GFX_PREF(Live, "webgl.allow-fb-invalidation",           WebGLFBInvalidation, bool, false);
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/regress/bug1392105.js
@@ -0,0 +1,8 @@
+// |jit-test| --arm-asm-nop-fill=1
+
+var code = "(module ";
+for (var i = 0; i < 100; i++)
+  code += "(func (param i32) (result i32) (i32.add (i32.const 1) (get_local 0))) ";
+code += ")";
+var buf = wasmTextToBinary(code);
+WebAssembly.compile(buf);
--- a/mfbt/tests/TestTypeTraits.cpp
+++ b/mfbt/tests/TestTypeTraits.cpp
@@ -67,17 +67,17 @@ static_assert(IsPointer<bool**>::value,
 static_assert(IsPointer<void (*)(void)>::value,
               "void (*)(void) is a pointer");
 struct IsPointerTest { bool m; void f(); };
 static_assert(!IsPointer<IsPointerTest>::value,
               "IsPointerTest not a pointer");
 static_assert(IsPointer<IsPointerTest*>::value,
               "IsPointerTest* is a pointer");
 static_assert(!IsPointer<bool(IsPointerTest::*)()>::value,
-              "bool(IsPointerTest::*) not a pointer");
+              "bool(IsPointerTest::*)() not a pointer");
 static_assert(!IsPointer<void(IsPointerTest::*)(void)>::value,
               "void(IsPointerTest::*)(void) not a pointer");
 
 static_assert(!IsLvalueReference<bool>::value,
               "bool not an lvalue reference");
 static_assert(!IsLvalueReference<bool*>::value,
               "bool* not an lvalue reference");
 static_assert(IsLvalueReference<bool&>::value,
--- a/netwerk/base/nsITimedChannel.idl
+++ b/netwerk/base/nsITimedChannel.idl
@@ -1,22 +1,30 @@
 /* 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 "nsISupports.idl"
+interface nsIArray;
 interface nsIPrincipal;
 %{C++
 namespace mozilla {
 class TimeStamp;
 }
 %}
 
 native TimeStamp(mozilla::TimeStamp);
 
+[scriptable, uuid(c2d9e95b-9cc9-4f47-9ef6-1de0cf7ebc75)]
+interface nsIServerTiming : nsISupports {
+  [must_use] readonly attribute ACString name;
+  [must_use] readonly attribute double duration;
+  [must_use] readonly attribute ACString description;
+};
+
 // All properties return zero if the value is not available
 [scriptable, uuid(ca63784d-959c-4c3a-9a59-234a2a520de0)]
 interface nsITimedChannel : nsISupports {
   // Set this attribute to true to enable collection of timing data.
   // channelCreationTime will be available even with this attribute set to
   // false.
   attribute boolean timingEnabled;
 
@@ -95,9 +103,11 @@ interface nsITimedChannel : nsISupports 
   readonly attribute PRTime responseEndTime;
   readonly attribute PRTime cacheReadStartTime;
   readonly attribute PRTime cacheReadEndTime;
   readonly attribute PRTime redirectStartTime;
   readonly attribute PRTime redirectEndTime;
 
   // If this attribute is false, this resource MUST NOT be reported in resource timing.
   [noscript] attribute boolean reportResourceTiming;
+
+  readonly attribute nsIArray serverTiming;
 };
--- a/netwerk/protocol/http/HttpBackgroundChannelChild.cpp
+++ b/netwerk/protocol/http/HttpBackgroundChannelChild.cpp
@@ -174,19 +174,20 @@ HttpBackgroundChannelChild::RecvOnTransp
                                            aCount,
                                            aData);
 
   return IPC_OK();
 }
 
 IPCResult
 HttpBackgroundChannelChild::RecvOnStopRequest(
-                                            const nsresult& aChannelStatus,
-                                            const ResourceTimingStruct& aTiming,
-                                            const TimeStamp& aLastActiveTabOptHit)
+                                    const nsresult& aChannelStatus,
+                                    const ResourceTimingStruct& aTiming,
+                                    const TimeStamp& aLastActiveTabOptHit,
+                                    const nsHttpHeaderArray& aResponseTrailers)
 {
   LOG(("HttpBackgroundChannelChild::RecvOnStopRequest [this=%p]\n", this));
   MOZ_ASSERT(OnSocketThread());
 
   // It's enough to set this from (just before) OnStopRequest notification, since
   // we don't need this value sooner than a channel was done loading - everything
   // this timestamp affects takes place only after a channel's OnStopRequest.
   nsHttp::SetLastActiveTabLoadOptimizationHit(aLastActiveTabOptHit);
@@ -195,28 +196,32 @@ HttpBackgroundChannelChild::RecvOnStopRe
     return IPC_OK();
   }
 
   if (IsWaitingOnStartRequest()) {
     LOG(("  > pending until OnStartRequest [status=%" PRIx32 "]\n",
          static_cast<uint32_t>(aChannelStatus)));
 
     mQueuedRunnables.AppendElement(
-      NewRunnableMethod<const nsresult, const ResourceTimingStruct, const TimeStamp>(
+      NewRunnableMethod<const nsresult,
+                        const ResourceTimingStruct,
+                        const TimeStamp,
+                        const nsHttpHeaderArray>(
         "HttpBackgroundChannelChild::RecvOnStopRequest",
         this,
         &HttpBackgroundChannelChild::RecvOnStopRequest,
         aChannelStatus,
         aTiming,
-        aLastActiveTabOptHit));
+        aLastActiveTabOptHit,
+        aResponseTrailers));
 
     return IPC_OK();
   }
 
-  mChannelChild->ProcessOnStopRequest(aChannelStatus, aTiming);
+  mChannelChild->ProcessOnStopRequest(aChannelStatus, aTiming, aResponseTrailers);
 
   return IPC_OK();
 }
 
 IPCResult
 HttpBackgroundChannelChild::RecvOnProgress(const int64_t& aProgress,
                                            const int64_t& aProgressMax)
 {
--- a/netwerk/protocol/http/HttpBackgroundChannelChild.h
+++ b/netwerk/protocol/http/HttpBackgroundChannelChild.h
@@ -44,17 +44,18 @@ protected:
   IPCResult RecvOnTransportAndData(const nsresult& aChannelStatus,
                                    const nsresult& aTransportStatus,
                                    const uint64_t& aOffset,
                                    const uint32_t& aCount,
                                    const nsCString& aData) override;
 
   IPCResult RecvOnStopRequest(const nsresult& aChannelStatus,
                               const ResourceTimingStruct& aTiming,
-                              const TimeStamp& aLastActiveTabOptHit) override;
+                              const TimeStamp& aLastActiveTabOptHit,
+                              const nsHttpHeaderArray& aResponseTrailers) override;
 
   IPCResult RecvOnProgress(const int64_t& aProgress,
                            const int64_t& aProgressMax) override;
 
   IPCResult RecvOnStatus(const nsresult& aStatus) override;
 
   IPCResult RecvFlushedForDiversion() override;
 
--- a/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
@@ -215,46 +215,50 @@ HttpBackgroundChannelParent::OnTransport
   }
 
   return SendOnTransportAndData(aChannelStatus, aTransportStatus,
                                 aOffset, aCount, aData);
 }
 
 bool
 HttpBackgroundChannelParent::OnStopRequest(const nsresult& aChannelStatus,
-                                           const ResourceTimingStruct& aTiming)
+                                           const ResourceTimingStruct& aTiming,
+                                           const nsHttpHeaderArray& aResponseTrailers)
 {
   LOG(("HttpBackgroundChannelParent::OnStopRequest [this=%p "
         "status=%" PRIx32 "]\n", this, static_cast<uint32_t>(aChannelStatus)));
   AssertIsInMainProcess();
 
   if (NS_WARN_IF(!mIPCOpened)) {
     return false;
   }
 
   if (!IsOnBackgroundThread()) {
     MutexAutoLock lock(mBgThreadMutex);
     nsresult rv = mBackgroundThread->Dispatch(
-      NewRunnableMethod<const nsresult, const ResourceTimingStruct>(
+      NewRunnableMethod<const nsresult,
+                        const ResourceTimingStruct,
+                        const nsHttpHeaderArray>(
         "net::HttpBackgroundChannelParent::OnStopRequest",
         this,
         &HttpBackgroundChannelParent::OnStopRequest,
         aChannelStatus,
-        aTiming),
+        aTiming,
+        aResponseTrailers),
       NS_DISPATCH_NORMAL);
 
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
 
     return NS_SUCCEEDED(rv);
   }
 
   // See the child code for why we do this.
   TimeStamp lastActTabOpt = nsHttp::GetLastActiveTabLoadOptimizationHit();
 
-  return SendOnStopRequest(aChannelStatus, aTiming, lastActTabOpt);
+  return SendOnStopRequest(aChannelStatus, aTiming, lastActTabOpt, aResponseTrailers);
 }
 
 bool
 HttpBackgroundChannelParent::OnProgress(const int64_t& aProgress,
                                         const int64_t& aProgressMax)
 {
   LOG(("HttpBackgroundChannelParent::OnProgress [this=%p progress=%" PRId64
        " max=%" PRId64 "]\n", this, aProgress, aProgressMax));
--- a/netwerk/protocol/http/HttpBackgroundChannelParent.h
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.h
@@ -47,17 +47,18 @@ public:
   bool OnTransportAndData(const nsresult& aChannelStatus,
                           const nsresult& aTransportStatus,
                           const uint64_t& aOffset,
                           const uint32_t& aCount,
                           const nsCString& aData);
 
   // To send OnStopRequest message over background channel.
   bool OnStopRequest(const nsresult& aChannelStatus,
-                     const ResourceTimingStruct& aTiming);
+                     const ResourceTimingStruct& aTiming,
+                     const nsHttpHeaderArray& aResponseTrailers);
 
   // To send OnProgress message over background channel.
   bool OnProgress(const int64_t& aProgress,
                   const int64_t& aProgressMax);
 
   // To send OnStatus message over background channel.
   bool OnStatus(const nsresult& aStatus);
 
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -29,16 +29,17 @@
 #include "nsEscape.h"
 #include "nsStreamListenerWrapper.h"
 #include "nsISecurityConsoleMessage.h"
 #include "nsURLHelper.h"
 #include "nsICookieService.h"
 #include "nsIStreamConverterService.h"
 #include "nsCRT.h"
 #include "nsContentUtils.h"
+#include "nsIMutableArray.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIObserverService.h"
 #include "nsProxyRelease.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDocShell.h"
 #include "nsINetworkInterceptController.h"
 #include "mozilla/dom/Performance.h"
 #include "mozIThirdPartyUtil.h"
@@ -60,16 +61,17 @@
 #include "mozilla/net/PartiallySeekableInputStream.h"
 #include "nsIHttpHeaderVisitor.h"
 #include "nsIMIMEInputStream.h"
 #include "nsIXULRuntime.h"
 #include "nsICacheInfoChannel.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsHttpChannel.h"
 #include "nsRedirectHistoryEntry.h"
+#include "nsServerTiming.h"
 
 #include <algorithm>
 #include "HttpBaseChannel.h"
 
 namespace mozilla {
 namespace net {
 
 static
@@ -4445,10 +4447,51 @@ HttpBaseChannel::CallTypeSniffers(void *
 
   nsAutoCString newType;
   NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType);
   if (!newType.IsEmpty()) {
     chan->SetContentType(newType);
   }
 }
 
+template <class T>
+static void
+ParseServerTimingHeader(const nsAutoPtr<T> &aHeader,
+                        nsIMutableArray* aOutput)
+{
+  if (!aHeader) {
+    return;
+  }
+
+  nsAutoCString serverTimingHeader;
+  Unused << aHeader->GetHeader(nsHttp::Server_Timing, serverTimingHeader);
+  if (serverTimingHeader.IsEmpty()) {
+    return;
+  }
+
+  ServerTimingParser parser(serverTimingHeader);
+  parser.Parse();
+
+  nsTArray<nsCOMPtr<nsIServerTiming>> array = parser.TakeServerTimingHeaders();
+  for (const auto &data : array) {
+    aOutput->AppendElement(data);
+  }
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetServerTiming(nsIArray **aServerTiming)
+{
+  NS_ENSURE_ARG_POINTER(aServerTiming);
+
+  nsTArray<nsCOMPtr<nsIServerTiming>> data;
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  ParseServerTimingHeader(mResponseHead, array);
+  ParseServerTimingHeader(mResponseTrailers, array);
+
+  array.forget(aServerTiming);
+  return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -334,16 +334,17 @@ public:
         // header with it.
         nsCOMPtr<nsIHttpChannel> mChannel;
 
         bool mReady;
     };
 
     nsHttpResponseHead * GetResponseHead() const { return mResponseHead; }
     nsHttpRequestHead * GetRequestHead() { return &mRequestHead; }
+    nsHttpHeaderArray * GetResponseTrailers() const { return mResponseTrailers; }
 
     const NetAddr& GetSelfAddr() { return mSelfAddr; }
     const NetAddr& GetPeerAddr() { return mPeerAddr; }
 
     MOZ_MUST_USE nsresult OverrideSecurityInfo(nsISupports* aSecurityInfo);
 
 public: /* Necko internal use only... */
     int64_t GetAltDataLength() { return mAltDataLength; }
@@ -515,16 +516,17 @@ protected:
   nsCOMPtr<nsISupports>             mOwner;
 
   nsHttpRequestHead                 mRequestHead;
   // Upload throttling.
   nsCOMPtr<nsIInputChannelThrottleQueue> mThrottleQueue;
   nsCOMPtr<nsIInputStream>          mUploadStream;
   nsCOMPtr<nsIRunnable>             mUploadCloneableCallback;
   nsAutoPtr<nsHttpResponseHead>     mResponseHead;
+  nsAutoPtr<nsHttpHeaderArray>      mResponseTrailers;
   RefPtr<nsHttpConnectionInfo>      mConnectionInfo;
   nsCOMPtr<nsIProxyInfo>            mProxyInfo;
   nsCOMPtr<nsISupports>             mSecurityInfo;
 
   nsCString                         mSpec; // ASCII encoded URL spec
   nsCString                         mContentTypeHint;
   nsCString                         mContentCharsetHint;
   nsCString                         mUserSetCookieHeader;
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -974,38 +974,43 @@ HttpChannelChild::DoOnDataAvailable(nsIR
   }
 }
 
 class StopRequestEvent : public NeckoTargetChannelEvent<HttpChannelChild>
 {
  public:
   StopRequestEvent(HttpChannelChild* child,
                    const nsresult& channelStatus,
-                   const ResourceTimingStruct& timing)
+                   const ResourceTimingStruct& timing,
+                   const nsHttpHeaderArray& aResponseTrailers)
   : NeckoTargetChannelEvent<HttpChannelChild>(child)
   , mChannelStatus(channelStatus)
-  , mTiming(timing) {}
-
-  void Run() { mChild->OnStopRequest(mChannelStatus, mTiming); }
+  , mTiming(timing)
+  , mResponseTrailers(aResponseTrailers) {}
+
+  void Run() { mChild->OnStopRequest(mChannelStatus, mTiming, mResponseTrailers); }
 
  private:
   nsresult mChannelStatus;
   ResourceTimingStruct mTiming;
+  nsHttpHeaderArray mResponseTrailers;
 };
 
 void
 HttpChannelChild::ProcessOnStopRequest(const nsresult& aChannelStatus,
-                                       const ResourceTimingStruct& aTiming)
+                                       const ResourceTimingStruct& aTiming,
+                                       const nsHttpHeaderArray& aResponseTrailers)
 {
   LOG(("HttpChannelChild::ProcessOnStopRequest [this=%p]\n", this));
   MOZ_ASSERT(OnSocketThread());
   MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
     "Should not be receiving any more callbacks from parent!");
 
-  mEventQ->RunOrEnqueue(new StopRequestEvent(this, aChannelStatus, aTiming),
+  mEventQ->RunOrEnqueue(new StopRequestEvent(this, aChannelStatus,
+                                             aTiming, aResponseTrailers),
                         mDivertingToParent);
 }
 
 class MaybeDivertOnStopHttpEvent : public NeckoTargetChannelEvent<HttpChannelChild>
 {
  public:
   MaybeDivertOnStopHttpEvent(HttpChannelChild* child,
                              const nsresult& channelStatus)
@@ -1031,17 +1036,18 @@ HttpChannelChild::MaybeDivertOnStop(cons
        static_cast<uint32_t>(aChannelStatus)));
   if (mDivertingToParent) {
     SendDivertOnStopRequest(aChannelStatus);
   }
 }
 
 void
 HttpChannelChild::OnStopRequest(const nsresult& channelStatus,
-                                const ResourceTimingStruct& timing)
+                                const ResourceTimingStruct& timing,
+                                const nsHttpHeaderArray& aResponseTrailers)
 {
   LOG(("HttpChannelChild::OnStopRequest [this=%p status=%" PRIx32 "]\n",
        this, static_cast<uint32_t>(channelStatus)));
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mDivertingToParent) {
     MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
       "Should not be processing any more callbacks from parent!");
@@ -1084,16 +1090,18 @@ HttpChannelChild::OnStopRequest(const ns
   mRedirectEndTimeStamp = timing.redirectEnd;
   mTransferSize = timing.transferSize;
   mEncodedBodySize = timing.encodedBodySize;
   mProtocolVersion = timing.protocolVersion;
 
   mCacheReadStart = timing.cacheReadStart;
   mCacheReadEnd = timing.cacheReadEnd;
 
+  mResponseTrailers = new nsHttpHeaderArray(aResponseTrailers);
+
   DoPreOnStopRequest(channelStatus);
 
   { // We must flush the queue before we Send__delete__
     // (although we really shouldn't receive any msgs after OnStop),
     // so make sure this goes out of scope before then.
     AutoEventEnqueuer ensureSerialDispatch(mEventQ);
 
     DoOnStopRequest(this, channelStatus, mListenerContext);
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -237,17 +237,18 @@ private:
   // Callbacks while receiving OnTransportAndData/OnStopRequest/OnProgress/
   // OnStatus/FlushedForDiversion/DivertMessages on background IPC channel.
   void ProcessOnTransportAndData(const nsresult& aChannelStatus,
                                  const nsresult& aStatus,
                                  const uint64_t& aOffset,
                                  const uint32_t& aCount,
                                  const nsCString& aData);
   void ProcessOnStopRequest(const nsresult& aStatusCode,
-                            const ResourceTimingStruct& aTiming);
+                            const ResourceTimingStruct& aTiming,
+                            const nsHttpHeaderArray& aResponseTrailers);
   void ProcessOnProgress(const int64_t& aProgress, const int64_t& aProgressMax);
   void ProcessOnStatus(const nsresult& aStatus);
   void ProcessFlushedForDiversion();
   void ProcessDivertMessages();
   void ProcessNotifyTrackingProtectionDisabled();
   void ProcessNotifyTrackingResource();
   void ProcessSetClassifierMatchedInfo(const nsCString& aList,
                                        const nsCString& aProvider,
@@ -410,17 +411,19 @@ private:
   void MaybeDivertOnData(const nsCString& data,
                          const uint64_t& offset,
                          const uint32_t& count);
   void OnTransportAndData(const nsresult& channelStatus,
                           const nsresult& status,
                           const uint64_t& offset,
                           const uint32_t& count,
                           const nsCString& data);
-  void OnStopRequest(const nsresult& channelStatus, const ResourceTimingStruct& timing);
+  void OnStopRequest(const nsresult& channelStatus,
+                     const ResourceTimingStruct& timing,
+                     const nsHttpHeaderArray& aResponseTrailers);
   void MaybeDivertOnStop(const nsresult& aChannelStatus);
   void OnProgress(const int64_t& progress, const int64_t& progressMax);
   void OnStatus(const nsresult& status);
   void FailedAsyncOpen(const nsresult& status);
   void HandleAsyncAbort();
   void Redirect1Begin(const uint32_t& registrarId,
                       const URIParams& newUri,
                       const uint32_t& redirectFlags,
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -1591,22 +1591,26 @@ HttpChannelParent::OnStopRequest(nsIRequ
   mChannel->GetCacheReadStart(&timing.cacheReadStart);
   mChannel->GetCacheReadEnd(&timing.cacheReadEnd);
 
   RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
   if (httpChannelImpl) {
     httpChannelImpl->SetWarningReporter(nullptr);
   }
 
+  nsHttpHeaderArray *responseTrailer = mChannel->GetResponseTrailers();
+
   // Either IPC channel is closed or background channel
   // is ready to send OnStopRequest.
   MOZ_ASSERT(mIPCClosed || mBgParent);
 
   if (mIPCClosed ||
-      !mBgParent || !mBgParent->OnStopRequest(aStatusCode, timing)) {
+      !mBgParent ||
+      !mBgParent->OnStopRequest(aStatusCode, timing,
+                                responseTrailer ? *responseTrailer : nsHttpHeaderArray())) {
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // HttpChannelParent::nsIStreamListener
--- a/netwerk/protocol/http/NullHttpChannel.cpp
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -890,16 +890,22 @@ NullHttpChannel::SetReportResourceTiming
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 NullHttpChannel::GetReportResourceTiming(bool* _retval) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+NS_IMETHODIMP
+NullHttpChannel::GetServerTiming(nsIArray **aServerTiming)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 #define IMPL_TIMING_ATTR(name)                                 \
 NS_IMETHODIMP                                                  \
 NullHttpChannel::Get##name##Time(PRTime* _retval) {            \
     TimeStamp stamp;                                           \
     Get##name(&stamp);                                         \
     if (stamp.IsNull()) {                                      \
         *_retval = 0;                                          \
         return NS_OK;                                          \
--- a/netwerk/protocol/http/PHttpBackgroundChannel.ipdl
+++ b/netwerk/protocol/http/PHttpBackgroundChannel.ipdl
@@ -6,16 +6,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBackground;
 include NeckoChannelParams;
 include PURLClassifierInfo;
 
 include "mozilla/net/NeckoMessageUtils.h";
 
+using class nsHttpHeaderArray from "nsHttpHeaderArray.h";
 using struct mozilla::net::ResourceTimingStruct from "mozilla/net/TimingStruct.h";
 
 namespace mozilla {
 namespace net {
 
 //-------------------------------------------------------------------
 async protocol PHttpBackgroundChannel
 {
@@ -31,18 +32,20 @@ child:
   // Combines a single OnDataAvailable and its associated OnProgress &
   // OnStatus calls into one IPDL message
   async OnTransportAndData(nsresult  channelStatus,
                            nsresult  transportStatus,
                            uint64_t  offset,
                            uint32_t  count,
                            nsCString data);
 
-  async OnStopRequest(nsresult channelStatus, ResourceTimingStruct timing,
-                      TimeStamp lastActiveTabOptimization);
+  async OnStopRequest(nsresult channelStatus,
+                      ResourceTimingStruct timing,
+                      TimeStamp lastActiveTabOptimization,
+                      nsHttpHeaderArray responseTrailers);
 
   async OnProgress(int64_t progress, int64_t progressMax);
 
   async OnStatus(nsresult status);
 
   // Parent has been suspended for diversion; no more events to be enqueued.
   async FlushedForDiversion();
 
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -40,16 +40,17 @@ EXPORTS.mozilla.net += [
     'AltDataOutputStreamParent.h',
     'HttpAuthUtils.h',
     'HttpBackgroundChannelChild.h',
     'HttpBackgroundChannelParent.h',
     'HttpBaseChannel.h',
     'HttpChannelChild.h',
     'HttpChannelParent.h',
     'HttpInfo.h',
+    'nsServerTiming.h',
     'NullHttpChannel.h',
     'PHttpChannelParams.h',
     'PSpdyPush.h',
     'TimingStruct.h',
 ]
 
 SOURCES += [
     'nsHttpChannelAuthProvider.cpp', # redefines GetAuthType
@@ -90,16 +91,17 @@ UNIFIED_SOURCES += [
     'nsHttpConnectionInfo.cpp',
     'nsHttpConnectionMgr.cpp',
     'nsHttpDigestAuth.cpp',
     'nsHttpHeaderArray.cpp',
     'nsHttpNTLMAuth.cpp',
     'nsHttpRequestHead.cpp',
     'nsHttpResponseHead.cpp',
     'nsHttpTransaction.cpp',
+    'nsServerTiming.cpp',
     'NullHttpChannel.cpp',
     'NullHttpTransaction.cpp',
     'TunnelUtils.cpp',
 ]
 
 # These files cannot be built in unified mode because of OS X headers.
 SOURCES += [
     'nsHttpHandler.cpp',
--- a/netwerk/protocol/http/nsHttp.cpp
+++ b/netwerk/protocol/http/nsHttp.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/HashFunctions.h"
 #include "nsCRT.h"
 #include "nsHttpRequestHead.h"
 #include "nsHttpResponseHead.h"
 #include "nsHttpHandler.h"
 #include "nsICacheEntry.h"
 #include "nsIRequest.h"
 #include <errno.h>
+#include <functional>
 
 namespace mozilla {
 namespace net {
 
 // define storage for all atoms
 namespace nsHttp {
 #define HTTP_ATOM(_name, _value) nsHttpAtom _name = { _value };
 #include "nsHttpAtomList.h"
@@ -598,133 +599,241 @@ void EnsureBuffer(UniquePtr<char[]> &buf
     localEnsureBuffer<char> (buf, newSize, preserve, objSize);
 }
 
 void EnsureBuffer(UniquePtr<uint8_t[]> &buf, uint32_t newSize,
                   uint32_t preserve, uint32_t &objSize)
 {
     localEnsureBuffer<uint8_t> (buf, newSize, preserve, objSize);
 }
-///
 
-void
-ParsedHeaderValueList::Tokenize(char *input, uint32_t inputLen, char **token,
-                                uint32_t *tokenLen, bool *foundEquals, char **next)
-{
-    if (foundEquals) {
-        *foundEquals = false;
-    }
-    if (next) {
-        *next = nullptr;
-    }
-    if (inputLen < 1 || !input || !token) {
-        return;
-    }
-
-    bool foundFirst = false;
-    bool inQuote = false;
-    bool foundToken = false;
-    *token = input;
-    *tokenLen = inputLen;
-
-    for (uint32_t index = 0; !foundToken && index < inputLen; ++index) {
-        // strip leading cruft
-        if (!foundFirst &&
-            (input[index] == ' ' || input[index] == '"' || input[index] == '\t')) {
-            (*token)++;
-        } else {
-            foundFirst = true;
-        }
+static bool
+IsTokenSymbol(signed char chr) {
+  if (chr < 33 || chr == 127 ||
+      chr == '(' || chr == ')' || chr == '<' || chr == '>' ||
+      chr == '@' || chr == ',' || chr == ';' || chr == ':' ||
+      chr == '"' || chr == '/' || chr == '[' || chr == ']' ||
+      chr == '?' || chr == '=' || chr == '{' || chr == '}' || chr == '\\') {
+    return false;
+  }
+  return true;
+}
 
-        if (input[index] == '"') {
-            inQuote = !inQuote;
-            continue;
-        }
-
-        if (inQuote) {
-            continue;
-        }
-
-        if (input[index] == '=' || input[index] == ';') {
-            *tokenLen = (input + index) - *token;
-            if (next && ((index + 1) < inputLen)) {
-                *next = input + index + 1;
-            }
-            foundToken = true;
-            if (foundEquals && input[index] == '=') {
-                *foundEquals = true;
-            }
-            break;
-        }
+ParsedHeaderPair::ParsedHeaderPair(const char *name, int32_t nameLen,
+                                   const char *val, int32_t valLen,
+                                   bool isQuotedValue)
+    : mName(nsDependentCSubstring(nullptr, 0u))
+    , mValue(nsDependentCSubstring(nullptr, 0u))
+    , mIsQuotedValue(isQuotedValue)
+{
+    if (nameLen > 0) {
+        mName.Rebind(name, name + nameLen);
     }
-
-    if (!foundToken) {
-        *tokenLen = (input + inputLen) - *token;
-    }
-
-    // strip trailing cruft
-    for (char *index = *token + *tokenLen - 1; index >= *token; --index) {
-        if (*index != ' ' && *index != '\t' && *index != '"') {
-            break;
-        }
-        --(*tokenLen);
-        if (*index == '"') {
-            break;
+    if (valLen > 0) {
+        if (mIsQuotedValue) {
+            RemoveQuotedStringEscapes(val, valLen);
+            mValue.Rebind(mUnquotedValue.BeginWriting(), mUnquotedValue.Length());
+        } else {
+            mValue.Rebind(val, val + valLen);
         }
     }
 }
 
-ParsedHeaderValueList::ParsedHeaderValueList(char *t, uint32_t len)
+void
+ParsedHeaderPair::RemoveQuotedStringEscapes(const char *val, int32_t valLen)
 {
-    char *name = nullptr;
-    uint32_t nameLen = 0;
-    char *value = nullptr;
-    uint32_t valueLen = 0;
-    char *next = nullptr;
-    bool foundEquals;
-
-    while (t) {
-        Tokenize(t, len, &name, &nameLen, &foundEquals, &next);
-        if (next) {
-            len -= next - t;
+    mUnquotedValue.Truncate();
+    const char *c = val;
+    for (int32_t i = 0; i < valLen; ++i) {
+        if (c[i] == '\\' && c[i + 1]) {
+            ++i;
         }
-        t = next;
-        if (foundEquals && t) {
-            Tokenize(t, len, &value, &valueLen, nullptr, &next);
-            if (next) {
-                len -= next - t;
-            }
-            t = next;
-        }
-        mValues.AppendElement(ParsedHeaderPair(name, nameLen, value, valueLen));
-        value = name = nullptr;
-        valueLen = nameLen = 0;
-        next = nullptr;
+        mUnquotedValue.Append(c[i]);
     }
 }
 
-ParsedHeaderValueListList::ParsedHeaderValueListList(const nsCString &fullHeader)
-    : mFull(fullHeader)
+static
+void Tokenize(const char *input, uint32_t inputLen, const char token,
+              const std::function<void(const char *, uint32_t)>& consumer)
 {
-    char *t = mFull.BeginWriting();
-    uint32_t len = mFull.Length();
-    char *last = t;
+    auto trimWhitespace =
+        [] (const char *in, uint32_t inLen, const char **out, uint32_t *outLen) {
+            *out = in;
+            *outLen = inLen;
+            if (inLen == 0) {
+                return;
+            }
+
+            // Trim leading space
+            while (nsCRT::IsAsciiSpace(**out)) {
+                (*out)++;
+                --(*outLen);
+            }
+
+            // Trim tailing space
+            for (const char *i = *out + *outLen - 1; i >= *out; --i) {
+                if (!nsCRT::IsAsciiSpace(*i)) {
+                    break;
+                }
+                --(*outLen);
+            }
+        };
+
+    const char *first = input;
     bool inQuote = false;
-    for (uint32_t index = 0; index < len; ++index) {
-        if (t[index] == '"') {
+    const char *result = nullptr;
+    uint32_t resultLen = 0;
+    for (uint32_t index = 0; index < inputLen; ++index) {
+        if (inQuote && input[index] == '\\' && input[index + 1]) {
+            index++;
+            continue;
+        }
+        if (input[index] == '"') {
             inQuote = !inQuote;
             continue;
         }
         if (inQuote) {
             continue;
         }
-        if (t[index] == ',') {
-            mValues.AppendElement(ParsedHeaderValueList(last, (t + index) - last));
-            last = t + index + 1;
+        if (input[index] == token) {
+            trimWhitespace(first, (input + index) - first,
+                           &result, &resultLen);
+            consumer(result, resultLen);
+            first = input + index + 1;
+        }
+    }
+
+    trimWhitespace(first, (input + inputLen) - first,
+                   &result, &resultLen);
+    consumer(result, resultLen);
+}
+
+ParsedHeaderValueList::ParsedHeaderValueList(const char *t,
+                                             uint32_t len,
+                                             bool allowInvalidValue)
+{
+    if (!len) {
+        return;
+    }
+
+    ParsedHeaderValueList *self = this;
+    auto consumer = [=] (const char *output, uint32_t outputLength) {
+        self->ParseNameAndValue(output, allowInvalidValue);
+    };
+
+    Tokenize(t, len, ';', consumer);
+}
+
+void
+ParsedHeaderValueList::ParseNameAndValue(const char *input, bool allowInvalidValue)
+{
+    const char *nameStart = input;
+    const char *nameEnd = nullptr;
+    const char *valueStart = input;
+    const char *valueEnd = nullptr;
+    bool isQuotedString = false;
+    bool invalidValue = false;
+
+    for (; *input && *input != ';' && *input != ',' &&
+           !nsCRT::IsAsciiSpace(*input) && *input != '='; input++)
+        ;
+
+    nameEnd = input;
+
+    if (!(nameEnd - nameStart)) {
+        return;
+    }
+
+    // Check whether param name is a valid token.
+    for (const char *c = nameStart; c < nameEnd; c++) {
+        if (!IsTokenSymbol(*c)) {
+            nameEnd = c;
+            break;
         }
     }
-    if (!inQuote) {
-        mValues.AppendElement(ParsedHeaderValueList(last, (t + len) - last));
+
+    if (!(nameEnd - nameStart)) {
+        return;
+    }
+
+    while (nsCRT::IsAsciiSpace(*input)) {
+        ++input;
+    }
+
+    if (!*input || *input++ != '=') {
+        mValues.AppendElement(ParsedHeaderPair(nameStart, nameEnd - nameStart,
+                                               nullptr, 0, false));
+        return;
+    }
+
+    while (nsCRT::IsAsciiSpace(*input)) {
+        ++input;
     }
+
+    if (*input != '"') {
+        // The value is a token, not a quoted string.
+        valueStart = input;
+        for (valueEnd = input;
+             *valueEnd && !nsCRT::IsAsciiSpace (*valueEnd) &&
+             *valueEnd != ';' && *valueEnd != ',';
+             valueEnd++)
+          ;
+        input = valueEnd;
+        if (!allowInvalidValue) {
+            for (const char *c = valueStart; c < valueEnd; c++) {
+                if (!IsTokenSymbol(*c)) {
+                    valueEnd = c;
+                    break;
+                }
+            }
+        }
+    } else {
+        bool foundQuotedEnd = false;
+        isQuotedString = true;
+
+        ++input;
+        valueStart = input;
+        for (valueEnd = input; *valueEnd; ++valueEnd) {
+            if (*valueEnd == '\\' && *(valueEnd + 1)) {
+                ++valueEnd;
+            }
+            else if (*valueEnd == '"') {
+                foundQuotedEnd = true;
+                break;
+            }
+        }
+        if (!foundQuotedEnd) {
+            invalidValue = true;
+        }
+
+        input = valueEnd;
+        // *valueEnd != null means that *valueEnd is quote character.
+        if (*valueEnd) {
+            input++;
+        }
+    }
+
+    if (invalidValue) {
+        valueEnd = valueStart;
+    }
+
+    mValues.AppendElement(ParsedHeaderPair(nameStart, nameEnd - nameStart,
+                                           valueStart, valueEnd - valueStart,
+                                           isQuotedString));
+}
+
+ParsedHeaderValueListList::ParsedHeaderValueListList(const nsCString &fullHeader,
+                                                     bool allowInvalidValue)
+    : mFull(fullHeader)
+{
+    auto &values = mValues;
+    auto consumer =
+        [&values, allowInvalidValue] (const char *output, uint32_t outputLength) {
+            values.AppendElement(ParsedHeaderValueList(output,
+                                                       outputLength,
+                                                       allowInvalidValue));
+        };
+
+    Tokenize(mFull.BeginReading(), mFull.Length(), ',', consumer);
 }
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -261,52 +261,60 @@ void EnsureBuffer(UniquePtr<uint8_t[]> &
 
 // h2=":443"; ma=60; single
 // results in 3 mValues = {{h2, :443}, {ma, 60}, {single}}
 
 class ParsedHeaderPair
 {
 public:
     ParsedHeaderPair(const char *name, int32_t nameLen,
-                     const char *val, int32_t valLen)
-    {
-        if (nameLen > 0) {
-            mName.Rebind(name, name + nameLen);
-        }
-        if (valLen > 0) {
-            mValue.Rebind(val, val + valLen);
-        }
-    }
+                     const char *val, int32_t valLen, bool isQuotedValue);
 
     ParsedHeaderPair(ParsedHeaderPair const &copy)
         : mName(copy.mName)
         , mValue(copy.mValue)
+        , mUnquotedValue(copy.mUnquotedValue)
+        , mIsQuotedValue(copy.mIsQuotedValue)
     {
+        if (mIsQuotedValue) {
+            mValue.Rebind(mUnquotedValue.BeginReading(), mUnquotedValue.Length());
+        }
     }
 
     nsDependentCSubstring mName;
     nsDependentCSubstring mValue;
+
+private:
+    nsCString mUnquotedValue;
+    bool mIsQuotedValue;
+
+    void RemoveQuotedStringEscapes(const char *val, int32_t valLen);
 };
 
 class ParsedHeaderValueList
 {
 public:
-    ParsedHeaderValueList(char *t, uint32_t len);
+    ParsedHeaderValueList(const char *t, uint32_t len, bool allowInvalidValue);
     nsTArray<ParsedHeaderPair> mValues;
 
 private:
-    void ParsePair(char *t, uint32_t len);
-    void Tokenize(char *input, uint32_t inputLen, char **token,
-                  uint32_t *tokenLen, bool *foundEquals, char **next);
+    void ParseNameAndValue(const char *input, bool allowInvalidValue);
 };
 
 class ParsedHeaderValueListList
 {
 public:
-    explicit ParsedHeaderValueListList(const nsCString &txt);
+    // RFC 7231 section 3.2.6 defines the syntax of the header field values.
+    // |allowInvalidValue| indicates whether the rule will be used to check
+    // the input text.
+    // Note that ParsedHeaderValueListList is currently used to parse
+    // Alt-Svc and Server-Timing header. |allowInvalidValue| is set to true
+    // when parsing Alt-Svc for historical reasons.
+    explicit ParsedHeaderValueListList(const nsCString &txt,
+                                       bool allowInvalidValue = true);
     nsTArray<ParsedHeaderValueList> mValues;
 
 private:
     nsCString mFull;
 };
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpAtomList.h
+++ b/netwerk/protocol/http/nsHttpAtomList.h
@@ -69,16 +69,17 @@ HTTP_ATOM(Pragma,                    "Pr
 HTTP_ATOM(Prefer,                    "Prefer")
 HTTP_ATOM(Proxy_Authenticate,        "Proxy-Authenticate")
 HTTP_ATOM(Proxy_Authorization,       "Proxy-Authorization")
 HTTP_ATOM(Proxy_Connection,          "Proxy-Connection")
 HTTP_ATOM(Range,                     "Range")
 HTTP_ATOM(Referer,                   "Referer")
 HTTP_ATOM(Retry_After,               "Retry-After")
 HTTP_ATOM(Server,                    "Server")
+HTTP_ATOM(Server_Timing,             "Server-Timing")
 HTTP_ATOM(Service_Worker_Allowed,    "Service-Worker-Allowed")
 HTTP_ATOM(Set_Cookie,                "Set-Cookie")
 HTTP_ATOM(Set_Cookie2,               "Set-Cookie2")
 HTTP_ATOM(Status_URI,                "Status-URI")
 HTTP_ATOM(Strict_Transport_Security, "Strict-Transport-Security")
 HTTP_ATOM(TE,                        "TE")
 HTTP_ATOM(Title,                     "Title")
 HTTP_ATOM(Timeout,                   "Timeout")
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -7208,16 +7208,18 @@ nsHttpChannel::OnStopRequest(nsIRequest 
         if (request == mTransactionPump && mCacheEntry && !mDidReval &&
             !mCustomConditionalRequest &&
             !mAsyncOpenTime.IsNull() && !mOnStartRequestTimestamp.IsNull()) {
             uint64_t onStartTime = (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds();
             uint64_t onStopTime = (TimeStamp::Now() - mAsyncOpenTime).ToMilliseconds();
             Unused << mCacheEntry->SetNetworkTimes(onStartTime, onStopTime);
         }
 
+        mResponseTrailers = mTransaction->TakeResponseTrailers();
+
         // at this point, we're done with the transaction
         mTransactionTimings = mTransaction->Timings();
         mTransaction = nullptr;
         mTransactionPump = nullptr;
 
         // We no longer need the dns prefetch object
         if (mDNSPrefetch && mDNSPrefetch->TimingsValid()
             && !mTransactionTimings.requestStart.IsNull()
--- a/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
@@ -115,17 +115,27 @@ nsHttpChunkedDecoder::ParseChunkRemainin
 
         if (mWaitEOF) {
             if (*buf) {
                 LOG(("got trailer: %s\n", buf));
                 // allocate a header array for the trailers on demand
                 if (!mTrailers) {
                     mTrailers = new nsHttpHeaderArray();
                 }
-                Unused << mTrailers->ParseHeaderLine(nsDependentCSubstring(buf, count));
+
+                nsHttpAtom hdr = {0};
+                nsAutoCString headerNameOriginal;
+                nsAutoCString val;
+                if (NS_SUCCEEDED(mTrailers->ParseHeaderLine(nsDependentCSubstring(buf, count),
+                                                            &hdr, &headerNameOriginal, &val))) {
+                    if (hdr == nsHttp::Server_Timing) {
+                        Unused << mTrailers->SetHeaderFromNet(hdr, headerNameOriginal,
+                                                              val, true);
+                    }
+                }
             }
             else {
                 mWaitEOF = false;
                 mReachedEOF = true;
                 LOG(("reached end of chunked-body\n"));
             }
         }
         else if (*buf) {
--- a/netwerk/protocol/http/nsHttpChunkedDecoder.h
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.h
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 #ifndef nsHttpChunkedDecoder_h__
 #define nsHttpChunkedDecoder_h__
 
+#include "nsAutoPtr.h"
 #include "nsError.h"
 #include "nsString.h"
 #include "nsHttpHeaderArray.h"
 
 namespace mozilla { namespace net {
 
 class nsHttpChunkedDecoder
 {
@@ -24,33 +25,31 @@ public:
     bool ReachedEOF() { return mReachedEOF; }
 
     // called by the transaction to handle chunked content.
     MOZ_MUST_USE nsresult HandleChunkedContent(char *buf,
                                                uint32_t count,
                                                uint32_t *contentRead,
                                                uint32_t *contentRemaining);
 
-    nsHttpHeaderArray *Trailers() { return mTrailers; }
+    nsHttpHeaderArray *Trailers() { return mTrailers.get(); }
 
-    nsHttpHeaderArray *TakeTrailers() { nsHttpHeaderArray *h = mTrailers;
-                                        mTrailers = nullptr;
-                                        return h; }
+    nsHttpHeaderArray *TakeTrailers() { return mTrailers.forget(); }
 
     uint32_t GetChunkRemaining() { return mChunkRemaining; }
 
 private:
     MOZ_MUST_USE nsresult ParseChunkRemaining(char *buf,
                                               uint32_t count,
                                               uint32_t *countRead);
 
 private:
-    nsHttpHeaderArray *mTrailers;
-    uint32_t           mChunkRemaining;
-    nsCString          mLineBuf; // may hold a partial line
-    bool               mReachedEOF;
-    bool               mWaitEOF;
+    nsAutoPtr<nsHttpHeaderArray>  mTrailers;
+    uint32_t                      mChunkRemaining;
+    nsCString                     mLineBuf; // may hold a partial line
+    bool                          mReachedEOF;
+    bool                          mWaitEOF;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -131,16 +131,18 @@ nsHttpTransaction::nsHttpTransaction()
     , mContentDecoding(false)
     , mContentDecodingCheck(false)
     , mDeferredSendProgress(false)
     , mWaitingOnPipeOut(false)
     , mReportedStart(false)
     , mReportedResponseHeader(false)
     , mForTakeResponseHead(nullptr)
     , mResponseHeadTaken(false)
+    , mForTakeResponseTrailers(nullptr)
+    , mResponseTrailersTaken(false)
     , mTopLevelOuterContentWindowId(0)
     , mSubmittedRatePacing(false)
     , mPassedRatePacing(false)
     , mSynchronousRatePaceRequest(false)
     , mClassOfService(0)
     , m0RTTInProgress(false)
     , mDoNotTryEarlyData(false)
     , mEarlyDataDisposition(EARLY_NONE)
@@ -481,16 +483,28 @@ nsHttpTransaction::TakeResponseHead()
         return nullptr;
     }
 
     head = mResponseHead;
     mResponseHead = nullptr;
     return head;
 }
 
+nsHttpHeaderArray *
+nsHttpTransaction::TakeResponseTrailers()
+{
+    MOZ_ASSERT(!mResponseTrailersTaken, "TakeResponseTrailers called 2x");
+
+    // Lock TakeResponseTrailers() against main thread
+    MutexAutoLock lock(*nsHttp::GetLock());
+
+    mResponseTrailersTaken = true;
+    return mForTakeResponseTrailers.forget();
+}
+
 void
 nsHttpTransaction::SetProxyConnectFailed()
 {
     mProxyConnectFailed = true;
 }
 
 nsHttpRequestHead *
 nsHttpTransaction::RequestHead()
@@ -1771,16 +1785,21 @@ nsHttpTransaction::HandleContent(char *b
     }
 
     LOG(("nsHttpTransaction::HandleContent [this=%p count=%u read=%u mContentRead=%" PRId64 " mContentLength=%" PRId64 "]\n",
         this, count, *contentRead, mContentRead, mContentLength));
 
     // check for end-of-file
     if ((mContentRead == mContentLength) ||
         (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
+        MutexAutoLock lock(*nsHttp::GetLock());
+        mForTakeResponseTrailers = mChunkedDecoder
+            ? mChunkedDecoder->TakeTrailers()
+            : nullptr;
+
         // the transaction is done with a complete response.
         mTransactionDone = true;
         mResponseIsComplete = true;
         ReleaseBlockingTransaction();
 
         if (TimingEnabled()) {
             SetResponseEnd(TimeStamp::Now());
         }
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -25,16 +25,17 @@ class nsIHttpActivityObserver;
 class nsIEventTarget;
 class nsIInputStream;
 class nsIOutputStream;
 class nsIRequestContext;
 
 namespace mozilla { namespace net {
 
 class nsHttpChunkedDecoder;
+class nsHttpHeaderArray;
 class nsHttpRequestHead;
 class nsHttpResponseHead;
 
 //-----------------------------------------------------------------------------
 // nsHttpTransaction represents a single HTTP transaction.  It is thread-safe,
 // intended to run on the socket thread.
 //-----------------------------------------------------------------------------
 
@@ -99,16 +100,20 @@ public:
     nsISupports           *HttpChannel()    { return mChannel; }
 
     void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
 
     // Called to take ownership of the response headers; the transaction
     // will drop any reference to the response headers after this call.
     nsHttpResponseHead *TakeResponseHead();
 
+    // Called to take ownership of the trailer headers.
+    // Returning null if there is no trailer.
+    nsHttpHeaderArray *TakeResponseTrailers();
+
     // Provides a thread safe reference of the connection
     // nsHttpTransaction::Connection should only be used on the socket thread
     already_AddRefed<nsAHttpConnection> GetConnectionReference();
 
     // Called to set/find out if the transaction generated a complete response.
     bool ResponseIsComplete() { return mResponseIsComplete; }
     void SetResponseIsComplete() { mResponseIsComplete = true; }
 
@@ -372,16 +377,18 @@ private:
 
     // For Restart-In-Progress Functionality
     bool                            mReportedStart;
     bool                            mReportedResponseHeader;
 
     // protected by nsHttp::GetLock()
     nsHttpResponseHead             *mForTakeResponseHead;
     bool                            mResponseHeadTaken;
+    nsAutoPtr<nsHttpHeaderArray>    mForTakeResponseTrailers;
+    bool                            mResponseTrailersTaken;
 
     // The time when the transaction was submitted to the Connection Manager
     TimeStamp                       mPendingTime;
 
     uint64_t                        mTopLevelOuterContentWindowId;
 
 // For Rate Pacing via an EventTokenBucket
 public:
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/nsServerTiming.cpp
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsServerTiming.h"
+
+NS_IMPL_ISUPPORTS(nsServerTiming, nsIServerTiming)
+
+NS_IMETHODIMP
+nsServerTiming::GetName(nsACString &aName)
+{
+  aName.Assign(mName);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsServerTiming::GetDuration(double *aDuration)
+{
+  *aDuration = mDuration;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsServerTiming::GetDescription(nsACString &aDescription)
+{
+  aDescription.Assign(mDescription);
+  return NS_OK;
+}
+
+namespace mozilla {
+namespace net {
+
+static double
+ParseDouble(const nsACString& aString)
+{
+  nsresult rv;
+  double val = PromiseFlatCString(aString).ToDouble(&rv);
+  return NS_FAILED(rv) ? 0.0f : val;
+}
+
+void
+ServerTimingParser::Parse()
+{
+  // https://w3c.github.io/server-timing/#the-server-timing-header-field
+  // Server-Timing             = #server-timing-metric
+  // server-timing-metric      = metric-name *( OWS ";" OWS server-timing-param )
+  // metric-name               = token
+  // server-timing-param       = server-timing-param-name OWS "=" OWS server-timing-param-value
+  // server-timing-param-name  = token
+  // server-timing-param-value = token / quoted-string
+
+  ParsedHeaderValueListList parsedHeader(mValue, false);
+  for (uint32_t index = 0; index < parsedHeader.mValues.Length(); ++index) {
+    if (parsedHeader.mValues[index].mValues.IsEmpty()) {
+      continue;
+    }
+
+    // According to spec, the first ParsedHeaderPair's name is metric-name.
+    RefPtr<nsServerTiming> timingHeader = new nsServerTiming();
+    mServerTimingHeaders.AppendElement(timingHeader);
+    timingHeader->SetName(parsedHeader.mValues[index].mValues[0].mName);
+
+    if (parsedHeader.mValues[index].mValues.Length() == 1) {
+      continue;
+    }
+
+    // Try to find duration and description from the rest ParsedHeaderPairs.
+    bool foundDuration = false;
+    bool foundDescription = false;
+    for (uint32_t pairIndex = 1;
+         pairIndex < parsedHeader.mValues[index].mValues.Length();
+         ++pairIndex) {
+      nsDependentCSubstring &currentName =
+        parsedHeader.mValues[index].mValues[pairIndex].mName;
+      nsDependentCSubstring &currentValue =
+        parsedHeader.mValues[index].mValues[pairIndex].mValue;
+
+      // We should only take the value from the first
+      // occurrence of server-timing-param-name ("dur" and "desc").
+      if (currentName.LowerCaseEqualsASCII("dur") &&
+          currentValue.BeginReading() &&
+          !foundDuration) {
+        timingHeader->SetDuration(ParseDouble(currentValue));
+        foundDuration = true;
+      } else if (currentName.LowerCaseEqualsASCII("desc") &&
+                 !currentValue.IsEmpty() &&
+                 !foundDescription) {
+        timingHeader->SetDescription(currentValue);
+        foundDescription = true;
+      }
+
+      if (foundDuration && foundDescription) {
+        break;
+      }
+    }
+  }
+}
+
+nsTArray<nsCOMPtr<nsIServerTiming>>&&
+ServerTimingParser::TakeServerTimingHeaders()
+{
+  return Move(mServerTimingHeaders);
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/nsServerTiming.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef nsServerTiming_h__
+#define nsServerTiming_h__
+
+#include "nsITimedChannel.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsServerTiming final : public nsIServerTiming
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISERVERTIMING
+
+  nsServerTiming() = default;
+
+  void SetName(const nsACString &aName)
+  {
+    mName = aName;
+  }
+
+  void SetDuration(double aDuration)
+  {
+    mDuration = aDuration;
+  }
+
+  void SetDescription(const nsACString &aDescription)
+  {
+    mDescription = aDescription;
+  }
+
+private:
+  virtual ~nsServerTiming() = default;
+
+  nsCString mName;
+  double mDuration;
+  nsCString mDescription;
+};
+
+namespace mozilla {
+namespace net {
+
+class ServerTimingParser
+{
+public:
+  explicit ServerTimingParser(const nsCString &value)
+    : mValue(value)
+  {}
+  void Parse();
+  nsTArray<nsCOMPtr<nsIServerTiming>>&& TakeServerTimingHeaders();
+
+private:
+  nsCString mValue;
+  nsTArray<nsCOMPtr<nsIServerTiming>> mServerTimingHeaders;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/netwerk/test/gtest/TestServerTimingHeader.cpp
@@ -0,0 +1,232 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/net/nsServerTiming.h"
+#include <string>
+#include <vector>
+
+void testServerTimingHeader(const char* headerValue,
+                            std::vector<std::vector<std::string>> expectedResults)
+{
+  nsAutoCString header(headerValue);
+  ServerTimingParser parser(header);
+  parser.Parse();
+
+  nsTArray<nsCOMPtr<nsIServerTiming>> results = parser.TakeServerTimingHeaders();
+
+  ASSERT_EQ(results.Length(), expectedResults.size());
+
+  unsigned i = 0;
+  for (const auto& header : results) {
+    std::vector<std::string> expectedResult(expectedResults[i++]);
+    nsCString name;
+    mozilla::Unused << header->GetName(name);
+    ASSERT_TRUE(name.Equals(expectedResult[0].c_str()));
+
+    double duration;
+    mozilla::Unused << header->GetDuration(&duration);
+    ASSERT_EQ(duration, atof(expectedResult[1].c_str()));
+
+    nsCString description;
+    mozilla::Unused << header->GetDescription(description);
+    ASSERT_TRUE(description.Equals(expectedResult[2].c_str()));
+  }
+}
+
+TEST(TestServerTimingHeader, HeaderParsing) {
+  // Test cases below are copied from
+  // https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/network/HTTPParsersTest.cpp
+
+  testServerTimingHeader("", {});
+  testServerTimingHeader("metric", {{"metric", "0", ""}});
+  testServerTimingHeader("metric;dur", {{"metric", "0", ""}});
+  testServerTimingHeader("metric;dur=123.4", {{"metric", "123.4", ""}});
+  testServerTimingHeader("metric;dur=\"123.4\"", {{"metric", "123.4", ""}});
+
+  testServerTimingHeader("metric;desc", {{"metric", "0", ""}});
+  testServerTimingHeader("metric;desc=description",
+                         {{"metric", "0", "description"}});
+  testServerTimingHeader("metric;desc=\"description\"",
+                         {{"metric", "0", "description"}});
+
+  testServerTimingHeader("metric;dur;desc", {{"metric", "0", ""}});
+  testServerTimingHeader("metric;dur=123.4;desc", {{"metric", "123.4", ""}});
+  testServerTimingHeader("metric;dur;desc=description",
+                         {{"metric", "0", "description"}});
+  testServerTimingHeader("metric;dur=123.4;desc=description",
+                         {{"metric", "123.4", "description"}});
+  testServerTimingHeader("metric;desc;dur", {{"metric", "0", ""}});
+  testServerTimingHeader("metric;desc;dur=123.4", {{"metric", "123.4", ""}});
+  testServerTimingHeader("metric;desc=description;dur",
+                         {{"metric", "0", "description"}});
+  testServerTimingHeader("metric;desc=description;dur=123.4",
+                         {{"metric", "123.4", "description"}});
+
+  // special chars in name
+  testServerTimingHeader("aB3!#$%&'*+-.^_`|~",
+                         {{"aB3!#$%&'*+-.^_`|~", "0", ""}});
+
+  // delimiter chars in quoted description
+  testServerTimingHeader("metric;desc=\"descr;,=iption\";dur=123.4",
+                         {{"metric", "123.4", "descr;,=iption"}});
+
+  // whitespace
+  testServerTimingHeader("metric ; ", {{"metric", "0", ""}});
+  testServerTimingHeader("metric , ", {{"metric", "0", ""}});
+  testServerTimingHeader("metric ; dur = 123.4 ; desc = description",
+                         {{"metric", "123.4", "description"}});
+  testServerTimingHeader("metric ; desc = description ; dur = 123.4",
+                         {{"metric", "123.4", "description"}});
+
+  // multiple entries
+  testServerTimingHeader(
+      "metric1;dur=12.3;desc=description1,metric2;dur=45.6;"
+      "desc=description2,metric3;dur=78.9;desc=description3",
+      {{"metric1", "12.3", "description1"},
+       {"metric2", "45.6", "description2"},
+       {"metric3", "78.9", "description3"}});
+  testServerTimingHeader("metric1,metric2 ,metric3, metric4 , metric5",
+                         {{"metric1", "0", ""},
+                          {"metric2", "0", ""},
+                          {"metric3", "0", ""},
+                          {"metric4", "0", ""},
+                          {"metric5", "0", ""}});
+
+  // quoted-strings
+  // metric;desc=\ --> ''
+  testServerTimingHeader("metric;desc=\\", {{"metric", "0", ""}});
+  // metric;desc=" --> ''
+  testServerTimingHeader("metric;desc=\"", {{"metric", "0", ""}});
+  // metric;desc=\\ --> ''
+  testServerTimingHeader("metric;desc=\\\\", {{"metric", "0", ""}});
+  // metric;desc=\" --> ''
+  testServerTimingHeader("metric;desc=\\\"", {{"metric", "0", ""}});
+  // metric;desc="\ --> ''
+  testServerTimingHeader("metric;desc=\"\\", {{"metric", "0", ""}});
+  // metric;desc="" --> ''
+  testServerTimingHeader("metric;desc=\"\"", {{"metric", "0", ""}});
+  // metric;desc=\\\ --> ''
+  testServerTimingHeader("metric;desc=\\\\\\", {{"metric", "0", ""}});
+  // metric;desc=\\" --> ''
+  testServerTimingHeader("metric;desc=\\\\\"", {{"metric", "0", ""}});
+  // metric;desc=\"\ --> ''
+  testServerTimingHeader("metric;desc=\\\"\\", {{"metric", "0", ""}});
+  // metric;desc=\"" --> ''
+  testServerTimingHeader("metric;desc=\\\"\"", {{"metric", "0", ""}});
+  // metric;desc="\\ --> ''
+  testServerTimingHeader("metric;desc=\"\\\\", {{"metric", "0", ""}});
+  // metric;desc="\" --> ''
+  testServerTimingHeader("metric;desc=\"\\\"", {{"metric", "0", ""}});
+  // metric;desc=""\ --> ''
+  testServerTimingHeader("metric;desc=\"\"\\", {{"metric", "0", ""}});
+  // metric;desc=""" --> ''
+  testServerTimingHeader("metric;desc=\"\"\"", {{"metric", "0", ""}});
+  // metric;desc=\\\\ --> ''
+  testServerTimingHeader("metric;desc=\\\\\\\\", {{"metric", "0", ""}});
+  // metric;desc=\\\" --> ''
+  testServerTimingHeader("metric;desc=\\\\\\\"", {{"metric", "0", ""}});
+  // metric;desc=\\"\ --> ''
+  testServerTimingHeader("metric;desc=\\\\\"\\", {{"metric", "0", ""}});
+  // metric;desc=\\"" --> ''
+  testServerTimingHeader("metric;desc=\\\\\"\"", {{"metric", "0", ""}});
+  // metric;desc=\"\\ --> ''
+  testServerTimingHeader("metric;desc=\\\"\\\\", {{"metric", "0", ""}});
+  // metric;desc=\"\" --> ''
+  testServerTimingHeader("metric;desc=\\\"\\\"", {{"metric", "0", ""}});
+  // metric;desc=\""\ --> ''
+  testServerTimingHeader("metric;desc=\\\"\"\\", {{"metric", "0", ""}});
+  // metric;desc=\""" --> ''
+  testServerTimingHeader("metric;desc=\\\"\"\"", {{"metric", "0", ""}});
+  // metric;desc="\\\ --> ''
+  testServerTimingHeader("metric;desc=\"\\\\\\", {{"metric", "0", ""}});
+  // metric;desc="\\" --> '\'
+  testServerTimingHeader("metric;desc=\"\\\\\"", {{"metric", "0", "\\"}});
+  // metric;desc="\"\ --> ''
+  testServerTimingHeader("metric;desc=\"\\\"\\", {{"metric", "0", ""}});
+  // metric;desc="\"" --> '"'
+  testServerTimingHeader("metric;desc=\"\\\"\"", {{"metric", "0", "\""}});
+  // metric;desc=""\\ --> ''
+  testServerTimingHeader("metric;desc=\"\"\\\\", {{"metric", "0", ""}});
+  // metric;desc=""\" --> ''
+  testServerTimingHeader("metric;desc=\"\"\\\"", {{"metric", "0", ""}});
+  // metric;desc="""\ --> ''
+  testServerTimingHeader("metric;desc=\"\"\"\\", {{"metric", "0", ""}});
+  // metric;desc="""" --> ''
+  testServerTimingHeader("metric;desc=\"\"\"\"", {{"metric", "0", ""}});
+
+  // duplicate entry names
+  testServerTimingHeader(
+      "metric;dur=12.3;desc=description1,metric;dur=45.6;"
+      "desc=description2",
+      {{"metric", "12.3", "description1"}, {"metric", "45.6", "description2"}});
+
+  // non-numeric durations
+  testServerTimingHeader("metric;dur=foo", {{"metric", "0", ""}});
+  testServerTimingHeader("metric;dur=\"foo\"", {{"metric", "0", ""}});
+
+  // unrecognized param names
+  testServerTimingHeader(
+      "metric;foo=bar;desc=description;foo=bar;dur=123.4;foo=bar",
+      {{"metric", "123.4", "description"}});
+
+  // duplicate param names
+  testServerTimingHeader("metric;dur=123.4;dur=567.8",
+                         {{"metric", "123.4", ""}});
+  testServerTimingHeader("metric;desc=description1;desc=description2",
+                         {{"metric", "0", "description1"}});
+  testServerTimingHeader("metric;dur=foo;dur=567.8", {{"metric", "", ""}});
+
+  // unspecified param values
+  testServerTimingHeader("metric;dur;dur=123.4", {{"metric", "123.4", ""}});
+  testServerTimingHeader("metric;desc;desc=description",
+                         {{"metric", "0", "description"}});
+
+  // param name case
+  testServerTimingHeader("metric;DuR=123.4;DeSc=description",
+                         {{"metric", "123.4", "description"}});
+
+  // nonsense
+  testServerTimingHeader("metric=foo;dur;dur=123.4,metric2",
+                         {{"metric", "123.4", ""}, {"metric2", "0", ""}});
+  testServerTimingHeader("metric\"foo;dur;dur=123.4,metric2",
+                         {{"metric", "", ""}});
+
+  // nonsense - return zero entries
+  testServerTimingHeader(" ", {});
+  testServerTimingHeader("=", {});
+  testServerTimingHeader("[", {});
+  testServerTimingHeader("]", {});
+  testServerTimingHeader(";", {});
+  testServerTimingHeader(",", {});
+  testServerTimingHeader("=;", {});
+  testServerTimingHeader(";=", {});
+  testServerTimingHeader("=,", {});
+  testServerTimingHeader(",=", {});
+  testServerTimingHeader(";,", {});
+  testServerTimingHeader(",;", {});
+  testServerTimingHeader("=;,", {});
+
+  // Invalid token
+  testServerTimingHeader("met=ric", {{"met", "0", ""}});
+  testServerTimingHeader("met ric", {{"met", "0", ""}});
+  testServerTimingHeader("met[ric", {{"met", "0", ""}});
+  testServerTimingHeader("met]ric", {{"met", "0", ""}});
+  testServerTimingHeader("metric;desc=desc=123, metric2",
+                         {{"metric", "0", "desc"}, {"metric2", "0", ""}});
+  testServerTimingHeader("met ric;desc=de sc  , metric2",
+                         {{"met", "0", "de"}, {"metric2", "0", ""}});
+
+  // test cases from https://w3c.github.io/server-timing/#examples
+  testServerTimingHeader(" miss, ,db;dur=53, app;dur=47.2 ",
+                         {{"miss", "0", ""}, {"db", "53", ""}, {"app", "47.2", ""}});
+  testServerTimingHeader(" customView, dc;desc=atl ",
+                         {{"customView", "0", ""}, {"dc", "0", "atl"}});
+  testServerTimingHeader(" total;dur=123.4 ",
+                         {{"total", "123.4", ""}});
+
+  // test cases for comma in quoted string
+  testServerTimingHeader("     metric ; desc=\"descr\\\"\\\";,=iption\";dur=123.4",
+                         {{"metric", "123.4", "descr\"\";,=iption"}});
+  testServerTimingHeader(" metric2;dur=\"123.4\";;desc=\",;\\\",;,\";;,  metric  ;  desc = \" \\\", ;\\\" \"; dur=123.4,",
+                         {{"metric2", "123.4", ",;\",;,"}, {"metric", "123.4", " \", ;\" "}});
+}
\ No newline at end of file
--- a/netwerk/test/gtest/moz.build
+++ b/netwerk/test/gtest/moz.build
@@ -7,16 +7,17 @@
 UNIFIED_SOURCES += [
     'TestBufferedInputStream.cpp',
     'TestHeaders.cpp',
     'TestHttpAuthUtils.cpp',
     'TestMozURL.cpp',
     'TestPartiallySeekableInputStream.cpp',
     'TestProtocolProxyService.cpp',
     'TestReadStreamToString.cpp',
+    'TestServerTimingHeader.cpp',
     'TestStandardURL.cpp',
     'TestURIMutator.cpp',
 ]
 
 TEST_DIRS += [
     'parse-ftp',
 ]
 
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_header_Server_Timing.js
@@ -0,0 +1,84 @@
+/* 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/. */
+
+//
+//  HTTP Server-Timing header test
+//
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+  return "http://localhost:" + httpServer.identity.primaryPort + "/content";
+});
+
+let httpServer = null;
+
+function make_and_open_channel(url, callback) {
+  let chan = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+  chan.asyncOpen2(new ChannelListener(callback, null, CL_ALLOW_UNKNOWN_CL));
+}
+
+var respnseServerTiming = [{metric:"metric", duration:"123.4", description:"description"},
+                           {metric:"metric2", duration:"456.78", description:"description1"}];
+var trailerServerTiming = [{metric:"metric3", duration:"789.11", description:"description2"},
+                           {metric:"metric4", duration:"1112.13", description:"description3"}];
+
+function createServerTimingHeader(headerData) {
+  var header = "";
+  for (var i = 0; i < headerData.length; i++) {
+    header += "Server-Timing:" + headerData[i].metric + ";" +
+              "dur=" + headerData[i].duration + ";" +
+              "desc=" + headerData[i].description + "\r\n";
+  }
+  return header;
+}
+
+function contentHandler(metadata, response)
+{
+  var body = "c\r\ndata reached\r\n3\r\nhej\r\n0\r\n";
+
+  response.seizePower();
+  response.write("HTTP/1.1 200 OK\r\n");
+  response.write("Content-Type: text/plain\r\n");
+  response.write(createServerTimingHeader(respnseServerTiming));
+
+  response.write("Transfer-Encoding: chunked\r\n");
+  response.write("\r\n");
+  response.write(body);
+  response.write(createServerTimingHeader(trailerServerTiming));
+  response.write("\r\n");
+  response.finish();
+}
+
+function run_test()
+{
+  httpServer = new HttpServer();
+  httpServer.registerPathHandler("/content", contentHandler);
+  httpServer.start(-1);
+
+  do_test_pending();
+  make_and_open_channel(URL, readServerContent);
+}
+
+function checkServerTimingContent(headers) {
+  var expectedResult = respnseServerTiming.concat(trailerServerTiming);
+  Assert.equal(headers.length, expectedResult.length);
+
+  for (var i = 0; i < expectedResult.length; i++) {
+    let header = headers.queryElementAt(i, Ci.nsIServerTiming);
+    Assert.equal(header.name, expectedResult[i].metric);
+    Assert.equal(header.description, expectedResult[i].description);
+    Assert.equal(header.duration, parseFloat(expectedResult[i].duration));
+  }
+}
+
+function readServerContent(request, buffer)
+{
+  let channel = request.QueryInterface(Ci.nsITimedChannel);
+  let headers = channel.serverTiming.QueryInterface(Ci.nsIArray);
+  checkServerTimingContent(headers);
+
+  httpServer.stop(do_test_finished);
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -405,8 +405,9 @@ skip-if = os == "android"
 [test_1351443-missing-NewChannel2.js]
 [test_bug1312782_http1.js]
 [test_bug1355539_http1.js]
 [test_bug1378385_http1.js]
 [test_tls_flags_separate_connections.js]
 [test_tls_flags.js]
 [test_uri_mutator.js]
 [test_bug1411316_http1.js]
+[test_header_Server_Timing.js]
--- a/rdf/build/nsRDFModule.cpp
+++ b/rdf/build/nsRDFModule.cpp
@@ -9,17 +9,16 @@
 #include "nsRDFService.h"
 #include "nsIRDFContainer.h"
 #include "nsIRDFContainerUtils.h"
 #include "nsIRDFCompositeDataSource.h"
 #include "nsIRDFContentSink.h"
 #include "nsISupports.h"
 #include "nsRDFBaseDataSources.h"
 #include "nsRDFBuiltInDataSources.h"
-#include "nsFileSystemDataSource.h"
 #include "nsRDFCID.h"
 #include "nsIComponentManager.h"
 #include "rdf.h"
 #include "nsIServiceManager.h"
 #include "nsILocalStore.h"
 #include "nsRDFXMLParser.h"
 #include "nsRDFXMLSerializer.h"
 
@@ -61,48 +60,45 @@ MAKE_CTOR(RDFCompositeDataSource,RDFComp
 MAKE_CTOR(RDFContainer,RDFContainer,RDFContainer)
 
 MAKE_CTOR(RDFContainerUtils,RDFContainerUtils,RDFContainerUtils)
 
 MAKE_CTOR(RDFContentSink,RDFContentSink,RDFContentSink)
 MAKE_CTOR(RDFDefaultResource,DefaultResource,RDFResource)
 
 NS_DEFINE_NAMED_CID(NS_RDFCOMPOSITEDATASOURCE_CID);
-NS_DEFINE_NAMED_CID(NS_RDFFILESYSTEMDATASOURCE_CID);
 NS_DEFINE_NAMED_CID(NS_RDFINMEMORYDATASOURCE_CID);
 NS_DEFINE_NAMED_CID(NS_RDFXMLDATASOURCE_CID);
 NS_DEFINE_NAMED_CID(NS_RDFDEFAULTRESOURCE_CID);
 NS_DEFINE_NAMED_CID(NS_RDFCONTENTSINK_CID);
 NS_DEFINE_NAMED_CID(NS_RDFCONTAINER_CID);
 NS_DEFINE_NAMED_CID(NS_RDFCONTAINERUTILS_CID);
 NS_DEFINE_NAMED_CID(NS_RDFSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_RDFXMLPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_RDFXMLSERIALIZER_CID);
 NS_DEFINE_NAMED_CID(NS_LOCALSTORE_CID);
 
 
 static const mozilla::Module::CIDEntry kRDFCIDs[] = {
     { &kNS_RDFCOMPOSITEDATASOURCE_CID, false, nullptr, CreateNewRDFCompositeDataSource },
-    { &kNS_RDFFILESYSTEMDATASOURCE_CID, false, nullptr, FileSystemDataSource::Create },
     { &kNS_RDFINMEMORYDATASOURCE_CID, false, nullptr, NS_NewRDFInMemoryDataSource },
     { &kNS_RDFXMLDATASOURCE_CID, false, nullptr, CreateNewRDFXMLDataSource },
     { &kNS_RDFDEFAULTRESOURCE_CID, false, nullptr, CreateNewRDFDefaultResource },
     { &kNS_RDFCONTENTSINK_CID, false, nullptr, CreateNewRDFContentSink },
     { &kNS_RDFCONTAINER_CID, false, nullptr, CreateNewRDFContainer },
     { &kNS_RDFCONTAINERUTILS_CID, false, nullptr, CreateNewRDFContainerUtils },
     { &kNS_RDFSERVICE_CID, false, nullptr, RDFServiceImpl::CreateSingleton },
     { &kNS_RDFXMLPARSER_CID, false, nullptr, nsRDFXMLParser::Create },
     { &kNS_RDFXMLSERIALIZER_CID, false, nullptr, nsRDFXMLSerializer::Create },
     { &kNS_LOCALSTORE_CID, false, nullptr, NS_NewLocalStore },
     { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kRDFContracts[] = {
     { NS_RDF_DATASOURCE_CONTRACTID_PREFIX "composite-datasource", &kNS_RDFCOMPOSITEDATASOURCE_CID },
-    { NS_RDF_DATASOURCE_CONTRACTID_PREFIX "files", &kNS_RDFFILESYSTEMDATASOURCE_CID },
     { NS_RDF_DATASOURCE_CONTRACTID_PREFIX "in-memory-datasource", &kNS_RDFINMEMORYDATASOURCE_CID },
     { NS_RDF_DATASOURCE_CONTRACTID_PREFIX "xml-datasource", &kNS_RDFXMLDATASOURCE_CID },
     { NS_RDF_RESOURCE_FACTORY_CONTRACTID, &kNS_RDFDEFAULTRESOURCE_CID },
     { NS_RDF_CONTRACTID "/content-sink;1", &kNS_RDFCONTENTSINK_CID },
     { NS_RDF_CONTRACTID "/container;1", &kNS_RDFCONTAINER_CID },
     { NS_RDF_CONTRACTID "/container-utils;1", &kNS_RDFCONTAINERUTILS_CID },
     { NS_RDF_CONTRACTID "/rdf-service;1", &kNS_RDFSERVICE_CID },
     { NS_RDF_CONTRACTID "/xml-parser;1", &kNS_RDFXMLPARSER_CID },
--- a/rdf/datasource/moz.build
+++ b/rdf/datasource/moz.build
@@ -4,17 +4,16 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     'nsILocalStore.h',
 ]
 
 UNIFIED_SOURCES += [
-    'nsFileSystemDataSource.cpp',
     'nsLocalStore.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 # "This is a dependency on rdfutil.h: it'll go away once that becomes
 # a first-class XPCOM interface."
 LOCAL_INCLUDES += [
deleted file mode 100644
--- a/rdf/datasource/nsFileSystemDataSource.cpp
+++ /dev/null
@@ -1,1322 +0,0 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* 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/. */
-
-/*
-  Implementation for a file system RDF data store.
- */
-
-#include "nsFileSystemDataSource.h"
-
-#include <ctype.h> // for toupper()
-#include <stdio.h>
-#include "nsArrayEnumerator.h"
-#include "nsCOMArray.h"
-#include "nsIRDFDataSource.h"
-#include "nsIRDFObserver.h"
-#include "nsIServiceManager.h"
-#include "nsString.h"
-#include "nsRDFCID.h"
-#include "rdfutil.h"
-#include "rdf.h"
-#include "nsEnumeratorUtils.h"
-#include "nsIURL.h"
-#include "nsIFileURL.h"
-#include "nsNetUtil.h"
-#include "nsIInputStream.h"
-#include "nsIChannel.h"
-#include "nsIFile.h"
-#include "nsEscape.h"
-#include "nsCRTGlue.h"
-#include "nsAutoPtr.h"
-#include "prtime.h"
-
-#ifdef XP_WIN
-#include "windef.h"
-#include "winbase.h"
-#include "nsILineInputStream.h"
-#include "nsDirectoryServiceDefs.h"
-#endif
-
-#define NS_MOZICON_SCHEME           "moz-icon:"
-
-static const char kFileProtocol[]         = "file://";
-
-bool
-FileSystemDataSource::isFileURI(nsIRDFResource *r)
-{
-    bool        isFileURIFlag = false;
-    const char  *uri = nullptr;
-
-    r->GetValueConst(&uri);
-    if ((uri) && (!strncmp(uri, kFileProtocol, sizeof(kFileProtocol) - 1)))
-    {
-        // XXX HACK HACK HACK
-        if (!strchr(uri, '#'))
-        {
-            isFileURIFlag = true;
-        }
-    }
-    return(isFileURIFlag);
-}
-
-
-
-bool
-FileSystemDataSource::isDirURI(nsIRDFResource* source)
-{
-    nsresult    rv;
-    const char  *uri = nullptr;
-
-    rv = source->GetValueConst(&uri);
-    if (NS_FAILED(rv)) return(false);
-
-    nsCOMPtr<nsIFile> aDir;
-
-    rv = NS_GetFileFromURLSpec(nsDependentCString(uri), getter_AddRefs(aDir));
-    if (NS_FAILED(rv)) return(false);
-
-    bool isDirFlag = false;
-
-    rv = aDir->IsDirectory(&isDirFlag);
-    if (NS_FAILED(rv)) return(false);
-
-    return(isDirFlag);
-}
-
-
-nsresult
-FileSystemDataSource::Init()
-{
-    nsresult rv;
-
-    mRDFService = do_GetService("@mozilla.org/rdf/rdf-service;1");
-    NS_ENSURE_TRUE(mRDFService, NS_ERROR_FAILURE);
-
-    rv =  mRDFService->GetResource(NS_LITERAL_CSTRING("NC:FilesRoot"),
-                                   getter_AddRefs(mNC_FileSystemRoot));
-    nsresult tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI  "child"),
-                                   getter_AddRefs(mNC_Child));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI  "Name"),
-                                   getter_AddRefs(mNC_Name));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI  "URL"),
-                                   getter_AddRefs(mNC_URL));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI  "Icon"),
-                                   getter_AddRefs(mNC_Icon));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI  "Content-Length"),
-                                   getter_AddRefs(mNC_Length));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI  "IsDirectory"),
-                                   getter_AddRefs(mNC_IsDirectory));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(WEB_NAMESPACE_URI "LastModifiedDate"),
-                                   getter_AddRefs(mWEB_LastMod));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI  "FileSystemObject"),
-                                   getter_AddRefs(mNC_FileSystemObject));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI  "pulse"),
-                                   getter_AddRefs(mNC_pulse));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"),
-                                   getter_AddRefs(mRDF_InstanceOf));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"),
-                                   getter_AddRefs(mRDF_type));
-
-    static const char16_t kTrue[] = {'t','r','u','e','\0'};
-    static const char16_t kFalse[] = {'f','a','l','s','e','\0'};
-
-    tmp = mRDFService->GetLiteral(kTrue, getter_AddRefs(mLiteralTrue));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    tmp = mRDFService->GetLiteral(kFalse, getter_AddRefs(mLiteralFalse));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-#ifdef USE_NC_EXTENSION
-    rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "extension"),
-                                  getter_AddRefs(mNC_extension));
-    NS_ENSURE_SUCCESS(rv, rv);
-#endif
-
-#ifdef XP_WIN
-    rv =  mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "IEFavorite"),
-                                  getter_AddRefs(mNC_IEFavoriteObject));
-    tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "IEFavoriteFolder"),
-                                   getter_AddRefs(mNC_IEFavoriteFolder));
-    if (NS_FAILED(tmp)) {
-      rv = tmp;
-    }
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-    nsCOMPtr<nsIFile> file;
-    NS_GetSpecialDirectory(NS_WIN_FAVORITES_DIR, getter_AddRefs(file));
-    if (file)
-    {
-        nsCOMPtr<nsIURI> furi;
-        NS_NewFileURI(getter_AddRefs(furi), file);
-        NS_ENSURE_TRUE(furi, NS_ERROR_FAILURE);
-
-        file->GetNativePath(ieFavoritesDir);
-    }
-#endif
-
-    return NS_OK;
-}
-
-//static
-nsresult
-FileSystemDataSource::Create(nsISupports* aOuter, const nsIID& aIID, void **aResult)
-{
-    NS_ENSURE_NO_AGGREGATION(aOuter);
-
-    RefPtr<FileSystemDataSource> self = new FileSystemDataSource();
-    if (!self)
-        return NS_ERROR_OUT_OF_MEMORY;
-
-    nsresult rv = self->Init();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    return self->QueryInterface(aIID, aResult);
-}
-
-NS_IMPL_ISUPPORTS(FileSystemDataSource, nsIRDFDataSource)
-
-NS_IMETHODIMP
-FileSystemDataSource::GetURI(nsACString& aURI)
-{
-    aURI.AssignLiteral("rdf:files");
-    return NS_OK;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::GetSource(nsIRDFResource* property,
-                                nsIRDFNode* target,
-                                bool tv,
-                                nsIRDFResource** source /* out */)
-{
-    NS_PRECONDITION(property != nullptr, "null ptr");
-    if (! property)
-        return NS_ERROR_NULL_POINTER;
-
-    NS_PRECONDITION(target != nullptr, "null ptr");
-    if (! target)
-        return NS_ERROR_NULL_POINTER;
-
-    NS_PRECONDITION(source != nullptr, "null ptr");
-    if (! source)
-        return NS_ERROR_NULL_POINTER;
-
-    *source = nullptr;
-    return NS_RDF_NO_VALUE;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::GetSources(nsIRDFResource *property,
-                                 nsIRDFNode *target,
-                                 bool tv,
-                                 nsISimpleEnumerator **sources /* out */)
-{
-//  MOZ_ASSERT_UNREACHABLE("write me");
-    return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::GetTarget(nsIRDFResource *source,
-                                nsIRDFResource *property,
-                                bool tv,
-                                nsIRDFNode **target /* out */)
-{
-    NS_PRECONDITION(source != nullptr, "null ptr");
-    if (! source)
-        return NS_ERROR_NULL_POINTER;
-
-    NS_PRECONDITION(property != nullptr, "null ptr");
-    if (! property)
-        return NS_ERROR_NULL_POINTER;
-
-    NS_PRECONDITION(target != nullptr, "null ptr");
-    if (! target)
-        return NS_ERROR_NULL_POINTER;
-
-    *target = nullptr;
-
-    nsresult        rv = NS_RDF_NO_VALUE;
-
-    // we only have positive assertions in the file system data source.
-    if (! tv)
-        return NS_RDF_NO_VALUE;
-
-    if (source == mNC_FileSystemRoot)
-    {
-        if (property == mNC_pulse)
-        {
-            nsIRDFLiteral   *pulseLiteral;
-            mRDFService->GetLiteral(u"12", &pulseLiteral);
-            *target = pulseLiteral;
-            return NS_OK;
-        }
-    }
-    else if (isFileURI(source))
-    {
-        if (property == mNC_Name)
-        {
-            nsCOMPtr<nsIRDFLiteral> name;
-            rv = GetName(source, getter_AddRefs(name));
-            if (NS_FAILED(rv)) return(rv);
-            if (!name)  rv = NS_RDF_NO_VALUE;
-            if (rv == NS_RDF_NO_VALUE)  return(rv);
-            return name->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
-        }
-        else if (property == mNC_URL)
-        {
-            nsCOMPtr<nsIRDFLiteral> url;
-            rv = GetURL(source, nullptr, getter_AddRefs(url));
-            if (NS_FAILED(rv)) return(rv);
-            if (!url)   rv = NS_RDF_NO_VALUE;
-            if (rv == NS_RDF_NO_VALUE)  return(rv);
-
-            return url->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
-        }
-        else if (property == mNC_Icon)
-        {
-            nsCOMPtr<nsIRDFLiteral> url;
-            bool isFavorite = false;
-            rv = GetURL(source, &isFavorite, getter_AddRefs(url));
-            if (NS_FAILED(rv)) return(rv);
-            if (isFavorite || !url) rv = NS_RDF_NO_VALUE;
-            if (rv == NS_RDF_NO_VALUE)  return(rv);
-
-            const char16_t *uni = nullptr;
-            url->GetValueConst(&uni);
-            if (!uni)   return(NS_RDF_NO_VALUE);
-            nsAutoString    urlStr;
-            urlStr.AssignLiteral(NS_MOZICON_SCHEME);
-            urlStr.Append(uni);
-
-            rv = mRDFService->GetLiteral(urlStr.get(), getter_AddRefs(url));
-            if (NS_FAILED(rv) || !url)    return(NS_RDF_NO_VALUE);
-            return url->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
-        }
-        else if (property == mNC_Length)
-        {
-            nsCOMPtr<nsIRDFInt> fileSize;
-            rv = GetFileSize(source, getter_AddRefs(fileSize));
-            if (NS_FAILED(rv)) return(rv);
-            if (!fileSize)  rv = NS_RDF_NO_VALUE;
-            if (rv == NS_RDF_NO_VALUE)  return(rv);
-
-            return fileSize->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
-        }
-        else  if (property == mNC_IsDirectory)
-        {
-            *target = (isDirURI(source)) ? mLiteralTrue : mLiteralFalse;
-            NS_ADDREF(*target);
-            return NS_OK;
-        }
-        else if (property == mWEB_LastMod)
-        {
-            nsCOMPtr<nsIRDFDate> lastMod;
-            rv = GetLastMod(source, getter_AddRefs(lastMod));
-            if (NS_FAILED(rv)) return(rv);
-            if (!lastMod)   rv = NS_RDF_NO_VALUE;
-            if (rv == NS_RDF_NO_VALUE)  return(rv);
-
-            return lastMod->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
-        }
-        else if (property == mRDF_type)
-        {
-            nsCString type;
-            rv = mNC_FileSystemObject->GetValueUTF8(type);
-            if (NS_FAILED(rv)) return(rv);
-
-#ifdef  XP_WIN
-            // under Windows, if its an IE favorite, return that type
-            if (!ieFavoritesDir.IsEmpty())
-            {
-                nsCString uri;
-                rv = source->GetValueUTF8(uri);
-                if (NS_FAILED(rv)) return(rv);
-
-                NS_ConvertUTF8toUTF16 theURI(uri);
-
-                if (theURI.Find(ieFavoritesDir) == 0)
-                {
-                    if (theURI[theURI.Length() - 1] == '/')
-                    {
-                        rv = mNC_IEFavoriteFolder->GetValueUTF8(type);
-                    }
-                    else
-                    {
-                        rv = mNC_IEFavoriteObject->GetValueUTF8(type);
-                    }
-                    if (NS_FAILED(rv)) return(rv);
-                }
-            }
-#endif
-
-            NS_ConvertUTF8toUTF16 url(type);
-            nsCOMPtr<nsIRDFLiteral> literal;
-            mRDFService->GetLiteral(url.get(), getter_AddRefs(literal));
-            rv = literal->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
-            return(rv);
-        }
-        else if (property == mNC_pulse)
-        {
-            nsCOMPtr<nsIRDFLiteral> pulseLiteral;
-            mRDFService->GetLiteral(u"12", getter_AddRefs(pulseLiteral));
-            rv = pulseLiteral->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
-            return(rv);
-        }
-        else if (property == mNC_Child)
-        {
-            // Oh this is evil. Somebody kill me now.
-            nsCOMPtr<nsISimpleEnumerator> children;
-            rv = GetFolderList(source, false, true, getter_AddRefs(children));
-            if (NS_FAILED(rv) || (rv == NS_RDF_NO_VALUE)) return(rv);
-
-            bool hasMore;
-            rv = children->HasMoreElements(&hasMore);
-            if (NS_FAILED(rv)) return(rv);
-
-            if (hasMore)
-            {
-                nsCOMPtr<nsISupports> isupports;
-                rv = children->GetNext(getter_AddRefs(isupports));
-                if (NS_FAILED(rv)) return(rv);
-
-                return isupports->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
-            }
-        }
-#ifdef USE_NC_EXTENSION
-        else if (property == mNC_extension)
-        {
-            nsCOMPtr<nsIRDFLiteral> extension;
-            rv = GetExtension(source, getter_AddRefs(extension));
-            if (!extension)    rv = NS_RDF_NO_VALUE;
-            if (rv == NS_RDF_NO_VALUE) return(rv);
-            return extension->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
-        }
-#endif
-    }
-
-    return(NS_RDF_NO_VALUE);
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::GetTargets(nsIRDFResource *source,
-                nsIRDFResource *property,
-                bool tv,
-                nsISimpleEnumerator **targets /* out */)
-{
-    NS_PRECONDITION(source != nullptr, "null ptr");
-    if (! source)
-        return NS_ERROR_NULL_POINTER;
-
-    NS_PRECONDITION(property != nullptr, "null ptr");
-    if (! property)
-        return NS_ERROR_NULL_POINTER;
-
-    NS_PRECONDITION(targets != nullptr, "null ptr");
-    if (! targets)
-        return NS_ERROR_NULL_POINTER;
-
-    *targets = nullptr;
-
-    // we only have positive assertions in the file system data source.
-    if (! tv)
-        return NS_RDF_NO_VALUE;
-
-    nsresult rv;
-
-    if (source == mNC_FileSystemRoot)
-    {
-        if (property == mNC_Child)
-        {
-            return GetVolumeList(targets);
-        }
-        else if (property == mNC_pulse)
-        {
-            nsCOMPtr<nsIRDFLiteral> pulseLiteral;
-            mRDFService->GetLiteral(u"12",
-                                    getter_AddRefs(pulseLiteral));
-            return NS_NewSingletonEnumerator(targets, pulseLiteral);
-        }
-    }
-    else if (isFileURI(source))
-    {
-        if (property == mNC_Child)
-        {
-            return GetFolderList(source, false, false, targets);
-        }
-        else if (property == mNC_Name)
-        {
-            nsCOMPtr<nsIRDFLiteral> name;
-            rv = GetName(source, getter_AddRefs(name));
-            if (NS_FAILED(rv)) return rv;
-
-            return NS_NewSingletonEnumerator(targets, name);
-        }
-        else if (property == mNC_URL)
-        {
-            nsCOMPtr<nsIRDFLiteral> url;
-            rv = GetURL(source, nullptr, getter_AddRefs(url));
-            if (NS_FAILED(rv)) return rv;
-
-            return NS_NewSingletonEnumerator(targets, url);
-        }
-        else if (property == mRDF_type)
-        {
-            nsCString uri;
-            rv = mNC_FileSystemObject->GetValueUTF8(uri);
-            if (NS_FAILED(rv)) return rv;
-
-            NS_ConvertUTF8toUTF16 url(uri);
-
-            nsCOMPtr<nsIRDFLiteral> literal;
-            rv = mRDFService->GetLiteral(url.get(), getter_AddRefs(literal));
-            if (NS_FAILED(rv)) return rv;
-
-            return NS_NewSingletonEnumerator(targets, literal);
-        }
-        else if (property == mNC_pulse)
-        {
-            nsCOMPtr<nsIRDFLiteral> pulseLiteral;
-            rv = mRDFService->GetLiteral(u"12",
-                getter_AddRefs(pulseLiteral));
-            if (NS_FAILED(rv)) return rv;
-
-            return NS_NewSingletonEnumerator(targets, pulseLiteral);
-        }
-    }
-
-    return NS_NewEmptyEnumerator(targets);
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::Assert(nsIRDFResource *source,
-                       nsIRDFResource *property,
-                       nsIRDFNode *target,
-                       bool tv)
-{
-    return NS_RDF_ASSERTION_REJECTED;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::Unassert(nsIRDFResource *source,
-                         nsIRDFResource *property,
-                         nsIRDFNode *target)
-{
-    return NS_RDF_ASSERTION_REJECTED;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::Change(nsIRDFResource* aSource,
-                             nsIRDFResource* aProperty,
-                             nsIRDFNode* aOldTarget,
-                             nsIRDFNode* aNewTarget)
-{
-    return NS_RDF_ASSERTION_REJECTED;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::Move(nsIRDFResource* aOldSource,
-                           nsIRDFResource* aNewSource,
-                           nsIRDFResource* aProperty,
-                           nsIRDFNode* aTarget)
-{
-    return NS_RDF_ASSERTION_REJECTED;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::HasAssertion(nsIRDFResource *source,
-                             nsIRDFResource *property,
-                             nsIRDFNode *target,
-                             bool tv,
-                             bool *hasAssertion /* out */)
-{
-    NS_PRECONDITION(source != nullptr, "null ptr");
-    if (! source)
-        return NS_ERROR_NULL_POINTER;
-
-    NS_PRECONDITION(property != nullptr, "null ptr");
-    if (! property)
-        return NS_ERROR_NULL_POINTER;
-
-    NS_PRECONDITION(target != nullptr, "null ptr");
-    if (! target)
-        return NS_ERROR_NULL_POINTER;
-
-    NS_PRECONDITION(hasAssertion != nullptr, "null ptr");
-    if (! hasAssertion)
-        return NS_ERROR_NULL_POINTER;
-
-    // we only have positive assertions in the file system data source.
-    *hasAssertion = false;
-
-    if (! tv) {
-        return NS_OK;
-    }
-
-    if ((source == mNC_FileSystemRoot) || isFileURI(source))
-    {
-        if (property == mRDF_type)
-        {
-            nsCOMPtr<nsIRDFResource> resource( do_QueryInterface(target) );
-            if (resource.get() == mRDF_type)
-            {
-                *hasAssertion = true;
-            }
-        }
-#ifdef USE_NC_EXTENSION
-        else if (property == mNC_extension)
-        {
-            // Cheat just a little here by making dirs always match
-            if (isDirURI(source))
-            {
-                *hasAssertion = true;
-            }
-            else
-            {
-                nsCOMPtr<nsIRDFLiteral> extension;
-                GetExtension(source, getter_AddRefs(extension));
-                if (extension.get() == target)
-                {
-                    *hasAssertion = true;
-                }
-            }
-        }
-#endif
-        else if (property == mNC_IsDirectory)
-        {
-            bool isDir = isDirURI(source);
-            bool isEqual = false;
-            target->EqualsNode(mLiteralTrue, &isEqual);
-            if (isEqual)
-            {
-                *hasAssertion = isDir;
-            }
-            else
-            {
-                target->EqualsNode(mLiteralFalse, &isEqual);
-                if (isEqual)
-                    *hasAssertion = !isDir;
-            }
-        }
-    }
-
-    return NS_OK;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result)
-{
-    return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result)
-{
-    *result = false;
-
-    if (aSource == mNC_FileSystemRoot)
-    {
-        *result = (aArc == mNC_Child || aArc == mNC_pulse);
-    }
-    else if (isFileURI(aSource))
-    {
-        if (aArc == mNC_pulse)
-        {
-            *result = true;
-        }
-        else if (isDirURI(aSource))
-        {
-#ifdef  XP_WIN
-            *result = isValidFolder(aSource);
-#else
-            *result = true;
-#endif
-        }
-        else if (aArc == mNC_pulse || aArc == mNC_Name || aArc == mNC_Icon ||
-                 aArc == mNC_URL || aArc == mNC_Length || aArc == mWEB_LastMod ||
-                 aArc == mNC_FileSystemObject || aArc == mRDF_InstanceOf ||
-                 aArc == mRDF_type)
-        {
-            *result = true;
-        }
-    }
-    return NS_OK;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::ArcLabelsIn(nsIRDFNode *node,
-                            nsISimpleEnumerator ** labels /* out */)
-{
-//  MOZ_ASSERT_UNREACHABLE("write me");
-    return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::ArcLabelsOut(nsIRDFResource *source,
-                   nsISimpleEnumerator **labels /* out */)
-{
-    NS_PRECONDITION(source != nullptr, "null ptr");
-    if (! source)
-    return NS_ERROR_NULL_POINTER;
-
-    NS_PRECONDITION(labels != nullptr, "null ptr");
-    if (! labels)
-    return NS_ERROR_NULL_POINTER;
-
-    if (source == mNC_FileSystemRoot)
-    {
-        nsCOMArray<nsIRDFResource> resources;
-        resources.SetCapacity(2);
-
-        resources.AppendObject(mNC_Child);
-        resources.AppendObject(mNC_pulse);
-
-        return NS_NewArrayEnumerator(labels, resources);
-    }
-    else if (isFileURI(source))
-    {
-        nsCOMArray<nsIRDFResource> resources;
-        resources.SetCapacity(2);
-
-        if (isDirURI(source))
-        {
-#ifdef  XP_WIN
-            if (isValidFolder(source))
-            {
-                resources.AppendObject(mNC_Child);
-            }
-#else
-            resources.AppendObject(mNC_Child);
-#endif
-            resources.AppendObject(mNC_pulse);
-        }
-
-        return NS_NewArrayEnumerator(labels, resources);
-    }
-
-    return NS_NewEmptyEnumerator(labels);
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::GetAllResources(nsISimpleEnumerator** aCursor)
-{
-    MOZ_ASSERT_UNREACHABLE("sorry!");
-    return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::AddObserver(nsIRDFObserver *n)
-{
-    return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::RemoveObserver(nsIRDFObserver *n)
-{
-    return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::GetAllCmds(nsIRDFResource* source,
-                                     nsISimpleEnumerator/*<nsIRDFResource>*/** commands)
-{
-    return(NS_NewEmptyEnumerator(commands));
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::IsCommandEnabled(nsISupports/*<nsIRDFResource>*/* aSources,
-                                       nsIRDFResource*   aCommand,
-                                       nsISupports/*<nsIRDFResource>*/* aArguments,
-                                       bool* aResult)
-{
-    return(NS_ERROR_NOT_IMPLEMENTED);
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::DoCommand(nsISupports/*<nsIRDFResource>*/* aSources,
-                                nsIRDFResource*   aCommand,
-                                nsISupports/*<nsIRDFResource>*/* aArguments)
-{
-    return(NS_ERROR_NOT_IMPLEMENTED);
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::BeginUpdateBatch()
-{
-    return NS_OK;
-}
-
-
-
-NS_IMETHODIMP
-FileSystemDataSource::EndUpdateBatch()
-{
-    return NS_OK;
-}
-
-
-
-nsresult
-FileSystemDataSource::GetVolumeList(nsISimpleEnumerator** aResult)
-{
-    nsCOMArray<nsIRDFResource> volumes;
-    nsCOMPtr<nsIRDFResource> vol;
-
-#ifdef XP_WIN
-
-    int32_t         driveType;
-    wchar_t         drive[32];
-    int32_t         volNum;
-
-    for (volNum = 0; volNum < 26; volNum++)
-    {
-        swprintf_s(drive, 32, L"%c:\\", volNum + (char16_t)'A');
-
-        driveType = GetDriveTypeW(drive);
-        if (driveType != DRIVE_UNKNOWN && driveType != DRIVE_NO_ROOT_DIR)
-        {
-          nsAutoCString url;
-          url.AppendPrintf("file:///%c|/", volNum + 'A');
-          nsresult rv = mRDFService->GetResource(url, getter_AddRefs(vol));
-          if (NS_FAILED(rv))
-            return rv;
-
-          volumes.AppendObject(vol);
-        }
-    }
-#endif
-
-#ifdef XP_UNIX
-    mRDFService->GetResource(NS_LITERAL_CSTRING("file:///"), getter_AddRefs(vol));
-    volumes.AppendObject(vol);
-#endif
-
-    return NS_NewArrayEnumerator(aResult, volumes);
-}
-
-
-
-#ifdef  XP_WIN
-bool
-FileSystemDataSource::isValidFolder(nsIRDFResource *source)
-{
-    bool    isValid = true;
-    if (ieFavoritesDir.IsEmpty())    return(isValid);
-
-    nsresult        rv;
-    nsCString       uri;
-    rv = source->GetValueUTF8(uri);
-    if (NS_FAILED(rv)) return(isValid);
-
-    NS_ConvertUTF8toUTF16 theURI(uri);
-    if (theURI.Find(ieFavoritesDir) == 0)
-    {
-        isValid = false;
-
-        nsCOMPtr<nsISimpleEnumerator>   folderEnum;
-        if (NS_SUCCEEDED(rv = GetFolderList(source, true, false, getter_AddRefs(folderEnum))))
-        {
-            bool        hasAny = false, hasMore;
-            while (NS_SUCCEEDED(folderEnum->HasMoreElements(&hasMore)) &&
-                   hasMore)
-            {
-                hasAny = true;
-
-                nsCOMPtr<nsISupports>       isupports;
-                if (NS_FAILED(rv = folderEnum->GetNext(getter_AddRefs(isupports))))
-                    break;
-                nsCOMPtr<nsIRDFResource>    res = do_QueryInterface(isupports);
-                if (!res)   break;
-
-                nsCOMPtr<nsIRDFLiteral>     nameLiteral;
-                if (NS_FAILED(rv = GetName(res, getter_AddRefs(nameLiteral))))
-                    break;
-
-                const char16_t         *uniName;
-                if (NS_FAILED(rv = nameLiteral->GetValueConst(&uniName)))
-                    break;
-                nsAutoString            name(uniName);
-
-                // An empty folder, or a folder that contains just "desktop.ini",
-                // is considered to be a IE Favorite; otherwise, its a folder
-                if (!name.LowerCaseEqualsLiteral("desktop.ini"))
-                {
-                    isValid = true;
-                    break;
-                }
-            }
-            if (!hasAny) isValid = true;
-        }
-    }
-    return(isValid);
-}
-#endif
-
-
-
-nsresult
-FileSystemDataSource::GetFolderList(nsIRDFResource *source, bool allowHidden,
-                bool onlyFirst, nsISimpleEnumerator** aResult)
-{
-    if (!isDirURI(source))
-        return(NS_RDF_NO_VALUE);
-
-    nsresult                    rv;
-
-    const char      *parentURI = nullptr;
-    rv = source->GetValueConst(&parentURI);
-    if (NS_FAILED(rv))
-        return(rv);
-    if (!parentURI)
-        return(NS_ERROR_UNEXPECTED);
-
-    nsCOMPtr<nsIURI>    aIURI;
-    if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(aIURI), nsDependentCString(parentURI))))
-        return(rv);
-
-    nsCOMPtr<nsIFileURL>    fileURL = do_QueryInterface(aIURI);
-    if (!fileURL)
-        return NS_OK;
-
-    nsCOMPtr<nsIFile>   aDir;
-    if (NS_FAILED(rv = fileURL->GetFile(getter_AddRefs(aDir))))
-        return(rv);
-
-    // ensure that we DO NOT resolve aliases
-    aDir->SetFollowLinks(false);
-
-    nsCOMPtr<nsISimpleEnumerator>   dirContents;
-    if (NS_FAILED(rv = aDir->GetDirectoryEntries(getter_AddRefs(dirContents))))
-        return(rv);
-    if (!dirContents)
-        return(NS_ERROR_UNEXPECTED);
-
-    nsCOMArray<nsIRDFResource> resources;
-    bool            hasMore;
-    while(NS_SUCCEEDED(rv = dirContents->HasMoreElements(&hasMore)) &&
-          hasMore)
-    {
-        nsCOMPtr<nsISupports>   isupports;
-        if (NS_FAILED(rv = dirContents->GetNext(getter_AddRefs(isupports))))
-            break;
-
-        nsCOMPtr<nsIFile>   aFile = do_QueryInterface(isupports);
-        if (!aFile)
-            break;
-
-        if (!allowHidden)
-        {
-            bool            hiddenFlag = false;
-            if (NS_FAILED(rv = aFile->IsHidden(&hiddenFlag)))
-                break;
-            if (hiddenFlag)
-                continue;
-        }
-
-        nsAutoString leafStr;
-        if (NS_FAILED(rv = aFile->GetLeafName(leafStr)))
-            break;
-        if (leafStr.IsEmpty())
-            continue;
-
-        nsAutoCString           fullURI;
-        fullURI.Assign(parentURI);
-        if (fullURI.Last() != '/')
-        {
-            fullURI.Append('/');
-        }
-
-        nsAutoCString leaf;
-        bool escaped = NS_Escape(NS_ConvertUTF16toUTF8(leafStr), leaf, url_Path);
-        leafStr.Truncate();
-
-        if (!escaped) {
-            continue;
-        }
-
-        // using nsEscape() [above] doesn't escape slashes, so do that by hand
-        int32_t         aOffset;
-        while ((aOffset = leaf.FindChar('/')) >= 0)
-        {
-            leaf.Cut((uint32_t)aOffset, 1);
-            leaf.InsertLiteral("%2F", (uint32_t)aOffset);
-        }
-
-        // append the encoded name
-        fullURI.Append(leaf);
-
-        bool            dirFlag = false;
-        rv = aFile->IsDirectory(&dirFlag);
-        if (NS_SUCCEEDED(rv) && dirFlag)
-        {
-            fullURI.Append('/');
-        }
-
-        nsCOMPtr<nsIRDFResource>    fileRes;
-        mRDFService->GetResource(fullURI, getter_AddRefs(fileRes));
-
-        resources.AppendObject(fileRes);
-
-        if (onlyFirst)
-            break;
-    }
-
-    return NS_NewArrayEnumerator(aResult, resources);
-}
-
-nsresult
-FileSystemDataSource::GetLastMod(nsIRDFResource *source, nsIRDFDate **aResult)
-{
-    *aResult = nullptr;
-
-    nsresult        rv;
-    const char      *uri = nullptr;
-
-    rv = source->GetValueConst(&uri);
-    if (NS_FAILED(rv)) return(rv);
-    if (!uri)
-        return(NS_ERROR_UNEXPECTED);
-
-    nsCOMPtr<nsIURI>    aIURI;
-    if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(aIURI), nsDependentCString(uri))))
-        return(rv);
-
-    nsCOMPtr<nsIFileURL>    fileURL = do_QueryInterface(aIURI);
-    if (!fileURL)
-        return NS_OK;
-
-    nsCOMPtr<nsIFile>   aFile;
-    if (NS_FAILED(rv = fileURL->GetFile(getter_AddRefs(aFile))))
-        return(rv);
-    if (!aFile)
-        return(NS_ERROR_UNEXPECTED);
-
-    // ensure that we DO NOT resolve aliases
-    aFile->SetFollowLinks(false);
-
-    PRTime lastModDate;
-    if (NS_FAILED(rv = aFile->GetLastModifiedTime(&lastModDate)))
-        return(rv);
-
-    // convert from milliseconds to seconds
-    mRDFService->GetDateLiteral(lastModDate * PR_MSEC_PER_SEC, aResult);
-
-    return(NS_OK);
-}
-
-
-
-nsresult
-FileSystemDataSource::GetFileSize(nsIRDFResource *source, nsIRDFInt **aResult)
-{
-    *aResult = nullptr;
-
-    nsresult        rv;
-    const char      *uri = nullptr;
-
-    rv = source->GetValueConst(&uri);
-    if (NS_FAILED(rv))
-        return(rv);
-    if (!uri)
-        return(NS_ERROR_UNEXPECTED);
-
-    nsCOMPtr<nsIURI>    aIURI;
-    if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(aIURI), nsDependentCString(uri))))
-        return(rv);
-
-    nsCOMPtr<nsIFileURL>    fileURL = do_QueryInterface(aIURI);
-    if (!fileURL)
-        return NS_OK;
-
-    nsCOMPtr<nsIFile>   aFile;
-    if (NS_FAILED(rv = fileURL->GetFile(getter_AddRefs(aFile))))
-        return(rv);
-    if (!aFile)
-        return(NS_ERROR_UNEXPECTED);
-
-    // ensure that we DO NOT resolve aliases
-    aFile->SetFollowLinks(false);
-
-    // don't do anything with directories
-    bool    isDir = false;
-    if (NS_FAILED(rv = aFile->IsDirectory(&isDir)))
-        return(rv);
-    if (isDir)
-        return(NS_RDF_NO_VALUE);
-
-    int64_t     aFileSize64;
-    if (NS_FAILED(rv = aFile->GetFileSize(&aFileSize64)))
-        return(rv);
-
-    // convert 64bits to 32bits
-    int32_t aFileSize32 = int32_t(aFileSize64);
-    mRDFService->GetIntLiteral(aFileSize32, aResult);
-
-    return(NS_OK);
-}
-
-
-
-nsresult
-FileSystemDataSource::GetName(nsIRDFResource *source, nsIRDFLiteral **aResult)
-{
-    nsresult        rv;
-    const char      *uri = nullptr;
-
-    rv = source->GetValueConst(&uri);
-    if (NS_FAILED(rv))
-        return(rv);
-    if (!uri)
-        return(NS_ERROR_UNEXPECTED);
-
-    nsCOMPtr<nsIURI>    aIURI;
-    if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(aIURI), nsDependentCString(uri))))
-        return(rv);
-
-    nsCOMPtr<nsIFileURL>    fileURL = do_QueryInterface(aIURI);
-    if (!fileURL)
-        return NS_OK;
-
-    nsCOMPtr<nsIFile>   aFile;
-    if (NS_FAILED(rv = fileURL->GetFile(getter_AddRefs(aFile))))
-        return(rv);
-    if (!aFile)
-        return(NS_ERROR_UNEXPECTED);
-
-    // ensure that we DO NOT resolve aliases
-    aFile->SetFollowLinks(false);
-
-    nsAutoString name;
-    if (NS_FAILED(rv = aFile->GetLeafName(name)))
-        return(rv);
-    if (name.IsEmpty())
-        return(NS_ERROR_UNEXPECTED);
-
-#ifdef  XP_WIN
-    // special hack for IE favorites under Windows; strip off the
-    // trailing ".url" or ".lnk" at the end of IE favorites names
-    int32_t nameLen = name.Length();
-    if ((strncmp(uri, ieFavoritesDir.get(), ieFavoritesDir.Length()) == 0) && (nameLen > 4))
-    {
-        nsAutoString extension;
-        name.Right(extension, 4);
-        if (extension.LowerCaseEqualsLiteral(".url") ||
-            extension.LowerCaseEqualsLiteral(".lnk"))
-        {
-            name.Truncate(nameLen - 4);
-        }
-    }
-#endif
-
-    mRDFService->GetLiteral(name.get(), aResult);
-
-    return NS_OK;
-}
-
-
-
-#ifdef USE_NC_EXTENSION
-nsresult
-FileSystemDataSource::GetExtension(nsIRDFResource *source, nsIRDFLiteral **aResult)
-{
-    nsCOMPtr<nsIRDFLiteral> name;
-    nsresult rv = GetName(source, getter_AddRefs(name));
-    if (NS_FAILED(rv))
-        return rv;
-
-    const char16_t* unicodeLeafName;
-    rv = name->GetValueConst(&unicodeLeafName);
-    if (NS_FAILED(rv))
-        return rv;
-
-    nsAutoString filename(unicodeLeafName);
-    int32_t lastDot = filename.RFindChar('.');
-    if (lastDot == -1)
-    {
-        mRDFService->GetLiteral(EmptyString().get(), aResult);
-    }
-    else
-    {
-        nsAutoString extension;
-        filename.Right(extension, (filename.Length() - lastDot));
-        mRDFService->GetLiteral(extension.get(), aResult);
-    }
-
-    return NS_OK;
-}
-#endif
-
-#ifdef  XP_WIN
-nsresult
-FileSystemDataSource::getIEFavoriteURL(nsIRDFResource *source, nsString aFileURL, nsIRDFLiteral **urlLiteral)
-{
-    nsresult        rv = NS_OK;
-
-    *urlLiteral = nullptr;
-
-    nsCOMPtr<nsIFile> f;
-    NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileURL), getter_AddRefs(f));
-
-    bool value;
-
-    if (NS_SUCCEEDED(f->IsDirectory(&value)) && value)
-    {
-        if (isValidFolder(source))
-            return(NS_RDF_NO_VALUE);
-        aFileURL.AppendLiteral("desktop.ini");
-    }
-    else if (aFileURL.Length() > 4)
-    {
-        nsAutoString    extension;
-
-        aFileURL.Right(extension, 4);
-        if (!extension.LowerCaseEqualsLiteral(".url"))
-        {
-            return(NS_RDF_NO_VALUE);
-        }
-    }
-
-    nsCOMPtr<nsIInputStream> strm;
-    NS_NewLocalFileInputStream(getter_AddRefs(strm),f);
-    nsCOMPtr<nsILineInputStream> linereader = do_QueryInterface(strm, &rv);
-
-    nsAutoString    line;
-    nsAutoCString   cLine;
-    while(NS_SUCCEEDED(rv))
-    {
-        bool    isEOF;
-        rv = linereader->ReadLine(cLine, &isEOF);
-        CopyASCIItoUTF16(cLine, line);
-
-        if (isEOF)
-        {
-            if (line.Find("URL=", true) == 0)
-            {
-                line.Cut(0, 4);
-                rv = mRDFService->GetLiteral(line.get(), urlLiteral);
-                break;
-            }
-            else if (line.Find("CDFURL=", true) == 0)
-            {
-                line.Cut(0, 7);
-                rv = mRDFService->GetLiteral(line.get(), urlLiteral);
-                break;
-            }
-            line.Truncate();
-        }
-    }
-
-    return(rv);
-}
-#endif
-
-
-
-nsresult
-FileSystemDataSource::GetURL(nsIRDFResource *source, bool *isFavorite, nsIRDFLiteral** aResult)
-{
-    if (isFavorite) *isFavorite = false;
-
-    nsresult        rv;
-    nsCString       uri;
-
-    rv = source->GetValueUTF8(uri);
-    if (NS_FAILED(rv))
-        return(rv);
-
-    NS_ConvertUTF8toUTF16 url(uri);
-
-#ifdef  XP_WIN
-    // under Windows, if its an IE favorite, munge the URL
-    if (!ieFavoritesDir.IsEmpty())
-    {
-        if (url.Find(ieFavoritesDir) == 0)
-        {
-            if (isFavorite) *isFavorite = true;
-            rv = getIEFavoriteURL(source, url, aResult);
-            return(rv);
-        }
-    }
-#endif
-
-    // if we fall through to here, its not any type of bookmark
-    // stored in the platform native file system, so just set the URL
-
-    mRDFService->GetLiteral(url.get(), aResult);
-
-    return(NS_OK);
-}
deleted file mode 100644
--- a/rdf/datasource/nsFileSystemDataSource.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* 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/. */
-
-#ifndef nsFileSystemDataSource_h__
-#define nsFileSystemDataSource_h__
-
-#include "nsIRDFDataSource.h"
-#include "nsIRDFLiteral.h"
-#include "nsIRDFResource.h"
-#include "nsIRDFService.h"
-#include "nsCOMPtr.h"
-#include "nsString.h"
-#include "mozilla/Attributes.h"
-
-#if defined(XP_UNIX) || defined(XP_WIN)
-#define USE_NC_EXTENSION
-#endif
-
-class FileSystemDataSource final : public nsIRDFDataSource
-{
-public:
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIRDFDATASOURCE
-
-    static nsresult Create(nsISupports* aOuter,
-                           const nsIID& aIID, void **aResult);
-
-    nsresult Init();
-
-private:
-    FileSystemDataSource() { }
-    ~FileSystemDataSource() { }
-
-    // helper methods
-    bool     isFileURI(nsIRDFResource* aResource);
-    bool     isDirURI(nsIRDFResource* aSource);
-    nsresult GetVolumeList(nsISimpleEnumerator **aResult);
-    nsresult GetFolderList(nsIRDFResource *source, bool allowHidden, bool onlyFirst, nsISimpleEnumerator **aResult);
-    nsresult GetName(nsIRDFResource *source, nsIRDFLiteral** aResult);
-    nsresult GetURL(nsIRDFResource *source, bool *isFavorite, nsIRDFLiteral** aResult);
-    nsresult GetFileSize(nsIRDFResource *source, nsIRDFInt** aResult);
-    nsresult GetLastMod(nsIRDFResource *source, nsIRDFDate** aResult);
-
-    nsCOMPtr<nsIRDFService>    mRDFService;
-
-    // pseudo-constants
-    nsCOMPtr<nsIRDFResource>       mNC_FileSystemRoot;
-    nsCOMPtr<nsIRDFResource>       mNC_Child;
-    nsCOMPtr<nsIRDFResource>       mNC_Name;
-    nsCOMPtr<nsIRDFResource>       mNC_URL;
-    nsCOMPtr<nsIRDFResource>       mNC_Icon;
-    nsCOMPtr<nsIRDFResource>       mNC_Length;
-    nsCOMPtr<nsIRDFResource>       mNC_IsDirectory;
-    nsCOMPtr<nsIRDFResource>       mWEB_LastMod;
-    nsCOMPtr<nsIRDFResource>       mNC_FileSystemObject;
-    nsCOMPtr<nsIRDFResource>       mNC_pulse;
-    nsCOMPtr<nsIRDFResource>       mRDF_InstanceOf;
-    nsCOMPtr<nsIRDFResource>       mRDF_type;
-
-    nsCOMPtr<nsIRDFLiteral>        mLiteralTrue;
-    nsCOMPtr<nsIRDFLiteral>        mLiteralFalse;
-
-#ifdef USE_NC_EXTENSION
-    nsresult GetExtension(nsIRDFResource *source, nsIRDFLiteral** aResult);
-    nsCOMPtr<nsIRDFResource>       mNC_extension;
-#endif
-
-#ifdef  XP_WIN
-    bool     isValidFolder(nsIRDFResource *source);
-    nsresult getIEFavoriteURL(nsIRDFResource *source, nsString aFileURL, nsIRDFLiteral **urlLiteral);
-    nsCOMPtr<nsIRDFResource>       mNC_IEFavoriteObject;
-    nsCOMPtr<nsIRDFResource>       mNC_IEFavoriteFolder;
-    nsCString                      ieFavoritesDir;
-#endif
-};
-
-#endif // nsFileSystemDataSource_h__
--- a/testing/mozharness/configs/unittests/mac_unittest.py
+++ b/testing/mozharness/configs/unittests/mac_unittest.py
@@ -172,17 +172,18 @@ config = {
     },
     # local reftest suites
     "all_reftest_suites": {
         "crashtest": {
             'options': ["--suite=crashtest"],
             'tests': ["tests/reftest/tests/testing/crashtest/crashtests.list"]
         },
         "jsreftest": {
-            'options':["--extra-profile-file=tests/jsreftest/tests/user.js"],
+            'options':["--extra-profile-file=tests/jsreftest/tests/user.js",
+                       "--suite=jstestbrowser"],
             'tests': ["tests/jsreftest/tests/jstests.list"]
         },
         "reftest": {
             'options': ["--suite=reftest"],
             'tests': ["tests/reftest/tests/layout/reftests/reftest.list"]
         },
         "reftest-stylo": {
             "options": ["--suite=reftest",
--- a/testing/mozharness/configs/unittests/win_taskcluster_unittest.py
+++ b/testing/mozharness/configs/unittests/win_taskcluster_unittest.py
@@ -196,17 +196,18 @@ config = {
     },
     # local reftest suites
     "all_reftest_suites": {
         "crashtest": {
             'options': ["--suite=crashtest"],
             'tests': ["tests/reftest/tests/testing/crashtest/crashtests.list"]
         },
         "jsreftest": {
-            'options':["--extra-profile-file=tests/jsreftest/tests/user.js"],
+            'options':["--extra-profile-file=tests/jsreftest/tests/user.js",
+                       "--suite=jstestbrowser"],
             'tests': ["tests/jsreftest/tests/jstests.list"]
         },
         "reftest": {
             'options': ["--suite=reftest"],
             'tests': ["tests/reftest/tests/layout/reftests/reftest.list"]
         },
         "reftest-gpu": {
             'options': ["--suite=reftest",
--- a/testing/mozharness/configs/unittests/win_unittest.py
+++ b/testing/mozharness/configs/unittests/win_unittest.py
@@ -184,17 +184,18 @@ config = {
     },
     # local reftest suites
     "all_reftest_suites": {
         "crashtest": {
             'options': ["--suite=crashtest"],
             'tests': ["tests/reftest/tests/testing/crashtest/crashtests.list"]
         },
         "jsreftest": {
-            'options':["--extra-profile-file=tests/jsreftest/tests/user.js"],
+            'options':["--extra-profile-file=tests/jsreftest/tests/user.js",
+                       "--suite=jstestbrowser"],
             'tests': ["tests/jsreftest/tests/jstests.list"]
         },
         "reftest": {
             'options': ["--suite=reftest"],
             'tests': ["tests/reftest/tests/layout/reftests/reftest.list"]
         },
         "reftest-gpu": {
             'options': ["--suite=reftest",
--- a/testing/mozharness/mozharness/mozilla/testing/verify_tools.py
+++ b/testing/mozharness/mozharness/mozilla/testing/verify_tools.py
@@ -26,16 +26,17 @@ verify_config_options = [
 
 class VerifyToolsMixin(object):
     """Utility functions for test verification."""
 
     def __init__(self):
         self.verify_suites = {}
         self.verify_downloaded = False
         self.reftest_test_dir = None
+        self.jsreftest_test_dir = None
 
     def _find_misc_tests(self, dirs, changed_files):
         manifests = [
             (os.path.join(dirs['abs_mochitest_dir'], 'tests', 'mochitest.ini'), 'plain'),
             (os.path.join(dirs['abs_mochitest_dir'], 'chrome', 'chrome.ini'), 'chrome'),
             (os.path.join(dirs['abs_mochitest_dir'], 'browser', 'browser-chrome.ini'), 'browser-chrome'),
             (os.path.join(dirs['abs_mochitest_dir'], 'a11y', 'a11y.ini'), 'a11y'),
             (os.path.join(dirs['abs_xpcshell_dir'], 'tests', 'xpcshell.ini'), 'xpcshell'),
@@ -46,28 +47,47 @@ class VerifyToolsMixin(object):
                 man = TestManifest([path], strict=False)
                 active = man.active_tests(exists=False, disabled=False, filters=[], **mozinfo.info)
                 tests_by_path.update({t['relpath']:(suite,t.get('subsuite')) for t in active})
                 self.info("Verification updated with manifest %s" % path)
 
         ref_manifests = [
             (os.path.join(dirs['abs_reftest_dir'], 'tests', 'layout', 'reftests', 'reftest.list'), 'reftest'),
             (os.path.join(dirs['abs_reftest_dir'], 'tests', 'testing', 'crashtest', 'crashtests.list'), 'crashtest'),
-            # TODO (os.path.join(dirs['abs_test_install_dir'], 'jsreftest', 'tests', 'jstests.list'), 'jstestbrowser'),
         ]
         sys.path.append(dirs['abs_reftest_dir'])
         import manifest
         self.reftest_test_dir = os.path.join(dirs['abs_reftest_dir'], 'tests')
         for (path, suite) in ref_manifests:
             if os.path.exists(path):
                 man = manifest.ReftestManifest()
                 man.load(path)
                 tests_by_path.update({os.path.relpath(t,self.reftest_test_dir):(suite,None) for t in man.files})
                 self.info("Verification updated with manifest %s" % path)
 
+        suite = 'jsreftest'
+        self.jsreftest_test_dir = os.path.join(dirs['abs_test_install_dir'], 'jsreftest', 'tests')
+        path = os.path.join(self.jsreftest_test_dir, 'jstests.list')
+        if os.path.exists(path):
+            man = manifest.ReftestManifest()
+            man.load(path)
+            for t in man.files:
+                # expect manifest test to look like:
+                #    ".../tests/jsreftest/tests/jsreftest.html?test=test262/.../some_test.js"
+                # while the test is in mercurial at:
+                #    js/src/tests/test262/.../some_test.js
+                epos = t.find('=')
+                if epos > 0:
+                    relpath = t[epos+1:]
+                    relpath = os.path.join('js', 'src', 'tests', relpath)
+                    tests_by_path.update({relpath:(suite,None)})
+                else:
+                    self.warning("unexpected jsreftest test format: %s" % str(t))
+            self.info("Verification updated with manifest %s" % path)
+
         # for each changed file, determine if it is a test file, and what suite it is in
         for file in changed_files:
             # manifest paths use os.sep (like backslash on Windows) but
             # automation-relevance uses posixpath.sep
             file = file.replace(posixpath.sep, os.sep)
             entry = tests_by_path.get(file)
             if entry:
                 self.info("Verification found test %s" % file)
@@ -186,20 +206,27 @@ class VerifyToolsMixin(object):
             # not in verify mode: run once, with no additional args
             args = [[]]
         else:
             # in verify mode, run nothing by default (unsupported suite or no files modified)
             args = []
             # otherwise, run once for each file in requested suite
             references = re.compile(r"(-ref|-noref|-noref.)\.")
             files = []
+            jsreftest_extra_dir = os.path.join('js', 'src', 'tests')
+            # For some suites, the test path needs to be updated before passing to
+            # the test harness.
             for file in self.verify_suites.get(suite):
                 if (self.config.get('verify_category') != "web-platform" and
                     suite in ['reftest', 'crashtest']):
                     file = os.path.join(self.reftest_test_dir, file)
+                elif (self.config.get('verify_category') != "web-platform" and
+                      suite == 'jsreftest'):
+                    file = os.path.relpath(file, jsreftest_extra_dir)
+                    file = os.path.join(self.jsreftest_test_dir, file)
                 files.append(file)
             for file in files:
                 if self.config.get('verify_category') == "web-platform":
                     args.append(['--verify-log-full', '--verify', file])
                 else:
                     if suite == 'reftest':
                         # Special handling for modified reftest reference files:
                         #  - if both test and reference modified, verify the test file
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[instantiation-error-3.html]
-  disabled: bug 1426195
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/module-in-xhtml.xhtml.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[module-in-xhtml.xhtml]
-  type: testharness
-  [module script in XHTML documents should be evaluated.]
-    expected: FAIL
-
--- a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/instantiation-error-3.html
@@ -23,9 +23,9 @@
 
     function unreachable() { log.push("unexpected"); }
 </script>
 <script type="module" src="./cycle-unresolvable.js"
     onerror="unreachable()" onload="log.push(1)" nomodule></script>
 <script type="module" src="./cycle-unresolvable-a.js"
     onerror="unreachable()" onload="log.push(2)"></script>
 <script type="module" src="./cycle-unresolvable.js"
-    onerror="unreachable()" onload="log.push(3)" async></script>
+    onerror="unreachable()" onload="log.push(3)"></script>
--- a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-in-xhtml.xhtml
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/module/module-in-xhtml.xhtml
@@ -5,12 +5,16 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 </head>
 <body>
 <script type="module">
 window.evaluated_module_script = true;
 </script>
 <script>
-test(() => assert_true(window.evaluated_module_script), "module script in XHTML documents should be evaluated.");
+  var test = async_test("module script in XHTML documents should be evaluated.");
+  window.addEventListener("load", () => {
+    test.step(() => { assert_true(window.evaluated_module_script); });
+    test.done();
+  });
 </script>
 </body>
 </html>
deleted file mode 100644
--- a/toolkit/content/findUtils.js
+++ /dev/null
@@ -1,106 +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/. */
-
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-var gFindBundle;
-
-function nsFindInstData() {}
-nsFindInstData.prototype =
-{
-  // set the next three attributes on your object to override the defaults
-  browser: null,
-
-  get rootSearchWindow() { return this._root || this.window.content; },
-  set rootSearchWindow(val) { this._root = val; },
-
-  get currentSearchWindow() {
-    if (this._current)
-      return this._current;
-
-    var focusedWindow = this.window.document.commandDispatcher.focusedWindow;
-    if (!focusedWindow || focusedWindow == this.window)
-      focusedWindow = this.window.content;
-
-    return focusedWindow;
-  },
-  set currentSearchWindow(val) { this._current = val; },
-
-  get webBrowserFind() { return this.browser.webBrowserFind; },
-
-  init() {
-    var findInst = this.webBrowserFind;
-    // set up the find to search the focussedWindow, bounded by the content window.
-    var findInFrames = findInst.QueryInterface(Components.interfaces.nsIWebBrowserFindInFrames);
-    findInFrames.rootSearchFrame = this.rootSearchWindow;
-    findInFrames.currentSearchFrame = this.currentSearchWindow;
-
-    // always search in frames for now. We could add a checkbox to the dialog for this.
-    findInst.searchFrames = true;
-  },
-
-  window,
-  _root: null,
-  _current: null
-};
-
-// browser is the <browser> element
-// rootSearchWindow is the window to constrain the search to (normally window.content)
-// currentSearchWindow is the frame to start searching (can be, and normally, rootSearchWindow)
-function findInPage(findInstData) {
-  // is the dialog up already?
-  if ("findDialog" in window && window.findDialog)
-    window.findDialog.focus();
-  else {
-    findInstData.init();
-    window.findDialog = window.openDialog("chrome://global/content/finddialog.xul", "_blank", "chrome,resizable=no,dependent=yes", findInstData);
-  }
-}
-
-function findAgainInPage(findInstData, reverse) {
-  if ("findDialog" in window && window.findDialog)
-    window.findDialog.focus();
-  else {
-    // get the find service, which stores global find state, and init the
-    // nsIWebBrowser find with it. We don't assume that there was a previous
-    // Find that set this up.
-    var findService = Components.classes["@mozilla.org/find/find_service;1"]
-                           .getService(Components.interfaces.nsIFindService);
-
-    var searchString = findService.searchString;
-    if (searchString.length == 0) {
-      // no previous find text
-      findInPage(findInstData);
-      return;
-    }
-
-    findInstData.init();
-    var findInst = findInstData.webBrowserFind;
-    findInst.searchString  = searchString;
-    findInst.matchCase     = findService.matchCase;
-    findInst.wrapFind      = findService.wrapFind;
-    findInst.entireWord    = findService.entireWord;
-    findInst.findBackwards = findService.findBackwards ^ reverse;
-
-    var found = findInst.findNext();
-    if (!found) {
-      if (!gFindBundle)
-        gFindBundle = document.getElementById("findBundle");
-
-      Services.prompt.alert(window, gFindBundle.getString("notFoundTitle"), gFindBundle.getString("notFoundWarning"));
-    }
-
-    // Reset to normal value, otherwise setting can get changed in find dialog
-    findInst.findBackwards = findService.findBackwards;
-  }
-}
-
-function canFindAgainInPage() {
-    var findService = Components.classes["@mozilla.org/find/find_service;1"]
-                           .getService(Components.interfaces.nsIFindService);
-    return (findService.searchString.length > 0);
-}
-
deleted file mode 100644
--- a/toolkit/content/finddialog.js
+++ /dev/null
@@ -1,144 +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/. */
-
-// Defined in dialog.xml.
-/* globals moveToAlertPosition */
-
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource://gre/modules/FormHistory.jsm");
-
-var dialog; // Quick access to document/form elements.
-var gFindInst; // nsIWebBrowserFind that we're going to use
-var gFindInstData; // use this to update the find inst data
-
-function initDialogObject() {
-  // Create dialog object and initialize.
-  dialog = {};
-  dialog.findKey         = document.getElementById("dialog.findKey");
-  dialog.caseSensitive   = document.getElementById("dialog.caseSensitive");
-  dialog.wrap            = document.getElementById("dialog.wrap");
-  dialog.find            = document.getElementById("btnFind");
-  dialog.up              = document.getElementById("radioUp");
-  dialog.down            = document.getElementById("radioDown");
-  dialog.rg              = dialog.up.radioGroup;
-  dialog.bundle          = null;
-
-  // Move dialog to center, if it not been shown before
-  var windowElement = document.getElementById("findDialog");
-  if (!windowElement.hasAttribute("screenX") || !windowElement.hasAttribute("screenY")) {
-    sizeToContent();
-    moveToAlertPosition();
-  }
-}
-
-function fillDialog() {
-  // get the find service, which stores global find state
-  var findService = Components.classes["@mozilla.org/find/find_service;1"]
-                              .getService(Components.interfaces.nsIFindService);
-
-  // Set initial dialog field contents. Use the gFindInst attributes first,
-  // this is necessary for window.find()
-  dialog.findKey.value           = gFindInst.searchString ? gFindInst.searchString : findService.searchString;
-  dialog.caseSensitive.checked   = gFindInst.matchCase ? gFindInst.matchCase : findService.matchCase;
-  dialog.wrap.checked            = gFindInst.wrapFind ? gFindInst.wrapFind : findService.wrapFind;
-  var findBackwards              = gFindInst.findBackwards ? gFindInst.findBackwards : findService.findBackwards;
-  if (findBackwards)
-    dialog.rg.selectedItem = dialog.up;
-  else
-    dialog.rg.selectedItem = dialog.down;
-}
-
-function saveFindData() {
-  // get the find service, which stores global find state
-  var findService = Components.classes["@mozilla.org/find/find_service;1"]
-                         .getService(Components.interfaces.nsIFindService);
-
-  // Set data attributes per user input.
-  findService.searchString  = dialog.findKey.value;
-  findService.matchCase     = dialog.caseSensitive.checked;
-  findService.wrapFind      = dialog.wrap.checked;
-  findService.findBackwards = dialog.up.selected;
-}
-
-function onLoad() {
-  initDialogObject();
-
-  // get the find instance
-  var arg0 = window.arguments[0];
-  // If the dialog was opened from window.find(),
-  // arg0 will be an instance of nsIWebBrowserFind
-  if (arg0 instanceof Components.interfaces.nsIWebBrowserFind) {
-    gFindInst = arg0;
-  } else {
-    gFindInstData = arg0;
-    gFindInst = gFindInstData.webBrowserFind;
-  }
-
-  fillDialog();
-  doEnabling();
-
-  if (dialog.findKey.value)
-    dialog.findKey.select();
-  dialog.findKey.focus();
-}
-
-function onUnload() {
-  window.opener.findDialog = 0;
-}
-
-function onAccept() {
-  if (gFindInstData && gFindInst != gFindInstData.webBrowserFind) {
-    gFindInstData.init();
-    gFindInst = gFindInstData.webBrowserFind;
-  }
-
-  // Transfer dialog contents to the find service.
-  saveFindData();
-  updateFormHistory();
-
-  // set up the find instance
-  gFindInst.searchString  = dialog.findKey.value;
-  gFindInst.matchCase     = dialog.caseSensitive.checked;
-  gFindInst.wrapFind      = dialog.wrap.checked;
-  gFindInst.findBackwards = dialog.up.selected;
-
-  // Search.
-  var result = gFindInst.findNext();
-
-  if (!result) {
-    if (!dialog.bundle)
-      dialog.bundle = document.getElementById("findBundle");
-    Services.prompt.alert(window, dialog.bundle.getString("notFoundTitle"),
-                                  dialog.bundle.getString("notFoundWarning"));
-    dialog.findKey.select();
-    dialog.findKey.focus();
-  }
-  return false;
-}
-
-function doEnabling() {
-  dialog.find.disabled = !dialog.findKey.value;
-}
-
-function updateFormHistory() {
-  if (window.opener.PrivateBrowsingUtils &&
-      window.opener.PrivateBrowsingUtils.isWindowPrivate(window.opener) ||
-      !dialog.findKey.value)
-    return;
-
-  if (FormHistory.enabled) {
-    FormHistory.update({
-      op: "bump",
-      fieldname: "find-dialog",
-      value: dialog.findKey.value
-    }, {
-      handleError(aError) {
-        Components.utils.reportError("Saving find to form history failed: " +
-                                     aError.message);
-      }
-    });
-  }
-}
deleted file mode 100644
--- a/toolkit/content/finddialog.xul
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0"?> <!-- -*- Mode: HTML -*- -->
-# 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/.
-
-<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-
-<!DOCTYPE window SYSTEM "chrome://global/locale/finddialog.dtd">
-
-<dialog id="findDialog"
-  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-  orient="horizontal"
-  windowtype="findInPage"
-  onload="onLoad();"
-  onunload="onUnload();"
-  ondialogaccept="return onAccept();"
-  buttons="accept,cancel"
-  title="&findDialog.title;"
-  persist="screenX screenY">
-
-  <script type="application/javascript" src="chrome://global/content/finddialog.js"/>
-  <stringbundle id="findBundle" src="chrome://global/locale/finddialog.properties"/>
-
-  <hbox>
-    <vbox>
-      <hbox align="center">
-        <label value="&findField.label;" accesskey="&findField.accesskey;" control="dialog.findKey"/>
-        <textbox id="dialog.findKey" flex="1"
-                 type="autocomplete"
-                 autocompletesearch="form-history"
-                 autocompletesearchparam="find-dialog"
-                 oninput="doEnabling();"/>
-      </hbox>
-      <hbox align="center">
-        <vbox>
-          <checkbox id="dialog.caseSensitive" label="&caseSensitiveCheckbox.label;" accesskey="&caseSensitiveCheckbox.accesskey;"/>
-          <checkbox id="dialog.wrap" label="&wrapCheckbox.label;" accesskey="&wrapCheckbox.accesskey;" checked="true"/>
-        </vbox>
-        <groupbox orient="horizontal">
-          <caption label="&direction.label;"/>
-          <radiogroup orient="horizontal">
-            <radio id="radioUp" label="&up.label;" accesskey="&up.accesskey;"/>
-            <radio id="radioDown" label="&down.label;" accesskey="&down.accesskey;" selected="true"/>
-          </radiogroup>
-        </groupbox>
-      </hbox>
-    </vbox>
-    <vbox>
-      <button id="btnFind" label="&findButton.label;" accesskey="&findButton.accesskey;"
-              dlgtype="accept" icon="find"/>
-#ifdef XP_UNIX
-      <button label="&closeButton.label;" icon="close" dlgtype="cancel"/>
-#else
-      <button label="&cancelButton.label;" icon="cancel" dlgtype="cancel"/>
-#endif
-    </vbox>
-  </hbox>
-</dialog>
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -44,19 +44,16 @@ toolkit.jar:
    content/global/customizeToolbar.css
    content/global/customizeToolbar.js
    content/global/customizeToolbar.xul
 #endif
    content/global/datepicker.xhtml
 #ifndef MOZ_FENNEC
    content/global/editMenuOverlay.js
 *  content/global/editMenuOverlay.xul
-   content/global/finddialog.js
-*  content/global/finddialog.xul
-   content/global/findUtils.js
 #endif
    content/global/filepicker.properties
    content/global/globalOverlay.js
    content/global/mozilla.xhtml
    content/global/process-content.js
    content/global/resetProfile.css
    content/global/resetProfile.js
    content/global/resetProfile.xul
--- a/toolkit/content/moz.build
+++ b/toolkit/content/moz.build
@@ -258,19 +258,16 @@ with Files('*picker*'):
     BUG_COMPONENT = ('Toolkit', 'XUL Widgets')
 
 with Files('direction*'):
     BUG_COMPONENT = ('Toolkit', 'XUL Widgets')
 
 with Files('edit*'):
     BUG_COMPONENT = ('Toolkit', 'XUL Widgets')
 
-with Files('*find*'):
-    BUG_COMPONENT = ('Toolkit', 'Find Toolbar')
-
 with Files('globalOverlay.*'):
     BUG_COMPONENT = ('Toolkit', 'General')
 
 with Files('menulist.css'):
     BUG_COMPONENT = ('Toolkit', 'XUL Widgets')
 
 with Files('minimal-xul.css'):
     BUG_COMPONENT = ('Toolkit', 'XUL Widgets')
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -1106,16 +1106,17 @@ datepicker[type="grid"] {
 timepicker {
   -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#timepicker');
 }
 
 
 /*********** findbar ************/
 findbar {
   -moz-binding: url('chrome://global/content/bindings/findbar.xml#findbar');
+  overflow-x: hidden;
 }
 
 .findbar-textbox {
   -moz-binding: url("chrome://global/content/bindings/findbar.xml#findbar-textbox");
 }
 
 
 /*********** filefield ************/
deleted file mode 100644
--- a/toolkit/locales/en-US/chrome/global/finddialog.dtd
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- extracted from finddialog.xul -->
-
-<!ENTITY findDialog.title "Find in This Page">
-<!ENTITY findField.label "Find what:">
-<!ENTITY findField.accesskey "n">
-<!ENTITY caseSensitiveCheckbox.label "Match case">
-<!ENTITY caseSensitiveCheckbox.accesskey "c">
-<!ENTITY wrapCheckbox.label "Wrap">
-<!ENTITY wrapCheckbox.accesskey "W">
-<!ENTITY findButton.label "Find Next">
-<!ENTITY findButton.accesskey "F">
-<!ENTITY cancelButton.label "Cancel">
-<!ENTITY closeButton.label "Close">
-<!ENTITY up.label "Up">
-<!ENTITY up.accesskey "U">
-<!ENTITY down.label "Down">
-<!ENTITY down.accesskey "D">
-<!ENTITY direction.label "Direction">
deleted file mode 100644
--- a/toolkit/locales/en-US/chrome/global/finddialog.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-notFoundWarning=The text you entered was not found.
-notFoundTitle=Find
\ No newline at end of file
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -49,18 +49,16 @@
   locale/@AB_CD@/global/filefield.properties            (%chrome/global/filefield.properties)
 #ifdef MOZ_GTK
   locale/@AB_CD@/global/filepicker.dtd                  (%chrome/global/filepicker.dtd)
 #endif
   locale/@AB_CD@/global/filepicker.properties           (%chrome/global/filepicker.properties)
 #ifndef MOZ_FENNEC
   locale/@AB_CD@/global/findbar.dtd                     (%chrome/global/findbar.dtd)
   locale/@AB_CD@/global/findbar.properties              (%chrome/global/findbar.properties)
-  locale/@AB_CD@/global/finddialog.dtd                  (%chrome/global/finddialog.dtd)
-  locale/@AB_CD@/global/finddialog.properties           (%chrome/global/finddialog.properties)
 #endif
   locale/@AB_CD@/global/globalKeys.dtd                  (%chrome/global/globalKeys.dtd)
   locale/@AB_CD@/global/intl.css                        (%chrome/global/intl.css)
   locale/@AB_CD@/global/intl.properties                 (%chrome/global/intl.properties)
   locale/@AB_CD@/global/keys.properties                 (%chrome/global/keys.properties)
   locale/@AB_CD@/global/languageNames.properties        (%chrome/global/languageNames.properties)
   locale/@AB_CD@/global/mozilla.dtd                     (%chrome/global/mozilla.dtd)
 #ifndef MOZ_FENNEC
--- a/toolkit/themes/osx/global/dialog.css
+++ b/toolkit/themes/osx/global/dialog.css
@@ -18,19 +18,8 @@ dialog {
 }
 
 /* ::::: dialog buttons ::::: */
 
 .dialog-button {
   font: menu;
 }
 
-/*XXX - belongs to toolkit/content/finddialog.xul: */
-
-#findDialog,
-#findDialog > menu,
-#findDialog > groupbox {
-  font: menu !important;
-}
-
-#dialog\.caseSensitive {
-  margin-top: 8px;
-}