Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 28 Aug 2017 17:38:53 -0700
changeset 377280 1b4c59eef820
parent 377231 3529b653ede2 (current diff)
parent 377279 8be5e1248645 (diff)
child 377281 1e816df81bc6
child 377349 938f3700c6e7
child 377455 18bf4e0a7ec7
push id32405
push userkwierso@gmail.com
push date2017-08-29 00:39 +0000
treeherdermozilla-central@1b4c59eef820 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone57.0a1
first release with
nightly linux32
1b4c59eef820 / 57.0a1 / 20170829100404 / files
nightly linux64
1b4c59eef820 / 57.0a1 / 20170829100404 / files
nightly mac
1b4c59eef820 / 57.0a1 / 20170829100404 / files
nightly win32
1b4c59eef820 / 57.0a1 / 20170829100404 / files
nightly win64
1b4c59eef820 / 57.0a1 / 20170829100404 / 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 autoland to central, a=merge MozReview-Commit-ID: 5tolFjvaHmd
testing/web-platform/meta/encoding/replacement-encodings.html.ini
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -171,17 +171,16 @@ var whitelist = [
   // Bug 1351079
   {file: "resource://gre/modules/ISO8601DateUtils.jsm"},
   // Bug 1337345
   {file: "resource://gre/modules/Manifest.jsm"},
   // Bug 1351097
   {file: "resource://gre/modules/accessibility/AccessFu.jsm"},
   // Bug 1351637
   {file: "resource://gre/modules/sdk/bootstrap.js"},
-
 ];
 
 whitelist = new Set(whitelist.filter(item =>
   ("isFromDevTools" in item) == isDevtools &&
   (!item.skipNightly || !AppConstants.NIGHTLY_BUILD) &&
   (!item.platforms || item.platforms.includes(AppConstants.platform))
 ).map(item => item.file));
 
@@ -473,17 +472,18 @@ function findChromeUrlsFromArray(array, 
                        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))
+        /resource:\/\/gre.*\.[a-z]+/.test(string) ||
+        string.startsWith("resource://content-accessible/"))
       gReferencesFromCode.add(string);
   }
 }
 
 add_task(async function checkAllTheFiles() {
   let libxulPath = OS.Constants.Path.libxul;
   if (AppConstants.platform != "macosx")
     libxulPath = OS.Constants.Path.libDir + "/" + libxulPath;
@@ -535,16 +535,18 @@ add_task(async function checkAllTheFiles
   // Wait for all the files to have actually loaded:
   await Promise.all(allPromises);
 
   // Keep only chrome:// files, and filter out either the devtools paths or
   // 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 chromeFiles = [];
   for (let uri of uris) {
     uri = convertToCodeURI(uri.spec);
     if ((uri.startsWith("chrome://") || uri.startsWith("resource://")) &&
         isDevtools == devtoolsPrefixes.some(prefix => uri.startsWith(prefix)))
       chromeFiles.push(uri);
--- a/browser/base/content/test/static/browser_parsable_css.js
+++ b/browser/base/content/test/static/browser_parsable_css.js
@@ -12,17 +12,17 @@
 let whitelist = [
   // CodeMirror is imported as-is, see bug 1004423.
   {sourceName: /codemirror\.css$/i,
    isFromDevTools: true},
   // The debugger uses cross-browser CSS.
   {sourceName: /devtools\/client\/debugger\/new\/debugger.css/i,
    isFromDevTools: true},
    // Reps uses cross-browser CSS.
-   {sourceName: /devtools\/client\/shared\/components\/reps\/reps.css/i,
+   {sourceName: /devtools-client-shared\/components\/reps\/reps.css/i,
    isFromDevTools: true},
   // PDFjs is futureproofing its pseudoselectors, and those rules are dropped.
   {sourceName: /web\/viewer\.css$/i,
    errorMessage: /Unknown pseudo-class.*(fullscreen|selection)/i,
    isFromDevTools: false},
   // PDFjs rules needed for compat with other UAs.
   {sourceName: /web\/viewer\.css$/i,
    errorMessage: /Unknown property.*appearance/i,
@@ -283,51 +283,59 @@ add_task(async function checkAllTheCSS()
       manifestPromises.push(parseManifest(uri));
       return false;
     }
     return true;
   });
   // Wait for all manifest to be parsed
   await Promise.all(manifestPromises);
 
-  // We build a list of promises that get resolved when their respective
-  // files have loaded and produced no errors.
-  let allPromises = [];
-
   // filter out either the devtools paths or the non-devtools paths:
   let isDevtools = SimpleTest.harnessParameters.subsuite == "devtools";
   let devtoolsPathBits = ["webide", "devtools"];
   uris = uris.filter(uri => isDevtools == devtoolsPathBits.some(path => uri.spec.includes(path)));
 
-  for (let uri of uris) {
-    let linkEl = doc.createElement("link");
+  let loadCSS = chromeUri => new Promise(resolve => {
+    let linkEl, onLoad, onError;
+    onLoad = e => {
+      processCSSRules(linkEl.sheet);
+      resolve();
+      linkEl.removeEventListener("load", onLoad);
+      linkEl.removeEventListener("error", onError);
+    };
+    onError = e => {
+      ok(false, "Loading " + linkEl.getAttribute("href") + " threw an error!");
+      resolve();
+      linkEl.removeEventListener("load", onLoad);
+      linkEl.removeEventListener("error", onError);
+    };
+    linkEl = doc.createElement("link");
     linkEl.setAttribute("rel", "stylesheet");
-    allPromises.push(new Promise(resolve => {
-      let onLoad = (e) => {
-        processCSSRules(linkEl.sheet);
-        resolve();
-        linkEl.removeEventListener("load", onLoad);
-        linkEl.removeEventListener("error", onError);
-      };
-      let onError = (e) => {
-        ok(false, "Loading " + linkEl.getAttribute("href") + " threw an error!");
-        resolve();
-        linkEl.removeEventListener("load", onLoad);
-        linkEl.removeEventListener("error", onError);
-      };
-      linkEl.addEventListener("load", onLoad);
-      linkEl.addEventListener("error", onError);
-      linkEl.setAttribute("type", "text/css");
-      let chromeUri = convertToCodeURI(uri.spec);
-      linkEl.setAttribute("href", chromeUri + kPathSuffix);
-    }));
+    linkEl.setAttribute("type", "text/css");
+    linkEl.addEventListener("load", onLoad);
+    linkEl.addEventListener("error", onError);
+    linkEl.setAttribute("href", chromeUri + kPathSuffix);
     doc.head.appendChild(linkEl);
+  });
+
+  // We build a list of promises that get resolved when their respective
+  // files have loaded and produced no errors.
+  const kInContentCommonCSS = "chrome://global/skin/in-content/common.css";
+  let allPromises = uris.map((uri) => convertToCodeURI(uri.spec))
+                    .filter((uri) => uri !== kInContentCommonCSS);
+
+  // Make sure chrome://global/skin/in-content/common.css is loaded before other
+  // stylesheets in order to guarantee the --in-content variables can be
+  // correctly referenced.
+  if (allPromises.length !== uris.length) {
+    await loadCSS(kInContentCommonCSS);
   }
 
   // Wait for all the files to have actually loaded:
+  allPromises = allPromises.map(loadCSS);
   await Promise.all(allPromises);
 
   // Check if all the files referenced from CSS actually exist.
   for (let [image, references] of imageURIsToReferencesMap) {
     if (!chromeFileExists(image)) {
       for (let ref of references) {
         let ignored = false;
         for (let item of allowedImageReferences) {
--- a/browser/components/extensions/ext-c-devtools-panels.js
+++ b/browser/components/extensions/ext-c-devtools-panels.js
@@ -128,24 +128,142 @@ class ChildDevToolsPanel extends Extensi
     this.mm.removeMessageListener("Extension:DevToolsPanelShown", this);
     this.mm.removeMessageListener("Extension:DevToolsPanelHidden", this);
 
     this._panelContext = null;
     this.context = null;
   }
 }
 
+/**
+ * Represents an addon devtools inspector sidebar in the child process.
+ *
+ * @param {DevtoolsExtensionContext}
+ *   A devtools extension context running in a child process.
+ * @param {object} sidebarOptions
+ * @param {string} sidebarOptions.id
+ *   The id of the addon devtools sidebar registered in the main process.
+ */
+class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
+  constructor(context, {id}) {
+    super();
+
+    this.context = context;
+    this.context.callOnClose(this);
+
+    this.id = id;
+
+    this.mm = context.messageManager;
+    this.mm.addMessageListener("Extension:DevToolsInspectorSidebarShown", this);
+    this.mm.addMessageListener("Extension:DevToolsInspectorSidebarHidden", this);
+  }
+
+  close() {
+    this.mm.removeMessageListener("Extension:DevToolsInspectorSidebarShown", this);
+    this.mm.removeMessageListener("Extension:DevToolsInspectorSidebarHidden", this);
+
+    this.content = null;
+  }
+
+  receiveMessage({name, data}) {
+    // Filter out any message that is not related to the id of this
+    // toolbox panel.
+    if (!data || data.inspectorSidebarId !== this.id) {
+      return;
+    }
+
+    switch (name) {
+      case "Extension:DevToolsInspectorSidebarShown":
+        this.onParentSidebarShown();
+        break;
+      case "Extension:DevToolsInspectorSidebarHidden":
+        this.onParentSidebarHidden();
+        break;
+    }
+  }
+
+  onParentSidebarShown() {
+    // TODO: wait and emit sidebar contentWindow once sidebar.setPage is supported.
+    this.emit("shown");
+  }
+
+  onParentSidebarHidden() {
+    this.emit("hidden");
+  }
+
+  api() {
+    const {context, id} = this;
+
+    return {
+      onShown: new EventManager(
+        context, "devtoolsInspectorSidebar.onShown", fire => {
+          const listener = (eventName, panelContentWindow) => {
+            fire.asyncWithoutClone(panelContentWindow);
+          };
+          this.on("shown", listener);
+          return () => {
+            this.off("shown", listener);
+          };
+        }).api(),
+
+      onHidden: new EventManager(
+        context, "devtoolsInspectorSidebar.onHidden", fire => {
+          const listener = () => {
+            fire.async();
+          };
+          this.on("hidden", listener);
+          return () => {
+            this.off("hidden", listener);
+          };
+        }).api(),
+
+      setObject(jsonObject, rootTitle) {
+        return context.cloneScope.Promise.resolve().then(() => {
+          return context.childManager.callParentAsyncFunction(
+            "devtools.panels.elements.Sidebar.setObject",
+            [id, jsonObject, rootTitle]
+          );
+        });
+      },
+    };
+  }
+}
+
 this.devtools_panels = class extends ExtensionAPI {
   getAPI(context) {
     const themeChangeObserver = ExtensionChildDevToolsUtils.getThemeChangeObserver();
 
     return {
       devtools: {
         panels: {
+          elements: {
+            createSidebarPane(title) {
+              // NOTE: this is needed to be able to return to the caller (the extension)
+              // a promise object that it had the privileges to use (e.g. by marking this
+              // method async we will return a promise object which can only be used by
+              // chrome privileged code).
+              return context.cloneScope.Promise.resolve().then(async () => {
+                const sidebarId = await context.childManager.callParentAsyncFunction(
+                  "devtools.panels.elements.createSidebarPane", [title]);
+
+                const sidebar = new ChildDevToolsInspectorSidebar(context, {id: sidebarId});
+
+                const sidebarAPI = Cu.cloneInto(sidebar.api(),
+                                                context.cloneScope,
+                                                {cloneFunctions: true});
+
+                return sidebarAPI;
+              });
+            },
+          },
           create(title, icon, url) {
+            // NOTE: this is needed to be able to return to the caller (the extension)
+            // a promise object that it had the privileges to use (e.g. by marking this
+            // method async we will return a promise object which can only be used by
+            // chrome privileged code).
             return context.cloneScope.Promise.resolve().then(async () => {
               const panelId = await context.childManager.callParentAsyncFunction(
                 "devtools.panels.create", [title, icon, url]);
 
               const devtoolsPanel = new ChildDevToolsPanel(context, {id: panelId});
 
               const devtoolsPanelAPI = Cu.cloneInto(devtoolsPanel.api(),
                                                     context.cloneScope,
--- a/browser/components/extensions/ext-devtools-panels.js
+++ b/browser/components/extensions/ext-devtools-panels.js
@@ -262,51 +262,185 @@ class DevToolsSelectionObserver extends 
     this.destroyed = true;
   }
 
   onSelected(event) {
     this.emit("selectionChanged");
   }
 }
 
+/**
+ * Represents an addon devtools inspector sidebar in the main process.
+ *
+ * @param {ExtensionChildProxyContext} context
+ *        A devtools extension proxy context running in a main process.
+ * @param {object} options
+ * @param {string} options.id
+ *        The id of the addon devtools sidebar.
+ * @param {string} options.title
+ *        The title of the addon devtools sidebar.
+ */
+class ParentDevToolsInspectorSidebar {
+  constructor(context, sidebarOptions) {
+    const toolbox = context.devToolsToolbox;
+    if (!toolbox) {
+      // This should never happen when this constructor is called with a valid
+      // devtools extension context.
+      throw Error("Missing mandatory toolbox");
+    }
+
+    this.extension = context.extension;
+    this.visible = false;
+    this.destroyed = false;
+
+    this.toolbox = toolbox;
+    this.context = context;
+    this.sidebarOptions = sidebarOptions;
+
+    this.context.callOnClose(this);
+
+    this.id = this.sidebarOptions.id;
+    this.onSidebarSelect = this.onSidebarSelect.bind(this);
+    this.onSidebarCreated = this.onSidebarCreated.bind(this);
+
+    this.toolbox.once(`extension-sidebar-created-${this.id}`, this.onSidebarCreated);
+    this.toolbox.on(`inspector-sidebar-select`, this.onSidebarSelect);
+
+    // Set by setObject if the sidebar has not been created yet.
+    this._initializeSidebar = null;
+
+    this.toolbox.registerInspectorExtensionSidebar(this.id, {
+      title: sidebarOptions.title,
+    });
+  }
+
+  close() {
+    if (this.destroyed) {
+      throw new Error("Unable to close a destroyed DevToolsSelectionObserver");
+    }
+
+    this.toolbox.off(`extension-sidebar-created-${this.id}`, this.onSidebarCreated);
+    this.toolbox.off(`inspector-sidebar-select`, this.onSidebarSelect);
+
+    this.toolbox.unregisterInspectorExtensionSidebar(this.id);
+    this.extensionSidebar = null;
+    this._initializeSidebar = null;
+
+    this.destroyed = true;
+  }
+
+  onSidebarCreated(evt, sidebar) {
+    this.extensionSidebar = sidebar;
+
+    if (typeof this._initializeSidebar === "function") {
+      this._initializeSidebar();
+      this._initializeSidebar = null;
+    }
+  }
+
+  onSidebarSelect(what, id) {
+    if (!this.extensionSidebar) {
+      return;
+    }
+
+    if (!this.visible && id === this.id) {
+      // TODO: Wait for top level context if extension page
+      this.visible = true;
+      this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsInspectorSidebarShown", {
+        inspectorSidebarId: this.id,
+      });
+    } else if (this.visible && id !== this.id) {
+      this.visible = false;
+      this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsInspectorSidebarHidden", {
+        inspectorSidebarId: this.id,
+      });
+    }
+  }
+
+  setObject(object, rootTitle) {
+    // Nest the object inside an object, as the value of the `rootTitle` property.
+    if (rootTitle) {
+      object = {[rootTitle]: object};
+    }
+
+    if (this.extensionSidebar) {
+      this.extensionSidebar.setObject(object);
+    } else {
+      // Defer the sidebar.setObject call.
+      this._initializeSidebar = () => this.extensionSidebar.setObject(object);
+    }
+  }
+}
+
+const sidebarsById = new Map();
+
 this.devtools_panels = class extends ExtensionAPI {
   getAPI(context) {
     // An incremental "per context" id used in the generated devtools panel id.
     let nextPanelId = 0;
 
     const toolboxSelectionObserver = new DevToolsSelectionObserver(context);
 
+    function newBasePanelId() {
+      return `${context.extension.id}-${context.contextId}-${nextPanelId++}`;
+    }
+
     return {
       devtools: {
         panels: {
           elements: {
             onSelectionChanged: new EventManager(
               context, "devtools.panels.elements.onSelectionChanged", fire => {
                 const listener = (eventName) => {
                   fire.async();
                 };
                 toolboxSelectionObserver.on("selectionChanged", listener);
                 return () => {
                   toolboxSelectionObserver.off("selectionChanged", listener);
                 };
               }).api(),
+            createSidebarPane(title) {
+              const id = `devtools-inspector-sidebar-${makeWidgetId(newBasePanelId())}`;
+
+              const parentSidebar = new ParentDevToolsInspectorSidebar(context, {title, id});
+              sidebarsById.set(id, parentSidebar);
+
+              context.callOnClose({
+                close() {
+                  sidebarsById.delete(id);
+                },
+              });
+
+              // Resolved to the devtools sidebar id into the child addon process,
+              // where it will be used to identify the messages related
+              // to the panel API onShown/onHidden events.
+              return Promise.resolve(id);
+            },
+            // The following methods are used internally to allow the sidebar API
+            // piece that is running in the child process to asks the parent process
+            // to execute the sidebar methods.
+            Sidebar: {
+              setObject(sidebarId, jsonObject, rootTitle) {
+                const sidebar = sidebarsById.get(sidebarId);
+                return sidebar.setObject(jsonObject, rootTitle);
+              },
+            },
           },
           create(title, icon, url) {
             // Get a fallback icon from the manifest data.
             if (icon === "" && context.extension.manifest.icons) {
               const iconInfo = IconDetails.getPreferredIcon(context.extension.manifest.icons,
                                                             context.extension, 128);
               icon = iconInfo ? iconInfo.icon : "";
             }
 
             icon = context.extension.baseURI.resolve(icon);
             url = context.extension.baseURI.resolve(url);
 
-            const baseId = `${context.extension.id}-${context.contextId}-${nextPanelId++}`;
-            const id = `${makeWidgetId(baseId)}-devtools-panel`;
+            const id = `${makeWidgetId(newBasePanelId())}-devtools-panel`;
 
             new ParentDevToolsPanel(context, {title, icon, url, id});
 
             // Resolved to the devtools panel id into the child addon process,
             // where it will be used to identify the messages related
             // to the panel API onShown/onHidden events.
             return Promise.resolve(id);
           },
--- a/browser/components/extensions/schemas/devtools_panels.json
+++ b/browser/components/extensions/schemas/devtools_panels.json
@@ -19,17 +19,17 @@
             "name": "onSelectionChanged",
             "type": "function",
             "description": "Fired when an object is selected in the panel."
           }
         ],
         "functions": [
           {
             "name": "createSidebarPane",
-            "unsupported": true,
+            "async": "callback",
             "type": "function",
             "description": "Creates a pane within panel's sidebar.",
             "parameters": [
               {
                 "name": "title",
                 "type": "string",
                 "description": "Text that is displayed in sidebar caption."
               },
@@ -176,16 +176,17 @@
                 "type": "string",
                 "description": "A CSS-like size specification, such as <code>'100px'</code> or <code>'12ex'</code>."
               }
             ]
           },
           {
             "name": "setExpression",
             "unsupported": true,
+            "async": "callback",
             "type": "function",
             "description": "Sets an expression that is evaluated within the inspected page. The result is displayed in the sidebar pane.",
             "parameters": [
               {
                 "name": "expression",
                 "type": "string",
                 "description": "An expression to be evaluated in context of the inspected page. JavaScript objects and DOM nodes are displayed in an expandable tree similar to the console/watch."
               },
@@ -200,17 +201,17 @@
                 "type": "function",
                 "optional": true,
                 "description": "A callback invoked after the sidebar pane is updated with the expression evaluation results."
               }
             ]
           },
           {
             "name": "setObject",
-            "unsupported": true,
+            "async": "callback",
             "type": "function",
             "description": "Sets a JSON-compliant object to be displayed in the sidebar pane.",
             "parameters": [
               {
                 "name": "jsonObject",
                 "type": "string",
                 "description": "An object to be displayed in context of the inspected page. Evaluated in the context of the caller (API client)."
               },
@@ -240,32 +241,30 @@
                 "description": "Relative path of an extension page to display within the sidebar."
               }
             ]
           }
         ],
         "events": [
           {
             "name": "onShown",
-            "unsupported": true,
             "type": "function",
             "description": "Fired when the sidebar pane becomes visible as a result of user switching to the panel that hosts it.",
             "parameters": [
               {
                 "name": "window",
                 "type": "object",
                 "isInstanceOf": "global",
                 "additionalProperties": { "type": "any" },
                 "description": "The JavaScript <code>window</code> object of the sidebar page, if one was set with the <code>setPage()</code> method."
               }
             ]
           },
           {
             "name": "onHidden",
-            "unsupported": true,
             "type": "function",
             "description": "Fired when the sidebar pane becomes hidden as a result of the user switching away from the panel that hosts the sidebar pane."
           }
         ]
       },
       {
         "id": "Button",
         "type": "object",
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -69,16 +69,17 @@ skip-if = (os == 'win' && !debug) # bug 
 [browser_ext_currentWindow.js]
 [browser_ext_devtools_inspectedWindow.js]
 [browser_ext_devtools_inspectedWindow_eval_bindings.js]
 [browser_ext_devtools_inspectedWindow_reload.js]
 [browser_ext_devtools_network.js]
 [browser_ext_devtools_page.js]
 [browser_ext_devtools_panel.js]
 [browser_ext_devtools_panels_elements.js]
+[browser_ext_devtools_panels_elements_sidebar.js]
 [browser_ext_find.js]
 [browser_ext_geckoProfiler_symbolicate.js]
 [browser_ext_getViews.js]
 [browser_ext_identity_indication.js]
 [browser_ext_incognito_views.js]
 [browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
 [browser_ext_menus.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_panels_elements_sidebar.js
@@ -0,0 +1,123 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
+                                  "resource://devtools/client/framework/gDevTools.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "devtools",
+                                  "resource://devtools/shared/Loader.jsm");
+
+add_task(async function test_devtools_panels_elements_sidebar() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
+
+  async function devtools_page() {
+    const sidebar1 = await browser.devtools.panels.elements.createSidebarPane("Test Sidebar 1");
+    const sidebar2 = await browser.devtools.panels.elements.createSidebarPane("Test Sidebar 2");
+
+    const onShownListener = (event, sidebarInstance) => {
+      browser.test.sendMessage(`devtools_sidebar_${event}`, sidebarInstance);
+    };
+
+    sidebar1.onShown.addListener(() => onShownListener("shown", "sidebar1"));
+    sidebar2.onShown.addListener(() => onShownListener("shown", "sidebar2"));
+    sidebar1.onHidden.addListener(() => onShownListener("hidden", "sidebar1"));
+    sidebar2.onHidden.addListener(() => onShownListener("hidden", "sidebar2"));
+
+    sidebar1.setObject({propertyName: "propertyValue"}, "Optional Root Object Title");
+    sidebar2.setObject({anotherPropertyName: 123});
+
+    browser.test.sendMessage("devtools_page_loaded");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      devtools_page: "devtools_page.html",
+    },
+    files: {
+      "devtools_page.html": `<!DOCTYPE html>
+      <html>
+       <head>
+         <meta charset="utf-8">
+       </head>
+       <body>
+         <script src="devtools_page.js"></script>
+       </body>
+      </html>`,
+      "devtools_page.js": devtools_page,
+    },
+  });
+
+  await extension.startup();
+
+  let target = devtools.TargetFactory.forTab(tab);
+
+  const toolbox = await gDevTools.showToolbox(target, "webconsole");
+  info("developer toolbox opened");
+
+  await extension.awaitMessage("devtools_page_loaded");
+
+  const waitInspector = toolbox.once("inspector-selected");
+  toolbox.selectTool("inspector");
+  await waitInspector;
+
+  const sidebarIds = Array.from(toolbox._inspectorExtensionSidebars.keys());
+
+  const inspector = await toolbox.getPanel("inspector");
+
+  inspector.sidebar.show(sidebarIds[0]);
+
+  const shownSidebarInstance = await extension.awaitMessage("devtools_sidebar_shown");
+
+  is(shownSidebarInstance, "sidebar1", "Got the shown event on the first extension sidebar");
+
+  const sidebarPanel1 = inspector.sidebar.getTabPanel(sidebarIds[0]);
+
+  ok(sidebarPanel1, "Got a rendered sidebar panel for the first registered extension sidebar");
+
+  is(sidebarPanel1.querySelectorAll("table.treeTable").length, 1,
+     "The first sidebar panel contains a rendered TreeView component");
+
+  is(sidebarPanel1.querySelectorAll("table.treeTable .stringCell").length, 1,
+     "The TreeView component contains the expected number of string cells.");
+
+  const sidebarPanel1Tree = sidebarPanel1.querySelector("table.treeTable");
+  ok(
+    sidebarPanel1Tree.innerText.includes("Optional Root Object Title"),
+    "The optional root object title has been included in the object tree"
+  );
+
+  inspector.sidebar.show(sidebarIds[1]);
+
+  const shownSidebarInstance2 = await extension.awaitMessage("devtools_sidebar_shown");
+  const hiddenSidebarInstance1 = await extension.awaitMessage("devtools_sidebar_hidden");
+
+  is(shownSidebarInstance2, "sidebar2", "Got the shown event on the second extension sidebar");
+  is(hiddenSidebarInstance1, "sidebar1", "Got the hidden event on the first extension sidebar");
+
+  const sidebarPanel2 = inspector.sidebar.getTabPanel(sidebarIds[1]);
+
+  ok(sidebarPanel2, "Got a rendered sidebar panel for the second registered extension sidebar");
+
+  is(sidebarPanel2.querySelectorAll("table.treeTable").length, 1,
+     "The second sidebar panel contains a rendered TreeView component");
+
+  is(sidebarPanel2.querySelectorAll("table.treeTable .numberCell").length, 1,
+     "The TreeView component contains the expected a cell of type number.");
+
+  await extension.unload();
+
+  is(Array.from(toolbox._inspectorExtensionSidebars.keys()).length, 0,
+     "All the registered sidebars have been unregistered on extension unload");
+
+  is(inspector.sidebar.getTabPanel(sidebarIds[0]), undefined,
+     "The first registered sidebar has been removed");
+
+  is(inspector.sidebar.getTabPanel(sidebarIds[1]), undefined,
+     "The second registered sidebar has been removed");
+
+  await gDevTools.closeToolbox(target);
+
+  await target.destroy();
+
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/browser/components/resistfingerprinting/test/mochitest/mochitest.ini
+++ b/browser/components/resistfingerprinting/test/mochitest/mochitest.ini
@@ -1,17 +1,19 @@
 [DEFAULT]
 tags = resistfingerprinting
+
 support-files =
   file_animation_api.html
   worker_child.js
   worker_grandchild.js
   !/dom/tests/mochitest/geolocation/network_geolocation.sjs
 
 [test_animation_api.html]
 [test_device_sensor_event.html]
 [test_geolocation.html]
 scheme = https
 [test_reduce_time_precision.html]
 [test_hide_gamepad_info.html]
 support-files = test_hide_gamepad_info_iframe.html
 [test_speech_synthesis.html]
 [test_bug1382499_touch_api.html]
+[test_bug863246_resource_uri.html]
new file mode 100644
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_bug863246_resource_uri.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/SpawnTask.js"></script>
+<script>
+/* global SimpleTest SpecialPowers add_task */
+
+function waitForDOMContentLoaded() {
+  return new Promise((aResolve) => {
+    document.addEventListener("DOMContentLoaded", aResolve);
+  });
+}
+
+function testResourceUri(aTest, aUri, aContentAccessible) {
+  return new Promise((aResolve) => {
+    let link = document.createElement("link");
+    link.rel = "stylesheet";
+    link.onload = () => {
+      SimpleTest.ok(aContentAccessible, aTest);
+      aResolve();
+    };
+    link.onerror = () => {
+      SimpleTest.ok(!aContentAccessible, aTest);
+      aResolve();
+    };
+    link.href = aUri;
+    document.head.appendChild(link);
+  });
+}
+
+add_task(async function() {
+  await waitForDOMContentLoaded();
+  await testResourceUri(
+      "resource://content-accessible is content-accessible",
+      "resource://content-accessible/viewsource.css",
+      true);
+  await testResourceUri(
+      "resource://gre-resources is not content-accessible",
+      "resource://gre-resources/html.css",
+      false);
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["security.all_resource_uri_content_accessible", true]
+    ]
+  });
+  await testResourceUri(
+      "security.all_resource_uri_content_accessible = true, resource://gre-resources is now content-accessible",
+      "resource://gre-resources/html.css",
+      true);
+});
+</script>
--- a/browser/components/sessionstore/test/browser_705597.js
+++ b/browser/components/sessionstore/test/browser_705597.js
@@ -37,21 +37,21 @@ function test() {
           ok(!entries[0].children, "history entry has no subframes");
 
           // Make sure that we reset the state.
           let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank",
                                                               triggeringPrincipal_base64}] }]}]};
           waitForBrowserState(blankState, finish);
         });
 
-        // reload the browser to deprecate the subframes
-        browser.reload();
+        // Force reload the browser to deprecate the subframes.
+        browser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
       });
 
-      // create a dynamic subframe
+      // Create a dynamic subframe.
       let doc = browser.contentDocument;
       let iframe = doc.createElement("iframe");
       doc.body.appendChild(iframe);
       iframe.setAttribute("src", "about:mozilla");
     });
   });
 }
 
--- a/browser/components/sessionstore/test/browser_707862.js
+++ b/browser/components/sessionstore/test/browser_707862.js
@@ -36,21 +36,21 @@ function test() {
           whenChildCount(newEntry, 0, function() {
             // Make sure that we reset the state.
             let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank",
                                                                 triggeringPrincipal_base64 }] }]}]};
             waitForBrowserState(blankState, finish);
           });
         });
 
-        // reload the browser to deprecate the subframes
-        browser.reload();
+        // Force reload the browser to deprecate the subframes.
+        browser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
       });
 
-      // create a dynamic subframe
+      // Create a dynamic subframe.
       let doc = browser.contentDocument;
       let iframe = doc.createElement("iframe");
       doc.body.appendChild(iframe);
       iframe.setAttribute("src", "about:mozilla");
     });
   });
 
   // This test relies on the test timing out in order to indicate failure so
--- a/browser/extensions/activity-stream/jar.mn
+++ b/browser/extensions/activity-stream/jar.mn
@@ -1,14 +1,14 @@
 # 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/.
 
 [features/activity-stream@mozilla.org] chrome.jar:
-% resource activity-stream %content/
+% resource activity-stream %content/ contentaccessible=yes
   content/lib/ (./lib/*)
   content/common/ (./common/*)
   content/vendor/Redux.jsm (./vendor/Redux.jsm)
   content/vendor/react.js (./vendor/react.js)
   content/vendor/react-dom.js (./vendor/react-dom.js)
   content/vendor/react-intl.js (./vendor/react-intl.js)
   content/vendor/redux.js (./vendor/redux.js)
   content/vendor/react-redux.js (./vendor/react-redux.js)
--- a/browser/extensions/onboarding/content/onboarding-tour-agent.js
+++ b/browser/extensions/onboarding/content/onboarding-tour-agent.js
@@ -63,8 +63,13 @@ document.getElementById("onboarding-over
       Mozilla.UITour.hideHighlight();
       break;
   }
   // Dismiss any highlights if a user tries to change to other tours.
   if (evt.target.classList.contains("onboarding-tour-item")) {
     Mozilla.UITour.hideHighlight();
   }
 });
+
+document.getElementById("onboarding-overlay-button").addEventListener("Agent:Destroy", () => {
+  Mozilla.UITour.hideHighlight();
+  Mozilla.UITour.hideMenu("urlbar");
+});
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -598,23 +598,24 @@ class Onboarding {
   }
 
   destroy() {
     if (!this.uiInitialized) {
       return;
     }
     this.uiInitialized = false;
 
+    this._overlayIcon.dispatchEvent(new this._window.CustomEvent("Agent:Destroy"));
+
     this._clearPrefObserver();
     this._overlayIcon.remove();
     this._overlay.remove();
     if (this._notificationBar) {
       this._notificationBar.remove();
     }
-
     this._tourItems = this._tourPages =
     this._overlayIcon = this._overlay = this._notificationBar = null;
   }
 
   toggleOverlay() {
     if (this._tourItems.length == 0) {
       // Lazy loading until first toggle.
       this._loadTours(this._tours);
--- a/browser/extensions/onboarding/jar.mn
+++ b/browser/extensions/onboarding/jar.mn
@@ -1,12 +1,14 @@
 # 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/.
 
 [features/onboarding@mozilla.org] chrome.jar:
-% resource onboarding %content/
+  # resource://onboarding/ is referenced in about:home and about:newtab,
+  # so make it content-accessible.
+% resource onboarding %content/ contentaccessible=yes
   content/ (content/*)
   # Package UITour-lib.js in here rather than under
   # /browser/components/uitour to avoid "unreferenced files" error when
   # Onboarding extension is not built.
   content/lib/UITour-lib.js (/browser/components/uitour/UITour-lib.js)
   content/modules/ (*.jsm)
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
@@ -20,16 +20,23 @@ function assertTourCompleted(tourId, exp
     } else {
       ok(!item.classList.contains("onboarding-complete"), `Should not set the incomplete #${args.tourId} tour with the complete style`);
       ok(!completedText, "Text label should not be present for an incomplete item");
       ok(!item.hasAttribute("aria-describedby"), "Incomplete item should not have aria-describedby attribute set");
     }
   });
 }
 
+function promisePopupChange(popup, expectedState) {
+  return new Promise(resolve => {
+    let event = expectedState == "open" ? "popupshown" : "popuphidden";
+    popup.addEventListener(event, resolve, { once: true });
+  });
+}
+
 add_task(async function test_set_right_tour_completed_style_on_overlay() {
   resetOnboardingDefaultState();
 
   let tourIds = TOUR_IDs;
   // Mark the tours of even number as completed
   for (let i = 0; i < tourIds.length; ++i) {
     setTourCompletedState(tourIds[i], i % 2 == 0);
   }
@@ -83,8 +90,84 @@ add_task(async function test_click_actio
     let tab = tabs[i];
     await assertOverlaySemantics(tab.linkedBrowser);
     for (let id of tourIds) {
       await assertTourCompleted(id, id == completedTourId, tab.linkedBrowser);
     }
     await BrowserTestUtils.removeTab(tab);
   }
 });
+
+add_task(async function test_clean_up_uitour_on_page_unload() {
+  resetOnboardingDefaultState();
+  await SpecialPowers.pushPrefEnv({set: [
+    ["browser.onboarding.newtour", "singlesearch,customize"],
+  ]});
+
+  let tab = await openTab(ABOUT_NEWTAB_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
+  await promiseOnboardingOverlayOpened(tab.linkedBrowser);
+
+  // Trigger UITour showHighlight and showMenu
+  let highlight = document.getElementById("UITourHighlightContainer");
+  let urlbarOpenPromise = promisePopupChange(gURLBar.popup, "open");
+  let highlightOpenPromise = promisePopupChange(highlight, "open");
+  BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-singlesearch", {}, tab.linkedBrowser);
+  BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-singlesearch-button", {}, tab.linkedBrowser);
+  BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-customize", {}, tab.linkedBrowser);
+  BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-customize-button", {}, tab.linkedBrowser);
+  await urlbarOpenPromise;
+  await highlightOpenPromise;
+  is(gURLBar.popup.state, "open", "Should show urlbar popup");
+  is(highlight.state, "open", "Should show UITour highlight");
+  is(highlight.getAttribute("targetName"), "customize", "UITour should highlight customize");
+
+  // Load another page to unload the current page
+  let urlbarClosePromise = promisePopupChange(gURLBar.popup, "closed");
+  let highlightClosePromise = promisePopupChange(highlight, "closed");
+  await BrowserTestUtils.loadURI(tab.linkedBrowser, "http://example.com");
+  await urlbarClosePromise;
+  await highlightClosePromise;
+  is(gURLBar.popup.state, "closed", "Should close urlbar popup after page unloaded");
+  is(highlight.state, "closed", "Should close UITour highlight after page unloaded");
+
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_clean_up_uitour_on_window_resize() {
+  resetOnboardingDefaultState();
+  await SpecialPowers.pushPrefEnv({set: [
+    ["browser.onboarding.newtour", "singlesearch,customize"],
+  ]});
+
+  let tab = await openTab(ABOUT_NEWTAB_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
+  await promiseOnboardingOverlayOpened(tab.linkedBrowser);
+
+  // Trigger UITour showHighlight and showMenu
+  let highlight = document.getElementById("UITourHighlightContainer");
+  let urlbarOpenPromise = promisePopupChange(gURLBar.popup, "open");
+  let highlightOpenPromise = promisePopupChange(highlight, "open");
+  BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-singlesearch", {}, tab.linkedBrowser);
+  BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-singlesearch-button", {}, tab.linkedBrowser);
+  BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-customize", {}, tab.linkedBrowser);
+  BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-tour-customize-button", {}, tab.linkedBrowser);
+  await urlbarOpenPromise;
+  await highlightOpenPromise;
+  is(gURLBar.popup.state, "open", "Should show urlbar popup");
+  is(highlight.state, "open", "Should show UITour highlight");
+  is(highlight.getAttribute("targetName"), "customize", "UITour should highlight customize");
+
+  // Resize window to destroy the onboarding tour
+  const originalWidth = window.innerWidth;
+  let urlbarClosePromise = promisePopupChange(gURLBar.popup, "closed");
+  let highlightClosePromise = promisePopupChange(highlight, "closed");
+  window.innerWidth = 300;
+  await urlbarClosePromise;
+  await highlightClosePromise;
+  is(gURLBar.popup.state, "closed", "Should close urlbar popup after window resized");
+  is(highlight.state, "closed", "Should close UITour highlight after window resized");
+
+  window.innerWidth = originalWidth;
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/browser/extensions/shield-recipe-client/jar.mn
+++ b/browser/extensions/shield-recipe-client/jar.mn
@@ -2,12 +2,12 @@
 # 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/.
 
 [features/shield-recipe-client@mozilla.org] chrome.jar:
 % resource shield-recipe-client %
   lib/ (./lib/*)
   data/ (./data/*)
   skin/  (skin/*)
-% resource shield-recipe-client-content %content/
+% resource shield-recipe-client-content %content/ contentaccessible=yes
   content/ (./content/*)
-% resource shield-recipe-client-vendor %vendor/
+% resource shield-recipe-client-vendor %vendor/ contentaccessible=yes
   vendor/ (./vendor/*)
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -678,19 +678,16 @@
 ; Services (gre) prefs
 @RESPATH@/defaults/pref/services-sync.js
 
 ; [Layout Engine Resources]
 ; Style Sheets, Graphics and other Resources used by the layout engine.
 @RESPATH@/res/EditorOverride.css
 @RESPATH@/res/contenteditable.css
 @RESPATH@/res/designmode.css
-@RESPATH@/res/ImageDocument.css
-@RESPATH@/res/TopLevelImageDocument.css
-@RESPATH@/res/TopLevelVideoDocument.css
 @RESPATH@/res/table-add-column-after-active.gif
 @RESPATH@/res/table-add-column-after-hover.gif
 @RESPATH@/res/table-add-column-after.gif
 @RESPATH@/res/table-add-column-before-active.gif
 @RESPATH@/res/table-add-column-before-hover.gif
 @RESPATH@/res/table-add-column-before.gif
 @RESPATH@/res/table-add-row-after-active.gif
 @RESPATH@/res/table-add-row-after-hover.gif
@@ -711,16 +708,19 @@
 @RESPATH@/res/fonts/*
 @RESPATH@/res/dtd/*
 @RESPATH@/res/html/*
 @RESPATH@/res/language.properties
 #ifdef XP_MACOSX
 @RESPATH@/res/MainMenu.nib/
 #endif
 
+; Content-accessible resources.
+@RESPATH@/contentaccessible/*
+
 ; svg
 @RESPATH@/res/svg.css
 @RESPATH@/components/dom_svg.xpt
 @RESPATH@/components/dom_smil.xpt
 
 ; [Personal Security Manager]
 ;
 ; NSS libraries are signed in the staging directory,
--- a/build/build-clang/clang-3.9-linux64.json
+++ b/build/build-clang/clang-3.9-linux64.json
@@ -5,18 +5,18 @@
     "build_type": "Release",
     "assertions": false,
     "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_390/final",
     "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_390/final",
     "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_390/final",
     "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_390/final",
     "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_390/final",
     "python_path": "/usr/bin/python2.7",
-    "gcc_dir": "/home/worker/workspace/build/src/gcc",
-    "cc": "/home/worker/workspace/build/src/gcc/bin/gcc",
-    "cxx": "/home/worker/workspace/build/src/gcc/bin/g++",
-    "as": "/home/worker/workspace/build/src/gcc/bin/gcc",
+    "gcc_dir": "/builds/worker/workspace/build/src/gcc",
+    "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
+    "cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
+    "as": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "patches": [
       "llvm-debug-frame.patch",
       "r277806.patch",
       "r285657.patch"
     ]
 }
--- a/build/build-clang/clang-4-linux64.json
+++ b/build/build-clang/clang-4-linux64.json
@@ -5,16 +5,16 @@
     "build_type": "Release",
     "assertions": false,
     "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_401/final",
     "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_401/final",
     "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_401/final",
     "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_401/final",
     "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_401/final",
     "python_path": "/usr/bin/python2.7",
-    "gcc_dir": "/home/worker/workspace/build/src/gcc",
-    "cc": "/home/worker/workspace/build/src/gcc/bin/gcc",
-    "cxx": "/home/worker/workspace/build/src/gcc/bin/g++",
-    "as": "/home/worker/workspace/build/src/gcc/bin/gcc",
+    "gcc_dir": "/builds/worker/workspace/build/src/gcc",
+    "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
+    "cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
+    "as": "/builds/worker/workspace/build/src/gcc/bin/gcc",
     "patches": [
       "llvm-debug-frame.patch"
     ]
 }
--- a/build/build-clang/clang-macosx64.json
+++ b/build/build-clang/clang-macosx64.json
@@ -6,24 +6,24 @@
     "assertions": false,
     "osx_cross_compile": true,
     "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_390/final",
     "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_390/final",
     "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_390/final",
     "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_390/final",
     "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_390/final",
     "python_path": "/usr/bin/python2.7",
-    "gcc_dir": "/home/worker/workspace/build/src/gcc",
-    "cc": "/home/worker/workspace/build/src/clang/bin/clang",
-    "cxx": "/home/worker/workspace/build/src/clang/bin/clang++",
-    "as": "/home/worker/workspace/build/src/clang/bin/clang",
-    "ar": "/home/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ar",
-    "ranlib": "/home/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ranlib",
-    "libtool": "/home/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-libtool",
-    "ld": "/home/worker/workspace/build/src/clang/bin/clang",
+    "gcc_dir": "/builds/worker/workspace/build/src/gcc",
+    "cc": "/builds/worker/workspace/build/src/clang/bin/clang",
+    "cxx": "/builds/worker/workspace/build/src/clang/bin/clang++",
+    "as": "/builds/worker/workspace/build/src/clang/bin/clang",
+    "ar": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ar",
+    "ranlib": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ranlib",
+    "libtool": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-libtool",
+    "ld": "/builds/worker/workspace/build/src/clang/bin/clang",
     "patches":[
       "llvm-debug-frame.patch",
       "compiler-rt-cross-compile.patch",
       "pr28831-r280042.patch",
       "r277806.patch",
       "r285657.patch"
     ]
 }
--- a/build/build-clang/clang-tidy-linux64.json
+++ b/build/build-clang/clang-tidy-linux64.json
@@ -7,13 +7,13 @@
     "build_clang_tidy": true,
     "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/trunk",
     "clang_repo": "https://llvm.org/svn/llvm-project/cfe/trunk",
     "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/trunk",
     "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/trunk",
     "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/trunk",
     "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/trunk",
     "python_path": "/usr/bin/python2.7",
-    "gcc_dir": "/home/worker/workspace/build/src/gcc",
-    "cc": "/home/worker/workspace/build/src/gcc/bin/gcc",
-    "cxx": "/home/worker/workspace/build/src/gcc/bin/g++",
-    "as": "/home/worker/workspace/build/src/gcc/bin/gcc"
+    "gcc_dir": "/builds/worker/workspace/build/src/gcc",
+    "cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
+    "cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
+    "as": "/builds/worker/workspace/build/src/gcc/bin/gcc"
 }
--- a/build/build-clang/clang-tidy-macosx64.json
+++ b/build/build-clang/clang-tidy-macosx64.json
@@ -8,20 +8,20 @@
     "osx_cross_compile": true,
     "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/trunk",
     "clang_repo": "https://llvm.org/svn/llvm-project/cfe/trunk",
     "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/trunk",
     "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/trunk",
     "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/trunk",
     "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/trunk",
     "python_path": "/usr/bin/python2.7",
-    "gcc_dir": "/home/worker/workspace/build/src/gcc",
-    "cc": "/home/worker/workspace/build/src/clang/bin/clang",
-    "cxx": "/home/worker/workspace/build/src/clang/bin/clang++",
-    "as": "/home/worker/workspace/build/src/clang/bin/clang",
-    "ar": "/home/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ar",
-    "ranlib": "/home/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ranlib",
-    "ld": "/home/worker/workspace/build/src/clang/bin/clang",
+    "gcc_dir": "/builds/worker/workspace/build/src/gcc",
+    "cc": "/builds/worker/workspace/build/src/clang/bin/clang",
+    "cxx": "/builds/worker/workspace/build/src/clang/bin/clang++",
+    "as": "/builds/worker/workspace/build/src/clang/bin/clang",
+    "ar": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ar",
+    "ranlib": "/builds/worker/workspace/build/src/cctools/bin/x86_64-apple-darwin11-ranlib",
+    "ld": "/builds/worker/workspace/build/src/clang/bin/clang",
     "patches": [
       "llvm-debug-frame.patch",
       "compiler-rt-cross-compile.patch"
     ]
 }
--- a/build/valgrind/x86_64-redhat-linux-gnu.sup
+++ b/build/valgrind/x86_64-redhat-linux-gnu.sup
@@ -331,44 +331,44 @@
    fun:sse2_composite_over_8888_8888
    fun:_moz_pixman_image_composite32
    fun:_clip_and_composite_boxes
    fun:_cairo_image_surface_fill
 }
 
 
 # Conditional jump or move depends on uninitialised value(s)
-#    at 0xF9D56AE: sse2_combine_over_u (in /home/worker/workspace/build/applic
-#    by 0xF9D05D4: general_composite_rect (in /home/worker/workspace/build/app
-#    by 0xF9F5B5F: _moz_pixman_image_composite32 (in /home/worker/workspace/bu
-#    by 0xF96CF63: _clip_and_composite (in /home/worker/workspace/build/applic
-#    by 0xF96D656: _clip_and_composite_boxes.part.32 (in /home/worker/workspac
-#    by 0xF96E328: _clip_and_composite_boxes (in /home/worker/workspace/build/
-#    by 0xF96F79D: _cairo_image_surface_fill (in /home/worker/workspace/build/
-#    by 0xF98790C: _cairo_surface_fill (in /home/worker/workspace/build/applic
+#    at 0xF9D56AE: sse2_combine_over_u (in /builds/worker/workspace/build/applic
+#    by 0xF9D05D4: general_composite_rect (in /builds/worker/workspace/build/app
+#    by 0xF9F5B5F: _moz_pixman_image_composite32 (in /builds/worker/workspace/bu
+#    by 0xF96CF63: _clip_and_composite (in /builds/worker/workspace/build/applic
+#    by 0xF96D656: _clip_and_composite_boxes.part.32 (in /builds/worker/workspac
+#    by 0xF96E328: _clip_and_composite_boxes (in /builds/worker/workspace/build/
+#    by 0xF96F79D: _cairo_image_surface_fill (in /builds/worker/workspace/build/
+#    by 0xF98790C: _cairo_surface_fill (in /builds/worker/workspace/build/applic
 #  Uninitialised value was created by a stack allocation
-#    at 0xF9D024D: general_composite_rect (in /home/worker/workspace/build/app
+#    at 0xF9D024D: general_composite_rect (in /builds/worker/workspace/build/app
 #
 {
    Bug 1248365: mochitest-libpixman-3
    Memcheck:Cond
    fun:sse2_combine_over_u
    fun:general_composite_rect
    fun:_moz_pixman_image_composite32
    fun:_clip_and_composite*
 }
 
 
 # Conditional jump or move depends on uninitialised value(s)
-#    at 0xE626A5C: mozilla::image::imgFrame::Optimize() (in /home/worker/work
+#    at 0xE626A5C: mozilla::image::imgFrame::Optimize() (in /builds/worker/work
 #    by 0xE626C68: mozilla::image::imgFrame::UnlockImageData() (in /home/work
 #    by 0xE608E8F: mozilla::image::RawAccessFrameRef::~RawAccessFrameRef() (i
-#    by 0xE61F5E4: mozilla::image::Decoder::~Decoder() (in /home/worker/works
+#    by 0xE61F5E4: mozilla::image::Decoder::~Decoder() (in /builds/worker/works
 #    by 0xE630E32: mozilla::image::nsIconDecoder::~nsIconDecoder() (in /home/
-#    by 0xE61A5B2: mozilla::image::Decoder::Release() (in /home/worker/worksp
+#    by 0xE61A5B2: mozilla::image::Decoder::Release() (in /builds/worker/worksp
 #    by 0xE61DD73: mozilla::image::NotifyDecodeCompleteWorker::~NotifyDecodeC
 #    by 0xE61DD8F: mozilla::image::NotifyDecodeCompleteWorker::~NotifyDecodeC
 #  Uninitialised value was created by a stack allocation
 #    at 0xB8E46B0: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.30.2)
 {
    Bug 1248365: mochitest-libpixman-4
    Memcheck:Cond
    fun:_ZN7mozilla5image8imgFrame8OptimizeEv
@@ -378,21 +378,21 @@
 }
 
 
 # Not sure what this.  I can't reproduce it locally despite much trying.
 # Syscall param sendmsg(msg.msg_iov[0]) points to uninitialised byte(s)
 #    at 0x4E4533D: ??? (syscall-template.S:82)
 #    by 0xE12C0A7: IPC::Channel::ChannelImpl::ProcessOutgoingMessages() (in /h
 #    by 0xE142FD0: RunnableMethod<IPC::Channel, bool (IPC::Channel::*)(IPC::Me
-#    by 0xE1240EA: MessageLoop::RunTask(Task*) (in /home/worker/workspace/buil
+#    by 0xE1240EA: MessageLoop::RunTask(Task*) (in /builds/worker/workspace/buil
 #    by 0xE128A46: MessageLoop::DeferOrRunPendingTask(MessageLoop::PendingTask
-#    by 0xE128B6D: MessageLoop::DoWork() (in /home/worker/workspace/build/appl
+#    by 0xE128B6D: MessageLoop::DoWork() (in /builds/worker/workspace/build/appl
 #    by 0xE12272C: base::MessagePumpLibevent::Run(base::MessagePump::Delegate*
-#    by 0xE124155: MessageLoop::Run() (in /home/worker/workspace/build/applica
+#    by 0xE124155: MessageLoop::Run() (in /builds/worker/workspace/build/applica
 {
    Bug 1248365: mochitest-sendmsg-1
    Memcheck:Param
    sendmsg(msg.msg_iov[0])
    obj:/lib/x86_64-linux-gnu/libpthread-2.15.so
    fun:_ZN3IPC7Channel11ChannelImpl23ProcessOutgoingMessagesEv
    fun:_ZN14RunnableMethodIN3IPC7ChannelEMS1_FbPNS0_7MessageEEN7mozilla5Tuple*
 }
@@ -401,24 +401,24 @@
 # I can't repro this either.
 # Conditional jump or move depends on uninitialised value(s)
 #    at 0x418E7E7C: ??? (in /usr/lib/x86_64-linux-gnu/libavcodec.so.53.35.0)
 #    by 0x4192D620: ??? (in /usr/lib/x86_64-linux-gnu/libavcodec.so.53.35.0)
 #    by 0x4192E717: ??? (in /usr/lib/x86_64-linux-gnu/libavcodec.so.53.35.0)
 #    by 0x41711BC4: ??? (in /usr/lib/x86_64-linux-gnu/libavcodec.so.53.35.0)
 #    by 0x41B08B6A: avcodec_open2 (in /usr/lib/x86_64-linux-gnu/libavcodec.so.
 #    by 0xEEAD89C: mozilla::FFmpegDataDecoder<53>::InitDecoder() (in /home/wor
-#    by 0xEEAE42B: mozilla::FFmpegVideoDecoder<53>::Init() (in /home/worker/wo
-#    by 0xEEA4C07: mozilla::H264Converter::Init() (in /home/worker/workspace/b
+#    by 0xEEAE42B: mozilla::FFmpegVideoDecoder<53>::Init() (in /builds/worker/wo
+#    by 0xEEA4C07: mozilla::H264Converter::Init() (in /builds/worker/workspace/b
 #  Uninitialised value was created by a heap allocation
 #    at 0x4C2D11F: realloc (vg_replace_malloc.c:785)
-#    by 0x406196: moz_xrealloc (in /home/worker/workspace/build/application/fi
+#    by 0x406196: moz_xrealloc (in /builds/worker/workspace/build/application/fi
 #    by 0xDEB43AC: nsTArrayInfallibleAllocator::ResultTypeProxy nsTArray_base<
 #    by 0xEEAD850: mozilla::FFmpegDataDecoder<53>::InitDecoder() (in /home/wor
-#    by 0xEEAE42B: mozilla::FFmpegVideoDecoder<53>::Init() (in /home/worker/wo
+#    by 0xEEAE42B: mozilla::FFmpegVideoDecoder<53>::Init() (in /builds/worker/wo
 {
    Bug 1248365: mochitest-libavcodec-1-c
    Memcheck:Cond
    obj:/*/libavcodec.so.53*
    obj:/*/libavcodec.so.53*
    obj:/*/libavcodec.so.53*
    obj:/*/libavcodec.so.53*
 }
@@ -430,17 +430,17 @@
    obj:/*/libavcodec.so.53*
    obj:/*/libavcodec.so.53*
 }
 
 
 # Not sure what this is, but I am inclined to think it is also probably a
 # SSE2-induced false positive similar to mochitest-libpixman-2 above.
 # Use of uninitialised value of size 8
-#    at 0xE4F3E89: FastConvertYUVToRGB32Row (in /home/worker/workspace/build/a
+#    at 0xE4F3E89: FastConvertYUVToRGB32Row (in /builds/worker/workspace/build/a
 #    by 0xE4F4A6D: mozilla::gfx::ConvertYCbCrToRGB32(unsigned char const*, uns
 #    by 0xE4F4B17: mozilla::gfx::ConvertYCbCrToRGB(mozilla::layers::PlanarYCbC
 #    by 0xE5227CB: mozilla::layers::PlanarYCbCrImage::GetAsSourceSurface() (in
 #    by 0xE5B2465: mozilla::layers::SharedPlanarYCbCrImage::GetAsSourceSurface
 #    by 0xE52FE44: mozilla::layers::BasicImageLayer::Paint(mozilla::gfx::DrawT
 #    by 0xE5618A1: mozilla::layers::BasicLayerManager::PaintSelfOrChildren(moz
 #    by 0xE560F83: mozilla::layers::BasicLayerManager::PaintLayer(gfxContext*,
 #  Uninitialised value was created by a stack allocation
@@ -468,21 +468,21 @@
    fun:_ZN6SkScan9FillIRect*
    fun:_ZN6SkScan9FillIRect*
 }
 
 # This is probably a V false positive, due to an insufficiently accurate
 # description of the ioctl(SIOCETHTOOL) behavior.
 # Syscall param ioctl(SIOCETHTOOL) points to uninitialised byte(s)
 #    at 0x5D5CBF7: ioctl (syscall-template.S:82)
-#    by 0xF58EB67: nr_stun_get_addrs (in /home/worker/workspace/build/applica
-#    by 0xF594791: nr_stun_find_local_addresses (in /home/worker/workspace/bu
-#    by 0xF58A237: nr_ice_get_local_addresses (in /home/worker/workspace/buil
-#    by 0xF58ADDE: nr_ice_gather (in /home/worker/workspace/build/application
-#    by 0xE43F35F: mozilla::NrIceCtx::StartGathering() (in /home/worker/works
+#    by 0xF58EB67: nr_stun_get_addrs (in /builds/worker/workspace/build/applica
+#    by 0xF594791: nr_stun_find_local_addresses (in /builds/worker/workspace/bu
+#    by 0xF58A237: nr_ice_get_local_addresses (in /builds/worker/workspace/buil
+#    by 0xF58ADDE: nr_ice_gather (in /builds/worker/workspace/build/application
+#    by 0xE43F35F: mozilla::NrIceCtx::StartGathering() (in /builds/worker/works
 #    by 0xE419560: mozilla::PeerConnectionMedia::EnsureIceGathering_s() (in /
 #    by 0xE41A11C: mozilla::runnable_args_memfn<RefPtr<mozilla::PeerConnectio
 #  Address 0x1cc3fb48 is on thread 6's stack
 #  in frame #1, created by nr_stun_get_addrs (???:)
 {
    Bug 1248365: mochitest-ioctl(SIOCETHTOOL)-1
    Memcheck:Param
    ioctl(SIOCETHTOOL)
@@ -497,26 +497,26 @@
 # Syscall param write(buf) points to uninitialised byte(s)
 #    at 0x4E44CCD: ??? (syscall-template.S:82)
 #    by 0x9F1FF56: ??? (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4)
 #    by 0x9F2679B: ??? (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4)
 #    by 0x9F22B98: ??? (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4)
 #    by 0x9F22C5F: FcConfigAppFontAddDir (in /usr/lib/x86_64-linux-gnu/libfon
 #    by 0xE850173: gfxFcPlatformFontList::ActivateBundledFonts() (in /home/wo
 #    by 0xE852258: gfxFcPlatformFontList::InitFontListForPlatform() (in /home
-#    by 0xE895E21: gfxPlatformFontList::InitFontList() (in /home/worker/works
+#    by 0xE895E21: gfxPlatformFontList::InitFontList() (in /builds/worker/works
 #  Address 0x2316663c is 156 bytes inside a block of size 1,448 alloc'd
 #    at 0x4C2CF71: malloc (vg_replace_malloc.c:299)
 #    by 0x9F1FD1D: ??? (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4)
 #    by 0x9F26788: ??? (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4)
 #    by 0x9F22B98: ??? (in /usr/lib/x86_64-linux-gnu/libfontconfig.so.1.4.4)
 #    by 0x9F22C5F: FcConfigAppFontAddDir (in /usr/lib/x86_64-linux-gnu/libfon
 #    by 0xE850173: gfxFcPlatformFontList::ActivateBundledFonts() (in /home/wo
 #    by 0xE852258: gfxFcPlatformFontList::InitFontListForPlatform() (in /home
-#    by 0xE895E21: gfxPlatformFontList::InitFontList() (in /home/worker/works
+#    by 0xE895E21: gfxPlatformFontList::InitFontList() (in /builds/worker/works
 {
    Bug 1248365: libfontconfig-1
    Memcheck:Param
    write(buf)
    obj:/*/libpthread*.so*
    obj:/*/libfontconfig.so*
    ...
    obj:/*/libfontconfig.so*
@@ -525,18 +525,18 @@
 
 
 # There's nothing we can do about these short of throwing in
 # --show-mismatched-frees=no, but that's a bit drastic, so for now,
 # just suppress them.  A typical error is:
 #
 # Mismatched free() / delete / delete []
 #    at 0x4C2BE97: free (vg_replace_malloc.c:530)
-#    by 0xFCD09EC: ots::ots_post_free(ots::Font*) (in /home/worker/workspace/
-#    by 0xFCC600E: ots::Font::~Font() (in /home/worker/workspace/build/applic
+#    by 0xFCD09EC: ots::ots_post_free(ots::Font*) (in /builds/worker/workspace/
+#    by 0xFCC600E: ots::Font::~Font() (in /builds/worker/workspace/build/applic
 #    by 0xFCCBFA5: ots::OTSContext::Process(ots::OTSStream*, unsigned char co
 #    by 0xE7D7C8D: gfxUserFontEntry::SanitizeOpenTypeData(unsigned char const
 #    by 0xE7E371D: gfxUserFontEntry::LoadPlatformFont(unsigned char const*, u
 #    by 0xE7E48AA: gfxUserFontEntry::FontDataDownloadComplete(unsigned char c
 #    by 0xF49D25B: nsFontFaceLoader::OnStreamComplete(nsIStreamLoader*, nsISu
 #  Address 0x15671f00 is 0 bytes inside a block of size 490 alloc'd
 #    at 0x4C2CAEE: operator new(unsigned long) (vg_replace_malloc.c:332)
 #    by 0xF6AB737: std::vector<unsigned short, std::allocator<unsigned short>
@@ -600,28 +600,16 @@
    ots mismatched frees, Jan 2017, #6
    Memcheck:Free
    fun:_ZdlPv
    fun:_ZN3ots14ots_glyf_parse*
    fun:_ZN12_GLOBAL__N_114ProcessGenericEPN3ots12OpenTypeFile*
    fun:_ZN3ots10OTSContext7ProcessEPNS_9OTSStream*
 }
 
-# apparent leaks in libLLVM-3.6-mesa.so, August 2017.  See bug 1382280.
-{
-   static-object-leaks-in-libLLVM-3.6-mesa.so.  See bug 1382280.
-   Memcheck:Leak
-   match-leak-kinds: definite
-   fun:_Znwm
-   ...
-   fun:__static_initialization_and_destruction_0
-   fun:_GLOBAL__sub_I_*.cpp
-   obj:/*/lib*/libLLVM-3.6-mesa.so
-}
-
 {
    map_or<selectors::parser::Combinator,bool,closure> #1 (see bug 1365915)
    Memcheck:Cond
    fun:map_or<selectors::parser::Combinator,bool,closure>
    fun:_ZN9selectors8matching33matches_complex_selector_internal*
    fun:_ZN9selectors8matching24matches_complex_selector*
    fun:matches_selector<style::gecko::wrapper::GeckoElement,closure>
 }
@@ -629,8 +617,18 @@
 {
    map_or<selectors::parser::Combinator,bool,closure> #2 (see bug 1365915)
    Memcheck:Cond
    fun:map_or<selectors::parser::Combinator,bool,closure>
    fun:_ZN9selectors8matching33matches_complex_selector_internal*
    fun:_ZN9selectors8matching24matches_complex_selector*
    fun:{{closure}}<closure>
 }
+
+# more leaks in libLLVM-3.6-mesa.so, August 2017.  See bug 1338651.
+{
+   static-object-leaks-in-libLLVM-3.6-mesa.so.  See bug 1338651.
+   Memcheck:Leak
+   match-leak-kinds: definite
+   fun:_Znwm
+   obj:/*/lib*/libLLVM-3.6-mesa.so
+   obj:/*/lib*/libLLVM-3.6-mesa.so
+}
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -48,16 +48,17 @@
 #include "nsIOService.h"
 #include "nsIContent.h"
 #include "nsDOMJSUtils.h"
 #include "nsAboutProtocolUtils.h"
 #include "nsIClassInfo.h"
 #include "nsIURIFixup.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsIChromeRegistry.h"
+#include "nsIResProtocolHandler.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingUtils.h"
 #include <stdint.h>
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StaticPtr.h"
@@ -910,20 +911,19 @@ nsScriptSecurityManager::CheckLoadURIFla
     // Check for chrome target URI
     bool hasFlags = false;
     rv = NS_URIChainHasFlags(aTargetBaseURI,
                              nsIProtocolHandler::URI_IS_UI_RESOURCE,
                              &hasFlags);
     NS_ENSURE_SUCCESS(rv, rv);
     if (hasFlags) {
         if (aFlags & nsIScriptSecurityManager::ALLOW_CHROME) {
-
-            // For now, don't change behavior for resource:// or moz-icon:// and
-            // just allow them.
-            if (!targetScheme.EqualsLiteral("chrome")) {
+            // For now, don't change behavior for moz-icon:// and just allow it.
+            if (!targetScheme.EqualsLiteral("chrome")
+                    && !targetScheme.EqualsLiteral("resource")) {
                 return NS_OK;
             }
 
             // Allow a URI_IS_UI_RESOURCE source to link to a URI_IS_UI_RESOURCE
             // target if ALLOW_CHROME is set.
             //
             // ALLOW_CHROME is a flag that we pass on all loads _except_ docshell
             // loads (since docshell loads run the loaded content with its origin
@@ -934,25 +934,61 @@ nsScriptSecurityManager::CheckLoadURIFla
             rv = NS_URIChainHasFlags(aSourceBaseURI,
                                      nsIProtocolHandler::URI_IS_UI_RESOURCE,
                                      &sourceIsUIResource);
             NS_ENSURE_SUCCESS(rv, rv);
             if (sourceIsUIResource) {
                 return NS_OK;
             }
 
-            // Allow the load only if the chrome package is whitelisted.
-            nsCOMPtr<nsIXULChromeRegistry> reg(do_GetService(
-                                                 NS_CHROMEREGISTRY_CONTRACTID));
-            if (reg) {
+            if (targetScheme.EqualsLiteral("resource")) {
+                // Mochitests that need to load resource:// URIs not declared
+                // content-accessible in manifests should set the preference
+                // "security.all_resource_uri_content_accessible" true.
+                static bool sSecurityPrefCached = false;
+                static bool sAllResourceUriContentAccessible = false;
+                if (!sSecurityPrefCached) {
+                    sSecurityPrefCached = true;
+                    Preferences::AddBoolVarCache(
+                            &sAllResourceUriContentAccessible,
+                            "security.all_resource_uri_content_accessible",
+                            false);
+                }
+                if (sAllResourceUriContentAccessible) {
+                    return NS_OK;
+                }
+
+                nsCOMPtr<nsIProtocolHandler> ph;
+                rv = sIOService->GetProtocolHandler("resource", getter_AddRefs(ph));
+                NS_ENSURE_SUCCESS(rv, rv);
+                if (!ph) {
+                    return NS_ERROR_DOM_BAD_URI;
+                }
+
+                nsCOMPtr<nsIResProtocolHandler> rph = do_QueryInterface(ph);
+                if (!rph) {
+                    return NS_ERROR_DOM_BAD_URI;
+                }
+
                 bool accessAllowed = false;
-                reg->AllowContentToAccess(aTargetBaseURI, &accessAllowed);
+                rph->AllowContentToAccess(aTargetBaseURI, &accessAllowed);
                 if (accessAllowed) {
                     return NS_OK;
                 }
+            } else {
+                // Allow the load only if the chrome package is whitelisted.
+                nsCOMPtr<nsIXULChromeRegistry> reg(
+                        do_GetService(NS_CHROMEREGISTRY_CONTRACTID));
+                if (reg) {
+                    bool accessAllowed = false;
+                    reg->AllowContentToAccess(aTargetBaseURI, &accessAllowed);
+                    if (accessAllowed) {
+                        return NS_OK;
+                    }
+                }
             }
         }
 
         static bool sCanLoadChromeInContent = false;
         static bool sCachedCanLoadChromeInContentPref = false;
         if (!sCachedCanLoadChromeInContentPref) {
             sCachedCanLoadChromeInContentPref = true;
             mozilla::Preferences::AddBoolVarCache(&sCanLoadChromeInContent,
--- a/caps/tests/mochitest/test_bug292789.html
+++ b/caps/tests/mochitest/test_bug292789.html
@@ -95,13 +95,18 @@ function loadImage(uri, expect, callback
     img.onload = success;
     img.expected = expect;
     img.callback = callback;
     img.src = uri;
     // document.getElementById("content").appendChild(img);
 }
 
 // Start off the script src test, and have it start the img tests when complete.
-testScriptSrc(runImgTest);
+// Temporarily allow content to access all resource:// URIs.
+SpecialPowers.pushPrefEnv({
+  set: [
+    ["security.all_resource_uri_content_accessible", true]
+  ]
+}, () => testScriptSrc(runImgTest));
 </script>
 </pre>
 </body>
 </html>
--- a/chrome/RegistryMessageUtils.h
+++ b/chrome/RegistryMessageUtils.h
@@ -37,22 +37,24 @@ struct ChromePackage
   }
 };
 
 struct SubstitutionMapping
 {
   nsCString scheme;
   nsCString path;
   SerializedURI resolvedURI;
+  uint32_t flags;
 
   bool operator ==(const SubstitutionMapping& rhs) const
   {
     return scheme.Equals(rhs.scheme) &&
            path.Equals(rhs.path) &&
-           resolvedURI == rhs.resolvedURI;
+           resolvedURI == rhs.resolvedURI &&
+           flags == rhs.flags;
   }
 };
 
 struct OverrideMapping
 {
   SerializedURI originalURI;
   SerializedURI overrideURI;
 
@@ -135,29 +137,33 @@ struct ParamTraits<SubstitutionMapping>
 {
   typedef SubstitutionMapping paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.scheme);
     WriteParam(aMsg, aParam.path);
     WriteParam(aMsg, aParam.resolvedURI);
+    WriteParam(aMsg, aParam.flags);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     nsCString scheme, path;
     SerializedURI resolvedURI;
+    uint32_t flags;
 
     if (ReadParam(aMsg, aIter, &scheme) &&
         ReadParam(aMsg, aIter, &path) &&
-        ReadParam(aMsg, aIter, &resolvedURI)) {
+        ReadParam(aMsg, aIter, &resolvedURI) &&
+        ReadParam(aMsg, aIter, &flags)) {
       aResult->scheme = scheme;
       aResult->path = path;
       aResult->resolvedURI = resolvedURI;
+      aResult->flags = flags;
       return true;
     }
     return false;
   }
 
   static void Log(const paramType& aParam, std::wstring* aLog)
   {
     aLog->append(StringPrintf(L"[%s://%s, %s, %u]",
--- a/chrome/nsChromeRegistryChrome.cpp
+++ b/chrome/nsChromeRegistryChrome.cpp
@@ -922,15 +922,23 @@ nsChromeRegistryChrome::ManifestResource
 
   if (!CanLoadResource(resolved)) {
     LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
                           "Warning: cannot register non-local URI '%s' as a resource.",
                           uri);
     return;
   }
 
-  rv = rph->SetSubstitution(host, resolved);
+  // By default, Firefox resources are not content-accessible unless the
+  // manifests opts in.
+  bool contentAccessible = (flags & nsChromeRegistry::CONTENT_ACCESSIBLE);
+
+  uint32_t substitutionFlags = 0;
+  if (contentAccessible) {
+    substitutionFlags |= nsIResProtocolHandler::ALLOW_CONTENT_ACCESS;
+  }
+  rv = rph->SetSubstitutionWithFlags(host, resolved, substitutionFlags);
   if (NS_FAILED(rv)) {
     LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
                           "Warning: cannot set substitution for '%s'.",
                           uri);
   }
 }
--- a/chrome/nsChromeRegistryContent.cpp
+++ b/chrome/nsChromeRegistryContent.cpp
@@ -109,17 +109,17 @@ nsChromeRegistryContent::RegisterSubstit
   if (aSubstitution.resolvedURI.spec.Length()) {
     rv = NS_NewURI(getter_AddRefs(resolvedURI),
                    aSubstitution.resolvedURI.spec,
                    nullptr, nullptr, io);
     if (NS_FAILED(rv))
       return;
   }
 
-  rv = sph->SetSubstitution(aSubstitution.path, resolvedURI);
+  rv = sph->SetSubstitutionWithFlags(aSubstitution.path, resolvedURI, aSubstitution.flags);
   if (NS_FAILED(rv))
     return;
 }
 
 void
 nsChromeRegistryContent::RegisterOverride(const OverrideMapping& aOverride)
 {
   nsCOMPtr<nsIIOService> io (do_GetIOService());
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -349,24 +349,35 @@ function waitForServiceWorkerRegistered(
  * promise that will resolve when it has successfully unregistered itself and the
  * about:debugging UI has fully processed this update.
  *
  * @param {Tab} tab
  * @param {Node} serviceWorkersElement
  * @return {Promise} Resolves when the service worker is unregistered.
  */
 function* unregisterServiceWorker(tab, serviceWorkersElement) {
-  let onMutation = waitForMutation(serviceWorkersElement, { childList: true });
+  // Get the initial count of service worker registrations.
+  let registrations = serviceWorkersElement.querySelectorAll(".target-container");
+  let registrationCount = registrations.length;
+
+  // Wait until the registration count is decreased by one.
+  let isRemoved = waitUntil(() => {
+    registrations = serviceWorkersElement.querySelectorAll(".target-container");
+    return registrations.length === registrationCount - 1;
+  }, 100);
+
+  // Unregister the service worker from the content page
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     // Retrieve the `sw` promise created in the html page
     let { sw } = content.wrappedJSObject;
     let registration = yield sw;
     yield registration.unregister();
   });
-  return onMutation;
+
+  return isRemoved;
 }
 
 /**
  * Waits for the creation of a new window, usually used with create private
  * browsing window.
  * Returns a promise that will resolve when the window is successfully created.
  * @param {window} win
  */
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -93,16 +93,17 @@ loader.lazyGetter(this, "registerHarOver
  *        chrome codebase when passing DOM messages
  */
 function Toolbox(target, selectedTool, hostType, contentWindow, frameId) {
   this._target = target;
   this._win = contentWindow;
   this.frameId = frameId;
 
   this._toolPanels = new Map();
+  this._inspectorExtensionSidebars = new Map();
   this._telemetry = new Telemetry();
 
   this._initInspector = null;
   this._inspector = null;
 
   // Map of frames (id => frame-info) and currently selected frame id.
   this.frameMap = new Map();
   this.selectedFrameId = null;
@@ -1451,16 +1452,71 @@ Toolbox.prototype = {
     if (this.isReady) {
       buildPanel();
     } else {
       this.once("ready", buildPanel);
     }
   },
 
   /**
+   * Retrieve the registered inspector extension sidebars
+   * (used by the inspector panel during its deferred initialization).
+   */
+  get inspectorExtensionSidebars() {
+    return this._inspectorExtensionSidebars;
+  },
+
+  /**
+   * Register an extension sidebar for the inspector panel.
+   *
+   * @param {String} id
+   *        An unique sidebar id
+   * @param {Object} options
+   * @param {String} options.title
+   *        A title for the sidebar
+   */
+  async registerInspectorExtensionSidebar(id, options) {
+    this._inspectorExtensionSidebars.set(id, options);
+
+    // Defer the extension sidebar creation if the inspector
+    // has not been created yet (and do not create the inspector
+    // only to register an extension sidebar).
+    if (!this._inspector) {
+      return;
+    }
+
+    const inspector = this.getPanel("inspector");
+    inspector.addExtensionSidebar(id, options);
+  },
+
+  /**
+   * Unregister an extension sidebar for the inspector panel.
+   *
+   * @param {String} id
+   *        An unique sidebar id
+   */
+  unregisterInspectorExtensionSidebar(id) {
+    const sidebarDef = this._inspectorExtensionSidebars.get(id);
+    if (!sidebarDef) {
+      return;
+    }
+
+    this._inspectorExtensionSidebars.delete(id);
+
+    // Remove the created sidebar instance if the inspector panel
+    // has been already created.
+    if (!this._inspector) {
+      return;
+    }
+
+    const inspector = this.getPanel("inspector");
+    inspector.removeExtensionSidebar(id);
+  },
+
+  /**
    * Unregister and unload an additional tool from this particular toolbox.
    *
    * @param {string} toolId
    *        the id of the additional tool to unregister and remove.
    */
   removeAdditionalTool(toolId) {
     if (!this.hasAdditionalTool(toolId)) {
       throw new Error("Tool definition not registered to this toolbox: " +
@@ -1476,19 +1532,17 @@ Toolbox.prototype = {
   /**
    * Ensure the tool with the given id is loaded.
    *
    * @param {string} id
    *        The id of the tool to load.
    */
   loadTool: function (id) {
     if (id === "inspector" && !this._inspector) {
-      return this.initInspector().then(() => {
-        return this.loadTool(id);
-      });
+      return this.initInspector().then(() => this.loadTool(id));
     }
 
     let deferred = defer();
     let iframe = this.doc.getElementById("toolbox-panel-iframe-" + id);
 
     if (iframe) {
       let panel = this._toolPanels.get(id);
       if (panel) {
@@ -2301,17 +2355,17 @@ Toolbox.prototype = {
     this.inspectObjectActor(packet.objectActor, packet.inspectFromAnnotation);
   },
 
   inspectObjectActor: async function (objectActor, inspectFromAnnotation) {
     if (objectActor.preview &&
         objectActor.preview.nodeType === domNodeConstants.ELEMENT_NODE) {
       // Open the inspector and select the DOM Element.
       await this.loadTool("inspector");
-      const inspector = await this.getPanel("inspector");
+      const inspector = this.getPanel("inspector");
       const nodeFound = await inspector.inspectNodeActor(objectActor.actor,
                                                          inspectFromAnnotation);
       if (nodeFound) {
         await this.selectTool("inspector");
       }
     } else if (objectActor.type !== "null" &&
                objectActor.type !== "undefined") {
       // Open then split console and inspect the object in the variables view,
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/actions/index.js
@@ -0,0 +1,17 @@
+/* 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/. */
+
+"use strict";
+
+const { createEnum } = require("devtools/client/shared/enum");
+
+createEnum([
+
+  // Update the extension sidebar with an object TreeView.
+  "EXTENSION_SIDEBAR_OBJECT_TREEVIEW_UPDATE",
+
+  // Remove an extension sidebar from the inspector store.
+  "EXTENSION_SIDEBAR_REMOVE"
+
+], module.exports);
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/actions/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+    'index.js',
+    'sidebar.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/actions/sidebar.js
@@ -0,0 +1,35 @@
+/* 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/. */
+
+"use strict";
+
+const {
+  EXTENSION_SIDEBAR_OBJECT_TREEVIEW_UPDATE,
+  EXTENSION_SIDEBAR_REMOVE,
+} = require("./index");
+
+module.exports = {
+
+  /**
+   * Update the sidebar with an object treeview.
+   */
+  updateObjectTreeView(sidebarId, object) {
+    return {
+      type: EXTENSION_SIDEBAR_OBJECT_TREEVIEW_UPDATE,
+      sidebarId,
+      object,
+    };
+  },
+
+  /**
+   * Remove the extension sidebar from the inspector store.
+   */
+  removeExtensionSidebar(sidebarId) {
+    return {
+      type: EXTENSION_SIDEBAR_REMOVE,
+      sidebarId,
+    };
+  }
+
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/components/ExtensionSidebar.js
@@ -0,0 +1,63 @@
+/* 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/. */
+
+"use strict";
+
+const {
+  addons,
+  createClass, createFactory,
+  DOM: dom,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
+
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+
+const ObjectTreeView = createFactory(require("./ObjectTreeView"));
+
+/**
+ * The ExtensionSidebar is a React component with 2 supported viewMode:
+ * - an ObjectTreeView UI, used to show the JS objects (used by the sidebar.setObject
+ *   and sidebar.setExpression WebExtensions APIs)
+ * - an ExtensionPage UI used to show an extension page (used by the sidebar.setPage
+ *   WebExtensions APIs).
+ *
+ * TODO: implement the ExtensionPage viewMode.
+ */
+const ExtensionSidebar = createClass({
+  displayName: "ExtensionSidebar",
+
+  propTypes: {
+    id: PropTypes.string.isRequired,
+    extensionsSidebar: PropTypes.object.isRequired,
+  },
+
+  mixins: [ addons.PureRenderMixin ],
+
+  render() {
+    const { id, extensionsSidebar } = this.props;
+
+    let {
+      viewMode = "empty-sidebar",
+      object
+    } = extensionsSidebar[id] || {};
+
+    let sidebarContentEl;
+
+    switch (viewMode) {
+      case "object-treeview":
+        sidebarContentEl = ObjectTreeView({ object });
+        break;
+      case "empty-sidebar":
+        break;
+      default:
+        throw new Error(`Unknown ExtensionSidebar viewMode: "${viewMode}"`);
+    }
+
+    const className = "devtools-monospace extension-sidebar inspector-tabpanel";
+
+    return dom.div({ id, className }, sidebarContentEl);
+  }
+});
+
+module.exports = connect(state => state)(ExtensionSidebar);
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/components/ObjectTreeView.js
@@ -0,0 +1,61 @@
+/* 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/. */
+
+"use strict";
+
+const {
+  addons,
+  createClass, createFactory,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
+
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
+const { Rep } = REPS;
+const TreeViewClass = require("devtools/client/shared/components/tree/tree-view");
+const TreeView = createFactory(TreeViewClass);
+
+/**
+ * The ObjectTreeView React Component is used in the ExtensionSidebar component to provide
+ * a UI viewMode which shows a tree view of the passed JavaScript object.
+ */
+const ObjectTreeView = createClass({
+  displayName: "ObjectTreeView",
+
+  propTypes: {
+    object: PropTypes.object.isRequired,
+  },
+
+  mixins: [ addons.PureRenderMixin ],
+
+  render() {
+    const { object } = this.props;
+
+    let columns = [{
+      "id": "value",
+    }];
+
+    // Render the node value (omitted on the root element if it has children).
+    let renderValue = props => {
+      if (props.member.level === 0 && props.member.hasChildren) {
+        return undefined;
+      }
+
+      return Rep(Object.assign({}, props, {
+        cropLimit: 50,
+      }));
+    };
+
+    return TreeView({
+      object,
+      mode: MODE.SHORT,
+      columns,
+      renderValue,
+      expandedNodes: TreeViewClass.getExpandedNodes(object, {
+        maxLevel: 1, maxNodes: 1,
+      }),
+    });
+  },
+});
+
+module.exports = ObjectTreeView;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/components/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+    'ExtensionSidebar.js',
+    'ObjectTreeView.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/extension-sidebar.js
@@ -0,0 +1,100 @@
+/* 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/. */
+
+"use strict";
+
+const {
+  createElement, createFactory,
+} = require("devtools/client/shared/vendor/react");
+
+const { Provider } = require("devtools/client/shared/vendor/react-redux");
+
+const ExtensionSidebarComponent = createFactory(require("./components/ExtensionSidebar"));
+
+const {
+  updateObjectTreeView,
+  removeExtensionSidebar,
+} = require("./actions/sidebar");
+
+/**
+ * ExtensionSidebar instances represents Inspector sidebars installed by add-ons
+ * using the devtools.panels.elements.createSidebarPane WebExtensions API.
+ *
+ * The WebExtensions API registers the extensions' sidebars on the toolbox instance
+ * (using the registerInspectorExtensionSidebar method) and, once the Inspector has been
+ * created, the toolbox uses the Inpector createExtensionSidebar method to create the
+ * ExtensionSidebar instances and then it registers them to the Inspector.
+ *
+ * @param {Inspector} inspector
+ *        The inspector where the sidebar should be hooked to.
+ * @param {Object} options
+ * @param {String} options.id
+ *        The unique id of the sidebar.
+ * @param {String} options.title
+ *        The title of the sidebar.
+ */
+class ExtensionSidebar {
+  constructor(inspector, {id, title}) {
+    this.inspector = inspector;
+    this.store = inspector.store;
+    this.id = id;
+    this.title = title;
+
+    this.destroyed = false;
+  }
+
+  /**
+   * Lazily create a React ExtensionSidebarComponent wrapped into a Redux Provider.
+   */
+  get provider() {
+    if (!this._provider) {
+      this._provider = createElement(Provider, {
+        store: this.store,
+        key: this.id,
+      }, ExtensionSidebarComponent({
+        id: this.id,
+      }));
+    }
+
+    return this._provider;
+  }
+
+  /**
+   * Destroy the ExtensionSidebar instance, dispatch a removeExtensionSidebar Redux action
+   * (which removes the related state from the Inspector store) and clear any reference
+   * to the inspector, the Redux store and the lazily created Redux Provider component.
+   *
+   * This method is called by the inspector when the ExtensionSidebar is being removed
+   * (or when the inspector is being destroyed).
+   */
+  destroy() {
+    if (this.destroyed) {
+      throw new Error(`ExtensionSidebar instances cannot be destroyed more than once`);
+    }
+
+    // Remove the data related to this extension from the inspector store.
+    this.store.dispatch(removeExtensionSidebar(this.id));
+
+    this.inspector = null;
+    this.store = null;
+    this._provider = null;
+
+    this.destroyed = true;
+  }
+
+  /**
+   * Dispatch an objectTreeView action to change the SidebarComponent into an
+   * ObjectTreeView React Component, which shows the passed javascript object
+   * in the sidebar.
+   */
+  setObject(object) {
+    if (this.removed) {
+      throw new Error("Unable to set an object preview on a removed ExtensionSidebar");
+    }
+
+    this.store.dispatch(updateObjectTreeView(this.id, object));
+  }
+}
+
+module.exports = ExtensionSidebar;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += [
+    'actions',
+    'components',
+    'reducers',
+]
+
+DevToolsModules(
+    'extension-sidebar.js',
+)
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/reducers/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+    'sidebar.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/reducers/sidebar.js
@@ -0,0 +1,41 @@
+/* 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/. */
+
+"use strict";
+
+const {
+  EXTENSION_SIDEBAR_OBJECT_TREEVIEW_UPDATE,
+  EXTENSION_SIDEBAR_REMOVE,
+} = require("../actions/index");
+
+const INITIAL_SIDEBAR = {};
+
+let reducers = {
+
+  [EXTENSION_SIDEBAR_OBJECT_TREEVIEW_UPDATE](sidebar, {sidebarId, object}) {
+    // Update the sidebar to a "object-treeview" which shows
+    // the passed object.
+    return Object.assign({}, sidebar, {
+      [sidebarId]: {
+        viewMode: "object-treeview",
+        object,
+      }
+    });
+  },
+
+  [EXTENSION_SIDEBAR_REMOVE](sidebar, {sidebarId}) {
+    // Remove the sidebar from the Redux store.
+    delete sidebar[sidebarId];
+    return Object.assign({}, sidebar);
+  },
+
+};
+
+module.exports = function (sidebar = INITIAL_SIDEBAR, action) {
+  let reducer = reducers[action.type];
+  if (!reducer) {
+    return sidebar;
+  }
+  return reducer(sidebar, action);
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+  // Extend from the shared list of defined globals for mochitests.
+  "extends": "../../../../.eslintrc.mochitests.js"
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/test/browser.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+  head.js
+  !/devtools/client/commandline/test/helpers.js
+  !/devtools/client/framework/test/shared-head.js
+  !/devtools/client/inspector/test/head.js
+  !/devtools/client/inspector/test/shared-head.js
+  !/devtools/client/shared/test/test-actor.js
+  !/devtools/client/shared/test/test-actor-registry.js
+
+[browser_inspector_extension_sidebar.js]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
@@ -0,0 +1,93 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+  const {inspector} = await openInspectorForURL("about:blank");
+  const {toolbox} = inspector;
+
+  const sidebarId = "an-extension-sidebar";
+  const sidebarTitle = "Sidebar Title";
+
+  const waitSidebarCreated = toolbox.once(`extension-sidebar-created-${sidebarId}`);
+
+  toolbox.registerInspectorExtensionSidebar(sidebarId, {title: sidebarTitle});
+
+  const sidebar = await waitSidebarCreated;
+
+  is(sidebar, inspector.getPanel(sidebarId),
+     "Got an extension sidebar instance equal to the one saved in the inspector");
+
+  is(sidebar.title, sidebarTitle,
+     "Got the expected title in the extension sidebar instance");
+
+  let inspectorStoreState = inspector.store.getState();
+
+  ok("extensionsSidebar" in inspectorStoreState,
+     "Got the extensionsSidebar sub-state in the inspector Redux store");
+
+  Assert.deepEqual(inspectorStoreState.extensionsSidebar, {},
+                   "The extensionsSidebar should be initially empty");
+
+  let object = {
+    propertyName: {
+      nestedProperty: "propertyValue",
+      anotherProperty: "anotherValue",
+    },
+  };
+
+  sidebar.setObject(object);
+
+  inspectorStoreState = inspector.store.getState();
+
+  is(Object.keys(inspectorStoreState.extensionsSidebar).length, 1,
+     "The extensionsSidebar state contains the newly registered extension sidebar state");
+
+  Assert.deepEqual(inspectorStoreState.extensionsSidebar, {
+    [sidebarId]: {
+      viewMode: "object-treeview",
+      object,
+    },
+  }, "Got the expected state for the registered extension sidebar");
+
+  const waitSidebarSelected = toolbox.once(`inspector-sidebar-select`);
+
+  inspector.sidebar.show(sidebarId);
+
+  await waitSidebarSelected;
+
+  const sidebarPanelContent = inspector.sidebar.getTabPanel(sidebarId);
+
+  ok(sidebarPanelContent, "Got a sidebar panel for the registered extension sidebar");
+
+  is(sidebarPanelContent.querySelectorAll("table.treeTable").length, 1,
+     "The sidebar panel contains a rendered TreeView component");
+
+  is(sidebarPanelContent.querySelectorAll("table.treeTable .stringCell").length, 2,
+     "The TreeView component contains the expected number of string cells.");
+
+  info("Change the inspected object in the extension sidebar object treeview");
+  sidebar.setObject({aNewProperty: 123});
+
+  is(sidebarPanelContent.querySelectorAll("table.treeTable .stringCell").length, 0,
+     "The TreeView component doesn't contains any string cells anymore.");
+
+  is(sidebarPanelContent.querySelectorAll("table.treeTable .numberCell").length, 1,
+     "The TreeView component contains one number cells.");
+
+  info("Remove the sidebar instance");
+
+  toolbox.unregisterInspectorExtensionSidebar(sidebarId);
+
+  ok(!inspector.sidebar.getTabPanel(sidebarId),
+     "The rendered extension sidebar has been removed");
+
+  inspectorStoreState = inspector.store.getState();
+
+  Assert.deepEqual(inspectorStoreState.extensionsSidebar, {},
+                   "The extensions sidebar Redux store data has been cleared");
+
+  await toolbox.destroy();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/extensions/test/head.js
@@ -0,0 +1,14 @@
+/* vim: set ts=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/. */
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from ../../test/head.js */
+
+"use strict";
+
+// Import the inspector's head.js first (which itself imports shared-head.js).
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
+  this);
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -17,16 +17,17 @@ var {Task} = require("devtools/shared/ta
 const {initCssProperties} = require("devtools/shared/fronts/css-properties");
 const nodeConstants = require("devtools/shared/dom-node-constants");
 const Telemetry = require("devtools/client/shared/telemetry");
 
 const Menu = require("devtools/client/framework/menu");
 const MenuItem = require("devtools/client/framework/menu-item");
 
 const {HTMLBreadcrumbs} = require("devtools/client/inspector/breadcrumbs");
+const ExtensionSidebar = require("devtools/client/inspector/extensions/extension-sidebar");
 const GridInspector = require("devtools/client/inspector/grids/grid-inspector");
 const {InspectorSearch} = require("devtools/client/inspector/inspector-search");
 const HighlightersOverlay = require("devtools/client/inspector/shared/highlighters-overlay");
 const ReflowTracker = require("devtools/client/inspector/shared/reflow-tracker");
 const {ToolSidebar} = require("devtools/client/inspector/toolsidebar");
 const MarkupView = require("devtools/client/inspector/markup/markup");
 const {CommandUtils} = require("devtools/client/shared/developer-toolbar");
 const {ViewHelpers} = require("devtools/client/shared/widgets/view-helpers");
@@ -288,16 +289,17 @@ Inspector.prototype = {
         this.setupToolbar();
 
         this.emit("ready");
         resolve(this);
       });
 
       this.setupSearchBox();
       this.setupSidebar();
+      this.setupExtensionSidebars();
     });
   },
 
   _onBeforeNavigate: function () {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
     this._destroyMarkup();
     this.isDirty = false;
@@ -551,16 +553,18 @@ Inspector.prototype = {
 
   onSidebarSelect: function (event, toolId) {
     // Save the currently selected sidebar panel
     Services.prefs.setCharPref("devtools.inspector.activeSidebar", toolId);
 
     // Then forces the panel creation by calling getPanel
     // (This allows lazy loading the panels only once we select them)
     this.getPanel(toolId);
+
+    this.toolbox.emit("inspector-sidebar-select", toolId);
   },
 
   /**
    * Lazily get and create panel instances displayed in the sidebar
    */
   getPanel: function (id) {
     if (this._panels.has(id)) {
       return this._panels.get(id);
@@ -649,16 +653,79 @@ Inspector.prototype = {
     // Setup the splitter before the sidebar is displayed so,
     // we don't miss any events.
     this.setupSplitter();
 
     this.sidebar.show(defaultTab);
   },
 
   /**
+   * Setup any extension sidebar already registered to the toolbox when the inspector.
+   * has been created for the first time.
+   */
+  setupExtensionSidebars() {
+    for (const [sidebarId, {title}] of this.toolbox.inspectorExtensionSidebars) {
+      this.addExtensionSidebar(sidebarId, {title});
+    }
+  },
+
+  /**
+   * Create a side-panel tab controlled by an extension
+   * using the devtools.panels.elements.createSidebarPane and sidebar object API
+   *
+   * @param {String} id
+   *        An unique id for the sidebar tab.
+   * @param {Object} options
+   * @param {String} options.title
+   *        The tab title
+   */
+  addExtensionSidebar: function (id, {title}) {
+    if (this._panels.has(id)) {
+      throw new Error(`Cannot create an extension sidebar for the existent id: ${id}`);
+    }
+
+    const extensionSidebar = new ExtensionSidebar(this, {id, title});
+
+    // TODO(rpl): pass some extension metadata (e.g. extension name and icon) to customize
+    // the render of the extension title (e.g. use the icon in the sidebar and show the
+    // extension name in a tooltip).
+    this.addSidebarTab(id, title, extensionSidebar.provider, false);
+
+    this._panels.set(id, extensionSidebar);
+
+    // Emit the created ExtensionSidebar instance to the listeners registered
+    // on the toolbox by the "devtools.panels.elements" WebExtensions API.
+    this.toolbox.emit(`extension-sidebar-created-${id}`, extensionSidebar);
+  },
+
+  /**
+   * Remove and destroy a side-panel tab controlled by an extension (e.g. when the
+   * extension has been disable/uninstalled while the toolbox and inspector were
+   * still open).
+   *
+   * @param {String} id
+   *        The id of the sidebar tab to destroy.
+   */
+  removeExtensionSidebar: function (id) {
+    if (!this._panels.has(id)) {
+      throw new Error(`Unable to find a sidebar panel with id "${id}"`);
+    }
+
+    const panel = this._panels.get(id);
+
+    if (!(panel instanceof ExtensionSidebar)) {
+      throw new Error(`The sidebar panel with id "${id}" is not an ExtensionSidebar`);
+    }
+
+    this._panels.delete(id);
+    this.sidebar.removeTab(id);
+    panel.destroy();
+  },
+
+  /**
    * Register a side-panel tab. This API can be used outside of
    * DevTools (e.g. from an extension) as well as by DevTools
    * code base.
    *
    * @param {string} tab uniq id
    * @param {string} title tab title
    * @param {React.Component} panel component. See `InspectorPanelTab` as an example.
    * @param {boolean} selected true if the panel should be selected
@@ -2105,16 +2172,20 @@ const buildFakeToolbox = Task.async(func
     getPanel: notImplemented,
     openSplitConsole: notImplemented,
     viewCssSourceInStyleEditor: notImplemented,
     viewJsSourceInDebugger: notImplemented,
     viewSource: notImplemented,
     viewSourceInDebugger: notImplemented,
     viewSourceInStyleEditor: notImplemented,
 
+    get inspectorExtensionSidebars() {
+      notImplemented();
+    },
+
     // For attachThread:
     highlightTool() {},
     unhighlightTool() {},
     selectTool() {},
     raise() {},
     getNotificationBox() {}
   };
 
--- a/devtools/client/inspector/inspector.xhtml
+++ b/devtools/client/inspector/inspector.xhtml
@@ -18,16 +18,17 @@
   <link rel="stylesheet" href="chrome://devtools/skin/animationinspector.css"/>
   <link rel="stylesheet" href="resource://devtools/client/shared/components/sidebar-toggle.css"/>
   <link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/tabs.css"/>
   <link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/tabbar.css"/>
   <link rel="stylesheet" href="resource://devtools/client/inspector/components/inspector-tab-panel.css"/>
   <link rel="stylesheet" href="resource://devtools/client/shared/components/splitter/split-box.css"/>
   <link rel="stylesheet" href="resource://devtools/client/inspector/layout/components/Accordion.css"/>
   <link rel="stylesheet" href="resource://devtools/client/shared/components/reps/reps.css"/>
+  <link rel="stylesheet" href="resource://devtools/client/shared/components/tree/tree-view.css"/>
 
   <script type="application/javascript"
           src="chrome://devtools/content/shared/theme-switching.js"></script>
   <script type="text/javascript">
     /* eslint-disable */
     var isInChrome = window.location.href.includes("chrome:");
     if (isInChrome) {
       var exports = {};
--- a/devtools/client/inspector/moz.build
+++ b/devtools/client/inspector/moz.build
@@ -1,16 +1,17 @@
 # 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/.
 
 DIRS += [
     'boxmodel',
     'components',
     'computed',
+    'extensions',
     'fonts',
     'grids',
     'layout',
     'markup',
     'rules',
     'shared'
 ]
 
--- a/devtools/client/inspector/reducers.js
+++ b/devtools/client/inspector/reducers.js
@@ -3,12 +3,13 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // This file exposes the Redux reducers of the box model, grid and grid highlighter
 // settings.
 
 exports.boxModel = require("devtools/client/inspector/boxmodel/reducers/box-model");
+exports.extensionsSidebar = require("devtools/client/inspector/extensions/reducers/sidebar");
 exports.fontOptions = require("devtools/client/inspector/fonts/reducers/font-options");
 exports.fonts = require("devtools/client/inspector/fonts/reducers/fonts");
 exports.grids = require("devtools/client/inspector/grids/reducers/grids");
 exports.highlighterSettings = require("devtools/client/inspector/grids/reducers/highlighter-settings");
--- a/devtools/client/jsonview/converter-child.js
+++ b/devtools/client/jsonview/converter-child.js
@@ -203,17 +203,17 @@ function initialHTML(doc) {
   } else if (platform.startsWith("Darwin")) {
     os = "mac";
   } else {
     os = "linux";
   }
 
   // The base URI is prepended to all URIs instead of using a <base> element
   // because the latter can be blocked by a CSP base-uri directive (bug 1316393)
-  let baseURI = "resource://devtools/client/jsonview/";
+  let baseURI = "resource://devtools-client-jsonview/";
 
   let style = doc.createElement("link");
   style.rel = "stylesheet";
   style.type = "text/css";
   style.href = baseURI + "css/main.css";
 
   let script = doc.createElement("script");
   script.src = baseURI + "lib/require.js";
--- a/devtools/client/jsonview/viewer-config.js
+++ b/devtools/client/jsonview/viewer-config.js
@@ -16,21 +16,21 @@
  * In order to use the developer version you need to specify the following
  * in your .mozconfig (see also bug 1181646):
  * ac_add_options --enable-debug-js-modules
  *
  * React module ID is using exactly the same (relative) path as the rest
  * of the code base, so it's consistent and modules can be easily reused.
  */
 require.config({
-  baseUrl: "resource://devtools/client/jsonview/",
+  baseUrl: "resource://devtools-client-jsonview/",
   paths: {
-    "devtools/client/shared": "resource://devtools/client/shared",
+    "devtools/client/shared": "resource://devtools-client-shared",
     "devtools/shared": "resource://devtools/shared",
     "devtools/client/shared/vendor/react":
       JSONView.debug
-      ? "resource://devtools/client/shared/vendor/react-dev"
-      : "resource://devtools/client/shared/vendor/react"
+      ? "resource://devtools-client-shared/vendor/react-dev"
+      : "resource://devtools-client-shared/vendor/react"
   }
 });
 
 // Load the main panel module
 requirejs(["json-viewer"]);
--- a/devtools/client/shared/components/tabs/tabbar.js
+++ b/devtools/client/shared/components/tabs/tabbar.js
@@ -117,18 +117,25 @@ let Tabbar = createClass({
     let index = this.getTabIndex(tabId);
     if (index < 0) {
       return;
     }
 
     let tabs = this.state.tabs.slice();
     tabs.splice(index, 1);
 
+    let activeTab = this.state.activeTab;
+
+    if (activeTab >= tabs.length) {
+      activeTab = tabs.length - 1;
+    }
+
     this.setState(Object.assign({}, this.state, {
-      tabs: tabs,
+      tabs,
+      activeTab,
     }));
   },
 
   select: function (tabId) {
     let index = this.getTabIndex(tabId);
     if (index < 0) {
       return;
     }
--- a/devtools/server/performance/framerate.js
+++ b/devtools/server/performance/framerate.js
@@ -1,99 +1,102 @@
 /* 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/. */
 "use strict";
 
 const { on, off } = require("devtools/shared/event-emitter");
-const { Class } = require("sdk/core/heritage");
 
 /**
  * A very simple utility for monitoring framerate. Takes a `tabActor`
  * and monitors framerate over time. The actor wrapper around this
  * can be found at devtools/server/actors/framerate.js
  */
-exports.Framerate = Class({
-  initialize: function (tabActor) {
+class Framerate {
+  constructor(tabActor) {
     this.tabActor = tabActor;
     this._contentWin = tabActor.window;
     this._onRefreshDriverTick = this._onRefreshDriverTick.bind(this);
     this._onGlobalCreated = this._onGlobalCreated.bind(this);
+
     on(this.tabActor, "window-ready", this._onGlobalCreated);
-  },
-  destroy: function (conn) {
+  }
+
+  destroy(conn) {
     off(this.tabActor, "window-ready", this._onGlobalCreated);
     this.stopRecording();
-  },
+  }
 
   /**
    * Starts monitoring framerate, storing the frames per second.
    */
-  startRecording: function () {
+  startRecording() {
     if (this._recording) {
       return;
     }
     this._recording = true;
     this._ticks = [];
     this._startTime = this.tabActor.docShell.now();
     this._rafID = this._contentWin.requestAnimationFrame(this._onRefreshDriverTick);
-  },
+  }
 
   /**
    * Stops monitoring framerate, returning the recorded values.
    */
-  stopRecording: function (beginAt = 0, endAt = Number.MAX_SAFE_INTEGER) {
+  stopRecording(beginAt = 0, endAt = Number.MAX_SAFE_INTEGER) {
     if (!this._recording) {
       return [];
     }
     let ticks = this.getPendingTicks(beginAt, endAt);
     this.cancelRecording();
     return ticks;
-  },
+  }
 
   /**
    * Stops monitoring framerate, without returning the recorded values.
    */
-  cancelRecording: function () {
+  cancelRecording() {
     this._contentWin.cancelAnimationFrame(this._rafID);
     this._recording = false;
     this._ticks = null;
     this._rafID = -1;
-  },
+  }
 
   /**
    * Returns whether this instance is currently recording.
    */
-  isRecording: function () {
+  isRecording() {
     return !!this._recording;
-  },
+  }
 
   /**
    * Gets the refresh driver ticks recorded so far.
    */
-  getPendingTicks: function (beginAt = 0, endAt = Number.MAX_SAFE_INTEGER) {
+  getPendingTicks(beginAt = 0, endAt = Number.MAX_SAFE_INTEGER) {
     if (!this._ticks) {
       return [];
     }
     return this._ticks.filter(e => e >= beginAt && e <= endAt);
-  },
+  }
 
   /**
    * Function invoked along with the refresh driver.
    */
-  _onRefreshDriverTick: function () {
+  _onRefreshDriverTick() {
     if (!this._recording) {
       return;
     }
     this._rafID = this._contentWin.requestAnimationFrame(this._onRefreshDriverTick);
     this._ticks.push(this.tabActor.docShell.now() - this._startTime);
-  },
+  }
 
   /**
    * When the content window for the tab actor is created.
    */
-  _onGlobalCreated: function (win) {
+  _onGlobalCreated(win) {
     if (this._recording) {
       this._contentWin.cancelAnimationFrame(this._rafID);
       this._rafID = this._contentWin.requestAnimationFrame(this._onRefreshDriverTick);
     }
   }
-});
+}
+
+exports.Framerate = Framerate;
--- a/devtools/server/performance/memory.js
+++ b/devtools/server/performance/memory.js
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const { reportException } = require("devtools/shared/DevToolsUtils");
-const { Class } = require("sdk/core/heritage");
 const { expectState } = require("devtools/server/actors/common");
+
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 loader.lazyRequireGetter(this, "DeferredTask",
   "resource://gre/modules/DeferredTask.jsm", true);
 loader.lazyRequireGetter(this, "StackFrameCache",
   "devtools/server/actors/utils/stack", true);
 loader.lazyRequireGetter(this, "ThreadSafeChromeUtils");
 loader.lazyRequireGetter(this, "HeapSnapshotFileUtils",
   "devtools/shared/heapsnapshot/HeapSnapshotFileUtils");
@@ -26,37 +26,34 @@ loader.lazyRequireGetter(this, "ChildPro
  * Using a tab-scoped actor with this instance will measure the memory footprint of its
  * parent tab. Using a global-scoped actor instance however, will measure the memory
  * footprint of the chrome window referenced by its root actor.
  *
  * To be consumed by actor's, like MemoryActor using this module to
  * send information over RDP, and TimelineActor for using more light-weight
  * utilities like GC events and measuring memory consumption.
  */
-exports.Memory = Class({
-  extends: EventEmitter,
+function Memory(parent, frameCache = new StackFrameCache()) {
+  EventEmitter.decorate(this);
 
-  /**
-   * Requires a root actor and a StackFrameCache.
-   */
-  initialize: function (parent, frameCache = new StackFrameCache()) {
-    this.parent = parent;
-    this._mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
-                  .getService(Ci.nsIMemoryReporterManager);
-    this.state = "detached";
-    this._dbg = null;
-    this._frameCache = frameCache;
+  this.parent = parent;
+  this._mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
+                .getService(Ci.nsIMemoryReporterManager);
+  this.state = "detached";
+  this._dbg = null;
+  this._frameCache = frameCache;
 
-    this._onGarbageCollection = this._onGarbageCollection.bind(this);
-    this._emitAllocations = this._emitAllocations.bind(this);
-    this._onWindowReady = this._onWindowReady.bind(this);
+  this._onGarbageCollection = this._onGarbageCollection.bind(this);
+  this._emitAllocations = this._emitAllocations.bind(this);
+  this._onWindowReady = this._onWindowReady.bind(this);
 
-    EventEmitter.on(this.parent, "window-ready", this._onWindowReady);
-  },
+  EventEmitter.on(this.parent, "window-ready", this._onWindowReady);
+}
 
+Memory.prototype = {
   destroy: function () {
     EventEmitter.off(this.parent, "window-ready", this._onWindowReady);
 
     this._mgr = null;
     if (this.state === "attached") {
       this.detach();
     }
   },
@@ -421,10 +418,11 @@ exports.Memory = Class({
 
   /**
    * Accesses the docshell to return the current process time.
    */
   _getCurrentTime: function () {
     return (this.parent.isRootActor ? this.parent.docShell :
                                       this.parent.originalDocShell).now();
   },
+};
 
-});
+exports.Memory = Memory;
--- a/devtools/server/performance/profiler.js
+++ b/devtools/server/performance/profiler.js
@@ -1,16 +1,16 @@
 /* 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/. */
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const Services = require("Services");
-const { Class } = require("sdk/core/heritage");
+
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/shared/DevToolsUtils");
 loader.lazyRequireGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm", true);
 loader.lazyRequireGetter(this, "Task", "devtools/shared/task", true);
 
 // Events piped from system observers to Profiler instances.
 const PROFILER_SYSTEM_EVENTS = [
   "console-api-profiler",
@@ -401,147 +401,148 @@ const ProfilerManager = (function () {
       this._poller.arm();
     }
   };
 })();
 
 /**
  * The profiler actor provides remote access to the built-in nsIProfiler module.
  */
-var Profiler = exports.Profiler = Class({
-  extends: EventEmitter,
+class Profiler {
+  constructor() {
+    EventEmitter.decorate(this);
 
-  initialize: function () {
     this.subscribedEvents = new Set();
     ProfilerManager.addInstance(this);
-  },
+  }
 
-  destroy: function () {
+  destroy() {
     this.unregisterEventNotifications({ events: Array.from(this.subscribedEvents) });
     this.subscribedEvents = null;
+
     ProfilerManager.removeInstance(this);
-  },
+  }
 
   /**
    * @see ProfilerManager.start
    */
-  start: function (options) {
+  start(options) {
     return ProfilerManager.start(options);
-  },
+  }
 
   /**
    * @see ProfilerManager.stop
    */
-  stop: function () {
+  stop() {
     return ProfilerManager.stop();
-  },
+  }
 
   /**
    * @see ProfilerManager.getProfile
    */
-  getProfile: function (request = {}) {
+  getProfile(request = {}) {
     return ProfilerManager.getProfile(request);
-  },
+  }
 
   /**
    * @see ProfilerManager.getFeatures
    */
-  getFeatures: function () {
+  getFeatures() {
     return ProfilerManager.getFeatures();
-  },
+  }
 
   /**
    * @see ProfilerManager.getBufferInfo
    */
-  getBufferInfo: function () {
+  getBufferInfo() {
     return ProfilerManager.getBufferInfo();
-  },
+  }
 
   /**
    * @see ProfilerManager.getStartOptions
    */
-  getStartOptions: function () {
+  getStartOptions() {
     return ProfilerManager.getStartOptions();
-  },
+  }
 
   /**
    * @see ProfilerManager.isActive
    */
-  isActive: function () {
+  isActive() {
     return ProfilerManager.isActive();
-  },
+  }
 
   /**
    * @see ProfilerManager.sharedLibraries
    */
-  sharedLibraries: function () {
+  sharedLibraries() {
     return ProfilerManager.sharedLibraries;
-  },
+  }
 
   /**
    * @see ProfilerManager.setProfilerStatusInterval
    */
-  setProfilerStatusInterval: function (interval) {
+  setProfilerStatusInterval(interval) {
     return ProfilerManager.setProfilerStatusInterval(interval);
-  },
+  }
 
   /**
    * Subscribes this instance to one of several events defined in
    * an events array.
    * - "console-api-profiler",
    * - "profiler-started",
    * - "profiler-stopped"
    * - "profiler-status"
    *
    * @param {Array<string>} data.event
    * @return {object}
    */
-  registerEventNotifications: function (data = {}) {
+  registerEventNotifications(data = {}) {
     let response = [];
     (data.events || []).forEach(e => {
       if (!this.subscribedEvents.has(e)) {
         if (e === "profiler-status") {
           ProfilerManager.subscribeToProfilerStatusEvents();
         }
         this.subscribedEvents.add(e);
         response.push(e);
       }
     });
     return { registered: response };
-  },
+  }
 
   /**
    * Unsubscribes this instance to one of several events defined in
    * an events array.
    *
    * @param {Array<string>} data.event
    * @return {object}
    */
-  unregisterEventNotifications: function (data = {}) {
+  unregisterEventNotifications(data = {}) {
     let response = [];
     (data.events || []).forEach(e => {
       if (this.subscribedEvents.has(e)) {
         if (e === "profiler-status") {
           ProfilerManager.unsubscribeToProfilerStatusEvents();
         }
         this.subscribedEvents.delete(e);
         response.push(e);
       }
     });
     return { registered: response };
-  },
-});
+  }
 
-/**
- * Checks whether or not the profiler module can currently run.
- * @return boolean
- */
-Profiler.canProfile = function () {
-  return nsIProfilerModule.CanProfile();
-};
+  /**
+   * Checks whether or not the profiler module can currently run.
+   * @return boolean
+   */
+  static canProfile() {
+    return nsIProfilerModule.CanProfile();
+  }
+}
 
 /**
  * JSON.stringify callback used in Profiler.prototype.observe.
  */
 function cycleBreaker(key, value) {
   if (key == "wrappedJSObject") {
     return undefined;
   }
@@ -567,8 +568,10 @@ function sanitizeHandler(handler, identi
     subject = JSON.parse(JSON.stringify(subject, cycleBreaker));
     data = (data && !Cu.isXrayWrapper(data) && data.wrappedJSObject) || data;
     data = JSON.parse(JSON.stringify(data, cycleBreaker));
 
     // Pass in clean data to the underlying handler
     return handler.call(this, subject, topic, data);
   }, identifier);
 }
+
+exports.Profiler = Profiler;
--- a/devtools/server/performance/recorder.js
+++ b/devtools/server/performance/recorder.js
@@ -42,30 +42,30 @@ const DRAIN_ALLOCATIONS_TIMEOUT = 2000;
 
 /**
  * A connection to underlying actors (profiler, memory, framerate, etc.)
  * shared by all tools in a target.
  *
  * @param Target target
  *        The target owning this connection.
  */
-exports.PerformanceRecorder = Class({
-  extends: EventEmitter,
+function PerformanceRecorder(conn, tabActor) {
+  EventEmitter.decorate(this);
 
-  initialize: function (conn, tabActor) {
-    this.conn = conn;
-    this.tabActor = tabActor;
+  this.conn = conn;
+  this.tabActor = tabActor;
 
-    this._pendingConsoleRecordings = [];
-    this._recordings = [];
+  this._pendingConsoleRecordings = [];
+  this._recordings = [];
 
-    this._onTimelineData = this._onTimelineData.bind(this);
-    this._onProfilerEvent = this._onProfilerEvent.bind(this);
-  },
+  this._onTimelineData = this._onTimelineData.bind(this);
+  this._onProfilerEvent = this._onProfilerEvent.bind(this);
+}
 
+PerformanceRecorder.prototype = {
   /**
    * Initializes a connection to the profiler and other miscellaneous actors.
    * If in the process of opening, or already open, nothing happens.
    *
    * @param {Object} options.systemClient
    *        Metadata about the client's system to attach to the recording models.
    *
    * @return object
@@ -474,17 +474,17 @@ exports.PerformanceRecorder = Class({
     if (this._memory.getState() === "attached") {
       allocationSettings = this._memory.getAllocationsSettings();
     }
 
     return Object.assign({}, allocationSettings, this._profiler.getStartOptions());
   },
 
   toString: () => "[object PerformanceRecorder]"
-});
+};
 
 /**
  * Creates an object of configurations based off of
  * preferences for a PerformanceRecording.
  */
 function getPerformanceRecordingPrefs() {
   return {
     withMarkers: true,
@@ -493,8 +493,10 @@ function getPerformanceRecordingPrefs() 
     withAllocations:
       Services.prefs.getBoolPref("devtools.performance.ui.enable-allocations"),
     allocationsSampleProbability:
       +Services.prefs.getCharPref("devtools.performance.memory.sample-probability"),
     allocationsMaxLogLength:
       Services.prefs.getIntPref("devtools.performance.memory.max-log-length")
   };
 }
+
+exports.PerformanceRecorder = PerformanceRecorder;
--- a/devtools/server/performance/timeline.js
+++ b/devtools/server/performance/timeline.js
@@ -16,52 +16,50 @@
  *   timeline.stop()
  *   timeline.isRecording()
  *
  * When markers are available, an event is emitted:
  *   timeline.on("markers", function(markers) {...})
  */
 
 const { Ci, Cu } = require("chrome");
-const { Class } = require("sdk/core/heritage");
+
 // Be aggressive about lazy loading, as this will run on every
 // toolbox startup
 loader.lazyRequireGetter(this, "Task", "devtools/shared/task", true);
 loader.lazyRequireGetter(this, "Memory", "devtools/server/performance/memory", true);
 loader.lazyRequireGetter(this, "Framerate", "devtools/server/performance/framerate", true);
 loader.lazyRequireGetter(this, "StackFrameCache", "devtools/server/actors/utils/stack", true);
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
 // How often do we pull markers from the docShells, and therefore, how often do
 // we send events to the front (knowing that when there are no markers in the
 // docShell, no event is sent). In milliseconds.
 const DEFAULT_TIMELINE_DATA_PULL_TIMEOUT = 200;
 
 /**
  * The timeline actor pops and forwards timeline markers registered in docshells.
  */
-exports.Timeline = Class({
-  extends: EventEmitter,
-  /**
-   * Initializes this actor with the provided connection and tab actor.
-   */
-  initialize: function (tabActor) {
-    this.tabActor = tabActor;
+function Timeline(tabActor) {
+  EventEmitter.decorate(this);
+
+  this.tabActor = tabActor;
 
-    this._isRecording = false;
-    this._stackFrames = null;
-    this._memory = null;
-    this._framerate = null;
+  this._isRecording = false;
+  this._stackFrames = null;
+  this._memory = null;
+  this._framerate = null;
 
-    // Make sure to get markers from new windows as they become available
-    this._onWindowReady = this._onWindowReady.bind(this);
-    this._onGarbageCollection = this._onGarbageCollection.bind(this);
-    EventEmitter.on(this.tabActor, "window-ready", this._onWindowReady);
-  },
+  // Make sure to get markers from new windows as they become available
+  this._onWindowReady = this._onWindowReady.bind(this);
+  this._onGarbageCollection = this._onGarbageCollection.bind(this);
+  EventEmitter.on(this.tabActor, "window-ready", this._onWindowReady);
+}
 
+Timeline.prototype = {
   /**
    * Destroys this actor, stopping recording first.
    */
   destroy: function () {
     this.stop();
 
     EventEmitter.off(this.tabActor, "window-ready", this._onWindowReady);
     this.tabActor = null;
@@ -352,9 +350,11 @@ exports.Timeline = Class({
         causeName: reason,
         nonincrementalReason: nonincrementalReason,
         cycle: gcCycleNumber,
         start,
         end,
       };
     }), endTime);
   },
-});
+};
+
+exports.Timeline = Timeline;
--- a/devtools/shared/jar.mn
+++ b/devtools/shared/jar.mn
@@ -1,10 +1,12 @@
 # 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/.
 
 devtools.jar:
 %   resource devtools %modules/devtools/
+%   resource devtools-client-jsonview resource://devtools/client/jsonview/ contentaccessible=yes
+%   resource devtools-client-shared resource://devtools/client/shared/ contentaccessible=yes
 # The typical approach would be to list all the resource files in this manifest
 # for installation.  Instead of doing this, use the DevToolsModules syntax via
 # moz.build files to do the installation so that we can enforce correct paths
 # based on source tree location.
--- a/devtools/shared/platform/content/test/test_clipboard.html
+++ b/devtools/shared/platform/content/test/test_clipboard.html
@@ -10,32 +10,47 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css"
         href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
 
 <script type="application/javascript">
 "use strict";
 var exports = {}
 </script>
-
-  <script type="application/javascript"
-	  src="resource://devtools/shared/platform/content/clipboard.js"></script>
-
 </head>
-<body onload="do_tests()">
+<body onload="pre_do_tests()">
 <script type="application/javascript">
 "use strict";
 
 const RESULT = "lark bunting";
 
 function doCopy(e) {
   console.log(e.isTrusted);
   copyString(RESULT);
 }
 
+async function pre_do_tests() {
+  // Temporarily allow content to access all resource:// URIs.
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["security.all_resource_uri_content_accessible", true]
+    ]
+  });
+
+  // Load script.
+  await (() => new Promise((resolve) => {
+    var script = document.createElement("script");
+    script.onload = resolve;
+    script.src = "resource://devtools/shared/platform/content/clipboard.js";
+    document.head.appendChild(script);
+  }))();
+
+  do_tests();
+}
+
 function do_tests() {
   let elt = document.querySelector("#key");
   elt.addEventListener("keydown", doCopy);
 
   // Set the clipboard to something other than what we expect.
   SpecialPowers.clipboardCopyString("snowy owl");
 
   elt.focus();
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9923,18 +9923,19 @@ nsDocShell::InternalLoad(nsIURI* aURI,
       // an iframe since that's more common.
       contentType = nsIContentPolicy::TYPE_INTERNAL_IFRAME;
     }
   } else {
     contentType = nsIContentPolicy::TYPE_DOCUMENT;
     isTargetTopLevelDocShell = true;
   }
 
-  // If there's no targetDocShell, that means we are about to create a new window,
-  // perform a content policy check before creating the window.
+  // If there's no targetDocShell, that means we are about to create a new
+  // window (or aWindowTarget is empty). Perform a content policy check before
+  // creating the window.
   if (!targetDocShell) {
     nsCOMPtr<Element> requestingElement;
     nsISupports* requestingContext = nullptr;
 
     if (contentType == nsIContentPolicy::TYPE_DOCUMENT) {
       if (XRE_IsContentProcess()) {
         // In e10s the child process doesn't have access to the element that
         // contains the browsing context (because that element is in the chrome
@@ -10736,26 +10737,27 @@ nsDocShell::InternalLoad(nsIURI* aURI,
 
   mLoadType = aLoadType;
 
   // mLSHE should be assigned to aSHEntry, only after Stop() has
   // been called. But when loading an error page, do not clear the
   // mLSHE for the real page.
   if (mLoadType != LOAD_ERROR_PAGE) {
     SetHistoryEntry(&mLSHE, aSHEntry);
+    if (aSHEntry) {
+      // We're making history navigation or a reload. Make sure our history ID
+      // points to the same ID as SHEntry's docshell ID.
+      mHistoryID = aSHEntry->DocshellID();
+    }
   }
 
   mSavingOldViewer = savePresentation;
 
   // If we have a saved content viewer in history, restore and show it now.
   if (aSHEntry && (mLoadType & LOAD_CMD_HISTORY)) {
-    // Make sure our history ID points to the same ID as
-    // SHEntry's docshell ID.
-    mHistoryID = aSHEntry->DocshellID();
-
     // It's possible that the previous viewer of mContentViewer is the
     // viewer that will end up in aSHEntry when it gets closed.  If that's
     // the case, we need to go ahead and force it into its shentry so we
     // can restore it.
     if (mContentViewer) {
       nsCOMPtr<nsIContentViewer> prevViewer;
       mContentViewer->GetPreviousViewer(getter_AddRefs(prevViewer));
       if (prevViewer) {
@@ -11968,22 +11970,26 @@ nsDocShell::OnNewURI(nsIURI* aURI, nsICh
     // If we already have a loading history entry, store the new cache key
     // in it.  Otherwise, since we're doing a reload and won't be updating
     // our history entry, store the cache key in our current history entry.
     if (mLSHE) {
       mLSHE->SetCacheKey(cacheKey);
     } else if (mOSHE) {
       mOSHE->SetCacheKey(cacheKey);
     }
-  }
-
-  // Clear subframe history on refresh or reload.
+
+    // Since we're force-reloading, clear all the sub frame history.
+    ClearFrameHistory(mLSHE);
+    ClearFrameHistory(mOSHE);
+  }
+
+  // Clear subframe history on refresh.
   // XXX: history.go(0) won't go this path as aLoadType is LOAD_HISTORY in this
   // case. One should re-validate after bug 1331865 fixed.
-  if (aLoadType == LOAD_REFRESH || (aLoadType & LOAD_CMD_RELOAD)) {
+  if (aLoadType == LOAD_REFRESH) {
     ClearFrameHistory(mLSHE);
     ClearFrameHistory(mOSHE);
   }
 
   if (updateSHistory) {
     // Update session history if necessary...
     if (!mLSHE && (mItemType == typeContent) && mURIResultedInDocument) {
       /* This is  a fresh page getting loaded for the first time
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -922,17 +922,18 @@ protected:
   int32_t mMarginWidth;
   int32_t mMarginHeight;
 
   // This can either be a content docshell or a chrome docshell. After
   // Create() is called, the type is not expected to change.
   int32_t mItemType;
 
   // Index into the SHTransaction list, indicating the previous and current
-  // transaction at the time that this DocShell begins to load
+  // transaction at the time that this DocShell begins to load. Consequently
+  // root docshell's indices can differ from child docshells'.
   int32_t mPreviousTransIndex;
   int32_t mLoadedTransIndex;
 
   uint32_t mSandboxFlags;
   nsWeakPtr mOnePermittedSandboxedNavigator;
 
   // The orientation lock as described by
   // https://w3c.github.io/screen-orientation/
--- a/docshell/test/navigation/file_bug1326251.html
+++ b/docshell/test/navigation/file_bug1326251.html
@@ -116,37 +116,37 @@
         opener.is(shistory.index, 4, 'shistory.index');
         opener.is(history.length, 6, 'history.length');
         let staticFrame = document.getElementById('staticFrame');
         let innerStaticFrame = staticFrame.contentDocument.getElementById('staticFrame');
         opener.is(innerStaticFrame.contentDocument.location.href, BASE_URL + 'frame1.html', 'innerStaticFrame location');
         opener.ok(!staticFrame.contentDocument.getElementById('dynamicFrame'), 'innerDynamicFrame should not exist');
 
         // Test 6: Insert and navigate inner dynamic frame and then reload outer
-        // frame. Verify that inner frame entries are all removed.
+        // frame. Verify that inner dynamic frame entries are all removed.
         staticFrame.width = '320px';
         staticFrame.height = '360px';
         let innerDynamicFrame = await createDynamicFrame(staticFrame.contentDocument, 'frame2.html');
         await loadUriInFrame(innerDynamicFrame, 'frame3.html');
         // staticFrame:       frame0 -> frame1 -> frame2 -> iframe_static
         // innerStaticFrame:                                frame0        -> frame1
         // innerDynamicFrame:                                                frame2 -> frame3
         opener.is(shistory.index, 5, 'shistory.index');
         opener.is(history.length, 6, 'history.length');
         let staticFrameLoadPromise = new Promise(resolve => {
           staticFrame.onload = resolve;
         });
         staticFrame.contentWindow.location.reload();
         await staticFrameLoadPromise;
         // staticFrame:       frame0 -> frame1 -> frame2 -> iframe_static
-        // innerStaticFrame:                                frame0
-        opener.is(shistory.index, 3, 'shistory.index');
-        opener.is(history.length, 4, 'history.length');
+        // innerStaticFrame:                                frame0        -> frame1
+        opener.is(shistory.index, 4, 'shistory.index');
+        opener.is(history.length, 5, 'history.length');
         innerStaticFrame = staticFrame.contentDocument.getElementById('staticFrame');
-        opener.is(innerStaticFrame.contentDocument.location.href, BASE_URL + 'frame0.html', 'innerStaticFrame location');
+        opener.is(innerStaticFrame.contentDocument.location.href, BASE_URL + 'frame1.html', 'innerStaticFrame location');
         opener.ok(!staticFrame.contentDocument.getElementById('dynamicFrame'), 'innerDynamicFrame should not exist');
         opener.nextTest();
         window.close();
       }
     ];
 
     function awaitOnload(frame, occurances=1) {
       return new Promise(function(resolve, reject) {
new file mode 100644
--- /dev/null
+++ b/docshell/test/navigation/file_bug1375833-frame1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>iframe test page 1</title>
+  </head>
+  <body>iframe test page 1</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/docshell/test/navigation/file_bug1375833-frame2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>iframe test page 2</title>
+  </head>
+  <body>iframe test page 2</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/docshell/test/navigation/file_bug1375833.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Test for bug 1375833</title>
+  </head>
+  <body onload="test();">
+    <iframe id="testFrame" src="file_bug1375833-frame1.html"></iframe>
+    <script type="application/javascript">
+    function test() {
+      let iframe = document.querySelector("#testFrame");
+      setTimeout(function() { iframe.src = "file_bug1375833-frame1.html"; }, 0);
+      iframe.onload = function(e) {
+        setTimeout(function() { iframe.src = "file_bug1375833-frame2.html"; }, 0);
+        iframe.onload = function() {
+          opener.postMessage(iframe.contentWindow.location.href, "*");
+        };
+      }
+    }
+    </script>
+  </body>
+</html>
--- a/docshell/test/navigation/mochitest.ini
+++ b/docshell/test/navigation/mochitest.ini
@@ -42,27 +42,31 @@ support-files =
   file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html
   file_bug1300461.html
   file_bug1300461_redirect.html
   file_bug1300461_redirect.html^headers^
   file_bug1300461_back.html
   file_contentpolicy_block_window.html
   file_bug1326251.html
   file_bug1326251_evict_cache.html
+  file_bug1375833.html
+  file_bug1375833-frame1.html
+  file_bug1375833-frame2.html
 
 [test_bug13871.html]
 [test_bug270414.html]
 [test_bug278916.html]
 [test_bug279495.html]
 [test_bug344861.html]
 skip-if = toolkit == "android" || toolkit == "windows" # disabled on Windows because of bug 1234520
 [test_bug386782.html]
 [test_bug430624.html]
 [test_bug430723.html]
 skip-if = (toolkit == 'android') || (!debug && (os == 'mac' || os == 'win')) # Bug 874423
+[test_bug1375833.html]
 [test_child.html]
 [test_grandchild.html]
 [test_not-opener.html]
 [test_opener.html]
 [test_popup-navigates-children.html]
 [test_reserved.html]
 skip-if = (toolkit == 'android') || (debug && e10s) #too slow on Android 4.3 aws only; bug 1030403; bug 1263213 for debug e10s
 [test_sessionhistory.html]
new file mode 100644
--- /dev/null
+++ b/docshell/test/navigation/test_bug1375833.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1375833
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1375833</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  SimpleTest.waitForExplicitFinish();
+
+  /**
+   * Test for Bug 1375833. It tests for 2 things in a normal reload -
+   * 1. Static frame history should not be dropped.
+   * 2. In a reload, docshell would parse the reloaded root document and
+   *    genearate new child docshells, and then use the child offset
+   */
+
+  let testWin = window.open("file_bug1375833.html");
+  let count = 0;
+  let webNav, shistory;
+  let frameDocShellId;
+  window.addEventListener("message", e => {
+    switch (count++) {
+    case 0:
+      ok(e.data.endsWith("file_bug1375833-frame2.html"), "check location");
+
+      webNav = SpecialPowers.wrap(testWin)
+               .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+               .getInterface(SpecialPowers.Ci.nsIWebNavigation);
+      shistory = webNav.sessionHistory;
+      is(shistory.count, 2, "check history length");
+      is(shistory.index, 1, "check history index");
+
+      frameDocShellId = String(getFrameDocShell().historyID);
+      ok(frameDocShellId, "sanity check for docshell ID");
+
+      testWin.location.reload();
+      break;
+    case 1:
+      ok(e.data.endsWith("file_bug1375833-frame2.html"), "check location");
+      is(shistory.count, 4, "check history length");
+      is(shistory.index, 3, "check history index");
+
+      let newFrameDocShellId = String(getFrameDocShell().historyID);
+      ok(newFrameDocShellId, "sanity check for docshell ID");
+      is(newFrameDocShellId, frameDocShellId, "check docshell ID remains after reload");
+
+      let entry = shistory.getEntryAtIndex(shistory.index, false);
+      let frameEntry = SpecialPowers.wrap(entry)
+                       .QueryInterface(SpecialPowers.Ci.nsISHContainer)
+                       .GetChildAt(0);
+      is(String(frameEntry.docshellID), frameDocShellId, "check newly added shentry uses the same docshell ID");
+
+      webNav.goBack();
+      break;
+    case 2:
+      ok(e.data.endsWith("file_bug1375833-frame1.html"), "check location");
+      is(shistory.count, 4, "check history length");
+      is(shistory.index, 2, "check history index");
+
+      webNav.goBack();
+      break;
+    case 3:
+      ok(e.data.endsWith("file_bug1375833-frame2.html"), "check location");
+      is(shistory.count, 4, "check history length");
+      is(shistory.index, 1, "check history index");
+
+      webNav.goBack();
+      break;
+    case 4:
+      ok(e.data.endsWith("file_bug1375833-frame1.html"), "check location");
+      is(shistory.count, 4, "check history length");
+      is(shistory.index, 0, "check history index");
+
+      testWin.close();
+      SimpleTest.finish();
+    }
+  });
+
+  function getFrameDocShell() {
+    return SpecialPowers.wrap(testWin.window[0])
+           .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+           .getInterface(SpecialPowers.Ci.nsIDocShell)
+  }
+
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1375833">Mozilla Bug 1375833</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/docshell/test/test_bug123696.html
+++ b/docshell/test/test_bug123696.html
@@ -24,18 +24,18 @@ SimpleTest.waitForExplicitFinish();
 
 function finishTest() {
   is(window.frames[0].frames[0].document.documentElement.textContent,
      "change2", "Reload should have reloaded correctly!");
   SimpleTest.finish();
 }
 
 function doReload() {
-  window.frames[0].frames[0].frameElement.onload = finishTest;
-  window.frames[0].frames[0].location.reload();
+  window.frames[0].frameElement.onload = finishTest;
+  window.frames[0].location.reload();
 }
 
 addLoadEvent(function() {
   window.frames[0].frames[0].frameElement.onload = doReload;
   window.frames[0].frames[0].frameElement.src = "javascript:parent.change2()";
 });
 
 
--- a/dom/html/ImageDocument.cpp
+++ b/dom/html/ImageDocument.cpp
@@ -274,19 +274,19 @@ ImageDocument::SetScriptGlobalObject(nsI
       target->AddEventListener(NS_LITERAL_STRING("click"), this, false);
     }
 
     target = do_QueryInterface(aScriptGlobalObject);
     target->AddEventListener(NS_LITERAL_STRING("resize"), this, false);
     target->AddEventListener(NS_LITERAL_STRING("keypress"), this, false);
 
     if (GetReadyStateEnum() != nsIDocument::READYSTATE_COMPLETE) {
-      LinkStylesheet(NS_LITERAL_STRING("resource://gre/res/ImageDocument.css"));
+      LinkStylesheet(NS_LITERAL_STRING("resource://content-accessible/ImageDocument.css"));
       if (!nsContentUtils::IsChildOfSameType(this)) {
-        LinkStylesheet(NS_LITERAL_STRING("resource://gre/res/TopLevelImageDocument.css"));
+        LinkStylesheet(NS_LITERAL_STRING("resource://content-accessible/TopLevelImageDocument.css"));
         LinkStylesheet(NS_LITERAL_STRING("chrome://global/skin/media/TopLevelImageDocument.css"));
       }
     }
     BecomeInteractive();
   }
 }
 
 void
--- a/dom/html/VideoDocument.cpp
+++ b/dom/html/VideoDocument.cpp
@@ -69,17 +69,17 @@ VideoDocument::SetScriptGlobalObject(nsI
 {
   // Set the script global object on the superclass before doing
   // anything that might require it....
   MediaDocument::SetScriptGlobalObject(aScriptGlobalObject);
 
   if (aScriptGlobalObject) {
     if (!nsContentUtils::IsChildOfSameType(this) &&
         GetReadyStateEnum() != nsIDocument::READYSTATE_COMPLETE) {
-      LinkStylesheet(NS_LITERAL_STRING("resource://gre/res/TopLevelVideoDocument.css"));
+      LinkStylesheet(NS_LITERAL_STRING("resource://content-accessible/TopLevelVideoDocument.css"));
       LinkStylesheet(NS_LITERAL_STRING("chrome://global/skin/media/TopLevelVideoDocument.css"));
       LinkScript(NS_LITERAL_STRING("chrome://global/content/TopLevelVideoDocument.js"));
     }
     BecomeInteractive();
   }
 }
 
 nsresult
--- a/dom/media/AudioBufferUtils.h
+++ b/dom/media/AudioBufferUtils.h
@@ -24,55 +24,59 @@ static inline uint32_t SamplesToFrames(u
   return aSamples / aChannels;
 }
 
 /**
  * Class that gets a buffer pointer from an audio callback and provides a safe
  * interface to manipulate this buffer, and to ensure we are not missing frames
  * by the end of the callback.
  */
-template<typename T, uint32_t CHANNELS>
+template<typename T>
 class AudioCallbackBufferWrapper
 {
 public:
-  AudioCallbackBufferWrapper()
+  explicit AudioCallbackBufferWrapper(uint32_t aChannels)
     : mBuffer(nullptr),
       mSamples(0),
-      mSampleWriteOffset(1)
-  {}
+      mSampleWriteOffset(1),
+      mChannels(aChannels)
+
+  {
+    MOZ_ASSERT(aChannels);
+  }
   /**
    * Set the buffer in this wrapper. This is to be called at the beginning of
    * the callback.
    */
   void SetBuffer(T* aBuffer, uint32_t aFrames) {
     MOZ_ASSERT(!mBuffer && !mSamples,
         "SetBuffer called twice.");
     mBuffer = aBuffer;
-    mSamples = FramesToSamples(CHANNELS, aFrames);
+    mSamples = FramesToSamples(mChannels, aFrames);
     mSampleWriteOffset = 0;
   }
 
   /**
    * Write some frames to the internal buffer. Free space in the buffer should
    * be check prior to calling this.
    */
   void WriteFrames(T* aBuffer, uint32_t aFrames) {
     MOZ_ASSERT(aFrames <= Available(),
         "Writing more that we can in the audio buffer.");
 
-    PodCopy(mBuffer + mSampleWriteOffset, aBuffer, FramesToSamples(CHANNELS,
+    PodCopy(mBuffer + mSampleWriteOffset, aBuffer, FramesToSamples(mChannels,
                                                                    aFrames));
-    mSampleWriteOffset += FramesToSamples(CHANNELS, aFrames);
+    mSampleWriteOffset += FramesToSamples(mChannels, aFrames);
   }
 
   /**
    * Number of frames that can be written to the buffer.
    */
   uint32_t Available() {
-    return SamplesToFrames(CHANNELS, mSamples - mSampleWriteOffset);
+    return SamplesToFrames(mChannels, mSamples - mSampleWriteOffset);
   }
 
   /**
    * Check that the buffer is completly filled, and reset internal state so this
    * instance can be reused.
    */
   void BufferFilled() {
     // It's okay to have exactly zero samples here, it can happen we have an
@@ -83,84 +87,92 @@ public:
     // all the streams were ended (no mixer callback occured).
     // XXX Remove this warning, or find a way to avoid it if the mixer callback
     // isn't called.
     NS_WARNING_ASSERTION(
       Available() == 0 || mSampleWriteOffset == 0,
       "Audio Buffer is not full by the end of the callback.");
     // Make sure the data returned is always set and not random!
     if (Available()) {
-      PodZero(mBuffer + mSampleWriteOffset, FramesToSamples(CHANNELS, Available()));
+      PodZero(mBuffer + mSampleWriteOffset, FramesToSamples(mChannels, Available()));
     }
     MOZ_ASSERT(mSamples, "Buffer not set.");
     mSamples = 0;
     mSampleWriteOffset = 0;
     mBuffer = nullptr;
   }
 
 private:
   /* This is not an owned pointer, but the pointer passed to use via the audio
    * callback. */
   T* mBuffer;
   /* The number of samples of this audio buffer. */
   uint32_t mSamples;
   /* The position at which new samples should be written. We want to return to
    * the audio callback iff this is equal to mSamples. */
   uint32_t mSampleWriteOffset;
+  uint32_t const mChannels;
 };
 
 /**
  * This is a class that interfaces with the AudioCallbackBufferWrapper, and is
  * responsible for storing the excess of data produced by the MediaStreamGraph
  * because of different rounding constraints, to be used the next time the audio
  * backend calls back.
  */
-template<typename T, uint32_t BLOCK_SIZE, uint32_t CHANNELS>
+template<typename T, uint32_t BLOCK_SIZE>
 class SpillBuffer
 {
 public:
-  SpillBuffer()
+  explicit SpillBuffer(uint32_t aChannels)
   : mPosition(0)
+  , mChannels(aChannels)
   {
-    PodArrayZero(mBuffer);
+    MOZ_ASSERT(aChannels);
+    mBuffer = MakeUnique<T[]>(BLOCK_SIZE * mChannels);
+    PodZero(mBuffer.get(), BLOCK_SIZE * mChannels);
   }
+
   /* Empty the spill buffer into the buffer of the audio callback. This returns
    * the number of frames written. */
-  uint32_t Empty(AudioCallbackBufferWrapper<T, CHANNELS>& aBuffer) {
+  uint32_t Empty(AudioCallbackBufferWrapper<T>& aBuffer) {
     uint32_t framesToWrite = std::min(aBuffer.Available(),
-                                      SamplesToFrames(CHANNELS, mPosition));
+                                      SamplesToFrames(mChannels, mPosition));
 
-    aBuffer.WriteFrames(mBuffer, framesToWrite);
+    aBuffer.WriteFrames(mBuffer.get(), framesToWrite);
 
-    mPosition -= FramesToSamples(CHANNELS, framesToWrite);
+    mPosition -= FramesToSamples(mChannels, framesToWrite);
     // If we didn't empty the spill buffer for some reason, shift the remaining data down
     if (mPosition > 0) {
-      PodMove(mBuffer, mBuffer + FramesToSamples(CHANNELS, framesToWrite),
+      MOZ_ASSERT(FramesToSamples(mChannels, framesToWrite) + mPosition <= BLOCK_SIZE * mChannels);
+      PodMove(mBuffer.get(), mBuffer.get() + FramesToSamples(mChannels, framesToWrite),
               mPosition);
     }
 
     return framesToWrite;
   }
   /* Fill the spill buffer from aInput, containing aFrames frames, return the
    * number of frames written to the spill buffer */
   uint32_t Fill(T* aInput, uint32_t aFrames) {
     uint32_t framesToWrite = std::min(aFrames,
-                                      BLOCK_SIZE - SamplesToFrames(CHANNELS,
+                                      BLOCK_SIZE - SamplesToFrames(mChannels,
                                                                    mPosition));
 
-    PodCopy(mBuffer + mPosition, aInput, FramesToSamples(CHANNELS,
+    MOZ_ASSERT(FramesToSamples(mChannels, framesToWrite) + mPosition <= BLOCK_SIZE * mChannels);
+    PodCopy(mBuffer.get() + mPosition, aInput, FramesToSamples(mChannels,
                                                          framesToWrite));
 
-    mPosition += FramesToSamples(CHANNELS, framesToWrite);
+    mPosition += FramesToSamples(mChannels, framesToWrite);
 
     return framesToWrite;
   }
 private:
   /* The spilled data. */
-  T mBuffer[BLOCK_SIZE * CHANNELS];
+  UniquePtr<T[]> mBuffer;
   /* The current write position, in samples, in the buffer when filling, or the
    * amount of buffer filled when emptying. */
   uint32_t mPosition;
+  uint32_t const mChannels;
 };
 
 } // namespace mozilla
 
 #endif // MOZILLA_SCRATCHBUFFER_H_
--- a/dom/media/AudioConverter.cpp
+++ b/dom/media/AudioConverter.cpp
@@ -60,16 +60,19 @@ AudioConverter::CanWorkInPlace() const
   // perform any upsampling in place (e.g. if incoming rate >= outgoing rate)
   return !needUpmix && (!needDownmix || canDownmixInPlace) &&
          (!needResample || canResampleInPlace);
 }
 
 size_t
 AudioConverter::ProcessInternal(void* aOut, const void* aIn, size_t aFrames)
 {
+  if (!aFrames) {
+    return 0;
+  }
   if (mIn.Channels() > mOut.Channels()) {
     return DownmixAudio(aOut, aIn, aFrames);
   } else if (mIn.Channels() < mOut.Channels()) {
     return UpmixAudio(aOut, aIn, aFrames);
   } else if (mIn.Layout() != mOut.Layout() && CanReorderAudio()) {
     ReOrderInterleavedChannels(aOut, aIn, aFrames);
   } else if (aIn != aOut) {
     memmove(aOut, aIn, FramesOutToBytes(aFrames));
--- a/dom/media/AudioConverter.h
+++ b/dom/media/AudioConverter.h
@@ -126,23 +126,18 @@ public:
   // Providing an empty buffer and resampling is expected, the resampler
   // will be drained.
   template <AudioConfig::SampleFormat Format, typename Value>
   AudioDataBuffer<Format, Value> Process(AudioDataBuffer<Format, Value>&& aBuffer)
   {
     MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format() && mIn.Format() == Format);
     AudioDataBuffer<Format, Value> buffer = Move(aBuffer);
     if (CanWorkInPlace()) {
-      size_t frames = SamplesInToFrames(buffer.Length());
-      frames = ProcessInternal(buffer.Data(), buffer.Data(), frames);
-      if (frames && mIn.Rate() != mOut.Rate()) {
-        frames = ResampleAudio(buffer.Data(), buffer.Data(), frames);
-      }
       AlignedBuffer<Value> temp = buffer.Forget();
-      temp.SetLength(FramesOutToSamples(frames));
+      Process(temp, temp.Data(), SamplesInToFrames(temp.Length()));
       return AudioDataBuffer<Format, Value>(Move(temp));;
     }
     return Process(buffer);
   }
 
   template <AudioConfig::SampleFormat Format, typename Value>
   AudioDataBuffer<Format, Value> Process(const AudioDataBuffer<Format, Value>& aBuffer)
   {
@@ -191,16 +186,48 @@ public:
     }
     size_t frames = ProcessInternal(aBuffer, aBuffer, aFrames);
     if (frames && mIn.Rate() != mOut.Rate()) {
       frames = ResampleAudio(aBuffer, aBuffer, aFrames);
     }
     return frames;
   }
 
+  template <typename Value>
+  size_t Process(AlignedBuffer<Value>& aOutBuffer, const Value* aInBuffer, size_t aFrames)
+  {
+    MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format());
+    MOZ_ASSERT((aFrames && aInBuffer) || !aFrames);
+    // Up/down mixing first
+    if (!aOutBuffer.SetLength(FramesOutToSamples(aFrames))) {
+      MOZ_ALWAYS_TRUE(aOutBuffer.SetLength(0));
+      return 0;
+    }
+    size_t frames = ProcessInternal(aOutBuffer.Data(), aInBuffer, aFrames);
+    MOZ_ASSERT(frames == aFrames);
+    // Check if resampling is needed
+    if (mIn.Rate() == mOut.Rate()) {
+      return frames;
+    }
+    // Prepare output in cases of drain or up-sampling
+    if ((!frames || mOut.Rate() > mIn.Rate()) &&
+        !aOutBuffer.SetLength(FramesOutToSamples(ResampleRecipientFrames(frames)))) {
+      MOZ_ALWAYS_TRUE(aOutBuffer.SetLength(0));
+      return 0;
+    }
+    if (!frames) {
+      frames = DrainResampler(aOutBuffer.Data());
+    } else {
+      frames = ResampleAudio(aOutBuffer.Data(), aInBuffer, frames);
+    }
+    // Update with the actual buffer length
+    MOZ_ALWAYS_TRUE(aOutBuffer.SetLength(FramesOutToSamples(frames)));
+    return frames;
+  }
+
   bool CanWorkInPlace() const;
   bool CanReorderAudio() const
   {
     return mIn.Layout().MappingTable(mOut.Layout());
   }
 
   const AudioConfig& InputConfig() const { return mIn; }
   const AudioConfig& OutputConfig() const { return mOut; }
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -555,16 +555,19 @@ StreamAndPromiseForOperation::StreamAndP
   , mPromise(aPromise)
   , mOperation(aOperation)
 {
   // MOZ_ASSERT(aPromise);
 }
 
 AudioCallbackDriver::AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl)
   : GraphDriver(aGraphImpl)
+  , mOuputChannels(mGraphImpl->AudioChannelCount())
+  , mScratchBuffer(mOuputChannels)
+  , mBuffer(mOuputChannels)
   , mSampleRate(0)
   , mInputChannels(1)
   , mIterationDurationMS(MEDIA_GRAPH_TARGET_PERIOD_MS)
   , mStarted(false)
   , mAudioInput(nullptr)
   , mAddedMixer(false)
   , mInCallback(false)
   , mMicrophoneActive(false)
@@ -621,25 +624,24 @@ AudioCallbackDriver::Init()
   uint32_t latency_frames;
   bool firstStream = CubebUtils::GetFirstStream();
 
   MOZ_ASSERT(!NS_IsMainThread(),
       "This is blocking and should never run on the main thread.");
 
   mSampleRate = output.rate = CubebUtils::PreferredSampleRate();
 
-  output.channels = mGraphImpl->AudioChannelCount();
   if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) {
     output.format = CUBEB_SAMPLE_S16NE;
   } else {
     output.format = CUBEB_SAMPLE_FLOAT32NE;
   }
 
-  // Graphs are always stereo for now.
-  output.layout = CUBEB_LAYOUT_STEREO;
+  output.channels = mOuputChannels;
+  output.layout = CUBEB_LAYOUT_UNDEFINED;
 
   Maybe<uint32_t> latencyPref = CubebUtils::GetCubebMSGLatencyInFrames();
   if (latencyPref) {
     latency_frames = latencyPref.value();
   } else {
     if (cubeb_get_min_latency(cubebContext, &output, &latency_frames) != CUBEB_OK) {
       NS_WARNING("Could not get minimal latency from cubeb.");
     }
@@ -918,17 +920,17 @@ AudioCallbackDriver::DataCallback(const 
   GraphTime stateComputedTime = StateComputedTime();
   if (stateComputedTime == 0) {
     MonitorAutoLock mon(mGraphImpl->GetMonitor());
     // Because this function is called during cubeb_stream_init (to prefill the
     // audio buffers), it can be that we don't have a message here (because this
     // driver is the first one for this graph), and the graph would exit. Simply
     // return here until we have messages.
     if (!mGraphImpl->MessagesQueued()) {
-      PodZero(aOutputBuffer, aFrames * mGraphImpl->AudioChannelCount());
+      PodZero(aOutputBuffer, aFrames * mOuputChannels);
       return aFrames;
     }
     mGraphImpl->SwapMessageQueues();
   }
 
   uint32_t durationMS = aFrames * 1000 / mSampleRate;
 
   // For now, simply average the duration with the previous
@@ -1005,17 +1007,17 @@ AudioCallbackDriver::DataCallback(const 
   mBuffer.BufferFilled();
 
   // Callback any observers for the AEC speaker data.  Note that one
   // (maybe) of these will be full-duplex, the others will get their input
   // data off separate cubeb callbacks.  Take care with how stuff is
   // removed/added to this list and TSAN issues, but input and output will
   // use separate callback methods.
   mGraphImpl->NotifyOutputData(aOutputBuffer, static_cast<size_t>(aFrames),
-                               mSampleRate, ChannelCount);
+                               mSampleRate, mOuputChannels);
 
   bool switching = false;
   {
     MonitorAutoLock mon(mGraphImpl->GetMonitor());
     switching = !!NextDriver();
   }
 
   if (switching && stillProcessing) {
--- a/dom/media/GraphDriver.h
+++ b/dom/media/GraphDriver.h
@@ -465,28 +465,28 @@ private:
   void PanOutputIfNeeded(bool aMicrophoneActive);
   /**
    * This is called when the output device used by the cubeb stream changes. */
   void DeviceChangedCallback();
   /* Start the cubeb stream */
   bool StartStream();
   friend class AsyncCubebTask;
   bool Init();
-  /* MediaStreamGraphs are always down/up mixed to stereo for now. */
-  static const uint32_t ChannelCount = 2;
+  /* MediaStreamGraphs are always down/up mixed to output channels. */
+  uint32_t mOuputChannels;
   /* The size of this buffer comes from the fact that some audio backends can
    * call back with a number of frames lower than one block (128 frames), so we
    * need to keep at most two block in the SpillBuffer, because we always round
    * up to block boundaries during an iteration.
    * This is only ever accessed on the audio callback thread. */
-  SpillBuffer<AudioDataValue, WEBAUDIO_BLOCK_SIZE * 2, ChannelCount> mScratchBuffer;
+  SpillBuffer<AudioDataValue, WEBAUDIO_BLOCK_SIZE * 2> mScratchBuffer;
   /* Wrapper to ensure we write exactly the number of frames we need in the
    * audio buffer cubeb passes us. This is only ever accessed on the audio
    * callback thread. */
-  AudioCallbackBufferWrapper<AudioDataValue, ChannelCount> mBuffer;
+  AudioCallbackBufferWrapper<AudioDataValue> mBuffer;
   /* cubeb stream for this graph. This is guaranteed to be non-null after Init()
    * has been called, and is synchronized internaly. */
   nsAutoRef<cubeb_stream> mAudioStream;
   /* The sample rate for the aforementionned cubeb stream. This is set on
    * initialization and can be read safely afterwards. */
   uint32_t mSampleRate;
   /* The number of input channels from cubeb.  Should be set before opening cubeb
    * and then be static. */
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -448,18 +448,20 @@ public:
   /**
    * Mark the media stream order as dirty.
    */
   void SetStreamOrderDirty()
   {
     mStreamOrderDirty = true;
   }
 
-  // Always stereo for now.
-  uint32_t AudioChannelCount() const { return 2; }
+  uint32_t AudioChannelCount() const
+  {
+    return std::min<uint32_t>(8, CubebUtils::MaxNumberOfChannels());
+  }
 
   double MediaTimeToSeconds(GraphTime aTime) const
   {
     NS_ASSERTION(aTime > -STREAM_TIME_MAX && aTime <= STREAM_TIME_MAX,
                  "Bad time");
     return static_cast<double>(aTime)/GraphRate();
   }
 
--- a/dom/media/gtest/TestAudioBuffers.cpp
+++ b/dom/media/gtest/TestAudioBuffers.cpp
@@ -1,57 +1,60 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
 
 #include <stdint.h>
 #include "AudioBufferUtils.h"
 #include "gtest/gtest.h"
+#include <vector>
 
 const uint32_t FRAMES = 256;
-const uint32_t CHANNELS = 2;
-const uint32_t SAMPLES = CHANNELS * FRAMES;
 
-TEST(AudioBuffers, Test)
+void test_for_number_of_channels(const uint32_t channels)
 {
-  mozilla::AudioCallbackBufferWrapper<float, CHANNELS> mBuffer;
-  mozilla::SpillBuffer<float, 128, CHANNELS> b;
-  float fromCallback[SAMPLES];
-  float other[SAMPLES];
+  const uint32_t samples = channels * FRAMES;
 
-  for (uint32_t i = 0; i < SAMPLES; i++) {
-    other[i] = 1.0;
-    fromCallback[i] = 0.0;
-  }
+  mozilla::AudioCallbackBufferWrapper<float> mBuffer(channels);
+  mozilla::SpillBuffer<float, 128> b(channels);
+  std::vector<float> fromCallback(samples, 0.0);
+  std::vector<float> other(samples, 1.0);
 
   // Set the buffer in the wrapper from the callback
-  mBuffer.SetBuffer(fromCallback, FRAMES);
+  mBuffer.SetBuffer(fromCallback.data(), FRAMES);
 
   // Fill the SpillBuffer with data.
-  ASSERT_TRUE(b.Fill(other, 15) == 15);
-  ASSERT_TRUE(b.Fill(other, 17) == 17);
-  for (uint32_t i = 0; i < 32 * CHANNELS; i++) {
+  ASSERT_TRUE(b.Fill(other.data(), 15) == 15);
+  ASSERT_TRUE(b.Fill(other.data(), 17) == 17);
+  for (uint32_t i = 0; i < 32 * channels; i++) {
     other[i] = 0.0;
   }
 
   // Empty it in the AudioCallbackBufferWrapper
   ASSERT_TRUE(b.Empty(mBuffer) == 32);
 
   // Check available return something reasonnable
   ASSERT_TRUE(mBuffer.Available() == FRAMES - 32);
 
   // Fill the buffer with the rest of the data
-  mBuffer.WriteFrames(other + 32 * CHANNELS, FRAMES - 32);
+  mBuffer.WriteFrames(other.data() + 32 * channels, FRAMES - 32);
 
   // Check the buffer is now full
   ASSERT_TRUE(mBuffer.Available() == 0);
 
-  for (uint32_t i = 0 ; i < SAMPLES; i++) {
+  for (uint32_t i = 0 ; i < samples; i++) {
     ASSERT_TRUE(fromCallback[i] == 1.0) <<
       "Difference at " << i << " (" << fromCallback[i] << " != " << 1.0 <<
       ")\n";
   }
 
-  ASSERT_TRUE(b.Fill(other, FRAMES) == 128);
-  ASSERT_TRUE(b.Fill(other, FRAMES) == 0);
+  ASSERT_TRUE(b.Fill(other.data(), FRAMES) == 128);
+  ASSERT_TRUE(b.Fill(other.data(), FRAMES) == 0);
   ASSERT_TRUE(b.Empty(mBuffer) == 0);
 }
+
+TEST(AudioBuffers, Test)
+{
+  for (uint32_t ch = 1; ch <= 8; ++ch) {
+    test_for_number_of_channels(ch);
+  }
+}
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -200,18 +200,20 @@ AudioContext::Constructor(const GlobalOb
                           ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
   if (!window) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
+  uint32_t maxChannelCount = std::min<uint32_t>(WebAudioUtils::MaxChannelCount,
+      CubebUtils::MaxNumberOfChannels());
   RefPtr<AudioContext> object =
-    new AudioContext(window, false);
+    new AudioContext(window, false,maxChannelCount);
   aRv = object->Init();
   if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
   }
 
   RegisterWeakMemoryReporter(object);
 
   return object.forget();
@@ -617,17 +619,18 @@ AudioContext::UpdatePannerSource()
   for (auto iter = mPannerNodes.Iter(); !iter.Done(); iter.Next()) {
     iter.Get()->GetKey()->FindConnectedSources();
   }
 }
 
 uint32_t
 AudioContext::MaxChannelCount() const
 {
-  return mIsOffline ? mNumberOfChannels : CubebUtils::MaxNumberOfChannels();
+  return std::min<uint32_t>(WebAudioUtils::MaxChannelCount,
+      mIsOffline ? mNumberOfChannels : CubebUtils::MaxNumberOfChannels());
 }
 
 uint32_t
 AudioContext::ActiveNodeCount() const
 {
   return mActiveNodes.Count();
 }
 
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -320,17 +320,17 @@ NS_INTERFACE_MAP_END_INHERITING(AudioNod
 
 NS_IMPL_ADDREF_INHERITED(AudioDestinationNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(AudioDestinationNode, AudioNode)
 
 AudioDestinationNode::AudioDestinationNode(AudioContext* aContext,
                                            bool aIsOffline,
                                            uint32_t aNumberOfChannels,
                                            uint32_t aLength, float aSampleRate)
-  : AudioNode(aContext, aIsOffline ? aNumberOfChannels : 2,
+  : AudioNode(aContext, aNumberOfChannels,
               ChannelCountMode::Explicit, ChannelInterpretation::Speakers)
   , mFramesToProduce(aLength)
   , mIsOffline(aIsOffline)
   , mAudioChannelSuspended(false)
   , mCaptured(false)
   , mAudible(AudioChannelService::AudibleState::eAudible)
 {
   nsPIDOMWindowInner* window = aContext->GetParentObject();
--- a/dom/media/webrtc/AudioOutputObserver.h
+++ b/dom/media/webrtc/AudioOutputObserver.h
@@ -3,16 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef AUDIOOUTPUTOBSERVER_H_
 #define AUDIOOUTPUTOBSERVER_H_
 
 #include "mozilla/StaticPtr.h"
 #include "nsAutoPtr.h"
 #include "AudioMixer.h"
+#include "MediaData.h"
 
 namespace webrtc {
 class SingleRwFifo;
 }
 
 namespace mozilla {
 
 typedef struct FarEndAudioChunk_ {
@@ -45,15 +46,16 @@ private:
   uint32_t mPlayoutChannels;
 
   nsAutoPtr<webrtc::SingleRwFifo> mPlayoutFifo;
   uint32_t mChunkSize;
 
   // chunking to 10ms support
   FarEndAudioChunk *mSaved; // can't be nsAutoPtr since we need to use free(), not delete
   uint32_t mSamplesSaved;
+  AlignedAudioBuffer mDownmixBuffer;
 };
 
 extern StaticRefPtr<AudioOutputObserver> gFarendObserver;
 
 }
 
 #endif
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -4,16 +4,17 @@
 
 #include "MediaEngineWebRTC.h"
 #include <stdio.h>
 #include <algorithm>
 #include "mozilla/Assertions.h"
 #include "MediaTrackConstraints.h"
 #include "mtransport/runnable_utils.h"
 #include "nsAutoPtr.h"
+#include "AudioConverter.h"
 
 // scoped_ptr.h uses FF
 #ifdef FF
 #undef FF
 #endif
 #include "webrtc/modules/audio_device/opensl/single_rw_fifo.h"
 
 #define CHANNELS 1
@@ -58,16 +59,17 @@ ScopedCustomReleasePtr<webrtc::VoENetwor
 ScopedCustomReleasePtr<webrtc::VoEAudioProcessing> MediaEngineWebRTCMicrophoneSource::mVoEProcessing;
 
 AudioOutputObserver::AudioOutputObserver()
   : mPlayoutFreq(0)
   , mPlayoutChannels(0)
   , mChunkSize(0)
   , mSaved(nullptr)
   , mSamplesSaved(0)
+  , mDownmixBuffer(MAX_SAMPLING_FREQ * MAX_CHANNELS / 100)
 {
   // Buffers of 10ms chunks
   mPlayoutFifo = new webrtc::SingleRwFifo(MAX_AEC_FIFO_DEPTH/10);
 }
 
 AudioOutputObserver::~AudioOutputObserver()
 {
   Clear();
@@ -96,23 +98,29 @@ AudioOutputObserver::Size()
   return mPlayoutFifo->size();
 }
 
 // static
 void
 AudioOutputObserver::InsertFarEnd(const AudioDataValue *aBuffer, uint32_t aFrames, bool aOverran,
                                   int aFreq, int aChannels)
 {
+  // Prepare for downmix if needed
+  int channels = aChannels;
+  if (aChannels > MAX_CHANNELS) {
+    channels = MAX_CHANNELS;
+  }
+
   if (mPlayoutChannels != 0) {
-    if (mPlayoutChannels != static_cast<uint32_t>(aChannels)) {
+    if (mPlayoutChannels != static_cast<uint32_t>(channels)) {
       MOZ_CRASH();
     }
   } else {
-    MOZ_ASSERT(aChannels <= MAX_CHANNELS);
-    mPlayoutChannels = static_cast<uint32_t>(aChannels);
+    MOZ_ASSERT(channels <= MAX_CHANNELS);
+    mPlayoutChannels = static_cast<uint32_t>(channels);
   }
   if (mPlayoutFreq != 0) {
     if (mPlayoutFreq != static_cast<uint32_t>(aFreq)) {
       MOZ_CRASH();
     }
   } else {
     MOZ_ASSERT(aFreq <= MAX_SAMPLING_FREQ);
     MOZ_ASSERT(!(aFreq % 100), "Sampling rate for far end data should be multiple of 100.");
@@ -130,28 +138,34 @@ AudioOutputObserver::InsertFarEnd(const 
     aOverran = false;
   }
   // Rechunk to 10ms.
   // The AnalyzeReverseStream() and WebRtcAec_BufferFarend() functions insist on 10ms
   // samples per call.  Annoying...
   while (aFrames) {
     if (!mSaved) {
       mSaved = (FarEndAudioChunk *) moz_xmalloc(sizeof(FarEndAudioChunk) +
-                                                (mChunkSize * aChannels - 1)*sizeof(int16_t));
+                                                (mChunkSize * channels - 1)*sizeof(int16_t));
       mSaved->mSamples = mChunkSize;
       mSaved->mOverrun = aOverran;
       aOverran = false;
     }
     uint32_t to_copy = mChunkSize - mSamplesSaved;
     if (to_copy > aFrames) {
       to_copy = aFrames;
     }
 
-    int16_t *dest = &(mSaved->mData[mSamplesSaved * aChannels]);
-    ConvertAudioSamples(aBuffer, dest, to_copy * aChannels);
+    int16_t* dest = &(mSaved->mData[mSamplesSaved * channels]);
+    if (aChannels > MAX_CHANNELS) {
+      AudioConverter converter(AudioConfig(aChannels, 0), AudioConfig(channels, 0));
+      converter.Process(mDownmixBuffer, aBuffer, to_copy);
+      ConvertAudioSamples(mDownmixBuffer.Data(), dest, to_copy * channels);
+    } else {
+      ConvertAudioSamples(aBuffer, dest, to_copy * channels);
+    }
 
 #ifdef LOG_FAREND_INSERTION
     if (fp) {
       fwrite(&(mSaved->mData[mSamplesSaved * aChannels]), to_copy * aChannels, sizeof(int16_t), fp);
     }
 #endif
     aFrames -= to_copy;
     mSamplesSaved += to_copy;
--- a/dom/security/test/mixedcontentblocker/test_bug803225.html
+++ b/dom/security/test/mixedcontentblocker/test_bug803225.html
@@ -14,18 +14,21 @@ https://bugzilla.mozilla.org/show_bug.cg
   var counter = 0;
   var settings = [ [true, true], [true, false], [false, true], [false, false] ];
 
   var blockActive;
   var blockDisplay;
 
   //Cycle through 4 different preference settings.
   function changePrefs(callback) {
-    let newPrefs = [["security.mixed_content.block_display_content", settings[counter][0]],
-                    ["security.mixed_content.block_active_content", settings[counter][1]]];
+    let newPrefs = [
+      ["security.all_resource_uri_content_accessible", true], // Temporarily allow content to access all resource:// URIs.
+      ["security.mixed_content.block_display_content", settings[counter][0]],
+      ["security.mixed_content.block_active_content", settings[counter][1]]
+    ];
 
     SpecialPowers.pushPrefEnv({"set": newPrefs}, function () {
       blockDisplay = SpecialPowers.getBoolPref("security.mixed_content.block_display_content");
       blockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content");
       counter++;
       callback();
     });
   }
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -889,20 +889,29 @@ private:
     return NS_OK;
   }
 
   nsresult
   LoadScript(uint32_t aIndex)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aIndex < mLoadInfos.Length());
+    MOZ_ASSERT_IF(IsMainWorkerScript(), mWorkerScriptType != DebuggerScript);
 
     WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
 
-    nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
+    // For JavaScript debugging, the devtools server must run on the same
+    // thread as the debuggee, indicating the worker uses content principal.
+    // However, in Bug 863246, web content will no longer be able to load
+    // resource:// URIs by default, so we need system principal to load
+    // debugger scripts.
+    nsIPrincipal* principal = (mWorkerScriptType == DebuggerScript) ?
+                              nsContentUtils::GetSystemPrincipal() :
+                              mWorkerPrivate->GetPrincipal();
+
     nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
     MOZ_DIAGNOSTIC_ASSERT(principal);
 
     NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadGroup, principal),
                    NS_ERROR_FAILURE);
 
     // Figure out our base URI.
     nsCOMPtr<nsIURI> baseURI = GetBaseURI(mIsMainScript, mWorkerPrivate);
--- a/dom/xml/resources/XMLPrettyPrint.css
+++ b/dom/xml/resources/XMLPrettyPrint.css
@@ -1,14 +1,14 @@
 @charset "UTF-8";
 /* 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/. */
 
-@import url("resource://gre-resources/viewsource.css");
+@import url("resource://content-accessible/viewsource.css");
 
 #header {
   background-color: #ccc;
   border-bottom: 3px solid black;
   padding: 0.5em;
   margin-bottom: 1em;
 }
 
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 310af2613e7508b22cad11e734b8c47e66447cc7
+Latest Commit: b7cec8b19d5d6061263c3639031caf41562a2e17
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -533,17 +533,17 @@ private:
   DECL_OVERRIDE_PREF(Live, "layers.advanced.canvas-background-color", LayersAllowCanvasBackgroundColorLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.caret-layers",            LayersAllowCaretLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.columnRule-layers",       LayersAllowColumnRuleLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.displaybuttonborder-layers", LayersAllowDisplayButtonBorder, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.filter-layers",           LayersAllowFilterLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.image-layers",            LayersAllowImageLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.outline-layers",          LayersAllowOutlineLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.solid-color",             LayersAllowSolidColorLayers, gfxPrefs::OverrideBase_WebRender());
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.table",                   LayersAllowTable, gfxPrefs::OverrideBase_WebRendest());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.table",                   LayersAllowTable, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.text-layers",             LayersAllowTextLayers, gfxPrefs::OverrideBase_WebRendest());
   DECL_GFX_PREF(Once, "layers.amd-switchable-gfx.enabled",     LayersAMDSwitchableGfxEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.async-pan-zoom.enabled",         AsyncPanZoomEnabledDoNotUseDirectly, bool, true);
   DECL_GFX_PREF(Once, "layers.async-pan-zoom.separate-event-thread", AsyncPanZoomSeparateEventThread, bool, false);
   DECL_GFX_PREF(Live, "layers.bench.enabled",                  LayersBenchEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.bufferrotation.enabled",         BufferRotationEnabled, bool, true);
   DECL_GFX_PREF(Live, "layers.child-process-shutdown",         ChildProcessShutdown, bool, true);
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -5,16 +5,17 @@ authors = ["Glenn Watson <gw@intuitionli
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["freetype-lib"]
 freetype-lib = ["freetype/servo-freetype-sys"]
 profiler = ["thread_profiler/thread_profiler"]
+debugger = ["ws", "serde_json", "serde", "serde_derive"]
 
 [dependencies]
 app_units = "0.5"
 bincode = "0.8"
 bit-set = "0.4"
 byteorder = "1.0"
 euclid = "0.15.1"
 fxhash = "0.2.1"
@@ -24,16 +25,20 @@ log = "0.3"
 num-traits = "0.1.32"
 time = "0.1"
 rayon = "0.8"
 webrender_api = {path = "../webrender_api"}
 bitflags = "0.9"
 gamma-lut = "0.2"
 thread_profiler = "0.1.1"
 plane-split = "0.6"
+ws = { optional = true, version = "0.7.3" }
+serde_json = { optional = true, version = "1.0" }
+serde = { optional = true, version = "1.0" }
+serde_derive = { optional = true, version = "1.0" }
 
 [dev-dependencies]
 angle = {git = "https://github.com/servo/angle", branch = "servo"}
 rand = "0.3"                # for the benchmarks
 servo-glutin = "0.11"     # for the example apps
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
 freetype = { version = "0.3", default-features = false }
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -3,18 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use gleam::gl;
 use glutin;
 use std::env;
 use std::path::PathBuf;
 use webrender;
 use webrender::api::*;
-use webrender::renderer::{PROFILER_DBG, RENDER_TARGET_DBG, TEXTURE_CACHE_DBG};
-use webrender::renderer::ExternalImageHandler;
 
 struct Notifier {
     window_proxy: glutin::WindowProxy,
 }
 
 impl Notifier {
     fn new(window_proxy: glutin::WindowProxy) -> Notifier {
         Notifier {
@@ -60,17 +58,17 @@ pub trait Example {
               resources: &mut ResourceUpdates,
               layout_size: LayoutSize,
               pipeline_id: PipelineId,
               document_id: DocumentId);
     fn on_event(&mut self,
                 event: glutin::Event,
                 api: &RenderApi,
                 document_id: DocumentId) -> bool;
-    fn get_external_image_handler(&self) -> Option<Box<ExternalImageHandler>> {
+    fn get_external_image_handler(&self) -> Option<Box<webrender::ExternalImageHandler>> {
         None
     }
 }
 
 pub fn main_wrapper(example: &mut Example,
                     options: Option<webrender::RendererOptions>)
 {
     let args: Vec<String> = env::args().collect();
@@ -108,17 +106,17 @@ pub fn main_wrapper(example: &mut Exampl
         resource_override_path: res_path,
         debug: true,
         precache_shaders: true,
         device_pixel_ratio: window.hidpi_factor(),
         .. options.unwrap_or(webrender::RendererOptions::default())
     };
 
     let size = DeviceUintSize::new(width, height);
-    let (mut renderer, sender) = webrender::renderer::Renderer::new(gl, opts).unwrap();
+    let (mut renderer, sender) = webrender::Renderer::new(gl, opts).unwrap();
     let api = sender.create_api();
     let document_id = api.add_document(size);
 
     let notifier = Box::new(Notifier::new(window.create_window_proxy()));
     renderer.set_render_notifier(notifier);
 
     if let Some(external_image_handler) = example.get_external_image_handler() {
         renderer.set_external_image_handler(external_image_handler);
@@ -157,29 +155,29 @@ pub fn main_wrapper(example: &mut Exampl
             match event {
                 glutin::Event::Closed |
                 glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) |
                 glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Q)) => break 'outer,
 
                 glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
                                              _, Some(glutin::VirtualKeyCode::P)) => {
                     let mut flags = renderer.get_debug_flags();
-                    flags.toggle(PROFILER_DBG);
+                    flags.toggle(webrender::PROFILER_DBG);
                     renderer.set_debug_flags(flags);
                 }
                 glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
                                              _, Some(glutin::VirtualKeyCode::O)) => {
                     let mut flags = renderer.get_debug_flags();
-                    flags.toggle(RENDER_TARGET_DBG);
+                    flags.toggle(webrender::RENDER_TARGET_DBG);
                     renderer.set_debug_flags(flags);
                 }
                 glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
                                              _, Some(glutin::VirtualKeyCode::I)) => {
                     let mut flags = renderer.get_debug_flags();
-                    flags.toggle(TEXTURE_CACHE_DBG);
+                    flags.toggle(webrender::TEXTURE_CACHE_DBG);
                     renderer.set_debug_flags(flags);
                 }
                 glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
                                              _, Some(glutin::VirtualKeyCode::M)) => {
                     api.notify_memory_pressure();
                 }
                 _ => {
                     if example.on_event(event, &api, document_id) {
--- a/gfx/webrender/examples/texture_cache_stress.rs
+++ b/gfx/webrender/examples/texture_cache_stress.rs
@@ -7,17 +7,16 @@ extern crate glutin;
 extern crate webrender;
 
 #[path="common/boilerplate.rs"]
 mod boilerplate;
 
 use boilerplate::{Example, HandyDandyRectBuilder};
 use std::mem;
 use webrender::api::*;
-use webrender::renderer::{ExternalImage, ExternalImageSource, ExternalImageHandler};
 
 struct ImageGenerator {
     patterns: [[u8; 3]; 6],
     next_pattern: usize,
     current_image: Vec<u8>,
 }
 
 impl ImageGenerator {
@@ -52,25 +51,25 @@ impl ImageGenerator {
         self.next_pattern = (self.next_pattern + 1) % self.patterns.len();
     }
 
     fn take(&mut self) -> Vec<u8> {
         mem::replace(&mut self.current_image, Vec::new())
     }
 }
 
-impl ExternalImageHandler for ImageGenerator {
-    fn lock(&mut self, _key: ExternalImageId, channel_index: u8) -> ExternalImage {
+impl webrender::ExternalImageHandler for ImageGenerator {
+    fn lock(&mut self, _key: ExternalImageId, channel_index: u8) -> webrender::ExternalImage {
         self.generate_image(channel_index as u32);
-        ExternalImage {
+        webrender::ExternalImage {
             u0: 0.0,
             v0: 0.0,
             u1: 1.0,
             v1: 1.0,
-            source: ExternalImageSource::RawData(&self.current_image)
+            source: webrender::ExternalImageSource::RawData(&self.current_image)
         }
     }
     fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {
     }
 }
 
 struct App {
     stress_keys: Vec<ImageKey>,
@@ -265,17 +264,17 @@ impl Example for App {
                 return true;
             }
             _ => {}
         }
 
         false
     }
 
-    fn get_external_image_handler(&self) -> Option<Box<ExternalImageHandler>> {
+    fn get_external_image_handler(&self) -> Option<Box<webrender::ExternalImageHandler>> {
         Some(Box::new(ImageGenerator::new()))
     }
 }
 
 fn main() {
     let mut app = App {
         image_key: None,
         stress_keys: Vec::new(),
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/debug_server.rs
@@ -0,0 +1,143 @@
+/* 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/. */
+
+use api::{ApiMsg, DebugCommand};
+use api::channel::MsgSender;
+use std::sync::mpsc::{channel, Receiver};
+use std::thread;
+use std::sync::mpsc::Sender;
+
+use ws;
+
+// Messages that are sent from the render backend to the renderer
+// debug command queue. These are sent in a separate queue so
+// that none of these types are exposed to the RenderApi interfaces.
+// We can't use select!() as it's not stable...
+pub enum DebugMsg {
+    FetchBatches(ws::Sender),
+}
+
+// Represents a connection to a client.
+struct Server {
+    ws: ws::Sender,
+    debug_tx: Sender<DebugMsg>,
+    api_tx: MsgSender<ApiMsg>,
+}
+
+impl ws::Handler for Server {
+    fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
+        match msg {
+            ws::Message::Text(string) => {
+                let cmd = match string.as_str() {
+                    "enable_profiler" => {
+                        DebugCommand::EnableProfiler(true)
+                    }
+                    "disable_profiler" => {
+                        DebugCommand::EnableProfiler(false)
+                    }
+                    "enable_texture_cache_debug" => {
+                        DebugCommand::EnableTextureCacheDebug(true)
+                    }
+                    "disable_texture_cache_debug" => {
+                        DebugCommand::EnableTextureCacheDebug(false)
+                    }
+                    "enable_render_target_debug" => {
+                        DebugCommand::EnableRenderTargetDebug(true)
+                    }
+                    "disable_render_target_debug" => {
+                        DebugCommand::EnableRenderTargetDebug(false)
+                    }
+                    "fetch_batches" => {
+                        let msg = DebugMsg::FetchBatches(self.ws.clone());
+                        self.debug_tx.send(msg).unwrap();
+                        DebugCommand::Flush
+                    }
+                    msg => {
+                        println!("unknown msg {}", msg);
+                        return Ok(());
+                    }
+                };
+
+                let msg = ApiMsg::DebugCommand(cmd);
+                self.api_tx.send(msg).unwrap();
+            }
+            ws::Message::Binary(..) => {}
+        }
+
+        Ok(())
+    }
+}
+
+// Spawn a thread for a given renderer, and wait for
+// client connections.
+pub struct DebugServer {
+    join_handle: Option<thread::JoinHandle<()>>,
+    broadcaster: ws::Sender,
+    pub debug_rx: Receiver<DebugMsg>,
+}
+
+impl DebugServer {
+    pub fn new(api_tx: MsgSender<ApiMsg>) -> DebugServer {
+        let (debug_tx, debug_rx) = channel();
+
+        let socket = ws::Builder::new().build(move |out| {
+            Server {
+                ws: out,
+                debug_tx: debug_tx.clone(),
+                api_tx: api_tx.clone(),
+            }
+        }).unwrap();
+
+        let broadcaster = socket.broadcaster();
+
+        let join_handle = Some(thread::spawn(move || {
+            socket.listen("127.0.0.1:3583").unwrap();
+        }));
+
+        DebugServer {
+            join_handle,
+            broadcaster,
+            debug_rx,
+        }
+    }
+}
+
+impl Drop for DebugServer {
+    fn drop(&mut self) {
+        self.broadcaster.shutdown().unwrap();
+        self.join_handle.take().unwrap().join().unwrap();
+    }
+}
+
+// A serializable list of debug information about batches
+// that can be sent to the client.
+#[derive(Serialize)]
+pub struct BatchInfo {
+    kind: &'static str,
+    count: usize,
+}
+
+#[derive(Serialize)]
+pub struct BatchList {
+    kind: &'static str,
+    batches: Vec<BatchInfo>,
+}
+
+impl BatchList {
+    pub fn new() -> BatchList {
+        BatchList {
+            kind: "batches",
+            batches: Vec::new(),
+        }
+    }
+
+    pub fn push(&mut self, kind: &'static str, count: usize) {
+        if count > 0 {
+            self.batches.push(BatchInfo {
+                kind,
+                count,
+            });
+        }
+    }
+}
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -438,196 +438,205 @@ pub struct RBOId(gl::GLuint);
 pub struct VBOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 struct IBOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct PBOId(gl::GLuint);
 
-const MAX_EVENTS_PER_FRAME: usize = 256;
+const MAX_TIMERS_PER_FRAME: usize = 256;
+const MAX_SAMPLERS_PER_FRAME: usize = 16;
 const MAX_PROFILE_FRAMES: usize = 4;
 
 pub trait NamedTag {
     fn get_label(&self) -> &str;
 }
 
 #[derive(Debug, Clone)]
-pub struct GpuSample<T> {
+pub struct GpuTimer<T> {
     pub tag: T,
     pub time_ns: u64,
 }
 
+#[derive(Debug, Clone)]
+pub struct GpuSampler<T> {
+    pub tag: T,
+    pub count: u64,
+}
+
+pub struct QuerySet<T> {
+    set: Vec<gl::GLuint>,
+    data: Vec<T>,
+    pending: gl::GLuint,
+}
+
+impl<T> QuerySet<T> {
+    fn new(set: Vec<gl::GLuint>) -> Self {
+        QuerySet {
+            set,
+            data: Vec::new(),
+            pending: 0,
+        }
+    }
+
+    fn reset(&mut self) {
+        self.data.clear();
+        self.pending = 0;
+    }
+
+    fn add(&mut self, value: T) -> Option<gl::GLuint> {
+        assert_eq!(self.pending, 0);
+        self.set.get(self.data.len())
+            .cloned()
+            .map(|query_id| {
+                self.data.push(value);
+                self.pending = query_id;
+                query_id
+            })
+    }
+
+    fn take<F: Fn(&mut T, gl::GLuint)>(&mut self, fun: F) -> Vec<T> {
+        let mut data = mem::replace(&mut self.data, Vec::new());
+        for (value, &query) in data.iter_mut().zip(self.set.iter()) {
+            fun(value, query)
+        }
+        data
+    }
+}
+
 pub struct GpuFrameProfile<T> {
     gl: Rc<gl::Gl>,
-    queries: Vec<gl::GLuint>,
-    samples: Vec<GpuSample<T>>,
-    next_query: usize,
-    pending_query: gl::GLuint,
+    timers: QuerySet<GpuTimer<T>>,
+    samplers: QuerySet<GpuSampler<T>>,
     frame_id: FrameId,
     inside_frame: bool,
 }
 
 impl<T> GpuFrameProfile<T> {
     fn new(gl: Rc<gl::Gl>) -> Self {
-        match gl.get_type() {
-            gl::GlType::Gl => {
-                let queries = gl.gen_queries(MAX_EVENTS_PER_FRAME as gl::GLint);
-                GpuFrameProfile {
-                    gl,
-                    queries,
-                    samples: Vec::new(),
-                    next_query: 0,
-                    pending_query: 0,
-                    frame_id: FrameId(0),
-                    inside_frame: false,
-                }
-            }
-            gl::GlType::Gles => {
-                GpuFrameProfile {
-                    gl,
-                    queries: Vec::new(),
-                    samples: Vec::new(),
-                    next_query: 0,
-                    pending_query: 0,
-                    frame_id: FrameId(0),
-                    inside_frame: false,
-                }
-            }
+        let (time_queries, sample_queries) = match gl.get_type() {
+            gl::GlType::Gl => (
+                gl.gen_queries(MAX_TIMERS_PER_FRAME as gl::GLint),
+                gl.gen_queries(MAX_SAMPLERS_PER_FRAME as gl::GLint),
+            ),
+            gl::GlType::Gles => (Vec::new(), Vec::new()),
+        };
+
+        GpuFrameProfile {
+            gl,
+            timers: QuerySet::new(time_queries),
+            samplers: QuerySet::new(sample_queries),
+            frame_id: FrameId(0),
+            inside_frame: false,
         }
     }
 
     fn begin_frame(&mut self, frame_id: FrameId) {
         self.frame_id = frame_id;
-        self.next_query = 0;
-        self.pending_query = 0;
-        self.samples.clear();
+        self.timers.reset();
+        self.samplers.reset();
         self.inside_frame = true;
     }
 
     fn end_frame(&mut self) {
+        self.done_marker();
+        self.done_sampler();
         self.inside_frame = false;
-        match self.gl.get_type() {
-            gl::GlType::Gl => {
-                if self.pending_query != 0 {
-                    self.gl.end_query(gl::TIME_ELAPSED);
-                }
-            }
-            gl::GlType::Gles => {},
+    }
+
+    fn done_marker(&mut self) {
+        debug_assert!(self.inside_frame);
+        if self.timers.pending != 0 {
+            self.gl.end_query(gl::TIME_ELAPSED);
+            self.timers.pending = 0;
         }
     }
 
-    fn add_marker(&mut self, tag: T) -> GpuMarker
-    where T: NamedTag {
-        debug_assert!(self.inside_frame);
-        match self.gl.get_type() {
-            gl::GlType::Gl => {
-                self.add_marker_gl(tag)
-            }
-            gl::GlType::Gles => {
-                self.add_marker_gles(tag)
-            }
-        }
-    }
-
-    fn add_marker_gl(&mut self, tag: T) -> GpuMarker
-    where T: NamedTag {
-        if self.pending_query != 0 {
-            self.gl.end_query(gl::TIME_ELAPSED);
-        }
+    fn add_marker(&mut self, tag: T) -> GpuMarker where T: NamedTag {
+        self.done_marker();
 
         let marker = GpuMarker::new(&self.gl, tag.get_label());
 
-        if self.next_query < MAX_EVENTS_PER_FRAME {
-            self.pending_query = self.queries[self.next_query];
-            self.gl.begin_query(gl::TIME_ELAPSED, self.pending_query);
-            self.samples.push(GpuSample {
-                tag,
-                time_ns: 0,
-            });
-        } else {
-            self.pending_query = 0;
+        if let Some(query) = self.timers.add(GpuTimer { tag, time_ns: 0 }) {
+            self.gl.begin_query(gl::TIME_ELAPSED, query);
         }
 
-        self.next_query += 1;
         marker
     }
 
-    fn add_marker_gles(&mut self, tag: T) -> GpuMarker
-    where T: NamedTag {
-        let marker = GpuMarker::new(&self.gl, tag.get_label());
-        self.samples.push(GpuSample {
-            tag,
-            time_ns: 0,
-        });
-        marker
+    fn done_sampler(&mut self) {
+        debug_assert!(self.inside_frame);
+        if self.samplers.pending != 0 {
+            self.gl.end_query(gl::SAMPLES_PASSED);
+            self.samplers.pending = 0;
+        }
+    }
+
+    fn add_sampler(&mut self, tag: T) where T: NamedTag {
+        self.done_sampler();
+
+        if let Some(query) = self.samplers.add(GpuSampler { tag, count: 0 }) {
+            self.gl.begin_query(gl::SAMPLES_PASSED, query);
+        }
     }
 
     fn is_valid(&self) -> bool {
-        self.next_query > 0 && self.next_query <= MAX_EVENTS_PER_FRAME
+        !self.timers.set.is_empty() || !self.samplers.set.is_empty()
     }
 
-    fn build_samples(&mut self) -> Vec<GpuSample<T>> {
+    fn build_samples(&mut self) -> (Vec<GpuTimer<T>>, Vec<GpuSampler<T>>) {
         debug_assert!(!self.inside_frame);
-        match self.gl.get_type() {
-            gl::GlType::Gl => {
-                self.build_samples_gl()
-            }
-            gl::GlType::Gles => {
-                self.build_samples_gles()
-            }
-        }
-    }
+        let gl = &self.gl;
 
-    fn build_samples_gl(&mut self) -> Vec<GpuSample<T>> {
-        for (index, sample) in self.samples.iter_mut().enumerate() {
-            sample.time_ns = self.gl.get_query_object_ui64v(self.queries[index], gl::QUERY_RESULT)
-        }
-
-        mem::replace(&mut self.samples, Vec::new())
-    }
-
-    fn build_samples_gles(&mut self) -> Vec<GpuSample<T>> {
-        mem::replace(&mut self.samples, Vec::new())
+        (self.timers.take(|timer, query| {
+            timer.time_ns = gl.get_query_object_ui64v(query, gl::QUERY_RESULT)
+         }),
+         self.samplers.take(|sampler, query| {
+            sampler.count = gl.get_query_object_ui64v(query, gl::QUERY_RESULT)
+         }),
+        )
     }
 }
 
 impl<T> Drop for GpuFrameProfile<T> {
     fn drop(&mut self) {
         match self.gl.get_type() {
             gl::GlType::Gl =>  {
-                self.gl.delete_queries(&self.queries);
+                self.gl.delete_queries(&self.timers.set);
+                self.gl.delete_queries(&self.samplers.set);
             }
             gl::GlType::Gles => {},
         }
     }
 }
 
 pub struct GpuProfiler<T> {
     frames: [GpuFrameProfile<T>; MAX_PROFILE_FRAMES],
     next_frame: usize,
 }
 
 impl<T> GpuProfiler<T> {
     pub fn new(gl: &Rc<gl::Gl>) -> GpuProfiler<T> {
         GpuProfiler {
             next_frame: 0,
             frames: [
-                      GpuFrameProfile::new(Rc::clone(gl)),
-                      GpuFrameProfile::new(Rc::clone(gl)),
-                      GpuFrameProfile::new(Rc::clone(gl)),
-                      GpuFrameProfile::new(Rc::clone(gl)),
-                    ],
+                GpuFrameProfile::new(Rc::clone(gl)),
+                GpuFrameProfile::new(Rc::clone(gl)),
+                GpuFrameProfile::new(Rc::clone(gl)),
+                GpuFrameProfile::new(Rc::clone(gl)),
+            ],
         }
     }
 
-    pub fn build_samples(&mut self) -> Option<(FrameId, Vec<GpuSample<T>>)> {
+    pub fn build_samples(&mut self) -> Option<(FrameId, Vec<GpuTimer<T>>, Vec<GpuSampler<T>>)> {
         let frame = &mut self.frames[self.next_frame];
         if frame.is_valid() {
-            Some((frame.frame_id, frame.build_samples()))
+            let (timers, samplers) = frame.build_samples();
+            Some((frame.frame_id, timers, samplers))
         } else {
             None
         }
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         let frame = &mut self.frames[self.next_frame];
         frame.begin_frame(frame_id);
@@ -638,16 +647,25 @@ impl<T> GpuProfiler<T> {
         frame.end_frame();
         self.next_frame = (self.next_frame + 1) % MAX_PROFILE_FRAMES;
     }
 
     pub fn add_marker(&mut self, tag: T) -> GpuMarker
     where T: NamedTag {
         self.frames[self.next_frame].add_marker(tag)
     }
+
+    pub fn add_sampler(&mut self, tag: T)
+    where T: NamedTag {
+        self.frames[self.next_frame].add_sampler(tag)
+    }
+
+    pub fn done_sampler(&mut self) {
+        self.frames[self.next_frame].done_sampler()
+    }
 }
 
 #[must_use]
 pub struct GpuMarker{
     gl: Rc<gl::Gl>,
 }
 
 impl GpuMarker {
@@ -684,16 +702,17 @@ impl Drop for GpuMarker {
             gl::GlType::Gl =>  {
                 self.gl.pop_group_marker_ext();
             }
             gl::GlType::Gles => {},
         }
     }
 }
 
+
 #[derive(Debug, Copy, Clone)]
 pub enum VertexUsageHint {
     Static,
     Dynamic,
     Stream,
 }
 
 impl VertexUsageHint {
@@ -800,16 +819,24 @@ impl Device {
     pub fn max_texture_size(&self) -> u32 {
         self.max_texture_size
     }
 
     pub fn get_capabilities(&self) -> &Capabilities {
         &self.capabilities
     }
 
+    pub fn reset_state(&mut self) {
+        self.bound_textures = [ TextureId::invalid(); 16 ];
+        self.bound_vao = 0;
+        self.bound_pbo = PBOId(0);
+        self.bound_read_fbo = FBOId(0);
+        self.bound_draw_fbo = FBOId(0);
+    }
+
     pub fn compile_shader(gl: &gl::Gl,
                           name: &str,
                           shader_type: gl::GLenum,
                           source: String)
                           -> Result<gl::GLuint, ShaderError> {
         debug!("compile {:?}", name);
         let id = gl.create_shader(shader_type);
         gl.shader_source(id, &[source.as_bytes()]);
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -989,32 +989,25 @@ impl Frame {
         // irregular size in the texture cache.
         //
         // For the case where we don't tile along an axis, we can still perform the repetition in
         // the shader (for this particular axis), and it is worth special-casing for this to avoid
         // generating many primitives.
         // This can happen with very tall and thin images used as a repeating background.
         // Apparently web authors do that...
 
-        let mut repeat_x = false;
-        let mut repeat_y = false;
+        let needs_repeat_x = info.stretch_size.width < item_rect.size.width;
+        let needs_repeat_y = info.stretch_size.height < item_rect.size.height;
 
-        if info.stretch_size.width < item_rect.size.width {
-            // If this assert blows up it means we haven't properly decomposed the image in decompose_image_row.
-            debug_assert!(image_size.width <= tile_size);
-            // we don't actually tile in this dimension so repeating can be done in the shader.
-            repeat_x = true;
-        }
+        let tiled_in_x = image_size.width > tile_size;
+        let tiled_in_y = image_size.height > tile_size;
 
-        if info.stretch_size.height < item_rect.size.height {
-            // If this assert blows up it means we haven't properly decomposed the image in decompose_image.
-            debug_assert!(image_size.height <= tile_size);
-            // we don't actually tile in this dimension so repeating can be done in the shader.
-            repeat_y = true;
-        }
+        // If we don't actually tile in this dimension, repeating can be done in the shader.
+        let shader_repeat_x = needs_repeat_x && !tiled_in_x;
+        let shader_repeat_y = needs_repeat_y && !tiled_in_y;
 
         let tile_size_f32 = tile_size as f32;
 
         // Note: this rounds down so it excludes the partially filled tiles on the right and
         // bottom edges (we handle them separately below).
         let num_tiles_x = (image_size.width / tile_size) as u16;
         let num_tiles_y = (image_size.height / tile_size) as u16;
 
@@ -1038,106 +1031,106 @@ impl Frame {
                 self.add_tile_primitive(clip_and_scroll,
                                         builder,
                                         item_rect,
                                         item_local_clip,
                                         info,
                                         TileOffset::new(tx, ty),
                                         stretched_tile_size,
                                         1.0, 1.0,
-                                        repeat_x, repeat_y);
+                                        shader_repeat_x, shader_repeat_y);
             }
             if leftover.width != 0 {
                 // Tiles on the right edge that are smaller than the tile size.
                 self.add_tile_primitive(clip_and_scroll,
                                         builder,
                                         item_rect,
                                         item_local_clip,
                                         info,
                                         TileOffset::new(num_tiles_x, ty),
                                         stretched_tile_size,
                                         (leftover.width as f32) / tile_size_f32,
                                         1.0,
-                                        repeat_x, repeat_y);
+                                        shader_repeat_x, shader_repeat_y);
             }
         }
 
         if leftover.height != 0 {
             for tx in 0..num_tiles_x {
                 // Tiles on the bottom edge that are smaller than the tile size.
                 self.add_tile_primitive(clip_and_scroll,
                                         builder,
                                         item_rect,
                                         item_local_clip,
                                         info,
                                         TileOffset::new(tx, num_tiles_y),
                                         stretched_tile_size,
                                         1.0,
                                         (leftover.height as f32) / tile_size_f32,
-                                        repeat_x,
-                                        repeat_y);
+                                        shader_repeat_x,
+                                        shader_repeat_y);
             }
 
             if leftover.width != 0 {
                 // Finally, the bottom-right tile with a "leftover" size.
                 self.add_tile_primitive(clip_and_scroll,
                                         builder,
                                         item_rect,
                                         item_local_clip,
                                         info,
                                         TileOffset::new(num_tiles_x, num_tiles_y),
                                         stretched_tile_size,
                                         (leftover.width as f32) / tile_size_f32,
                                         (leftover.height as f32) / tile_size_f32,
-                                        repeat_x,
-                                        repeat_y);
+                                        shader_repeat_x,
+                                        shader_repeat_y);
             }
         }
     }
 
     fn add_tile_primitive(&mut self,
                           clip_and_scroll: ClipAndScrollInfo,
                           builder: &mut FrameBuilder,
                           item_rect: &LayerRect,
                           item_local_clip: &LocalClip,
                           info: &ImageDisplayItem,
                           tile_offset: TileOffset,
                           stretched_tile_size: LayerSize,
                           tile_ratio_width: f32,
                           tile_ratio_height: f32,
-                          repeat_x: bool,
-                          repeat_y: bool) {
+                          shader_repeat_x: bool,
+                          shader_repeat_y: bool) {
         // If the the image is tiled along a given axis, we can't have the shader compute
         // the image repetition pattern. In this case we base the primitive's rectangle size
         // on the stretched tile size which effectively cancels the repetion (and repetition
         // has to be emulated by generating more primitives).
-        // If the image is not tiling along this axis, we can perform the repetition in the
+        // If the image is not tiled along this axis, we can perform the repetition in the
         // shader. in this case we use the item's size in the primitive (on that particular
         // axis).
-        // See the repeat_x/y code below.
+        // See the shader_repeat_x/y code below.
 
         let stretched_size = LayerSize::new(
             stretched_tile_size.width * tile_ratio_width,
             stretched_tile_size.height * tile_ratio_height,
         );
 
         let mut prim_rect = LayerRect::new(
             item_rect.origin + LayerVector2D::new(
                 tile_offset.x as f32 * stretched_tile_size.width,
                 tile_offset.y as f32 * stretched_tile_size.height,
             ),
             stretched_size,
         );
 
-        if repeat_x {
+        if shader_repeat_x {
             assert_eq!(tile_offset.x, 0);
             prim_rect.size.width = item_rect.size.width;
         }
 
-        if repeat_y {
+        if shader_repeat_y {
             assert_eq!(tile_offset.y, 0);
             prim_rect.size.height = item_rect.size.height;
         }
 
         // Fix up the primitive's rect if it overflows the original item rect.
         if let Some(prim_rect) = prim_rect.intersection(item_rect) {
             builder.add_image(clip_and_scroll,
                               prim_rect,
--- a/gfx/webrender/src/gpu_cache.rs
+++ b/gfx/webrender/src/gpu_cache.rs
@@ -463,16 +463,20 @@ impl<'a> GpuDataRequest<'a> {
     {
         self.texture.pending_blocks.push(block.into());
     }
 
     pub fn extend_from_slice(&mut self, blocks: &[GpuBlockData]) {
         self.texture.pending_blocks.extend_from_slice(blocks);
     }
 
+    pub fn current_used_block_num(&self) -> usize {
+        self.texture.pending_blocks.len() - self.start_index
+    }
+
     /// Consume the request and return the number of blocks written
     pub fn close(self) -> usize {
         self.texture.pending_blocks.len() - self.start_index
     }
 }
 
 impl<'a> Drop for GpuDataRequest<'a> {
     fn drop(&mut self) {
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -1,12 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+use api::DebugCommand;
 use device::TextureFilter;
 use fxhash::FxHasher;
 use profiler::BackendProfileCounters;
 use std::collections::{HashMap, HashSet};
 use std::f32;
 use std::hash::BuildHasherDefault;
 use std::{i32, usize};
 use std::path::PathBuf;
@@ -180,16 +181,17 @@ impl RendererFrame {
             pipeline_epoch_map,
             layers_bouncing_back,
             frame,
         }
     }
 }
 
 pub enum ResultMsg {
+    DebugCommand(DebugCommand),
     RefreshShader(PathBuf),
     NewFrame(DocumentId, RendererFrame, TextureUpdateList, BackendProfileCounters),
     UpdateResources { updates: TextureUpdateList, cancel_rendering: bool },
 }
 
 #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
 pub struct StackingContextIndex(pub usize);
 
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -7,17 +7,17 @@ A GPU based renderer for the web.
 
 It serves as an experimental render backend for [Servo](https://servo.org/),
 but it can also be used as such in a standalone application.
 
 # External dependencies
 WebRender currently depends on [FreeType](https://www.freetype.org/)
 
 # Api Structure
-The main entry point to WebRender is the `webrender::renderer::Renderer`.
+The main entry point to WebRender is the `webrender::Renderer`.
 
 By calling `Renderer::new(...)` you get a `Renderer`, as well as a `RenderApiSender`.
 Your `Renderer` is responsible to render the previously processed frames onto the screen.
 
 By calling `yourRenderApiSender.create_api()`, you'll get a `RenderApi` instance,
 which is responsible for managing resources and documents. A worker thread is used internally
 to untie the workload from the application thread and therefore be able to make better use of
 multicore systems.
@@ -50,16 +50,18 @@ extern crate bitflags;
 extern crate thread_profiler;
 
 mod border;
 mod clip_scroll_node;
 mod clip_scroll_tree;
 mod debug_colors;
 mod debug_font_data;
 mod debug_render;
+#[cfg(feature = "debugger")]
+mod debug_server;
 mod device;
 mod ellipse;
 mod frame;
 mod frame_builder;
 mod freelist;
 mod geometry;
 mod glyph_cache;
 mod glyph_rasterizer;
@@ -67,16 +69,17 @@ mod gpu_cache;
 mod internal_types;
 mod mask_cache;
 mod prim_store;
 mod print_tree;
 mod profiler;
 mod record;
 mod render_backend;
 mod render_task;
+mod renderer;
 mod resource_cache;
 mod scene;
 mod spring;
 mod texture_allocator;
 mod texture_cache;
 mod tiling;
 mod util;
 
@@ -103,18 +106,16 @@ mod platform {
         pub mod font;
     }
     #[cfg(target_os = "windows")]
     pub mod windows {
         pub mod font;
     }
 }
 
-pub mod renderer;
-
 #[cfg(target_os="macos")]
 extern crate core_graphics;
 #[cfg(target_os="macos")]
 extern crate core_text;
 
 #[cfg(all(unix, not(target_os="macos")))]
 extern crate freetype;
 
@@ -128,19 +129,28 @@ extern crate fxhash;
 extern crate gleam;
 extern crate num_traits;
 //extern crate notify;
 extern crate time;
 pub extern crate webrender_api;
 extern crate byteorder;
 extern crate rayon;
 extern crate plane_split;
+#[cfg(feature = "debugger")]
+extern crate ws;
+#[cfg(feature = "debugger")]
+extern crate serde_json;
+#[cfg(feature = "debugger")]
+#[macro_use]
+extern crate serde_derive;
 
 #[cfg(any(target_os="macos", target_os="windows"))]
 extern crate gamma_lut;
 
 pub use renderer::{ExternalImage, ExternalImageSource, ExternalImageHandler};
 pub use renderer::{GraphicsApi, GraphicsApiInfo, ReadPixelsFormat, Renderer, RendererOptions};
+pub use renderer::{CpuProfile, GpuProfile, DebugFlags, RendererKind};
+pub use renderer::{MAX_VERTEX_TEXTURE_WIDTH, PROFILER_DBG, RENDER_TARGET_DBG, TEXTURE_CACHE_DBG};
 
 pub use webrender_api as api;
 
 #[doc(hidden)]
 pub use device::build_shader_strings;
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -588,16 +588,18 @@ impl TextRunPrimitiveCpu {
     fn write_gpu_blocks(&self,
                         request: &mut GpuDataRequest) {
         request.push(self.color);
         request.push([self.offset.x,
                       self.offset.y,
                       self.subpx_dir as u32 as f32,
                       0.0]);
         request.extend_from_slice(&self.glyph_gpu_blocks);
+
+        assert!(request.current_used_block_num() <= MAX_VERTEX_TEXTURE_WIDTH);
     }
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 struct GlyphPrimitive {
     offset: LayerPoint,
     padding: LayerPoint,
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -1,14 +1,14 @@
 /* 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/. */
 
 use debug_render::DebugRenderer;
-use device::{Device, GpuMarker, GpuSample, NamedTag};
+use device::{Device, GpuMarker, GpuTimer, GpuSampler, NamedTag};
 use euclid::{Point2D, Size2D, Rect, vec2};
 use std::collections::vec_deque::VecDeque;
 use std::f32;
 use std::mem;
 use api::{ColorF, ColorU};
 use time::precise_time_ns;
 
 const GRAPH_WIDTH: f32 = 1024.0;
@@ -31,24 +31,29 @@ impl NamedTag for GpuProfileTag {
     }
 }
 
 trait ProfileCounter {
     fn description(&self) -> &'static str;
     fn value(&self) -> String;
 }
 
+impl<'a, T: ProfileCounter> ProfileCounter for &'a T {
+    fn description(&self) -> &'static str { (*self).description() }
+    fn value(&self) -> String { (*self).value() }
+}
+
 #[derive(Clone)]
 pub struct IntProfileCounter {
     description: &'static str,
     value: usize,
 }
 
 impl IntProfileCounter {
-    fn new(description: &'static str) -> IntProfileCounter {
+    fn new(description: &'static str) -> Self {
         IntProfileCounter {
             description,
             value: 0,
         }
     }
 
     fn reset(&mut self) {
         self.value = 0;
@@ -79,25 +84,40 @@ impl ProfileCounter for IntProfileCounte
         self.description
     }
 
     fn value(&self) -> String {
         format!("{}", self.value)
     }
 }
 
+pub struct FloatProfileCounter {
+    description: &'static str,
+    value: f32,
+}
+
+impl ProfileCounter for FloatProfileCounter {
+    fn description(&self) -> &'static str {
+        self.description
+    }
+
+    fn value(&self) -> String {
+        format!("{:.2}", self.value)
+    }
+}
+
 #[derive(Clone)]
 pub struct ResourceProfileCounter {
     description: &'static str,
     value: usize,
     size: usize,
 }
 
 impl ResourceProfileCounter {
-    fn new(description: &'static str) -> ResourceProfileCounter {
+    fn new(description: &'static str) -> Self {
         ResourceProfileCounter {
             description,
             value: 0,
             size: 0,
         }
     }
 
     #[allow(dead_code)]
@@ -139,17 +159,17 @@ pub struct Timer<'a> {
 impl<'a> Drop for Timer<'a> {
     fn drop(&mut self) {
         let end = precise_time_ns();
         *self.result += end - self.start;
     }
 }
 
 impl TimeProfileCounter {
-    pub fn new(description: &'static str, invert: bool) -> TimeProfileCounter {
+    pub fn new(description: &'static str, invert: bool) -> Self {
         TimeProfileCounter {
             description,
             nanoseconds: 0,
             invert,
         }
     }
 
     fn reset(&mut self) {
@@ -207,17 +227,17 @@ pub struct AverageTimeProfileCounter {
     start_ns: u64,
     sum_ns: u64,
     num_samples: u64,
     nanoseconds: u64,
     invert: bool,
 }
 
 impl AverageTimeProfileCounter {
-    pub fn new(description: &'static str, invert: bool, average_over_ns: u64) -> AverageTimeProfileCounter {
+    pub fn new(description: &'static str, invert: bool, average_over_ns: u64) -> Self {
         AverageTimeProfileCounter {
             description,
             average_over_ns,
             start_ns: precise_time_ns(),
             sum_ns: 0,
             num_samples: 0,
             nanoseconds: 0,
             invert,
@@ -264,26 +284,27 @@ impl ProfileCounter for AverageTimeProfi
         if self.invert {
             format!("{:.2} fps", 1000000000.0 / self.nanoseconds as f64)
         } else {
             format!("{:.2} ms", self.nanoseconds as f64 / 1000000.0)
         }
     }
 }
 
+
 pub struct FrameProfileCounters {
     pub total_primitives: IntProfileCounter,
     pub visible_primitives: IntProfileCounter,
     pub passes: IntProfileCounter,
     pub color_targets: IntProfileCounter,
     pub alpha_targets: IntProfileCounter,
 }
 
 impl FrameProfileCounters {
-    pub fn new() -> FrameProfileCounters {
+    pub fn new() -> Self {
         FrameProfileCounters {
             total_primitives: IntProfileCounter::new("Total Primitives"),
             visible_primitives: IntProfileCounter::new("Visible Primitives"),
             passes: IntProfileCounter::new("Passes"),
             color_targets: IntProfileCounter::new("Color Targets"),
             alpha_targets: IntProfileCounter::new("Alpha Targets"),
         }
     }
@@ -293,34 +314,34 @@ impl FrameProfileCounters {
 pub struct TextureCacheProfileCounters {
     pub pages_a8: ResourceProfileCounter,
     pub pages_rgb8: ResourceProfileCounter,
     pub pages_rgba8: ResourceProfileCounter,
     pub pages_rg8: ResourceProfileCounter,
 }
 
 impl TextureCacheProfileCounters {
-    pub fn new() -> TextureCacheProfileCounters {
+    pub fn new() -> Self {
         TextureCacheProfileCounters {
             pages_a8: ResourceProfileCounter::new("Texture A8 cached pages"),
             pages_rgb8: ResourceProfileCounter::new("Texture RGB8 cached pages"),
             pages_rgba8: ResourceProfileCounter::new("Texture RGBA8 cached pages"),
             pages_rg8: ResourceProfileCounter::new("Texture RG8 cached pages"),
         }
     }
 }
 
 #[derive(Clone)]
 pub struct GpuCacheProfileCounters {
     pub allocated_rows: IntProfileCounter,
     pub allocated_blocks: IntProfileCounter,
 }
 
 impl GpuCacheProfileCounters {
-    pub fn new() -> GpuCacheProfileCounters {
+    pub fn new() -> Self {
         GpuCacheProfileCounters {
             allocated_rows: IntProfileCounter::new("GPU cache rows"),
             allocated_blocks: IntProfileCounter::new("GPU cache blocks"),
         }
     }
 }
 
 #[derive(Clone)]
@@ -344,35 +365,36 @@ pub struct IpcProfileCounters {
     pub consume_time: TimeProfileCounter,
     pub send_time: TimeProfileCounter,
     pub total_time: TimeProfileCounter,
     pub display_lists: ResourceProfileCounter,
 }
 
 impl IpcProfileCounters {
     pub fn set(&mut self,
-               build_start: u64,
-               build_end: u64,
-               send_start: u64,
-               consume_start: u64,
-               consume_end: u64,
-               display_len: usize) {
+        build_start: u64,
+        build_end: u64,
+        send_start: u64,
+        consume_start: u64,
+        consume_end: u64,
+        display_len: usize,
+    ) {
         let build_time = build_end - build_start;
         let consume_time = consume_end - consume_start;
         let send_time = consume_start - send_start;
         self.build_time.inc(build_time);
         self.consume_time.inc(consume_time);
         self.send_time.inc(send_time);
         self.total_time.inc(build_time + consume_time + send_time);
         self.display_lists.inc(display_len);
     }
 }
 
 impl BackendProfileCounters {
-    pub fn new() -> BackendProfileCounters {
+    pub fn new() -> Self {
         BackendProfileCounters {
             total_time: TimeProfileCounter::new("Backend CPU Time", false),
             resources: ResourceProfileCounters {
                 font_templates: ResourceProfileCounter::new("Font Templates"),
                 image_templates: ResourceProfileCounter::new("Image Templates"),
                 texture_cache: TextureCacheProfileCounters::new(),
                 gpu_cache: GpuCacheProfileCounters::new(),
             },
@@ -402,38 +424,38 @@ pub struct RendererProfileCounters {
     pub draw_calls: IntProfileCounter,
     pub vertices: IntProfileCounter,
     pub vao_count_and_size: ResourceProfileCounter,
 }
 
 pub struct RendererProfileTimers {
     pub cpu_time: TimeProfileCounter,
     pub gpu_time: TimeProfileCounter,
-    pub gpu_samples: Vec<GpuSample<GpuProfileTag>>,
+    pub gpu_samples: Vec<GpuTimer<GpuProfileTag>>,
 }
 
 impl RendererProfileCounters {
-    pub fn new() -> RendererProfileCounters {
+    pub fn new() -> Self {
         RendererProfileCounters {
             frame_counter: IntProfileCounter::new("Frame"),
             frame_time: AverageTimeProfileCounter::new("FPS", true, ONE_SECOND_NS / 2),
             draw_calls: IntProfileCounter::new("Draw Calls"),
             vertices: IntProfileCounter::new("Vertices"),
             vao_count_and_size: ResourceProfileCounter::new("VAO"),
         }
     }
 
     pub fn reset(&mut self) {
         self.draw_calls.reset();
         self.vertices.reset();
     }
 }
 
 impl RendererProfileTimers {
-    pub fn new() -> RendererProfileTimers {
+    pub fn new() -> Self {
         RendererProfileTimers {
             cpu_time: TimeProfileCounter::new("Compositor CPU Time", false),
             gpu_samples: Vec::new(),
             gpu_time: TimeProfileCounter::new("GPU Time", false),
         }
     }
 }
 
@@ -444,17 +466,17 @@ struct GraphStats {
 }
 
 struct ProfileGraph {
     max_samples: usize,
     values: VecDeque<f32>,
 }
 
 impl ProfileGraph {
-    fn new(max_samples: usize) -> ProfileGraph {
+    fn new(max_samples: usize) -> Self {
         ProfileGraph {
             max_samples,
             values: VecDeque::new(),
         }
     }
 
     fn push(&mut self, ns: u64) {
         let ms = ns as f64 / 1000000.0;
@@ -480,20 +502,22 @@ impl ProfileGraph {
         if !self.values.is_empty() {
             stats.mean_value = stats.mean_value / self.values.len() as f32;
         }
 
         stats
     }
 
     fn draw_graph(&self,
-                  x: f32,
-                  y: f32,
-                  description: &'static str,
-                  debug_renderer: &mut DebugRenderer) -> Rect<f32> {
+        x: f32,
+        y: f32,
+        description: &'static str,
+        debug_renderer: &mut DebugRenderer,
+    ) -> Rect<f32>
+    {
         let size = Size2D::new(600.0, 120.0);
         let line_height = debug_renderer.line_height();
         let mut rect = Rect::new(Point2D::new(x, y), size);
         let stats = self.stats();
 
         let text_color = ColorU::new(255, 255, 0, 255);
         let text_origin = rect.origin + vec2(rect.size.width, 20.0);
         debug_renderer.add_text(text_origin.x,
@@ -558,46 +582,48 @@ impl ProfileGraph {
         }
 
         rect
     }
 }
 
 struct GpuFrame {
     total_time: u64,
-    samples: Vec<GpuSample<GpuProfileTag>>,
+    samples: Vec<GpuTimer<GpuProfileTag>>,
 }
 
 struct GpuFrameCollection {
     frames: VecDeque<GpuFrame>,
 }
 
 impl GpuFrameCollection {
-    fn new() -> GpuFrameCollection {
+    fn new() -> Self {
         GpuFrameCollection {
             frames: VecDeque::new(),
         }
     }
 
-    fn push(&mut self, total_time: u64, samples: Vec<GpuSample<GpuProfileTag>>) {
+    fn push(&mut self, total_time: u64, samples: Vec<GpuTimer<GpuProfileTag>>) {
         if self.frames.len() == 20 {
             self.frames.pop_back();
         }
         self.frames.push_front(GpuFrame {
             total_time,
             samples,
         });
     }
 }
 
 impl GpuFrameCollection {
     fn draw(&self,
-            x: f32,
-            y: f32,
-            debug_renderer: &mut DebugRenderer) -> Rect<f32> {
+        x: f32,
+        y: f32,
+        debug_renderer: &mut DebugRenderer,
+    ) -> Rect<f32>
+    {
         let bounding_rect = Rect::new(Point2D::new(x, y),
                                       Size2D::new(GRAPH_WIDTH + 2.0 * GRAPH_PADDING,
                                                   GRAPH_HEIGHT + 2.0 * GRAPH_PADDING));
         let graph_rect = bounding_rect.inflate(-GRAPH_PADDING, -GRAPH_PADDING);
 
         debug_renderer.add_quad(bounding_rect.origin.x,
                                 bounding_rect.origin.y,
                                 bounding_rect.origin.x + bounding_rect.size.width,
@@ -649,34 +675,35 @@ pub struct Profiler {
     backend_time: ProfileGraph,
     compositor_time: ProfileGraph,
     gpu_time: ProfileGraph,
     gpu_frames: GpuFrameCollection,
     ipc_time: ProfileGraph,
 }
 
 impl Profiler {
-    pub fn new() -> Profiler {
+    pub fn new() -> Self {
         Profiler {
             x_left: 0.0,
             y_left: 0.0,
             x_right: 0.0,
             y_right: 0.0,
             backend_time: ProfileGraph::new(600),
             compositor_time: ProfileGraph::new(600),
             gpu_time: ProfileGraph::new(600),
             gpu_frames: GpuFrameCollection::new(),
             ipc_time: ProfileGraph::new(600),
         }
     }
 
-    fn draw_counters(&mut self,
-                     counters: &[&ProfileCounter],
-                     debug_renderer: &mut DebugRenderer,
-                     left: bool) {
+    fn draw_counters<T: ProfileCounter>(&mut self,
+        counters: &[T],
+        debug_renderer: &mut DebugRenderer,
+        left: bool,
+    ) {
         let mut label_rect = Rect::zero();
         let mut value_rect = Rect::zero();
         let (mut current_x, mut current_y) = if left {
             (self.x_left, self.y_left)
         } else {
             (self.x_right, self.y_right)
         };
         let mut color_index = 0;
@@ -728,42 +755,44 @@ impl Profiler {
         if left {
             self.y_left = new_y;
         } else {
             self.y_right = new_y;
         }
     }
 
     pub fn draw_profile(&mut self,
-                        device: &mut Device,
-                        frame_profile: &FrameProfileCounters,
-                        backend_profile: &BackendProfileCounters,
-                        renderer_profile: &RendererProfileCounters,
-                        renderer_timers: &mut RendererProfileTimers,
-                        debug_renderer: &mut DebugRenderer) {
-
+        device: &mut Device,
+        frame_profile: &FrameProfileCounters,
+        backend_profile: &BackendProfileCounters,
+        renderer_profile: &RendererProfileCounters,
+        renderer_timers: &mut RendererProfileTimers,
+        gpu_samplers: &[GpuSampler<GpuProfileTag>],
+        screen_fraction: f32,
+        debug_renderer: &mut DebugRenderer,
+    ) {
         let _gm = GpuMarker::new(device.rc_gl(), "profile");
         self.x_left = 20.0;
         self.y_left = 40.0;
         self.x_right = 400.0;
         self.y_right = 40.0;
 
         let mut gpu_time = 0;
         let gpu_samples = mem::replace(&mut renderer_timers.gpu_samples, Vec::new());
         for sample in &gpu_samples {
             gpu_time += sample.time_ns;
         }
         renderer_timers.gpu_time.set(gpu_time);
 
         self.draw_counters(&[
-            &renderer_profile.frame_counter,
-            &renderer_profile.frame_time,
+            &renderer_profile.frame_time
         ], debug_renderer, true);
 
         self.draw_counters(&[
+            &renderer_profile.frame_counter,
             &frame_profile.total_primitives,
             &frame_profile.visible_primitives,
             &frame_profile.passes,
             &frame_profile.color_targets,
             &frame_profile.alpha_targets,
             &backend_profile.resources.gpu_cache.allocated_rows,
             &backend_profile.resources.gpu_cache.allocated_blocks,
         ], debug_renderer, true);
@@ -773,37 +802,52 @@ impl Profiler {
             &backend_profile.resources.image_templates,
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &backend_profile.resources.texture_cache.pages_a8,
             &backend_profile.resources.texture_cache.pages_rgb8,
             &backend_profile.resources.texture_cache.pages_rgba8,
             &backend_profile.resources.texture_cache.pages_rg8,
+            &backend_profile.ipc.display_lists,
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &backend_profile.ipc.build_time,
             &backend_profile.ipc.send_time,
             &backend_profile.ipc.consume_time,
             &backend_profile.ipc.total_time,
-            &backend_profile.ipc.display_lists,
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &renderer_profile.draw_calls,
             &renderer_profile.vertices,
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &backend_profile.total_time,
             &renderer_timers.cpu_time,
             &renderer_timers.gpu_time,
         ], debug_renderer, false);
 
+        let mut samplers = Vec::<FloatProfileCounter>::new();
+        // Gathering unique GPU samplers. This has O(N^2) complexity,
+        // but we only have a few samplers per target.
+        for sampler in gpu_samplers {
+            let value = sampler.count as f32 * screen_fraction;
+            match samplers.iter().position(|s| s.description as *const _ == sampler.tag.label as *const _) {
+                Some(pos) => samplers[pos].value += value,
+                None => samplers.push(FloatProfileCounter {
+                    description: sampler.tag.label,
+                    value,
+                }),
+            }
+        }
+        self.draw_counters(&samplers, debug_renderer, false);
+
         self.backend_time.push(backend_profile.total_time.nanoseconds);
         self.compositor_time.push(renderer_timers.cpu_time.nanoseconds);
         self.ipc_time.push(backend_profile.ipc.total_time.nanoseconds);
         self.gpu_time.push(gpu_time);
         self.gpu_frames.push(gpu_time, gpu_samples);
 
 
         let rect = self.backend_time.draw_graph(self.x_left, self.y_left, "CPU (backend)", debug_renderer);
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -456,16 +456,25 @@ impl RenderBackend {
                     let pending_update = self.resource_cache.pending_updates();
                     let msg = ResultMsg::UpdateResources { updates: pending_update, cancel_rendering: true };
                     self.result_tx.send(msg).unwrap();
                     // We use new_frame_ready to wake up the renderer and get the
                     // resource updates processed, but the UpdateResources message
                     // will cancel rendering the frame.
                     self.notifier.lock().unwrap().as_mut().unwrap().new_frame_ready();
                 }
+                ApiMsg::DebugCommand(option) => {
+                    let msg = ResultMsg::DebugCommand(option);
+                    self.result_tx.send(msg).unwrap();
+                    let notifier = self.notifier.lock();
+                    notifier.unwrap()
+                            .as_mut()
+                            .unwrap()
+                            .new_frame_ready();
+                }
                 ApiMsg::ShutDown => {
                     let notifier = self.notifier.lock();
                     notifier.unwrap()
                             .as_mut()
                             .unwrap()
                             .shut_down();
                     break;
                 }
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -243,16 +243,25 @@ impl RenderTask {
                     raw_clips: &[ClipWorkItem],
                     extra_clip: Option<ClipWorkItem>)
                     -> Option<RenderTask> {
         // Filter out all the clip instances that don't contribute to the result
         let mut inner_rect = Some(task_rect);
         let clips: Vec<_> = raw_clips.iter()
                                      .chain(extra_clip.iter())
                                      .filter(|&&(_, ref clip_info)| {
+            // If this clip does not contribute to a mask, then ensure
+            // it gets filtered out here. Otherwise, if a mask is
+            // created (by a different clip in the list), the allocated
+            // rectangle for the mask could end up being much bigger
+            // than is actually required.
+            if !clip_info.is_masking() {
+                return false;
+            }
+
             match clip_info.bounds.inner {
                 Some(ref inner) if !inner.device_rect.is_empty() => {
                     inner_rect = inner_rect.and_then(|r| r.intersection(&inner.device_rect));
                     !inner.device_rect.contains_rect(&task_rect)
                 }
                 _ => {
                     inner_rect = None;
                     true
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -4,49 +4,58 @@
 
 //! The webrender API.
 //!
 //! The `webrender::renderer` module provides the interface to webrender, which
 //! is accessible through [`Renderer`][renderer]
 //!
 //! [renderer]: struct.Renderer.html
 
+#[cfg(not(feature = "debugger"))]
+use api::ApiMsg;
+#[cfg(not(feature = "debugger"))]
+use api::channel::MsgSender;
+use api::DebugCommand;
 use debug_colors;
 use debug_render::DebugRenderer;
+#[cfg(feature = "debugger")]
+use debug_server::{BatchList, DebugMsg, DebugServer};
 use device::{DepthFunction, Device, FrameId, Program, TextureId, VertexDescriptor, GpuMarker, GpuProfiler, PBOId};
-use device::{GpuSample, TextureFilter, VAO, VertexUsageHint, FileWatcherHandler, TextureTarget, ShaderError};
+use device::{GpuTimer, TextureFilter, VAO, VertexUsageHint, FileWatcherHandler, TextureTarget, ShaderError};
 use device::{get_gl_format_bgra, VertexAttribute, VertexAttributeKind};
 use euclid::{Transform3D, rect};
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
 use internal_types::{FastHashMap, CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp};
 use internal_types::{TextureUpdateList, RenderTargetMode, TextureUpdateSource};
 use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, SourceTexture};
 use internal_types::{BatchTextures, TextureSampler};
 use profiler::{Profiler, BackendProfileCounters};
 use profiler::{GpuProfileTag, RendererProfileTimers, RendererProfileCounters};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use render_task::RenderTaskTree;
+#[cfg(feature = "debugger")]
+use serde_json;
 use std;
 use std::cmp;
 use std::collections::VecDeque;
 use std::f32;
 use std::marker::PhantomData;
 use std::mem;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::TextureCache;
 use rayon::ThreadPool;
 use rayon::Configuration as ThreadPoolConfig;
-use tiling::{AlphaBatchKind, BlurCommand, Frame, PrimitiveBatch, RenderTarget};
+use tiling::{AlphaBatchKey, AlphaBatchKind, BlurCommand, Frame, RenderTarget};
 use tiling::{AlphaRenderTarget, CacheClipInstance, PrimitiveInstance, ColorRenderTarget, RenderTargetKind};
 use time::precise_time_ns;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use util::TransformedRectKind;
 use api::{ColorF, Epoch, PipelineId, RenderApiSender, RenderNotifier};
 use api::{ExternalImageId, ExternalImageType, ImageFormat};
 use api::{DeviceIntRect, DeviceUintRect, DeviceIntPoint, DeviceIntSize, DeviceUintSize};
 use api::{BlobImageRenderer, channel, FontRenderMode};
@@ -75,16 +84,44 @@ const GPU_TAG_PRIM_GRADIENT: GpuProfileT
 const GPU_TAG_PRIM_ANGLE_GRADIENT: GpuProfileTag = GpuProfileTag { label: "AngleGradient", color: debug_colors::POWDERBLUE };
 const GPU_TAG_PRIM_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag { label: "RadialGradient", color: debug_colors::LIGHTPINK };
 const GPU_TAG_PRIM_BOX_SHADOW: GpuProfileTag = GpuProfileTag { label: "BoxShadow", color: debug_colors::CYAN };
 const GPU_TAG_PRIM_BORDER_CORNER: GpuProfileTag = GpuProfileTag { label: "BorderCorner", color: debug_colors::DARKSLATEGREY };
 const GPU_TAG_PRIM_BORDER_EDGE: GpuProfileTag = GpuProfileTag { label: "BorderEdge", color: debug_colors::LAVENDER };
 const GPU_TAG_PRIM_CACHE_IMAGE: GpuProfileTag = GpuProfileTag { label: "CacheImage", color: debug_colors::SILVER };
 const GPU_TAG_BLUR: GpuProfileTag = GpuProfileTag { label: "Blur", color: debug_colors::VIOLET };
 
+const GPU_SAMPLER_TAG_ALPHA: GpuProfileTag = GpuProfileTag { label: "Alpha Targets", color: debug_colors::BLACK };
+const GPU_SAMPLER_TAG_OPAQUE: GpuProfileTag = GpuProfileTag { label: "Opaque Pass", color: debug_colors::BLACK };
+const GPU_SAMPLER_TAG_TRANSPARENT: GpuProfileTag = GpuProfileTag { label: "Transparent Pass", color: debug_colors::BLACK };
+
+#[cfg(feature = "debugger")]
+impl AlphaBatchKind {
+    fn debug_name(&self) -> &'static str {
+        match *self {
+            AlphaBatchKind::Composite { .. } => "Composite",
+            AlphaBatchKind::HardwareComposite => "HardwareComposite",
+            AlphaBatchKind::SplitComposite => "SplitComposite",
+            AlphaBatchKind::Blend => "Blend",
+            AlphaBatchKind::Rectangle => "Rectangle",
+            AlphaBatchKind::TextRun => "TextRun",
+            AlphaBatchKind::Image(..) => "Image",
+            AlphaBatchKind::YuvImage(..) => "YuvImage",
+            AlphaBatchKind::AlignedGradient => "AlignedGradient",
+            AlphaBatchKind::AngleGradient => "AngleGradient",
+            AlphaBatchKind::RadialGradient => "RadialGradient",
+            AlphaBatchKind::BoxShadow => "BoxShadow",
+            AlphaBatchKind::CacheImage => "CacheImage",
+            AlphaBatchKind::BorderCorner => "BorderCorner",
+            AlphaBatchKind::BorderEdge => "BorderEdge",
+            AlphaBatchKind::Line => "Line",
+        }
+    }
+}
+
 bitflags! {
     #[derive(Default)]
     pub struct DebugFlags: u32 {
         const PROFILER_DBG      = 1 << 0;
         const RENDER_TARGET_DBG = 1 << 1;
         const TEXTURE_CACHE_DBG = 1 << 2;
     }
 }
@@ -133,17 +170,16 @@ enum VertexArrayKind {
     Blur,
     Clip,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub enum VertexFormat {
     PrimitiveInstances,
     Blur,
-    Clip,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum GraphicsApi {
     OpenGL,
 }
 
 #[derive(Clone, Debug)]
@@ -208,20 +244,20 @@ pub enum RendererKind {
 
 #[derive(Debug)]
 pub struct GpuProfile {
     pub frame_id: FrameId,
     pub paint_time_ns: u64,
 }
 
 impl GpuProfile {
-    fn new<T>(frame_id: FrameId, samples: &[GpuSample<T>]) -> GpuProfile {
+    fn new<T>(frame_id: FrameId, timers: &[GpuTimer<T>]) -> GpuProfile {
         let mut paint_time_ns = 0;
-        for sample in samples {
-            paint_time_ns += sample.time_ns;
+        for timer in timers {
+            paint_time_ns += timer.time_ns;
         }
         GpuProfile {
             frame_id,
             paint_time_ns,
         }
     }
 }
 
@@ -725,17 +761,16 @@ fn create_prim_shader(name: &'static str
         prefix.push_str(&format!("#define WR_FEATURE_{}\n", feature));
     }
 
     debug!("PrimShader {}", name);
 
     let vertex_descriptor = match vertex_format {
         VertexFormat::PrimitiveInstances => DESC_PRIM_INSTANCES,
         VertexFormat::Blur => DESC_BLUR,
-        VertexFormat::Clip => DESC_CLIP,
     };
 
     device.create_program(name,
                           &prefix,
                           &vertex_descriptor)
 }
 
 fn create_clip_shader(name: &'static str, device: &mut Device) -> Result<Program, ShaderError> {
@@ -775,16 +810,17 @@ pub enum ReadPixelsFormat {
     Rgba8,
     Bgra8,
 }
 
 /// The renderer is responsible for submitting to the GPU the work prepared by the
 /// RenderBackend.
 pub struct Renderer {
     result_rx: Receiver<ResultMsg>,
+    debug_server: DebugServer,
     device: Device,
     pending_texture_updates: Vec<TextureUpdateList>,
     pending_gpu_cache_updates: Vec<GpuCacheUpdateList>,
     pending_shader_updates: Vec<PathBuf>,
     current_frame: Option<RendererFrame>,
 
     // These are "cache shaders". These shaders are used to
     // draw intermediate results to cache targets. The results
@@ -913,16 +949,17 @@ impl Renderer {
     pub fn new(gl: Rc<gl::Gl>, mut options: RendererOptions) -> Result<(Renderer, RenderApiSender), InitError> {
 
         let (api_tx, api_rx) = try!{ channel::msg_channel() };
         let (payload_tx, payload_rx) = try!{ channel::payload_channel() };
         let (result_tx, result_rx) = channel();
         let gl_type = gl.get_type();
 
         let notifier = Arc::new(Mutex::new(None));
+        let debug_server = DebugServer::new(api_tx.clone());
 
         let file_watch_handler = FileWatcher {
             result_tx: result_tx.clone(),
             notifier: Arc::clone(&notifier),
         };
 
         let mut device = Device::new(
             gl,
@@ -1332,16 +1369,17 @@ impl Renderer {
         })};
 
         let gpu_cache_texture = CacheTexture::new(&mut device);
 
         let gpu_profile = GpuProfiler::new(device.rc_gl());
 
         let renderer = Renderer {
             result_rx,
+            debug_server,
             device,
             current_frame: None,
             pending_texture_updates: Vec::new(),
             pending_gpu_cache_updates: Vec::new(),
             pending_shader_updates: Vec::new(),
             cs_box_shadow,
             cs_text_run,
             cs_line,
@@ -1476,16 +1514,105 @@ impl Renderer {
                     // pressure event.
                     if cancel_rendering {
                         self.current_frame = None;
                     }
                 }
                 ResultMsg::RefreshShader(path) => {
                     self.pending_shader_updates.push(path);
                 }
+                ResultMsg::DebugCommand(command) => {
+                    self.handle_debug_command(command);
+                }
+            }
+        }
+    }
+
+    #[cfg(not(feature = "debugger"))]
+    fn update_debug_server(&self) {
+        // Avoid unused param warning.
+        let _ = &self.debug_server;
+    }
+
+    #[cfg(feature = "debugger")]
+    fn update_debug_server(&self) {
+        while let Ok(msg) = self.debug_server.debug_rx.try_recv() {
+            match msg {
+                DebugMsg::FetchBatches(sender) => {
+                    let mut batch_list = BatchList::new();
+
+                    if let Some(frame) = self.current_frame.as_ref().and_then(|frame| frame.frame.as_ref()) {
+                        for pass in &frame.passes {
+                            for target in &pass.alpha_targets.targets {
+                                batch_list.push("[Clip] Clear", target.clip_batcher.border_clears.len());
+                                batch_list.push("[Clip] Borders", target.clip_batcher.borders.len());
+                                batch_list.push("[Clip] Rectangles", target.clip_batcher.rectangles.len());
+                                for (_, items) in target.clip_batcher.images.iter() {
+                                    batch_list.push("[Clip] Image mask", items.len());
+                                }
+                            }
+
+                            for target in &pass.color_targets.targets {
+                                batch_list.push("[Cache] Vertical Blur", target.vertical_blurs.len());
+                                batch_list.push("[Cache] Horizontal Blur", target.horizontal_blurs.len());
+                                batch_list.push("[Cache] Box Shadow", target.box_shadow_cache_prims.len());
+                                batch_list.push("[Cache] Text Shadow", target.text_run_cache_prims.len());
+                                batch_list.push("[Cache] Lines", target.line_cache_prims.len());
+
+                                for batch in target.alpha_batcher
+                                                   .batch_list
+                                                   .opaque_batch_list
+                                                   .batches
+                                                   .iter()
+                                                   .rev() {
+                                    batch_list.push(batch.key.kind.debug_name(), batch.instances.len());
+                                }
+
+                                for batch in &target.alpha_batcher
+                                                    .batch_list
+                                                    .alpha_batch_list
+                                                    .batches {
+                                    batch_list.push(batch.key.kind.debug_name(), batch.instances.len());
+                                }
+                            }
+                        }
+                    }
+
+                    let json = serde_json::to_string(&batch_list).unwrap();
+                    sender.send(json).ok();
+                }
+            }
+        }
+    }
+
+    fn handle_debug_command(&mut self, command: DebugCommand) {
+        match command {
+            DebugCommand::EnableProfiler(enable) => {
+                if enable {
+                    self.debug_flags.insert(PROFILER_DBG);
+                } else {
+                    self.debug_flags.remove(PROFILER_DBG);
+                }
+            }
+            DebugCommand::EnableTextureCacheDebug(enable) => {
+                if enable {
+                    self.debug_flags.insert(TEXTURE_CACHE_DBG);
+                } else {
+                    self.debug_flags.remove(TEXTURE_CACHE_DBG);
+                }
+            }
+            DebugCommand::EnableRenderTargetDebug(enable) => {
+                if enable {
+                    self.debug_flags.insert(RENDER_TARGET_DBG);
+                } else {
+                    self.debug_flags.remove(RENDER_TARGET_DBG);
+                }
+            }
+            DebugCommand::Flush => {
+                self.update_debug_server();
             }
         }
     }
 
     /// Set a callback for handling external images.
     pub fn set_external_image_handler(&mut self, handler: Box<ExternalImageHandler>) {
         self.external_image_handler = Some(handler);
     }
@@ -1502,30 +1629,32 @@ impl Renderer {
     /// A Frame is supplied by calling [`generate_frame()`][genframe].
     /// [genframe]: ../../webrender_api/struct.DocumentApi.html#method.generate_frame
     pub fn render(&mut self, framebuffer_size: DeviceUintSize) {
         profile_scope!("render");
 
         if let Some(mut frame) = self.current_frame.take() {
             if let Some(ref mut frame) = frame.frame {
                 let mut profile_timers = RendererProfileTimers::new();
+                let mut profile_samplers = Vec::new();
 
                 {
                     //Note: avoiding `self.gpu_profile.add_marker` - it would block here
                     let _gm = GpuMarker::new(self.device.rc_gl(), "build samples");
                     // Block CPU waiting for last frame's GPU profiles to arrive.
                     // In general this shouldn't block unless heavily GPU limited.
-                    if let Some((gpu_frame_id, samples)) = self.gpu_profile.build_samples() {
+                    if let Some((gpu_frame_id, timers, samplers)) = self.gpu_profile.build_samples() {
                         if self.max_recorded_profiles > 0 {
                             while self.gpu_profiles.len() >= self.max_recorded_profiles {
                                 self.gpu_profiles.pop_front();
                             }
-                            self.gpu_profiles.push_back(GpuProfile::new(gpu_frame_id, &samples));
+                            self.gpu_profiles.push_back(GpuProfile::new(gpu_frame_id, &timers));
                         }
-                        profile_timers.gpu_samples = samples;
+                        profile_timers.gpu_samples = timers;
+                        profile_samplers = samplers;
                     }
                 }
 
                 let cpu_frame_id = profile_timers.cpu_time.profile(|| {
                     let cpu_frame_id = {
                         let _gm = GpuMarker::new(self.device.rc_gl(), "begin frame");
                         let frame_id = self.device.begin_frame(frame.device_pixel_ratio);
                         self.gpu_profile.begin_frame(frame_id);
@@ -1561,21 +1690,25 @@ impl Renderer {
                     let cpu_profile = CpuProfile::new(cpu_frame_id,
                                                       self.backend_profile_counters.total_time.get(),
                                                       profile_timers.cpu_time.get(),
                                                       self.profile_counters.draw_calls.get());
                     self.cpu_profiles.push_back(cpu_profile);
                 }
 
                 if self.debug_flags.contains(PROFILER_DBG) {
+                    let screen_fraction = 1.0 / //TODO: take device/pixel ratio into equation?
+                        (framebuffer_size.width as f32 * framebuffer_size.height as f32);
                     self.profiler.draw_profile(&mut self.device,
                                                &frame.profile_counters,
                                                &self.backend_profile_counters,
                                                &self.profile_counters,
                                                &mut profile_timers,
+                                               &profile_samplers,
+                                               screen_fraction,
                                                &mut self.debug);
                 }
 
                 self.profile_counters.reset();
                 self.profile_counters.frame_counter.inc();
 
                 let debug_size = DeviceUintSize::new(framebuffer_size.width as u32,
                                                      framebuffer_size.height as u32);
@@ -1716,32 +1849,33 @@ impl Renderer {
                 self.profile_counters.draw_calls.inc();
             }
         }
 
         self.profile_counters.vertices.add(6 * data.len());
     }
 
     fn submit_batch(&mut self,
-                    batch: &PrimitiveBatch,
+                    key: &AlphaBatchKey,
+                    instances: &[PrimitiveInstance],
                     projection: &Transform3D<f32>,
                     render_tasks: &RenderTaskTree,
                     render_target: Option<(TextureId, i32)>,
                     target_dimensions: DeviceUintSize) {
-        let transform_kind = batch.key.flags.transform_kind();
-        let needs_clipping = batch.key.flags.needs_clipping();
+        let transform_kind = key.flags.transform_kind();
+        let needs_clipping = key.flags.needs_clipping();
         debug_assert!(!needs_clipping ||
-                      match batch.key.blend_mode {
+                      match key.blend_mode {
                           BlendMode::Alpha |
                           BlendMode::PremultipliedAlpha |
                           BlendMode::Subpixel(..) => true,
                           BlendMode::None => false,
                       });
 
-        let marker = match batch.key.kind {
+        let marker = match key.kind {
             AlphaBatchKind::Composite { .. } => {
                 self.ps_composite.bind(&mut self.device, projection);
                 GPU_TAG_PRIM_COMPOSITE
             }
             AlphaBatchKind::HardwareComposite => {
                 self.ps_hw_composite.bind(&mut self.device, projection);
                 GPU_TAG_PRIM_HW_COMPOSITE
             }
@@ -1761,17 +1895,17 @@ impl Renderer {
                 }
                 GPU_TAG_PRIM_RECT
             }
             AlphaBatchKind::Line => {
                 self.ps_line.bind(&mut self.device, transform_kind, projection);
                 GPU_TAG_PRIM_LINE
             }
             AlphaBatchKind::TextRun => {
-                match batch.key.blend_mode {
+                match key.blend_mode {
                     BlendMode::Subpixel(..) => {
                         self.ps_text_run_subpixel.bind(&mut self.device, transform_kind, projection);
                     }
                     BlendMode::Alpha |
                     BlendMode::PremultipliedAlpha |
                     BlendMode::None => {
                         self.ps_text_run.bind(&mut self.device, transform_kind, projection);
                     }
@@ -1821,21 +1955,21 @@ impl Renderer {
             }
             AlphaBatchKind::CacheImage => {
                 self.ps_cache_image.bind(&mut self.device, transform_kind, projection);
                 GPU_TAG_PRIM_CACHE_IMAGE
             }
         };
 
         // Handle special case readback for composites.
-        match batch.key.kind {
+        match key.kind {
             AlphaBatchKind::Composite { task_id, source_id, backdrop_id } => {
                 // composites can't be grouped together because
                 // they may overlap and affect each other.
-                debug_assert!(batch.instances.len() == 1);
+                debug_assert!(instances.len() == 1);
                 let cache_texture = self.texture_resolver.resolve(&SourceTexture::CacheRGBA8);
 
                 // Before submitting the composite batch, do the
                 // framebuffer readbacks that are needed for each
                 // composite operation in this batch.
                 let cache_texture_dimensions = self.device.get_texture_dimensions(cache_texture);
 
                 let source = render_tasks.get(source_id);
@@ -1882,28 +2016,29 @@ impl Renderer {
 
                 // Restore draw target to current pass render target + layer.
                 self.device.bind_draw_target(render_target, Some(target_dimensions));
             }
             _ => {}
         }
 
         let _gm = self.gpu_profile.add_marker(marker);
-        self.draw_instanced_batch(&batch.instances,
+        self.draw_instanced_batch(instances,
                                   VertexArrayKind::Primitive,
-                                  &batch.key.textures);
+                                  &key.textures);
     }
 
     fn draw_color_target(&mut self,
-                         render_target: Option<(TextureId, i32)>,
-                         target: &ColorRenderTarget,
-                         target_size: DeviceUintSize,
-                         clear_color: Option<[f32; 4]>,
-                         render_tasks: &RenderTaskTree,
-                         projection: &Transform3D<f32>) {
+        render_target: Option<(TextureId, i32)>,
+        target: &ColorRenderTarget,
+        target_size: DeviceUintSize,
+        clear_color: Option<[f32; 4]>,
+        render_tasks: &RenderTaskTree,
+        projection: &Transform3D<f32>,
+    ) {
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_TARGET);
             self.device.bind_draw_target(render_target, Some(target_size));
             self.device.disable_depth();
             self.device.enable_depth_write();
             self.device.set_blend(false);
             self.device.set_blend_mode_alpha();
             match render_target {
@@ -1984,43 +2119,50 @@ impl Renderer {
 
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_LINE);
             self.cs_line.bind(&mut self.device, projection);
             self.draw_instanced_batch(&target.line_cache_prims,
                                       VertexArrayKind::Primitive,
                                       &BatchTextures::no_texture());
         }
 
+        //TODO: record the pixel count for cached primitives
+
         if !target.alpha_batcher.is_empty() {
             let _gm2 = GpuMarker::new(self.device.rc_gl(), "alpha batches");
             self.device.set_blend(false);
             let mut prev_blend_mode = BlendMode::None;
 
+            self.gpu_profile.add_sampler(GPU_SAMPLER_TAG_OPAQUE);
+
             //Note: depth equality is needed for split planes
             self.device.set_depth_func(DepthFunction::LessEqual);
             self.device.enable_depth();
             self.device.enable_depth_write();
 
             // Draw opaque batches front-to-back for maximum
             // z-buffer efficiency!
             for batch in target.alpha_batcher
                                .batch_list
-                               .opaque_batches
+                               .opaque_batch_list
+                               .batches
                                .iter()
                                .rev() {
-                self.submit_batch(batch,
+                self.submit_batch(&batch.key,
+                                  &batch.instances,
                                   &projection,
                                   render_tasks,
                                   render_target,
                                   target_size);
             }
 
             self.device.disable_depth_write();
+            self.gpu_profile.add_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
 
-            for batch in &target.alpha_batcher.batch_list.alpha_batches {
+            for batch in &target.alpha_batcher.batch_list.alpha_batch_list.batches {
                 if batch.key.blend_mode != prev_blend_mode {
                     match batch.key.blend_mode {
                         BlendMode::None => {
                             self.device.set_blend(false);
                         }
                         BlendMode::Alpha => {
                             self.device.set_blend(true);
                             self.device.set_blend_mode_alpha();
@@ -2032,33 +2174,38 @@ impl Renderer {
                         BlendMode::Subpixel(color) => {
                             self.device.set_blend(true);
                             self.device.set_blend_mode_subpixel(color);
                         }
                     }
                     prev_blend_mode = batch.key.blend_mode;
                 }
 
-                self.submit_batch(batch,
+                self.submit_batch(&batch.key,
+                                  &batch.instances,
                                   &projection,
                                   render_tasks,
                                   render_target,
                                   target_size);
             }
 
             self.device.disable_depth();
             self.device.set_blend(false);
+            self.gpu_profile.done_sampler();
         }
     }
 
     fn draw_alpha_target(&mut self,
-                         render_target: (TextureId, i32),
-                         target: &AlphaRenderTarget,
-                         target_size: DeviceUintSize,
-                         projection: &Transform3D<f32>) {
+        render_target: (TextureId, i32),
+        target: &AlphaRenderTarget,
+        target_size: DeviceUintSize,
+        projection: &Transform3D<f32>,
+    ) {
+        self.gpu_profile.add_sampler(GPU_SAMPLER_TAG_ALPHA);
+
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_TARGET);
             self.device.bind_draw_target(Some(render_target), Some(target_size));
             self.device.disable_depth();
             self.device.disable_depth_write();
 
             // TODO(gw): Applying a scissor rect and minimal clear here
             // is a very large performance win on the Intel and nVidia
@@ -2125,16 +2272,18 @@ impl Renderer {
                     ]
                 };
                 self.cs_clip_image.bind(&mut self.device, projection);
                 self.draw_instanced_batch(items,
                                           VertexArrayKind::Clip,
                                           &textures);
             }
         }
+
+        self.gpu_profile.done_sampler();
     }
 
     fn update_deferred_resolves(&mut self, frame: &mut Frame) {
         // The first thing we do is run through any pending deferred
         // resolves, and use a callback to get the UV rect for this
         // custom item. Then we patch the resource_rects structure
         // here before it's uploaded to the GPU.
         if !frame.deferred_resolves.is_empty() {
@@ -2154,16 +2303,20 @@ impl Renderer {
                     ExternalImageType::TextureRectHandle => TextureTarget::Rect,
                     ExternalImageType::TextureExternalHandle => TextureTarget::External,
                     ExternalImageType::ExternalBuffer => {
                         panic!("{:?} is not a suitable image type in update_deferred_resolves().",
                             ext_image.image_type);
                     }
                 };
 
+                // In order to produce the handle, the external image handler may call into
+                // the GL context and change some states.
+                self.device.reset_state();
+
                 let texture_id = match image.source {
                     ExternalImageSource::NativeTexture(texture_id) => TextureId::new(texture_id, texture_target),
                     _ => panic!("No native texture found."),
                 };
 
                 self.texture_resolver
                     .external_images
                     .insert((ext_image.id, ext_image.channel_index), texture_id);
@@ -2595,8 +2748,18 @@ impl Default for RendererOptions {
             max_texture_size: None,
             workers: None,
             blob_image_renderer: None,
             recorder: None,
             enable_render_on_scroll: true,
         }
     }
 }
+
+#[cfg(not(feature = "debugger"))]
+pub struct DebugServer;
+
+#[cfg(not(feature = "debugger"))]
+impl DebugServer {
+    pub fn new(_: MsgSender<ApiMsg>) -> DebugServer {
+        DebugServer
+    }
+}
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -12,17 +12,17 @@ use resource_cache::CacheItem;
 use std::cmp;
 use std::mem;
 use api::{ExternalImageType, ImageData, ImageFormat};
 use api::{DeviceUintRect, DeviceUintSize, DeviceUintPoint};
 use api::{ImageDescriptor};
 
 // The fixed number of layers for the shared texture cache.
 // There is one array texture per image format, allocated lazily.
-const TEXTURE_ARRAY_LAYERS: i32 = 2;
+const TEXTURE_ARRAY_LAYERS: i32 = 4;
 
 // The dimensions of each layer in the texture cache.
 const TEXTURE_LAYER_DIMENSIONS: u32 = 2048;
 
 // The size of each region (page) in a texture layer.
 const TEXTURE_REGION_DIMENSIONS: u32 = 512;
 
 // Maintains a simple freelist of texture IDs that are mapped
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -96,104 +96,153 @@ pub struct RenderTargetIndex(pub usize);
 pub struct RenderPassIndex(isize);
 
 #[derive(Debug)]
 struct DynamicTaskInfo {
     task_id: RenderTaskId,
     rect: DeviceIntRect,
 }
 
-pub struct BatchList {
-    pub alpha_batches: Vec<PrimitiveBatch>,
-    pub opaque_batches: Vec<PrimitiveBatch>,
+pub struct AlphaBatchList {
+    pub batches: Vec<AlphaPrimitiveBatch>,
 }
 
-impl BatchList {
-    fn new() -> BatchList {
-        BatchList {
-            alpha_batches: Vec::new(),
-            opaque_batches: Vec::new(),
+impl AlphaBatchList {
+    fn new() -> AlphaBatchList {
+        AlphaBatchList {
+            batches: Vec::new(),
         }
     }
 
-    fn with_suitable_batch<F>(&mut self,
-                              key: &AlphaBatchKey,
-                              item_bounding_rect: &DeviceIntRect,
-                              f: F) where F: Fn(&mut PrimitiveBatch) {
-        let batch = self.get_suitable_batch(key, item_bounding_rect);
-        f(batch)
-    }
-
     fn get_suitable_batch(&mut self,
                           key: &AlphaBatchKey,
-                          item_bounding_rect: &DeviceIntRect) -> &mut PrimitiveBatch {
-        let (batches, check_intersections) = match key.blend_mode {
-            BlendMode::None => {
-                (&mut self.opaque_batches, false)
-            }
-            BlendMode::Alpha | BlendMode::PremultipliedAlpha | BlendMode::Subpixel(..) => {
-                (&mut self.alpha_batches, true)
-            }
-        };
-
+                          item_bounding_rect: &DeviceIntRect) -> &mut Vec<PrimitiveInstance> {
         let mut selected_batch_index = None;
 
         // Composites always get added to their own batch.
         // This is because the result of a composite can affect
         // the input to the next composite. Perhaps we can
         // optimize this in the future.
         match key.kind {
             AlphaBatchKind::Composite { .. } => {}
             _ => {
-                'outer: for (batch_index, batch) in batches.iter()
-                                                           .enumerate()
-                                                           .rev()
-                                                           .take(10) {
+                'outer: for (batch_index, batch) in self.batches
+                                                        .iter()
+                                                        .enumerate()
+                                                        .rev()
+                                                        .take(10) {
                     if batch.key.is_compatible_with(key) {
                         selected_batch_index = Some(batch_index);
                         break;
                     }
 
                     // check for intersections
-                    if check_intersections {
-                        for item_rect in &batch.item_rects {
-                            if item_rect.intersects(item_bounding_rect) {
-                                break 'outer;
-                            }
+                    for item_rect in &batch.item_rects {
+                        if item_rect.intersects(item_bounding_rect) {
+                            break 'outer;
                         }
                     }
                 }
             }
         }
 
         if selected_batch_index.is_none() {
-            let new_batch = PrimitiveBatch::new(key.clone());
-            selected_batch_index = Some(batches.len());
-            batches.push(new_batch);
+            let new_batch = AlphaPrimitiveBatch::new(key.clone());
+            selected_batch_index = Some(self.batches.len());
+            self.batches.push(new_batch);
         }
 
-        let batch = &mut batches[selected_batch_index.unwrap()];
+        let batch = &mut self.batches[selected_batch_index.unwrap()];
         batch.item_rects.push(*item_bounding_rect);
 
-        batch
+        &mut batch.instances
+    }
+}
+
+pub struct OpaqueBatchList {
+    pub batches: Vec<OpaquePrimitiveBatch>,
+}
+
+impl OpaqueBatchList {
+    fn new() -> OpaqueBatchList {
+        OpaqueBatchList {
+            batches: Vec::new(),
+        }
+    }
+
+    fn get_suitable_batch(&mut self,
+                          key: &AlphaBatchKey) -> &mut Vec<PrimitiveInstance> {
+        let mut selected_batch_index = None;
+
+        for (batch_index, batch) in self.batches
+                                        .iter()
+                                        .enumerate()
+                                        .rev()
+                                        .take(10) {
+            if batch.key.is_compatible_with(key) {
+                selected_batch_index = Some(batch_index);
+                break;
+            }
+        }
+
+        if selected_batch_index.is_none() {
+            let new_batch = OpaquePrimitiveBatch::new(key.clone());
+            selected_batch_index = Some(self.batches.len());
+            self.batches.push(new_batch);
+        }
+
+        let batch = &mut self.batches[selected_batch_index.unwrap()];
+
+        &mut batch.instances
     }
 
     fn finalize(&mut self) {
         // Reverse the instance arrays in the opaque batches
         // to get maximum z-buffer efficiency by drawing
         // front-to-back.
         // TODO(gw): Maybe we can change the batch code to
         //           build these in reverse and avoid having
         //           to reverse the instance array here.
-        for batch in &mut self.opaque_batches {
+        for batch in &mut self.batches {
             batch.instances.reverse();
         }
     }
 }
 
+pub struct BatchList {
+    pub alpha_batch_list: AlphaBatchList,
+    pub opaque_batch_list: OpaqueBatchList,
+}
+
+impl BatchList {
+    fn new() -> BatchList {
+        BatchList {
+            alpha_batch_list: AlphaBatchList::new(),
+            opaque_batch_list: OpaqueBatchList::new(),
+        }
+    }
+
+    fn get_suitable_batch(&mut self,
+                          key: &AlphaBatchKey,
+                          item_bounding_rect: &DeviceIntRect) -> &mut Vec<PrimitiveInstance> {
+        match key.blend_mode {
+            BlendMode::None => {
+                self.opaque_batch_list.get_suitable_batch(key)
+            }
+            BlendMode::Alpha | BlendMode::PremultipliedAlpha | BlendMode::Subpixel(..) => {
+                self.alpha_batch_list.get_suitable_batch(key, item_bounding_rect)
+            }
+        }
+    }
+
+    fn finalize(&mut self) {
+        self.opaque_batch_list.finalize()
+    }
+}
+
 /// Encapsulates the logic of building batches for items that are blended.
 pub struct AlphaBatcher {
     pub batch_list: BatchList,
     tasks: Vec<RenderTaskId>,
 }
 
 impl AlphaRenderItem {
     fn add_to_batch(&self,
@@ -232,17 +281,17 @@ impl AlphaRenderItem {
 
                 let instance = CompositePrimitiveInstance::new(task_address,
                                                                src_task_address,
                                                                RenderTaskAddress(0),
                                                                filter_mode,
                                                                amount,
                                                                z);
 
-                batch.add_instance(PrimitiveInstance::from(instance));
+                batch.push(PrimitiveInstance::from(instance));
             }
             AlphaRenderItem::HardwareComposite(stacking_context_index, src_id, composite_op, z) => {
                 let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
                 let src_task_address = render_tasks.get_task_address(src_id);
                 let key = AlphaBatchKey::new(AlphaBatchKind::HardwareComposite,
                                              AlphaBatchKeyFlags::empty(),
                                              composite_op.to_blend_mode(),
                                              BatchTextures::no_texture());
@@ -250,17 +299,17 @@ impl AlphaRenderItem {
 
                 let instance = CompositePrimitiveInstance::new(task_address,
                                                                src_task_address,
                                                                RenderTaskAddress(0),
                                                                0,
                                                                0,
                                                                z);
 
-                batch.add_instance(PrimitiveInstance::from(instance));
+                batch.push(PrimitiveInstance::from(instance));
             }
             AlphaRenderItem::Composite(stacking_context_index,
                                        source_id,
                                        backdrop_id,
                                        mode,
                                        z) => {
                 let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
                 let key = AlphaBatchKey::new(AlphaBatchKind::Composite { task_id, source_id, backdrop_id },
@@ -273,17 +322,17 @@ impl AlphaRenderItem {
 
                 let instance = CompositePrimitiveInstance::new(task_address,
                                                                source_task_address,
                                                                backdrop_task_address,
                                                                mode as u32 as i32,
                                                                0,
                                                                z);
 
-                batch.add_instance(PrimitiveInstance::from(instance));
+                batch.push(PrimitiveInstance::from(instance));
             }
             AlphaRenderItem::Primitive(clip_scroll_group_index_opt, prim_index, z) => {
                 let prim_metadata = ctx.prim_store.get_metadata(prim_index);
                 let (transform_kind, packed_layer_index) = match clip_scroll_group_index_opt {
                     Some(group_index) => {
                         let group = &ctx.clip_scroll_group_store[group_index.0];
                         let bounding_rect = group.screen_bounding_rect.as_ref().unwrap();
                         (bounding_rect.0, group.packed_layer_index)
@@ -320,49 +369,50 @@ impl AlphaRenderItem {
 
                 match prim_metadata.prim_kind {
                     PrimitiveKind::Border => {
                         let border_cpu = &ctx.prim_store.cpu_borders[prim_metadata.cpu_prim_index.0];
                         // TODO(gw): Select correct blend mode for edges and corners!!
                         let corner_key = AlphaBatchKey::new(AlphaBatchKind::BorderCorner, flags, blend_mode, no_textures);
                         let edge_key = AlphaBatchKey::new(AlphaBatchKind::BorderEdge, flags, blend_mode, no_textures);
 
-                        batch_list.with_suitable_batch(&corner_key, item_bounding_rect, |batch| {
+                        // Work around borrow ck on borrowing batch_list twice.
+                        {
+                            let batch = batch_list.get_suitable_batch(&corner_key, item_bounding_rect);
                             for (i, instance_kind) in border_cpu.corner_instances.iter().enumerate() {
                                 let sub_index = i as i32;
                                 match *instance_kind {
                                     BorderCornerInstance::Single => {
-                                        batch.add_instance(base_instance.build(sub_index,
-                                                                               BorderCornerSide::Both as i32, 0));
+                                        batch.push(base_instance.build(sub_index,
+                                                                       BorderCornerSide::Both as i32, 0));
                                     }
                                     BorderCornerInstance::Double => {
-                                        batch.add_instance(base_instance.build(sub_index,
-                                                                               BorderCornerSide::First as i32, 0));
-                                        batch.add_instance(base_instance.build(sub_index,
-                                                                               BorderCornerSide::Second as i32, 0));
+                                        batch.push(base_instance.build(sub_index,
+                                                                       BorderCornerSide::First as i32, 0));
+                                        batch.push(base_instance.build(sub_index,
+                                                                       BorderCornerSide::Second as i32, 0));
                                     }
                                 }
                             }
-                        });
+                        }
 
-                        batch_list.with_suitable_batch(&edge_key, item_bounding_rect, |batch| {
-                            for border_segment in 0..4 {
-                                batch.add_instance(base_instance.build(border_segment, 0, 0));
-                            }
-                        });
+                        let batch = batch_list.get_suitable_batch(&edge_key, item_bounding_rect);
+                        for border_segment in 0..4 {
+                            batch.push(base_instance.build(border_segment, 0, 0));
+                        }
                     }
                     PrimitiveKind::Rectangle => {
                         let key = AlphaBatchKey::new(AlphaBatchKind::Rectangle, flags, blend_mode, no_textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
-                        batch.add_instance(base_instance.build(0, 0, 0));
+                        batch.push(base_instance.build(0, 0, 0));
                     }
                     PrimitiveKind::Line => {
                         let key = AlphaBatchKey::new(AlphaBatchKind::Line, flags, blend_mode, no_textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
-                        batch.add_instance(base_instance.build(0, 0, 0));
+                        batch.push(base_instance.build(0, 0, 0));
                     }
                     PrimitiveKind::Image => {
                         let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0];
 
                         let (color_texture_id, uv_address) = resolve_image(image_cpu.image_key,
                                                                            image_cpu.image_rendering,
                                                                            image_cpu.tile_offset,
                                                                            ctx.resource_cache,
@@ -393,17 +443,17 @@ impl AlphaRenderItem {
                         };
 
                         let textures = BatchTextures {
                             colors: [color_texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
                         };
 
                         let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
-                        batch.add_instance(base_instance.build(uv_address.as_int(gpu_cache), 0, 0));
+                        batch.push(base_instance.build(uv_address.as_int(gpu_cache), 0, 0));
                     }
                     PrimitiveKind::TextRun => {
                         let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
                         let font_size_dp = text_cpu.logical_font_size.scale_by(ctx.device_pixel_ratio);
 
                         // TODO(gw): avoid / recycle this allocation in the future.
                         let mut instances = Vec::new();
 
@@ -424,44 +474,44 @@ impl AlphaRenderItem {
                         if texture_id != SourceTexture::Invalid {
                             let textures = BatchTextures {
                                 colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid],
                             };
 
                             let key = AlphaBatchKey::new(AlphaBatchKind::TextRun, flags, blend_mode, textures);
                             let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
 
-                            batch.add_instances(&instances);
+                            batch.extend_from_slice(&instances);
                         }
                     }
                     PrimitiveKind::TextShadow => {
                         let cache_task_id = prim_metadata.render_task_id.expect("no render task!");
                         let cache_task_address = render_tasks.get_task_address(cache_task_id);
                         let textures = BatchTextures::render_target_cache();
                         let key = AlphaBatchKey::new(AlphaBatchKind::CacheImage, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
-                        batch.add_instance(base_instance.build(0, cache_task_address.0 as i32, 0));
+                        batch.push(base_instance.build(0, cache_task_address.0 as i32, 0));
                     }
                     PrimitiveKind::AlignedGradient => {
                         let gradient_cpu = &ctx.prim_store.cpu_gradients[prim_metadata.cpu_prim_index.0];
                         let key = AlphaBatchKey::new(AlphaBatchKind::AlignedGradient, flags, blend_mode, no_textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         for part_index in 0..(gradient_cpu.stops_count - 1) {
-                            batch.add_instance(base_instance.build(part_index as i32, 0, 0));
+                            batch.push(base_instance.build(part_index as i32, 0, 0));
                         }
                     }
                     PrimitiveKind::AngleGradient => {
                         let key = AlphaBatchKey::new(AlphaBatchKind::AngleGradient, flags, blend_mode, no_textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
-                        batch.add_instance(base_instance.build(0, 0, 0));
+                        batch.push(base_instance.build(0, 0, 0));
                     }
                     PrimitiveKind::RadialGradient => {
                         let key = AlphaBatchKey::new(AlphaBatchKind::RadialGradient, flags, blend_mode, no_textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
-                        batch.add_instance(base_instance.build(0, 0, 0));
+                        batch.push(base_instance.build(0, 0, 0));
                     }
                     PrimitiveKind::YuvImage => {
                         let mut textures = BatchTextures::no_texture();
                         let mut uv_rect_addresses = [0; 3];
                         let image_yuv_cpu = &ctx.prim_store.cpu_yuv_images[prim_metadata.cpu_prim_index.0];
 
                         //yuv channel
                         let channel_count = image_yuv_cpu.format.get_plane_num();
@@ -512,31 +562,31 @@ impl AlphaRenderItem {
                         ));
 
                         let key = AlphaBatchKey::new(AlphaBatchKind::YuvImage(buffer_kind, image_yuv_cpu.format, image_yuv_cpu.color_space),
                                                      flags,
                                                      blend_mode,
                                                      textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
 
-                        batch.add_instance(base_instance.build(uv_rect_addresses[0],
-                                                               uv_rect_addresses[1],
-                                                               uv_rect_addresses[2]));
+                        batch.push(base_instance.build(uv_rect_addresses[0],
+                                                       uv_rect_addresses[1],
+                                                       uv_rect_addresses[2]));
                     }
                     PrimitiveKind::BoxShadow => {
                         let box_shadow = &ctx.prim_store.cpu_box_shadows[prim_metadata.cpu_prim_index.0];
                         let cache_task_id = prim_metadata.render_task_id.unwrap();
                         let cache_task_address = render_tasks.get_task_address(cache_task_id);
 
                         let key = AlphaBatchKey::new(AlphaBatchKind::BoxShadow, flags, blend_mode, no_textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
 
                         for rect_index in 0..box_shadow.rects.len() {
-                            batch.add_instance(base_instance.build(rect_index as i32,
-                                                                   cache_task_address.0 as i32, 0));
+                            batch.push(base_instance.build(rect_index as i32,
+                                                           cache_task_address.0 as i32, 0));
                         }
                     }
                 }
             }
             AlphaRenderItem::SplitComposite(sc_index, task_id, gpu_handle, z) => {
                 let key = AlphaBatchKey::new(AlphaBatchKind::SplitComposite,
                                              AlphaBatchKeyFlags::empty(),
                                              BlendMode::PremultipliedAlpha,
@@ -548,17 +598,17 @@ impl AlphaRenderItem {
 
                 let instance = CompositePrimitiveInstance::new(task_address,
                                                                source_task_address,
                                                                RenderTaskAddress(0),
                                                                gpu_address,
                                                                0,
                                                                z);
 
-                batch.add_instance(PrimitiveInstance::from(instance));
+                batch.push(PrimitiveInstance::from(instance));
             }
         }
     }
 }
 
 impl AlphaBatcher {
     fn new() -> AlphaBatcher {
         AlphaBatcher {
@@ -591,18 +641,18 @@ impl AlphaBatcher {
                                   deferred_resolves);
             }
         }
 
         self.batch_list.finalize();
     }
 
     pub fn is_empty(&self) -> bool {
-        self.batch_list.opaque_batches.is_empty() &&
-        self.batch_list.alpha_batches.is_empty()
+        self.batch_list.opaque_batch_list.batches.is_empty() &&
+        self.batch_list.alpha_batch_list.batches.is_empty()
     }
 }
 
 /// Batcher managing draw calls into the clip mask (in the RT cache).
 #[derive(Debug)]
 pub struct ClipBatcher {
     /// Rectangle draws fill up the rectangles with rounded corners.
     pub rectangles: Vec<CacheClipInstance>,
@@ -1402,37 +1452,44 @@ impl From<CompositePrimitiveInstance> fo
                 0,
                 0,
             ]
         }
     }
 }
 
 #[derive(Debug)]
-pub struct PrimitiveBatch {
+pub struct AlphaPrimitiveBatch {
     pub key: AlphaBatchKey,
     pub instances: Vec<PrimitiveInstance>,
     pub item_rects: Vec<DeviceIntRect>,
 }
 
-impl PrimitiveBatch {
-    fn new(key: AlphaBatchKey) -> PrimitiveBatch {
-        PrimitiveBatch {
+impl AlphaPrimitiveBatch {
+    fn new(key: AlphaBatchKey) -> AlphaPrimitiveBatch {
+        AlphaPrimitiveBatch {
             key,
             instances: Vec::new(),
             item_rects: Vec::new(),
         }
     }
+}
 
-    fn add_instance(&mut self, instance: PrimitiveInstance) {
-        self.instances.push(instance);
-    }
+#[derive(Debug)]
+pub struct OpaquePrimitiveBatch {
+    pub key: AlphaBatchKey,
+    pub instances: Vec<PrimitiveInstance>,
+}
 
-    fn add_instances(&mut self, instances: &[PrimitiveInstance]) {
-        self.instances.extend_from_slice(instances);
+impl OpaquePrimitiveBatch {
+    fn new(key: AlphaBatchKey) -> OpaquePrimitiveBatch {
+        OpaquePrimitiveBatch {
+            key,
+            instances: Vec::new(),
+        }
     }
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct PackedLayerIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct StackingContextIndex(pub usize);
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -134,16 +134,28 @@ impl fmt::Debug for DocumentMsg {
             DocumentMsg::ScrollNodeWithId(..) => "DocumentMsg::ScrollNodeWithId",
             DocumentMsg::TickScrollingBounce => "DocumentMsg::TickScrollingBounce",
             DocumentMsg::GetScrollNodeState(..) => "DocumentMsg::GetScrollNodeState",
             DocumentMsg::GenerateFrame(..) => "DocumentMsg::GenerateFrame",
         })
     }
 }
 
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub enum DebugCommand {
+    // Display the frame profiler on screen.
+    EnableProfiler(bool),
+    // Display all texture cache pages on screen.
+    EnableTextureCacheDebug(bool),
+    // Display intermediate render targets on screen.
+    EnableRenderTargetDebug(bool),
+    // Flush any pending debug commands.
+    Flush,
+}
+
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
     /// Add/remove/update images and fonts.
     UpdateResources(ResourceUpdates),
     /// Gets the glyph dimensions
     GetGlyphDimensions(FontInstance, Vec<GlyphKey>, MsgSender<Vec<Option<GlyphDimensions>>>),
     /// Gets the glyph indices from a string
     GetGlyphIndices(FontKey, String, MsgSender<Vec<Option<u32>>>),
@@ -158,32 +170,35 @@ pub enum ApiMsg {
     /// An opaque handle that must be passed to the render notifier. It is used by Gecko
     /// to forward gecko-specific messages to the render thread preserving the ordering
     /// within the other messages.
     ExternalEvent(ExternalEvent),
     /// Removes all resources associated with a namespace.
     ClearNamespace(IdNamespace),
     /// Flush from the caches anything that isn't necessary, to free some memory.
     MemoryPressure,
+    /// Change debugging options.
+    DebugCommand(DebugCommand),
     ShutDown,
 }
 
 impl fmt::Debug for ApiMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             ApiMsg::UpdateResources(..) => "ApiMsg::UpdateResources",
             ApiMsg::GetGlyphDimensions(..) => "ApiMsg::GetGlyphDimensions",
             ApiMsg::GetGlyphIndices(..) => "ApiMsg::GetGlyphIndices",
             ApiMsg::CloneApi(..) => "ApiMsg::CloneApi",
             ApiMsg::AddDocument(..) => "ApiMsg::AddDocument",
             ApiMsg::UpdateDocument(..) => "ApiMsg::UpdateDocument",
             ApiMsg::DeleteDocument(..) => "ApiMsg::DeleteDocument",
             ApiMsg::ExternalEvent(..) => "ApiMsg::ExternalEvent",
             ApiMsg::ClearNamespace(..) => "ApiMsg::ClearNamespace",
             ApiMsg::MemoryPressure => "ApiMsg::MemoryPressure",
+            ApiMsg::DebugCommand(..) => "ApiMsg::DebugCommand",
             ApiMsg::ShutDown => "ApiMsg::ShutDown",
         })
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
 pub struct Epoch(pub u32);
@@ -414,17 +429,17 @@ impl RenderApi {
     /// ```
     pub fn set_root_pipeline(&self, document_id: DocumentId, pipeline_id: PipelineId) {
         self.send(document_id, DocumentMsg::SetRootPipeline(pipeline_id));
     }
 
     /// Supplies a new frame to WebRender.
     ///
     /// Non-blocking, it notifies a worker process which processes the display list.
-    /// When it's done and a RenderNotifier has been set in `webrender::renderer::Renderer`,
+    /// When it's done and a RenderNotifier has been set in `webrender::Renderer`,
     /// [new_frame_ready()][notifier] gets called.
     ///
     /// Note: Scrolling doesn't require an own Frame.
     ///
     /// Arguments:
     ///
     /// * `document_id`: Target Document ID.
     /// * `epoch`: The unique Frame ID, monotonically increasing.
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -15,16 +15,20 @@ use {ImageDisplayItem, ImageKey, ImageMa
 use {LayoutTransform, LayoutVector2D, LineDisplayItem, LineOrientation, LineStyle, LocalClip};
 use {MixBlendMode, PipelineId, PropertyBinding, PushStackingContextDisplayItem, RadialGradient};
 use {RadialGradientDisplayItem, RectangleDisplayItem, ScrollFrameDisplayItem, ScrollPolicy};
 use {ScrollSensitivity, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem};
 use {StickyFrameInfo, TextDisplayItem, TextShadow, TransformStyle};
 use {YuvColorSpace, YuvData, YuvImageDisplayItem};
 use std::marker::PhantomData;
 
+// We don't want to push a long text-run. If a text-run is too long, split it into several parts.
+// Please check the renderer::MAX_VERTEX_TEXTURE_WIDTH for the detail.
+pub const MAX_TEXT_RUN_LENGTH: usize = 2040;
+
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ItemRange<T> {
     start: usize,
     length: usize,
     _boo: PhantomData<T>,
 }
 
@@ -614,21 +618,23 @@ impl DisplayListBuilder {
         if size < Au::from_px(4096) {
             let item = SpecificDisplayItem::Text(TextDisplayItem {
                 color,
                 font_key,
                 size,
                 glyph_options,
             });
 
-            self.push_item(item, rect, local_clip);
-            self.push_iter(glyphs);
+            for split_glyphs in glyphs.chunks(MAX_TEXT_RUN_LENGTH) {
+                self.push_item(item, rect, local_clip);
+                self.push_iter(split_glyphs);
 
-            // Remember that we've seen these glyphs
-            self.cache_glyphs(font_key, color, glyphs.iter().map(|glyph| glyph.index));
+                // Remember that we've seen these glyphs
+                self.cache_glyphs(font_key, color, split_glyphs.iter().map(|glyph| glyph.index));
+            }
         }
     }
 
     fn cache_glyphs<I: Iterator<Item=GlyphIndex>>(&mut self,
                                                      font_key: FontKey,
                                                      color: ColorF,
                                                      glyphs: I) {
         let mut font_glyphs = self.glyphs.entry((font_key, color))
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -2,19 +2,19 @@ use std::collections::HashSet;
 use std::ffi::CString;
 use std::{mem, slice};
 use std::path::PathBuf;
 use std::sync::Arc;
 use std::os::raw::{c_void, c_char, c_float};
 use gleam::gl;
 
 use webrender_api::*;
-use webrender::renderer::{ReadPixelsFormat, Renderer, RendererOptions};
-use webrender::renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource};
-use webrender::renderer::DebugFlags;
+use webrender::{ReadPixelsFormat, Renderer, RendererOptions};
+use webrender::{ExternalImage, ExternalImageHandler, ExternalImageSource};
+use webrender::DebugFlags;
 use webrender::{ApiRecordingReceiver, BinaryRecorder};
 use thread_profiler::register_thread_with_profiler;
 use moz2d_renderer::Moz2dImageRenderer;
 use app_units::Au;
 use rayon;
 use euclid::SideOffsets2D;
 
 extern crate webrender_api;
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -162,18 +162,16 @@ enum class YuvColorSpace : uint32_t {
 struct Arc_VecU8;
 
 struct DocumentHandle;
 
 // The renderer is responsible for submitting to the GPU the work prepared by the
 // RenderBackend.
 struct Renderer;
 
-struct Tiles;
-
 struct Vec_u8;
 
 struct WrRenderedEpochs;
 
 struct WrState;
 
 struct WrThreadPool;
 
@@ -412,26 +410,16 @@ struct ColorF {
   bool operator==(const ColorF& aOther) const {
     return r == aOther.r &&
            g == aOther.g &&
            b == aOther.b &&
            a == aOther.a;
   }
 };
 
-struct TypedPoint2D_u16__Tiles {
-  uint16_t x;
-  uint16_t y;
-
-  bool operator==(const TypedPoint2D_u16__Tiles& aOther) const {
-    return x == aOther.x &&
-           y == aOther.y;
-  }
-};
-
 struct TypedPoint2D_f32__LayerPixel {
   float x;
   float y;
 
   bool operator==(const TypedPoint2D_f32__LayerPixel& aOther) const {
     return x == aOther.x &&
            y == aOther.y;
   }
@@ -613,16 +601,26 @@ struct TextShadow {
     return offset == aOther.offset &&
            color == aOther.color &&
            blur_radius == aOther.blur_radius;
   }
 };
 
 typedef YuvColorSpace WrYuvColorSpace;
 
+struct TypedPoint2D_u16__Tiles {
+  uint16_t x;
+  uint16_t y;
+
+  bool operator==(const TypedPoint2D_u16__Tiles& aOther) const {
+    return x == aOther.x &&
+           y == aOther.y;
+  }
+};
+
 typedef TypedPoint2D_u16__Tiles TileOffset;
 
 struct MutByteSlice {
   uint8_t *buffer;
   size_t len;
 
   bool operator==(const MutByteSlice& aOther) const {
     return buffer == aOther.buffer &&
@@ -1098,17 +1096,17 @@ void wr_dp_push_yuv_planar_image(WrState
                                  ImageRendering aImageRendering)
 WR_FUNC;
 
 extern bool wr_moz2d_render_cb(ByteSlice aBlob,
                                uint32_t aWidth,
                                uint32_t aHeight,
                                ImageFormat aFormat,
                                const uint16_t *aTileSize,
-                               const TileOffset *aTileoffest,
+                               const TileOffset *aTileOffset,
                                MutByteSlice aOutput);
 
 extern void wr_notifier_external_event(WrWindowId aWindowId,
                                        size_t aRawEvent);
 
 extern void wr_notifier_new_frame_ready(WrWindowId aWindowId);
 
 extern void wr_notifier_new_scroll_frame_ready(WrWindowId aWindowId,
--- a/intl/Encoding.h
+++ b/intl/Encoding.h
@@ -30,16 +30,19 @@ class Encoder;
 #define ENCODING_RS_NOT_NULL_CONST_ENCODING_PTR mozilla::NotNull<const mozilla::Encoding*>
 #define ENCODING_RS_ENCODER mozilla::Encoder
 #define ENCODING_RS_DECODER mozilla::Decoder
 
 #include "encoding_rs.h"
 
 extern "C" {
 
+mozilla::Encoding const*
+mozilla_encoding_for_name(uint8_t const* name, size_t name_len);
+
 nsresult
 mozilla_encoding_decode_to_nsstring(mozilla::Encoding const** encoding,
                                     uint8_t const* src,
                                     size_t src_len,
                                     nsAString* dst);
 
 nsresult
 mozilla_encoding_decode_to_nsstring_with_bom_removal(
@@ -255,17 +258,17 @@ public:
    * The motivating use case for this method is interoperability with
    * legacy Gecko code that represents encodings as name string instead of
    * type-safe `Encoding` objects. Using this method for other purposes is
    * most likely the wrong thing to do.
    */
   static inline NotNull<const mozilla::Encoding*> ForName(
     Span<const char> aName)
   {
-    return WrapNotNull(encoding_for_name(
+    return WrapNotNull(mozilla_encoding_for_name(
       reinterpret_cast<const uint8_t*>(aName.Elements()), aName.Length()));
   }
 
   /**
    * Writes the name of this encoding into `aName`.
    *
    * This name is appropriate to return as-is from the DOM
    * `document.characterSet` property.
--- a/intl/encoding_glue/Cargo.toml
+++ b/intl/encoding_glue/Cargo.toml
@@ -6,11 +6,11 @@ authors = ["Henri Sivonen <hsivonen@hsiv
 license = "MIT/Apache-2.0"
 
 [features]
 simd-accel = ["encoding_rs/simd-accel"]
 no-static-ideograph-encoder-tables = ["encoding_rs/no-static-ideograph-encoder-tables"]
 parallel-utf8 = ["encoding_rs/parallel-utf8"]
 
 [dependencies]
-encoding_rs = "0.6.5"
+encoding_rs = "0.7.0"
 nsstring = { path = "../../xpcom/rust/nsstring" }
 nserror = { path = "../../xpcom/rust/nserror" }
--- a/intl/encoding_glue/src/lib.rs
+++ b/intl/encoding_glue/src/lib.rs
@@ -11,16 +11,17 @@
 // "top-level directory" in the above notice refers to
 // third_party/rust/encoding_rs/.
 
 extern crate encoding_rs;
 extern crate nsstring;
 extern crate nserror;
 
 use std::slice;
+use std::cmp::Ordering;
 use encoding_rs::*;
 use nsstring::*;
 use nserror::*;
 
 // nsStringBuffer's internal bookkeeping takes 8 bytes from
 // the allocation. Plus one for termination.
 const NS_CSTRING_OVERHEAD: usize = 9;
 
@@ -47,16 +48,123 @@ macro_rules! try_dst_set_len {
     unsafe {
         if $dst.fallible_set_length(needed).is_err() {
             return $ret;
         }
     }
      )
 }
 
+static ENCODINGS_SORTED_BY_NAME: [&'static Encoding; 39] = [&GBK_INIT,
+                                                            &BIG5_INIT,
+                                                            &IBM866_INIT,
+                                                            &EUC_JP_INIT,
+                                                            &KOI8_R_INIT,
+                                                            &EUC_KR_INIT,
+                                                            &KOI8_U_INIT,
+                                                            &GB18030_INIT,
+                                                            &UTF_16BE_INIT,
+                                                            &UTF_16LE_INIT,
+                                                            &SHIFT_JIS_INIT,
+                                                            &MACINTOSH_INIT,
+                                                            &ISO_8859_2_INIT,
+                                                            &ISO_8859_3_INIT,
+                                                            &ISO_8859_4_INIT,
+                                                            &ISO_8859_5_INIT,
+                                                            &ISO_8859_6_INIT,
+                                                            &ISO_8859_7_INIT,
+                                                            &ISO_8859_8_INIT,
+                                                            &ISO_8859_10_INIT,
+                                                            &ISO_8859_13_INIT,
+                                                            &ISO_8859_14_INIT,
+                                                            &WINDOWS_874_INIT,
+                                                            &ISO_8859_15_INIT,
+                                                            &ISO_8859_16_INIT,
+                                                            &ISO_2022_JP_INIT,
+                                                            &REPLACEMENT_INIT,
+                                                            &WINDOWS_1250_INIT,
+                                                            &WINDOWS_1251_INIT,
+                                                            &WINDOWS_1252_INIT,
+                                                            &WINDOWS_1253_INIT,
+                                                            &WINDOWS_1254_INIT,
+                                                            &WINDOWS_1255_INIT,
+                                                            &WINDOWS_1256_INIT,
+                                                            &WINDOWS_1257_INIT,
+                                                            &WINDOWS_1258_INIT,
+                                                            &ISO_8859_8_I_INIT,
+                                                            &X_MAC_CYRILLIC_INIT,
+                                                            &X_USER_DEFINED_INIT];
+
+/// If the argument matches exactly (case-sensitively; no whitespace
+/// removal performed) the name of an encoding, returns
+/// `const Encoding*` representing that encoding. Otherwise panics.
+///
+/// The motivating use case for this function is interoperability with
+/// legacy Gecko code that represents encodings as name string instead of
+/// type-safe `Encoding` objects. Using this function for other purposes is
+/// most likely the wrong thing to do.
+///
+/// `name` must be non-`NULL` even if `name_len` is zero. When `name_len`
+/// is zero, it is OK for `name` to be something non-dereferencable,
+/// such as `0x1`. This is required due to Rust's optimization for slices
+/// within `Option`.
+///
+/// # Panics
+///
+/// Panics if the argument is not the name of an encoding.
+///
+/// # Undefined behavior
+///
+/// UB ensues if `name` and `name_len` don't designate a valid memory block
+/// of if `name` is `NULL`.
+#[no_mangle]
+pub unsafe extern "C" fn mozilla_encoding_for_name(name: *const u8, name_len: usize) -> *const Encoding {
+    let name_slice = ::std::slice::from_raw_parts(name, name_len);
+    encoding_for_name(name_slice)
+}
+
+/// If the argument matches exactly (case-sensitively; no whitespace
+/// removal performed) the name of an encoding, returns
+/// `&'static Encoding` representing that encoding. Otherwise panics.
+///
+/// The motivating use case for this method is interoperability with
+/// legacy Gecko code that represents encodings as name string instead of
+/// type-safe `Encoding` objects. Using this method for other purposes is
+/// most likely the wrong thing to do.
+///
+/// Available via the C wrapper.
+///
+/// # Panics
+///
+/// Panics if the argument is not the name of an encoding.
+#[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))]
+pub fn encoding_for_name(name: &[u8]) -> &'static Encoding {
+    // The length of `"UTF-8"` is unique, so it's easy to check the most
+    // common case first.
+    if name.len() == 5 {
+        assert_eq!(name, b"UTF-8", "Bogus encoding name");
+        return UTF_8;
+    }
+    match ENCODINGS_SORTED_BY_NAME.binary_search_by(
+        |probe| {
+            let bytes = probe.name().as_bytes();
+            let c = bytes.len().cmp(&name.len());
+            if c != Ordering::Equal {
+                return c;
+            }
+            let probe_iter = bytes.iter().rev();
+            let candidate_iter = name.iter().rev();
+            probe_iter.cmp(candidate_iter)
+        }
+    ) {
+        Ok(i) => ENCODINGS_SORTED_BY_NAME[i],
+        Err(_) => panic!("Bogus encoding name"),
+    }
+}
+
 #[no_mangle]
 pub unsafe extern "C" fn mozilla_encoding_decode_to_nsstring(encoding: *mut *const Encoding,
                                                              src: *const u8,
                                                              src_len: usize,
                                                              dst: *mut nsAString)
                                                              -> nsresult {
     let (rv, enc) = decode_to_nsstring(&**encoding, slice::from_raw_parts(src, src_len), &mut *dst);
     *encoding = enc as *const Encoding;
--- a/intl/gtest/TestEncoding.cpp
+++ b/intl/gtest/TestEncoding.cpp
@@ -22,22 +22,113 @@ ENCODING_TEST(ForLabel)
   nsAutoCString label("  uTf-8   ");
   ASSERT_EQ(Encoding::ForLabel(label), UTF_8_ENCODING);
   label.AssignLiteral("   cseucpkdfmTjapanese  ");
   ASSERT_EQ(Encoding::ForLabel(label), EUC_JP_ENCODING);
 }
 
 ENCODING_TEST(ForName)
 {
-  nsAutoCString encoding("UTF-8");
+  nsAutoCString encoding("GBK");
+  ASSERT_EQ(Encoding::ForName(encoding), GBK_ENCODING);
+  encoding.AssignLiteral("Big5");
+  ASSERT_EQ(Encoding::ForName(encoding), BIG5_ENCODING);
+  encoding.AssignLiteral("UTF-8");
   ASSERT_EQ(Encoding::ForName(encoding), UTF_8_ENCODING);
+  encoding.AssignLiteral("IBM866");
+  ASSERT_EQ(Encoding::ForName(encoding), IBM866_ENCODING);
   encoding.AssignLiteral("EUC-JP");
   ASSERT_EQ(Encoding::ForName(encoding), EUC_JP_ENCODING);
+  encoding.AssignLiteral("KOI8-R");
+  ASSERT_EQ(Encoding::ForName(encoding), KOI8_R_ENCODING);
+  encoding.AssignLiteral("EUC-KR");
+  ASSERT_EQ(Encoding::ForName(encoding), EUC_KR_ENCODING);
+  encoding.AssignLiteral("KOI8-U");
+  ASSERT_EQ(Encoding::ForName(encoding), KOI8_U_ENCODING);
+  encoding.AssignLiteral("gb18030");
+  ASSERT_EQ(Encoding::ForName(encoding), GB18030_ENCODING);
+  encoding.AssignLiteral("UTF-16BE");
+  ASSERT_EQ(Encoding::ForName(encoding), UTF_16BE_ENCODING);
+  encoding.AssignLiteral("UTF-16LE");
+  ASSERT_EQ(Encoding::ForName(encoding), UTF_16LE_ENCODING);
+  encoding.AssignLiteral("Shift_JIS");
+  ASSERT_EQ(Encoding::ForName(encoding), SHIFT_JIS_ENCODING);
+  encoding.AssignLiteral("macintosh");
+  ASSERT_EQ(Encoding::ForName(encoding), MACINTOSH_ENCODING);
+  encoding.AssignLiteral("ISO-8859-2");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_2_ENCODING);
+  encoding.AssignLiteral("ISO-8859-3");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_3_ENCODING);
+  encoding.AssignLiteral("ISO-8859-4");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_4_ENCODING);
+  encoding.AssignLiteral("ISO-8859-5");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_5_ENCODING);
+  encoding.AssignLiteral("ISO-8859-6");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_6_ENCODING);
+  encoding.AssignLiteral("ISO-8859-7");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_7_ENCODING);
+  encoding.AssignLiteral("ISO-8859-8");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_8_ENCODING);
+  encoding.AssignLiteral("ISO-8859-10");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_10_ENCODING);
+  encoding.AssignLiteral("ISO-8859-13");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_13_ENCODING);
+  encoding.AssignLiteral("ISO-8859-14");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_14_ENCODING);
+  encoding.AssignLiteral("windows-874");
+  ASSERT_EQ(Encoding::ForName(encoding), WINDOWS_874_ENCODING);
+  encoding.AssignLiteral("ISO-8859-15");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_15_ENCODING);
+  encoding.AssignLiteral("ISO-8859-16");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_16_ENCODING);
+  encoding.AssignLiteral("ISO-2022-JP");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_2022_JP_ENCODING);
+  encoding.AssignLiteral("replacement");
+  ASSERT_EQ(Encoding::ForName(encoding), REPLACEMENT_ENCODING);
+  encoding.AssignLiteral("windows-1250");
+  ASSERT_EQ(Encoding::ForName(encoding), WINDOWS_1250_ENCODING);
+  encoding.AssignLiteral("windows-1251");
+  ASSERT_EQ(Encoding::ForName(encoding), WINDOWS_1251_ENCODING);
+  encoding.AssignLiteral("windows-1252");
+  ASSERT_EQ(Encoding::ForName(encoding), WINDOWS_1252_ENCODING);
+  encoding.AssignLiteral("windows-1253");
+  ASSERT_EQ(Encoding::ForName(encoding), WINDOWS_1253_ENCODING);
+  encoding.AssignLiteral("windows-1254");
+  ASSERT_EQ(Encoding::ForName(encoding), WINDOWS_1254_ENCODING);
+  encoding.AssignLiteral("windows-1255");
+  ASSERT_EQ(Encoding::ForName(encoding), WINDOWS_1255_ENCODING);
+  encoding.AssignLiteral("windows-1256");
+  ASSERT_EQ(Encoding::ForName(encoding), WINDOWS_1256_ENCODING);
+  encoding.AssignLiteral("windows-1257");
+  ASSERT_EQ(Encoding::ForName(encoding), WINDOWS_1257_ENCODING);
+  encoding.AssignLiteral("windows-1258");
+  ASSERT_EQ(Encoding::ForName(encoding), WINDOWS_1258_ENCODING);
+  encoding.AssignLiteral("ISO-8859-8-I");
+  ASSERT_EQ(Encoding::ForName(encoding), ISO_8859_8_I_ENCODING);
+  encoding.AssignLiteral("x-mac-cyrillic");
+  ASSERT_EQ(Encoding::ForName(encoding), X_MAC_CYRILLIC_ENCODING);
+  encoding.AssignLiteral("x-user-defined");
+  ASSERT_EQ(Encoding::ForName(encoding), X_USER_DEFINED_ENCODING);
 }
 
+// Test disabled pending bug 1393711
+#if 0
+ENCODING_TEST(BogusName)
+{
+  nsAutoCString encoding("utf-8");
+  ASSERT_DEATH_IF_SUPPORTED(Encoding::ForName(encoding), "Bogus encoding name");
+  encoding.AssignLiteral("ISO-8859-1");
+  ASSERT_DEATH_IF_SUPPORTED(Encoding::ForName(encoding), "Bogus encoding name");
+  encoding.AssignLiteral("gbk");
+  ASSERT_DEATH_IF_SUPPORTED(Encoding::ForName(encoding), "Bogus encoding name");
+  encoding.AssignLiteral(" UTF-8 ");
+  ASSERT_DEATH_IF_SUPPORTED(Encoding::ForName(encoding), "Bogus encoding name");
+}
+#endif
+
 ENCODING_TEST(ForBOM)
 {
   nsAutoCString data("\xEF\xBB\xBF\x61");
   const Encoding* encoding;
   size_t bomLength;
   Tie(encoding, bomLength) = Encoding::ForBOM(data);
   ASSERT_EQ(encoding, UTF_8_ENCODING);
   ASSERT_EQ(bomLength, 3U);
--- a/layout/reftests/table-bordercollapse/reftest.list
+++ b/layout/reftests/table-bordercollapse/reftest.list
@@ -100,16 +100,16 @@ fails-if(webrender) == frame_vsides_rule
 == bordercolor-3.html bordercolor-3-ref.html
 == bordercolor-4.html bordercolor-4-ref.html
 == empty-toprow.html empty-toprow-ref.html
 == double_borders.html double_borders_ref.html
 == border-collapse-rtl.html border-collapse-rtl-ref.html
 # Fuzzy because for some reason the corner beveling is antialiased differently.
 # So get 40 pixels of fuzz, 20 at each beveled corner (because the border width
 # is 20px).
-fuzzy(255,40) fails-if(webrender) == border-style-outset-becomes-groove.html border-style-outset-becomes-groove-ref.html # bug 1382896 for webrender
+fuzzy(255,40) == border-style-outset-becomes-groove.html border-style-outset-becomes-groove-ref.html
 # Fuzzy because for some reason the corner beveling is antialiased differently.
 # So get 40 pixels of fuzz, 20 at each beveled corner (because the border width
 # is 20px).
-fuzzy(255,40) fails-if(webrender) == border-style-inset-becomes-ridge.html border-style-inset-becomes-ridge-ref.html # bug 1382896 for webrender
+fuzzy(255,40) == border-style-inset-becomes-ridge.html border-style-inset-becomes-ridge-ref.html
 fuzzy(2,11000) == 1324524.html 1324524-ref.html
 == 1384602-1a.html 1384602-1-ref.html
 == 1384602-1b.html 1384602-1-ref.html
--- a/layout/reftests/writing-mode/tables/reftest.list
+++ b/layout/reftests/writing-mode/tables/reftest.list
@@ -3,18 +3,18 @@
 == vertical-table-2a.html vertical-table-2-ref.html
 fuzzy-if(skiaContent,3,750) == vertical-table-2b.html vertical-table-2-ref.html
 == vertical-table-rowspan-1.html vertical-table-rowspan-1-ref.html
 == vertical-table-rowspan-2.html vertical-table-rowspan-2-ref.html
 == vertical-table-colspan-1.html vertical-table-colspan-1-ref.html
 == vertical-table-colspan-2.html vertical-table-colspan-2-ref.html
 == vertical-table-specified-width-1.html vertical-table-specified-width-1-ref.html
 asserts(1) asserts-if(styloVsGecko,2) == vertical-table-specified-width-2.html vertical-table-specified-width-2-ref.html # bug 1179741
-fuzzy-if(cocoaWidget,141,24) == vertical-border-collapse-1.html vertical-border-collapse-1-ref.html
-fuzzy-if(cocoaWidget,141,24) == vertical-border-collapse-2.html vertical-border-collapse-2-ref.html
+fuzzy-if(cocoaWidget,141,24) fails-if(webrender) == vertical-border-collapse-1.html vertical-border-collapse-1-ref.html # bug 1393907 for webrender
+fuzzy-if(cocoaWidget,141,24) fails-if(webrender) == vertical-border-collapse-2.html vertical-border-collapse-2-ref.html # bug 1393907 for webrender
 
 == fixed-table-layout-002-vlr.html fixed-table-layout-002-ref.html
 == fixed-table-layout-003-vlr.html fixed-table-layout-002-ref.html
 == fixed-table-layout-004-vlr.html fixed-table-layout-004-ref.html
 == fixed-table-layout-005-vlr.html fixed-table-layout-005-ref.html
 == fixed-table-layout-006-vlr.html fixed-table-layout-006-ref.html
 == fixed-table-layout-007-vlr.html fixed-table-layout-007-ref.html
 == fixed-table-layout-009-vlr.html fixed-table-layout-009-ref.html
@@ -75,19 +75,19 @@ fuzzy-if(winWidget,48,600) fuzzy-if(coco
 fuzzy-if(winWidget,48,600) fuzzy-if(cocoaWidget,19,97) HTTP(../..) == wm-row-progression-007.xht multicol-count-002-ref.xht
 
 fuzzy-if(Android,255,38) == table-caption-top-1.html table-caption-top-1-ref.html
 fuzzy-if(Android,255,38) == table-caption-bottom-1.html table-caption-bottom-1-ref.html
 fuzzy-if(Android,244,27) == table-caption-left-1.html table-caption-left-1-ref.html
 fuzzy-if(Android,244,27) == table-caption-right-1.html table-caption-right-1-ref.html
 
 == border-collapse-bevels-1a.html border-collapse-bevels-1-ref.html
-fuzzy-if(cocoaWidget,23,162) == border-collapse-bevels-1b.html border-collapse-bevels-1-ref.html
-fuzzy-if(cocoaWidget,23,162) == border-collapse-bevels-1c.html border-collapse-bevels-1-ref.html
-fuzzy-if(cocoaWidget,23,162) == border-collapse-bevels-1d.html border-collapse-bevels-1-ref.html
-fuzzy-if(cocoaWidget,23,162) == border-collapse-bevels-1e.html border-collapse-bevels-1-ref.html
+fuzzy-if(cocoaWidget,23,162) fails-if(webrender) == border-collapse-bevels-1b.html border-collapse-bevels-1-ref.html # bug 1393907 for webrender
+fuzzy-if(cocoaWidget,23,162) fails-if(webrender) == border-collapse-bevels-1c.html border-collapse-bevels-1-ref.html # bug 1393907 for webrender
+fuzzy-if(cocoaWidget,23,162) fails-if(webrender) == border-collapse-bevels-1d.html border-collapse-bevels-1-ref.html # bug 1393907 for webrender
+fuzzy-if(cocoaWidget,23,162) fails-if(webrender) == border-collapse-bevels-1e.html border-collapse-bevels-1-ref.html # bug 1393907 for webrender
 
 == vertical-rl-row-progression-1a.html vertical-rl-row-progression-1-ref.html
 == vertical-rl-row-progression-1b.html vertical-rl-row-progression-1-ref.html
 == sideways-lr-row-progression-1a.html sideways-lr-row-progression-1-ref.html
 == sideways-lr-row-progression-1b.html sideways-lr-row-progression-1-ref.html
 == sideways-rl-row-progression-1a.html sideways-rl-row-progression-1-ref.html
 == sideways-rl-row-progression-1b.html sideways-rl-row-progression-1-ref.html
--- a/layout/style/jar.mn
+++ b/layout/style/jar.mn
@@ -2,17 +2,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/.
 
 toolkit.jar:
 *  res/ua.css                                (res/ua.css)
 *  res/html.css                              (res/html.css)
    res/quirk.css                             (res/quirk.css)
    res/plaintext.css                         (res/plaintext.css)
-   res/viewsource.css                        (res/viewsource.css)
    res/counterstyles.css                     (res/counterstyles.css)
    res/noscript.css                          (res/noscript.css)
    res/noframes.css                          (res/noframes.css)
 *  res/forms.css                             (res/forms.css)
    res/number-control.css                    (res/number-control.css)
    res/arrow.gif                             (res/arrow.gif)
    res/arrow-left.gif                        (res/arrow-left.gif)
    res/arrow-right.gif                       (res/arrow-right.gif)
@@ -28,8 +27,9 @@ toolkit.jar:
    res/accessiblecaret-tilt-left@2x.png      (res/accessiblecaret-tilt-left@2x.png)
    res/accessiblecaret-tilt-left@2.25x.png   (res/accessiblecaret-tilt-left@2.25x.png)
    res/accessiblecaret-tilt-right@1x.png     (res/accessiblecaret-tilt-right@1x.png)
    res/accessiblecaret-tilt-right@1.5x.png   (res/accessiblecaret-tilt-right@1.5x.png)
    res/accessiblecaret-tilt-right@2x.png     (res/accessiblecaret-tilt-right@2x.png)
    res/accessiblecaret-tilt-right@2.25x.png  (res/accessiblecaret-tilt-right@2.25x.png)
 
 % resource gre-resources %res/
+% resource content-accessible resource://gre/contentaccessible/ contentaccessible=yes
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -295,17 +295,21 @@ LOCAL_INCLUDES += [
     '/image',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 RESOURCE_FILES += [
     'contenteditable.css',
     'designmode.css',
+]
+
+CONTENT_ACCESSIBLE_FILES += [
     'ImageDocument.css',
+    'res/viewsource.css',
     'TopLevelImageDocument.css',
     'TopLevelVideoDocument.css',
 ]
 
 GENERATED_FILES += [
     'nsStyleStructList.h',
 ]
 
--- a/layout/tables/nsTableFrame.cpp
+++ b/layout/tables/nsTableFrame.cpp
@@ -7452,17 +7452,16 @@ BCBlockDirSeg::CreateWebRenderCommands(B
   wr::BorderRadius borderRadii = wr::EmptyBorderRadius();
 
   // All border style is set to none except left side. So setting the widths of
   // each side to width of rect is fine.
   wr::BorderWidths borderWidths = wr::ToBorderWidths(transformedRect.size.width,
                                                      transformedRect.size.width,
                                                      transformedRect.size.width,
                                                      transformedRect.size.width);
-  transformedRect.size.width *= 2.0f;
   Range<const wr::BorderSide> wrsides(wrSide, 4);
   aBuilder.PushBorder(transformedRect,
                       transformedRect,
                       borderWidths,
                       wrsides,
                       borderRadii);
 }
 
@@ -7710,17 +7709,16 @@ BCInlineDirSeg::CreateWebRenderCommands(
   wr::BorderRadius borderRadii = wr::EmptyBorderRadius();
 
   // All border style is set to none except top side. So setting the widths of
   // each side to height of rect is fine.
   wr::BorderWidths borderWidths = wr::ToBorderWidths(transformedRect.size.height,
                                                      transformedRect.size.height,
                                                      transformedRect.size.height,
                                                      transformedRect.size.height);
-  transformedRect.size.height *= 2.0f;
   Range<const wr::BorderSide> wrsides(wrSide, 4);
   aBuilder.PushBorder(transformedRect,
                       transformedRect,
                       borderWidths,
                       wrsides,
                       borderRadii);
 }
 
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/CleanupAction.java
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/CleanupAction.java
@@ -1,16 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.dlc;
 
 import android.content.Context;
+import android.support.annotation.VisibleForTesting;
 
 import org.mozilla.gecko.dlc.catalog.DownloadContent;
 import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
 
 import java.io.File;
 
 /**
  * CleanupAction: Remove content that is no longer needed.
@@ -19,31 +20,35 @@ public class CleanupAction extends BaseA
     @Override
     public void perform(Context context, DownloadContentCatalog catalog) {
         for (DownloadContent content : catalog.getContentToDelete()) {
             if (!content.isAssetArchive()) {
                 continue; // We do not know how to clean up this content. But this means we didn't
                           // download it anyways.
             }
 
-            try {
-                File file = getDestinationFile(context, content);
+            cleanupContent(context, catalog, content);
+        }
+    }
 
-                if (!file.exists()) {
-                    // File does not exist. As good as deleting.
-                    catalog.remove(content);
-                    return;
-                }
+    @VisibleForTesting void cleanupContent(Context context, DownloadContentCatalog catalog, DownloadContent content) {
+        try {
+            final File file = getDestinationFile(context, content);
 
-                if (file.delete()) {
-                    // File has been deleted. Now remove it from the catalog.
-                    catalog.remove(content);
-                }
-            } catch (UnrecoverableDownloadContentException e) {
-                // We can't recover. Pretend the content is removed. It probably never existed in
-                // the first place.
+            if (!file.exists()) {
+                // File does not exist. As good as deleting.
                 catalog.remove(content);
-            } catch (RecoverableDownloadContentException e) {
-                // Try again next time.
+                return;
+            }
+
+            if (file.delete()) {
+                // File has been deleted. Now remove it from the catalog.
+                catalog.remove(content);
             }
+        } catch (UnrecoverableDownloadContentException e) {
+            // We can't recover. Pretend the content is removed. It probably never existed in
+            // the first place.
+            catalog.remove(content);
+        } catch (RecoverableDownloadContentException e) {
+            // Try again next time.
         }
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentService.java
@@ -128,16 +128,20 @@ public class DownloadContentService exte
             case ACTION_VERIFY_CONTENT:
                 action = new VerifyAction();
                 break;
 
             case ACTION_SYNCHRONIZE_CATALOG:
                 action = new SyncAction();
                 break;
 
+            case ACTION_CLEANUP_FILES:
+                action = new CleanupAction();
+                break;
+
             default:
                 Log.e(LOGTAG, "Unknown action: " + intent.getAction());
                 return;
         }
 
         action.perform(this, catalog);
         catalog.persistChanges();
     }
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java
@@ -82,17 +82,17 @@ public class IconGenerator implements Ic
         paint.setTextSize(textSize);
         paint.setAntiAlias(true);
 
         canvas.drawText(character,
                 canvas.getWidth() / 2,
                 (int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2)),
                 paint);
 
-        return IconResponse.createGenerated(favicon, color & 0x7FFFFFFF);
+        return IconResponse.createGenerated(favicon, color);
     }
 
     /**
      * Get a representative character for the given URL.
      *
      * For example this method will return "f" for "http://m.facebook.com/foobar".
      */
     @VisibleForTesting static String getRepresentativeCharacter(String url) {
--- a/mobile/android/base/java/org/mozilla/gecko/icons/processing/ColorProcessor.java
+++ b/mobile/android/base/java/org/mozilla/gecko/icons/processing/ColorProcessor.java
@@ -1,15 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.icons.processing;
 
+import android.graphics.Bitmap;
+import android.support.annotation.ColorInt;
 import android.support.v7.graphics.Palette;
 import android.util.Log;
 
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.icons.IconRequest;
 import org.mozilla.gecko.icons.IconResponse;
 import org.mozilla.gecko.util.HardwareUtils;
 
@@ -22,16 +24,24 @@ public class ColorProcessor implements P
     private static final int DEFAULT_COLOR = 0; // 0 == No color
 
     @Override
     public void process(IconRequest request, IconResponse response) {
         if (response.hasColor()) {
             return;
         }
 
+        final Bitmap bitmap = response.getBitmap();
+
+        final @ColorInt Integer edgeColor = getEdgeColor(bitmap);
+        if (edgeColor != null) {
+            response.updateColor(edgeColor);
+            return;
+        }
+
         if (HardwareUtils.isX86System()) {
             // (Bug 1318667) We are running into crashes when using the palette library with
             // specific icons on x86 devices. They take down the whole VM and are not recoverable.
             // Unfortunately our release icon is triggering this crash. Until we can switch to a
             // newer version of the support library where this does not happen, we are using our
             // own slower implementation.
             extractColorUsingCustomImplementation(response);
         } else {
@@ -53,9 +63,71 @@ public class ColorProcessor implements P
         }
     }
 
     private void extractColorUsingCustomImplementation(final IconResponse response) {
         final int dominantColor = BitmapUtils.getDominantColor(response.getBitmap());
 
         response.updateColor(dominantColor);
     }
+
+    /**
+     * If a bitmap has a consistent edge colour (i.e. if all the border pixels have the same colour),
+     * return that colour.
+     * @param bitmap
+     * @return The edge colour. null if there is no consistent edge color.
+     */
+    private @ColorInt Integer getEdgeColor(final Bitmap bitmap) {
+        final int width = bitmap.getWidth();
+        final int height = bitmap.getHeight();
+
+        // Only allocate an array once, with the max width we need once, to minimise the number
+        // of allocations.
+        @ColorInt int[] edge = new int[Math.max(width, height)];
+
+        // Top:
+        bitmap.getPixels(edge, 0, width, 0, 0, width, 1);
+        final @ColorInt Integer edgecolor = getEdgeColorFromSingleDimension(edge, width);
+        if (edgecolor == null) {
+            return null;
+        }
+
+        // Bottom:
+        bitmap.getPixels(edge, 0, width, 0, height - 1, width, 1);
+        if (!edgecolor.equals(getEdgeColorFromSingleDimension(edge, width))) {
+            return null;
+        }
+
+        // Left:
+        bitmap.getPixels(edge, 0, 1, 0, 0, 1, height);
+        if (!edgecolor.equals(getEdgeColorFromSingleDimension(edge, height))) {
+            return null;
+        }
+
+        // Right:
+        bitmap.getPixels(edge, 0, 1, width - 1, 0, 1, height);
+        if (!edgecolor.equals(getEdgeColorFromSingleDimension(edge, height))) {
+            return null;
+        }
+
+        return edgecolor;
+    }
+
+    /**
+     * Obtain the colour for a given edge if all colors are the same.
+     *
+     * @param edge An array containing the color values of the pixels constituting the edge of a bitmap.
+     * @param length The length of the array to be traversed. Must be smaller than, or equal to
+     * the total length of the array.
+     * @return The colour contained within the array, or null if colours vary.
+     */
+    private @ColorInt Integer getEdgeColorFromSingleDimension(@ColorInt int[] edge, int length) {
+        @ColorInt int color = edge[0];
+
+        for (int i = 1; i < length; ++i) {
+            if (edge[i] != color) {
+                return null;
+            }
+        }
+
+        return color;
+    }
 }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -609,16 +609,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'distribution/PartnerBookmarksProviderProxy.java',
     'distribution/PartnerBrowserCustomizationsClient.java',
     'distribution/ReferrerDescriptor.java',
     'distribution/ReferrerReceiver.java',
     'dlc/BaseAction.java',
     'dlc/catalog/DownloadContent.java',
     'dlc/catalog/DownloadContentBuilder.java',
     'dlc/catalog/DownloadContentCatalog.java',
+    'dlc/CleanupAction.java',
     'dlc/DownloadAction.java',
     'dlc/DownloadContentService.java',
     'dlc/DownloadContentTelemetry.java',
     'dlc/StudyAction.java',
     'dlc/SyncAction.java',
     'dlc/VerifyAction.java',
     'DoorHangerPopup.java',
     'DownloadsIntegration.java',
--- a/mobile/android/components/extensions/ext-browsingData.js
+++ b/mobile/android/components/extensions/ext-browsingData.js
@@ -89,12 +89,15 @@ this.browsingData = class extends Extens
           return Promise.resolve({options: {since: 0}, dataToRemove, dataRemovalPermitted});
         },
         removeCookies(options) {
           return clearCookies(options);
         },
         removeCache(options) {
           return Sanitizer.clearItem("cache");
         },
+        removeDownloads(options) {
+          return Sanitizer.clearItem("downloadHistory", options.since);
+        },
       },
     };
   }
 };
--- a/mobile/android/components/extensions/schemas/browsing_data.json
+++ b/mobile/android/components/extensions/schemas/browsing_data.json
@@ -240,17 +240,16 @@
           }
         ]
       },
       {
         "name": "removeDownloads",
         "description": "Clears the browser's list of downloaded files (<em>not</em> the downloaded files themselves).",
         "type": "function",
         "async": "callback",
-        "unsupported": true,
         "parameters": [
           {
             "$ref": "RemovalOptions",
             "name": "options"
           },
           {
             "name": "callback",
             "type": "function",
--- a/mobile/android/components/extensions/test/mochitest/chrome.ini
+++ b/mobile/android/components/extensions/test/mochitest/chrome.ini
@@ -2,13 +2,14 @@
 support-files =
   head.js
   ../../../../../../toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js
 tags = webextensions
 
 [test_ext_browserAction_getTitle_setTitle.html]
 [test_ext_browserAction_onClicked.html]
 [test_ext_browsingData_cookies_cache.html]
+[test_ext_browsingData_downloads.html]
 [test_ext_browsingData_settings.html]
 [test_ext_options_ui.html]
 [test_ext_pageAction_show_hide.html]
 [test_ext_pageAction_getPopup_setPopup.html]
 skip-if = os == 'android' # bug 1373170
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_browsingData_downloads.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>BrowsingData Settings test</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+var {Downloads} = Cu.import("resource://gre/modules/Downloads.jsm", {});
+
+const OLD_NAMES = {[Downloads.PUBLIC]: "old-public", [Downloads.PRIVATE]: "old-private"};
+const RECENT_NAMES = {[Downloads.PUBLIC]: "recent-public", [Downloads.PRIVATE]: "recent-private"};
+const REFERENCE_DATE = new Date();
+const OLD_DATE = new Date(Number(REFERENCE_DATE) - 10000);
+
+async function downloadExists(list, path) {
+  let listArray = await list.getAll();
+  return listArray.some(i => i.target.path == path);
+}
+
+async function checkDownloads(expectOldExists = true, expectRecentExists = true) {
+  for (let listType of [Downloads.PUBLIC, Downloads.PRIVATE]) {
+    let downloadsList = await Downloads.getList(listType);
+    is(
+      (await downloadExists(downloadsList, OLD_NAMES[listType])),
+      expectOldExists,
+      `Fake old download ${(expectOldExists) ? "was found" : "was removed"}.`);
+    is(
+      (await downloadExists(downloadsList, RECENT_NAMES[listType])),
+      expectRecentExists,
+      `Fake recent download ${(expectRecentExists) ? "was found" : "was removed"}.`);
+  }
+}
+
+async function setupDownloads() {
+  let downloadsList = await Downloads.getList(Downloads.ALL);
+  await downloadsList.removeFinished();
+
+  for (let listType of [Downloads.PUBLIC, Downloads.PRIVATE]) {
+    downloadsList = await Downloads.getList(listType);
+    let download = await Downloads.createDownload({
+      source: {
+        url: "https://bugzilla.mozilla.org/show_bug.cgi?id=1363001",
+        isPrivate: listType == Downloads.PRIVATE},
+      target: OLD_NAMES[listType],
+    });
+    download.startTime = OLD_DATE;
+    download.canceled = true;
+    await downloadsList.add(download);
+
+    download = await Downloads.createDownload({
+      source: {
+        url: "https://bugzilla.mozilla.org/show_bug.cgi?id=1363001",
+        isPrivate: listType == Downloads.PRIVATE},
+      target: RECENT_NAMES[listType],
+    });
+    download.startTime = REFERENCE_DATE;
+    download.canceled = true;
+    await downloadsList.add(download);
+  }
+
+  // Confirm everything worked.
+  downloadsList = await Downloads.getList(Downloads.ALL);
+  is((await downloadsList.getAll()).length, 4, "4 fake downloads added.");
+  checkDownloads();
+}
+
+add_task(async function testDownloads() {
+  function background() {
+    browser.test.onMessage.addListener(async (msg, options) => {
+      await browser.browsingData.removeDownloads(options);
+      browser.test.sendMessage("downloadsRemoved");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      permissions: ["browsingData"],
+    },
+  });
+
+  async function testRemovalMethod(method) {
+    // Clear downloads with no since value.
+    await setupDownloads();
+    extension.sendMessage(method, {});
+    await extension.awaitMessage("downloadsRemoved");
+    await checkDownloads(false, false);
+
+    // Clear downloads with recent since value.
+    await setupDownloads();
+    extension.sendMessage(method, {since: REFERENCE_DATE});
+    await extension.awaitMessage("downloadsRemoved");
+    await checkDownloads(true, false);
+
+    // Clear downloads with old since value.
+    await setupDownloads();
+    extension.sendMessage(method, {since: REFERENCE_DATE - 100000});
+    await extension.awaitMessage("downloadsRemoved");
+    await checkDownloads(false, false);
+  }
+
+  await extension.startup();
+
+  await testRemovalMethod("removeDownloads");
+
+  await extension.unload();
+});
+
+</script>
+</body>
+</html>
--- a/mobile/android/config/mozconfigs/android-api-15-gradle-dependencies/nightly
+++ b/mobile/android/config/mozconfigs/android-api-15-gradle-dependencies/nightly
@@ -46,9 +46,9 @@ export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_POCKET=1
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
 
 # End ../android-api-15-frontend/nightly.
 
 # Populated after checking out the sources and before building the
 # tree as part of the dependencies task bin/ scripts.
-ac_add_options --with-android-sdk="/home/worker/.mozbuild/android-sdk-linux"
+ac_add_options --with-android-sdk="/builds/worker/.mozbuild/android-sdk-linux"
--- a/mobile/android/config/mozconfigs/public-partner/distribution_sample/mozconfig1
+++ b/mobile/android/config/mozconfigs/public-partner/distribution_sample/mozconfig1
@@ -4,17 +4,17 @@
 ac_add_options --enable-profiling
 
 # Android
 ac_add_options --with-android-min-sdk=16
 ac_add_options --target=arm-linux-androideabi
 
 ac_add_options --with-branding=mobile/android/branding/nightly
 
-ac_add_options --with-android-distribution-directory=/home/worker/workspace/build/partner
+ac_add_options --with-android-distribution-directory=/builds/worker/workspace/build/partner
 
 # This will overwrite the default of stripping everything and keep the symbol table.
 # This is useful for profiling with eideticker. See bug 788680
 STRIP_FLAGS="--strip-debug"
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -454,18 +454,16 @@
 @BINPATH@/greprefs.js
 @BINPATH@/defaults/autoconfig/prefcalls.js
 
 ; [Layout Engine Resources]
 ; Style Sheets, Graphics and other Resources used by the layout engine.
 @BINPATH@/res/EditorOverride.css
 @BINPATH@/res/contenteditable.css
 @BINPATH@/res/designmode.css
-@BINPATH@/res/TopLevelImageDocument.css
-@BINPATH@/res/TopLevelVideoDocument.css
 @BINPATH@/res/table-add-column-after-active.gif
 @BINPATH@/res/table-add-column-after-hover.gif
 @BINPATH@/res/table-add-column-after.gif
 @BINPATH@/res/table-add-column-before-active.gif
 @BINPATH@/res/table-add-column-before-hover.gif
 @BINPATH@/res/table-add-column-before.gif
 @BINPATH@/res/table-add-row-after-active.gif
 @BINPATH@/res/table-add-row-after-hover.gif
@@ -485,16 +483,19 @@
 @BINPATH@/res/language.properties
 
 #ifndef MOZ_ANDROID_EXCLUDE_FONTS
 @BINPATH@/res/fonts/*
 #else
 @BINPATH@/res/fonts/*.properties
 #endif
 
+; Content-accessible resources.
+@BINPATH@/contentaccessible/*
+
 ; svg
 @BINPATH@/res/svg.css
 @BINPATH@/components/dom_svg.xpt
 @BINPATH@/components/dom_smil.xpt
 
 ; [Personal Security Manager]
 ;
 @BINPATH@/components/pipnss.xpt
--- a/mobile/android/modules/Sanitizer.jsm
+++ b/mobile/android/modules/Sanitizer.jsm
@@ -28,26 +28,44 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 function dump(a) {
   Services.console.logStringMessage(a);
 }
 
 this.EXPORTED_SYMBOLS = ["Sanitizer"];
 
 function Sanitizer() {}
 Sanitizer.prototype = {
-  clearItem: function(aItemName) {
+  clearItem: function(aItemName, startTime) {
+    // Only a subset of items support deletion with startTime.
+    // Those who do not will be rejected with error message.
+    if (typeof startTime != "undefined") {
+      switch (aItemName) {
+        // Normal call to DownloadFiles remove actual data from storage, but our web-extension consumer
+        // deletes only download history. So, for this reason we are passing a flag 'deleteFiles'.
+        case "downloadHistory":
+          this._clear("downloadFiles", { startTime, deleteFiles: false });
+          break;
+        default:
+          return Promise.reject({message: `Invalid argument: ${aItemName} does not support startTime argument.`});
+      }
+    } else {
+      this._clear(aItemName);
+    }
+  },
+
+ _clear: function(aItemName, options) {
     let item = this.items[aItemName];
     let canClear = item.canClear;
     if (typeof canClear == "function") {
       canClear(function clearCallback(aCanClear) {
         if (aCanClear)
-          return item.clear();
+          return item.clear(options);
       });
     } else if (canClear) {
-      return item.clear();
+      return item.clear(options);
     }
   },
 
   items: {
     cache: {
       clear: function() {
         return new Promise(function(resolve, reject) {
           let refObj = {};
@@ -228,47 +246,51 @@ Sanitizer.prototype = {
           handleError: function(aError) { Cu.reportError(aError); },
           handleCompletion: function(aReason) { aCallback(aReason == 0 && count > 0); }
         };
         FormHistory.count({}, countDone);
       }
     },
 
     downloadFiles: {
-      clear: Task.async(function* () {
+      clear: Task.async(function* ({ startTime = 0, deleteFiles = true} = {}) {
         let refObj = {};
         TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj);
 
         let list = yield Downloads.getList(Downloads.ALL);
         let downloads = yield list.getAll();
         var finalizePromises = [];
 
         // Logic copied from DownloadList.removeFinished. Ideally, we would
         // just use that method directly, but we want to be able to remove the
         // downloaded files as well.
         for (let download of downloads) {
           // Remove downloads that have been canceled, even if the cancellation
           // operation hasn't completed yet so we don't check "stopped" here.
-          // Failed downloads with partial data are also removed.
-          if (download.stopped && (!download.hasPartialData || download.error)) {
+          // Failed downloads with partial data are also removed. The startTime
+          // check is provided for addons that may want to delete only recent downloads.
+          if (download.stopped && (!download.hasPartialData || download.error) &&
+              download.startTime.getTime() >= startTime) {
             // Remove the download first, so that the views don't get the change
             // notifications that may occur during finalization.
             yield list.remove(download);
             // Ensure that the download is stopped and no partial data is kept.
             // This works even if the download state has changed meanwhile.  We
             // don't need to wait for the procedure to be complete before
             // processing the other downloads in the list.
             finalizePromises.push(download.finalize(true).then(() => null, Cu.reportError));
 
-            // Delete the downloaded files themselves.
-            OS.File.remove(download.target.path).then(() => null, ex => {
-              if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
-                Cu.reportError(ex);
-              }
-            });
+            if (deleteFiles) {
+              // Delete the downloaded files themselves.
+              OS.File.remove(download.target.path).then(() => null, ex => {
+                if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
+                  Cu.reportError(ex);
+                }
+              });
+            }
           }
         }
 
         yield Promise.all(finalizePromises);
         yield DownloadIntegration.forceSave();
         TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS", refObj);
       }),
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/dlc/TestCleanupAction.java
@@ -0,0 +1,134 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.dlc;
+
+import android.content.Context;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.dlc.catalog.DownloadContent;
+import org.mozilla.gecko.dlc.catalog.DownloadContentBuilder;
+import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.io.File;
+
+import dalvik.annotation.TestTarget;
+import edu.emory.mathcs.backport.java.util.Collections;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+@RunWith(RobolectricTestRunner.class)
+public class TestCleanupAction {
+    @Test
+    public void testEmptyCatalog() {
+        final DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
+        doReturn(Collections.emptyList()).when(catalog).getContentToDelete();
+
+        final CleanupAction action = spy(new CleanupAction());
+        action.perform(RuntimeEnvironment.application, catalog);
+
+        verify(catalog).getContentToDelete();
+        verify(action, never()).cleanupContent(any(Context.class), any(DownloadContentCatalog.class), any(DownloadContent.class));
+    }
+
+    @Test
+    public void testWithUnknownType() {
+        final DownloadContent content = new DownloadContentBuilder()
+                .setType("RandomType")
+                .build();
+
+        final DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
+        doReturn(Collections.singletonList(content)).when(catalog).getContentToDelete();
+
+        final CleanupAction action = spy(new CleanupAction());
+        action.perform(RuntimeEnvironment.application, catalog);
+
+        verify(catalog).getContentToDelete();
+        verify(action, never()).cleanupContent(any(Context.class), any(DownloadContentCatalog.class), any(DownloadContent.class));
+    }
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    @Test
+    public void testWithContentThatDoesNotExistAnymore() throws Exception {
+        final DownloadContent content = new DownloadContentBuilder()
+                .setType(DownloadContent.TYPE_ASSET_ARCHIVE)
+                .build();
+
+        final DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
+        doReturn(Collections.singletonList(content)).when(catalog).getContentToDelete();
+
+        final File file = mock(File.class);
+        doReturn(false).when(file).exists();
+
+        final CleanupAction action = spy(new CleanupAction());
+        doReturn(file).when(action).getDestinationFile(RuntimeEnvironment.application, content);
+
+        action.perform(RuntimeEnvironment.application, catalog);
+
+        verify(catalog).getContentToDelete();
+        verify(action).cleanupContent(RuntimeEnvironment.application, catalog, content);
+        verify(file).exists();
+        verify(file, never()).delete();
+        verify(catalog).remove(content);
+    }
+
+    @Test
+    public void testWithDeletableContent() throws Exception {
+        final DownloadContent content = new DownloadContentBuilder()
+                .setType(DownloadContent.TYPE_ASSET_ARCHIVE)
+                .build();
+
+        final DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
+        doReturn(Collections.singletonList(content)).when(catalog).getContentToDelete();
+
+        final File file = mock(File.class);
+        doReturn(true).when(file).exists();
+        doReturn(true).when(file).delete();
+
+        final CleanupAction action = spy(new CleanupAction());
+        doReturn(file).when(action).getDestinationFile(RuntimeEnvironment.application, content);
+
+        action.perform(RuntimeEnvironment.application, catalog);
+
+        verify(catalog).getContentToDelete();
+        verify(action).cleanupContent(RuntimeEnvironment.application, catalog, content);
+        verify(file).exists();
+        verify(file).delete();
+        verify(catalog).remove(content);
+    }
+
+    @Test
+    public void testWithFileThatCannotBeDeleted() throws Exception {
+        final DownloadContent content = new DownloadContentBuilder()
+                .setType(DownloadContent.TYPE_ASSET_ARCHIVE)
+                .build();
+
+        final DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
+        doReturn(Collections.singletonList(content)).when(catalog).getContentToDelete();
+
+        final File file = mock(File.class);
+        doReturn(true).when(file).exists();
+        doReturn(false).when(file).delete();
+
+        final CleanupAction action = spy(new CleanupAction());
+        doReturn(file).when(action).getDestinationFile(RuntimeEnvironment.application, content);
+
+        action.perform(RuntimeEnvironment.application, catalog);
+
+        verify(catalog).getContentToDelete();
+        verify(action).cleanupContent(RuntimeEnvironment.application, catalog, content);
+        verify(file).exists();
+        verify(file).delete();
+        verify(catalog, never()).remove(content);
+    }
+}
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/processing/TestColorProcessor.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/processing/TestColorProcessor.java
@@ -28,17 +28,17 @@ public class TestColorProcessor {
 
         Assert.assertFalse(response.hasColor());
         Assert.assertEquals(0, response.getColor());
 
         final Processor processor = new ColorProcessor();
         processor.process(null, response);
 
         Assert.assertTrue(response.hasColor());
-        Assert.assertEquals(Color.RED & 0x7FFFFFFF, response.getColor());
+        Assert.assertEquals(Color.RED, response.getColor());
     }
 
     private Bitmap createRedBitmapMock() {
         final Bitmap bitmap = mock(Bitmap.class);
 
         doReturn(1).when(bitmap).getWidth();
         doReturn(1).when(bitmap).getHeight();
 
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -112,31 +112,32 @@ SubstitutingProtocolHandler::ConstructIn
 //
 // IPC marshalling.
 //
 
 nsresult
 SubstitutingProtocolHandler::CollectSubstitutions(InfallibleTArray<SubstitutionMapping>& aMappings)
 {
   for (auto iter = mSubstitutions.ConstIter(); !iter.Done(); iter.Next()) {
-    nsCOMPtr<nsIURI> uri = iter.Data();
+    SubstitutionEntry& entry = iter.Data();
+    nsCOMPtr<nsIURI> uri = entry.baseURI;
     SerializedURI serialized;
     if (uri) {
       nsresult rv = uri->GetSpec(serialized.spec);
       NS_ENSURE_SUCCESS(rv, rv);
     }
-    SubstitutionMapping substitution = { mScheme, nsCString(iter.Key()), serialized };
+    SubstitutionMapping substitution = { mScheme, nsCString(iter.Key()), serialized, entry.flags };
     aMappings.AppendElement(substitution);
   }
 
   return NS_OK;
 }
 
 nsresult
-SubstitutingProtocolHandler::SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI)
+SubstitutingProtocolHandler::SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI, uint32_t aFlags)
 {
   if (GeckoProcessType_Content == XRE_GetProcessType()) {
     return NS_OK;
   }
 
   nsTArray<ContentParent*> parents;
   ContentParent::GetAll(parents);
   if (!parents.Length()) {
@@ -145,16 +146,17 @@ SubstitutingProtocolHandler::SendSubstit
 
   SubstitutionMapping mapping;
   mapping.scheme = mScheme;
   mapping.path = aRoot;
   if (aBaseURI) {
     nsresult rv = aBaseURI->GetSpec(mapping.resolvedURI.spec);
     NS_ENSURE_SUCCESS(rv, rv);
   }
+  mapping.flags = aFlags;
 
   for (uint32_t i = 0; i < parents.Length(); i++) {
     Unused << parents[i]->SendRegisterChromeItem(mapping);
   }
 
   return NS_OK;
 }
 
@@ -288,61 +290,92 @@ SubstitutingProtocolHandler::AllowPort(i
 
 //----------------------------------------------------------------------------
 // nsISubstitutingProtocolHandler
 //----------------------------------------------------------------------------
 
 nsresult
 SubstitutingProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI)
 {
+  // Add-ons use this API but they should not be able to make anything
+  // content-accessible.
+  return SetSubstitutionWithFlags(root, baseURI, 0);
+}
+
+nsresult
+SubstitutingProtocolHandler::SetSubstitutionWithFlags(const nsACString& root, nsIURI *baseURI, uint32_t flags)
+{
   if (!baseURI) {
     mSubstitutions.Remove(root);
     NotifyObservers(root, baseURI);
-    return SendSubstitution(root, baseURI);
+    return SendSubstitution(root, baseURI, flags);
   }
 
   // If baseURI isn't a same-scheme URI, we can set the substitution immediately.
   nsAutoCString scheme;
   nsresult rv = baseURI->GetScheme(scheme);
   NS_ENSURE_SUCCESS(rv, rv);
   if (!scheme.Equals(mScheme)) {
     if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")
         && !scheme.EqualsLiteral("app")) {
       NS_WARNING("Refusing to create substituting URI to non-file:// target");
       return NS_ERROR_INVALID_ARG;
     }
 
-    mSubstitutions.Put(root, baseURI);
+    SubstitutionEntry& entry = mSubstitutions.GetOrInsert(root);
+    entry.baseURI = baseURI;
+    entry.flags = flags;
     NotifyObservers(root, baseURI);
-    return SendSubstitution(root, baseURI);
+    return SendSubstitution(root, baseURI, flags);
   }
 
   // baseURI is a same-type substituting URI, let's resolve it first.
   nsAutoCString newBase;
   rv = ResolveURI(baseURI, newBase);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIURI> newBaseURI;
   rv = mIOService->NewURI(newBase, nullptr, nullptr, getter_AddRefs(newBaseURI));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mSubstitutions.Put(root, newBaseURI);
+  SubstitutionEntry& entry = mSubstitutions.GetOrInsert(root);
+  entry.baseURI = newBaseURI;
+  entry.flags = flags;
   NotifyObservers(root, baseURI);
-  return SendSubstitution(root, newBaseURI);
+  return SendSubstitution(root, newBaseURI, flags);
 }
 
 nsresult
 SubstitutingProtocolHandler::GetSubstitution(const nsACString& root, nsIURI **result)
 {
   NS_ENSURE_ARG_POINTER(result);
 
-  if (mSubstitutions.Get(root, result))
+  SubstitutionEntry entry;
+  if (mSubstitutions.Get(root, &entry)) {
+    nsCOMPtr<nsIURI> baseURI = entry.baseURI;
+    baseURI.forget(result);
     return NS_OK;
+  }
 
-  return GetSubstitutionInternal(root, result);
+  uint32_t flags;
+  return GetSubstitutionInternal(root, result, &flags);
+}
+
+nsresult
+SubstitutingProtocolHandler::GetSubstitutionFlags(const nsACString& root, uint32_t* flags)
+{
+  *flags = 0;
+  SubstitutionEntry entry;
+  if (mSubstitutions.Get(root, &entry)) {
+    *flags = entry.flags;
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIURI> baseURI;
+  return GetSubstitutionInternal(root, getter_AddRefs(baseURI), flags);
 }
 
 nsresult
 SubstitutingProtocolHandler::HasSubstitution(const nsACString& root, bool *result)
 {
   NS_ENSURE_ARG_POINTER(result);
   *result = HasSubstitution(root);
   return NS_OK;
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.h
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h
@@ -4,19 +4,19 @@
  * 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 SubstitutingProtocolHandler_h___
 #define SubstitutingProtocolHandler_h___
 
 #include "nsISubstitutingProtocolHandler.h"
 
-#include "nsInterfaceHashtable.h"
 #include "nsIOService.h"
 #include "nsISubstitutionObserver.h"
+#include "nsDataHashtable.h"
 #include "nsStandardURL.h"
 #include "mozilla/chrome/RegistryMessageUtils.h"
 #include "mozilla/Maybe.h"
 
 class nsIIOService;
 
 namespace mozilla {
 namespace net {
@@ -39,23 +39,26 @@ public:
   bool HasSubstitution(const nsACString& aRoot) const { return mSubstitutions.Get(aRoot, nullptr); }
 
   MOZ_MUST_USE nsresult CollectSubstitutions(InfallibleTArray<SubstitutionMapping>& aResources);
 
 protected:
   virtual ~SubstitutingProtocolHandler() {}
   void ConstructInternal();
 
-  MOZ_MUST_USE nsresult SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI);
+  MOZ_MUST_USE nsresult SendSubstitution(const nsACString& aRoot, nsIURI* aBaseURI, uint32_t aFlags);
+
+  nsresult GetSubstitutionFlags(const nsACString& root, uint32_t* flags);
 
   // Override this in the subclass to try additional lookups after checking
   // mSubstitutions.
-  virtual MOZ_MUST_USE nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult)
+  virtual MOZ_MUST_USE nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult, uint32_t* aFlags)
   {
     *aResult = nullptr;
+    *aFlags = 0;
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Override this in the subclass to check for special case when resolving URIs
   // _before_ checking substitutions.
   virtual MOZ_MUST_USE bool ResolveSpecialCases(const nsACString& aHost,
                                                 const nsACString& aPath,
                                                 const nsACString& aPathname,
@@ -69,23 +72,38 @@ protected:
   virtual MOZ_MUST_USE nsresult SubstituteChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result)
   {
     return NS_OK;
   }
 
   nsIIOService* IOService() { return mIOService; }
 
 private:
+  struct SubstitutionEntry
+  {
+    SubstitutionEntry()
+        : flags(0)
+    {
+    }
+
+    ~SubstitutionEntry()
+    {
+    }
+
+    nsCOMPtr<nsIURI> baseURI;
+    uint32_t flags;
+  };
+
   // Notifies all observers that a new substitution from |aRoot| to
   // |aBaseURI| has been set/installed for this protocol handler.
   void NotifyObservers(const nsACString& aRoot, nsIURI* aBaseURI);
 
   nsCString mScheme;
   Maybe<uint32_t> mFlags;
-  nsInterfaceHashtable<nsCStringHashKey,nsIURI> mSubstitutions;
+  nsDataHashtable<nsCStringHashKey, SubstitutionEntry> mSubstitutions;
   nsCOMPtr<nsIIOService> mIOService;
 
   // The list of observers added with AddObserver that will be
   // notified when substitutions are set or unset.
   nsTArray<nsCOMPtr<nsISubstitutionObserver>> mObservers;
 
   // In general, we expect the principal of a document loaded from a
   // substituting URI to be a codebase principal for that URI (rather than
--- a/netwerk/protocol/res/nsIResProtocolHandler.idl
+++ b/netwerk/protocol/res/nsIResProtocolHandler.idl
@@ -6,9 +6,10 @@
 #include "nsISubstitutingProtocolHandler.idl"
 
 /**
  * Protocol handler interface for the resource:// protocol
  */
 [scriptable, uuid(241d34ac-9ed5-46d7-910c-7a9d914aa0c5)]
 interface nsIResProtocolHandler : nsISubstitutingProtocolHandler
 {
+  boolean allowContentToAccess(in nsIURI url);
 };
--- a/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
+++ b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
@@ -10,27 +10,37 @@ interface nsISubstitutionObserver;
 /**
  * Protocol handler superinterface for a protocol which performs substitutions
  * from URIs of its scheme to URIs of another scheme.
  */
 [scriptable, uuid(154c64fd-a69e-4105-89f8-bd7dfe621372)]
 interface nsISubstitutingProtocolHandler : nsIProtocolHandler
 {
   /**
+   * Content script may access files in this package.
+   */
+  const short ALLOW_CONTENT_ACCESS = 1;
+
+  /**
    * Sets the substitution for the root key:
    *   resource://root/path ==> baseURI.resolve(path)
    *
    * A null baseURI removes the specified substitution.
    *
    * A root key should always be lowercase; however, this may not be
    * enforced.
    */
   [must_use] void setSubstitution(in ACString root, in nsIURI baseURI);
 
   /**
+   * Same as setSubstitution, but with specific flags.
+   */
+  [must_use] void setSubstitutionWithFlags(in ACString root, in nsIURI baseURI, in uint32_t flags);
+
+  /**
    * Gets the substitution for the root key.
    *
    * @throws NS_ERROR_NOT_AVAILABLE if none exists.
    */
   [must_use] nsIURI getSubstitution(in ACString root);
 
   /**
    * Returns TRUE if the substitution exists and FALSE otherwise.
--- a/netwerk/protocol/res/nsResProtocolHandler.cpp
+++ b/netwerk/protocol/res/nsResProtocolHandler.cpp
@@ -56,26 +56,46 @@ nsResProtocolHandler::Init()
 //----------------------------------------------------------------------------
 
 NS_IMPL_QUERY_INTERFACE(nsResProtocolHandler, nsIResProtocolHandler,
                         nsISubstitutingProtocolHandler, nsIProtocolHandler,
                         nsISupportsWeakReference)
 NS_IMPL_ADDREF_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
 NS_IMPL_RELEASE_INHERITED(nsResProtocolHandler, SubstitutingProtocolHandler)
 
+NS_IMETHODIMP
+nsResProtocolHandler::AllowContentToAccess(nsIURI *aURI, bool *aResult)
+{
+    *aResult = false;
+
+    nsAutoCString host;
+    nsresult rv = aURI->GetAsciiHost(host);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    uint32_t flags;
+    rv = GetSubstitutionFlags(host, &flags);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    *aResult = flags & nsISubstitutingProtocolHandler::ALLOW_CONTENT_ACCESS;
+    return NS_OK;
+}
+
 nsresult
-nsResProtocolHandler::GetSubstitutionInternal(const nsACString& root, nsIURI **result)
+nsResProtocolHandler::GetSubstitutionInternal(const nsACString& aRoot,
+                                              nsIURI** aResult,
+                                              uint32_t* aFlags)
 {
     nsAutoCString uri;
 
-    if (!ResolveSpecialCases(root, NS_LITERAL_CSTRING("/"), NS_LITERAL_CSTRING("/"), uri)) {
+    if (!ResolveSpecialCases(aRoot, NS_LITERAL_CSTRING("/"), NS_LITERAL_CSTRING("/"), uri)) {
         return NS_ERROR_NOT_AVAILABLE;
     }
 
-    return NS_NewURI(result, uri);
+    *aFlags = 0; // No content access.
+    return NS_NewURI(aResult, uri);
 }
 
 bool
 nsResProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
                                           const nsACString& aPath,
                                           const nsACString& aPathname,
                                           nsACString& aResult)
 {
@@ -93,8 +113,19 @@ nsResProtocolHandler::ResolveSpecialCase
 nsresult
 nsResProtocolHandler::SetSubstitution(const nsACString& aRoot, nsIURI* aBaseURI)
 {
     MOZ_ASSERT(!aRoot.Equals(""));
     MOZ_ASSERT(!aRoot.Equals(kAPP));
     MOZ_ASSERT(!aRoot.Equals(kGRE));
     return SubstitutingProtocolHandler::SetSubstitution(aRoot, aBaseURI);
 }
+
+nsresult
+nsResProtocolHandler::SetSubstitutionWithFlags(const nsACString& aRoot,
+                                               nsIURI* aBaseURI,
+                                               uint32_t aFlags)
+{
+    MOZ_ASSERT(!aRoot.Equals(""));
+    MOZ_ASSERT(!aRoot.Equals(kAPP));
+    MOZ_ASSERT(!aRoot.Equals(kGRE));
+    return SubstitutingProtocolHandler::SetSubstitutionWithFlags(aRoot, aBaseURI, aFlags);
+}
--- a/netwerk/protocol/res/nsResProtocolHandler.h
+++ b/netwerk/protocol/res/nsResProtocolHandler.h
@@ -29,16 +29,17 @@ public:
     nsResProtocolHandler()
       : SubstitutingProtocolHandler("resource", URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE,
                                     /* aEnforceFileOrJar = */ false)
     {}
 
     MOZ_MUST_USE nsresult Init();
 
     NS_IMETHOD SetSubstitution(const nsACString& aRoot, nsIURI* aBaseURI) override;
+    NS_IMETHOD SetSubstitutionWithFlags(const nsACString& aRoot, nsIURI* aBaseURI, uint32_t aFlags) override;
 
     NS_IMETHOD GetSubstitution(const nsACString& aRoot, nsIURI** aResult) override
     {
         return mozilla::SubstitutingProtocolHandler::GetSubstitution(aRoot, aResult);
     }
 
     NS_IMETHOD HasSubstitution(const nsACString& aRoot, bool* aResult) override
     {
@@ -56,17 +57,17 @@ public:
     }
 
     NS_IMETHOD RemoveObserver(nsISubstitutionObserver *aObserver) override
     {
         return mozilla::SubstitutingProtocolHandler::RemoveObserver(aObserver);
     }
 
 protected:
-    MOZ_MUST_USE nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult) override;
+    MOZ_MUST_USE nsresult GetSubstitutionInternal(const nsACString& aRoot, nsIURI** aResult, uint32_t* aFlags) override;
     virtual ~nsResProtocolHandler() {}
 
     MOZ_MUST_USE bool ResolveSpecialCases(const nsACString& aHost,
                                           const nsACString& aPath,
                                           const nsACString& aPathname,
                                           nsACString& aResult) override;
 
 private:
--- a/parser/html/nsHtml5ViewSourceUtils.cpp
+++ b/parser/html/nsHtml5ViewSourceUtils.cpp
@@ -45,12 +45,12 @@ nsHtml5HtmlAttributes*
 nsHtml5ViewSourceUtils::NewLinkAttributes()
 {
   nsHtml5HtmlAttributes* linkAttrs = new nsHtml5HtmlAttributes(0);
   nsHtml5String rel = nsHtml5Portability::newStringFromLiteral("stylesheet");
   linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_REL, rel, -1);
   nsHtml5String type = nsHtml5Portability::newStringFromLiteral("text/css");
   linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_TYPE, type, -1);
   nsHtml5String href = nsHtml5Portability::newStringFromLiteral(
-    "resource://gre-resources/viewsource.css");
+    "resource://content-accessible/viewsource.css");
   linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_HREF, href, -1);
   return linkAttrs;
 }
--- a/parser/htmlparser/tests/reftest/bug482921-1-ref.html
+++ b/parser/htmlparser/tests/reftest/bug482921-1-ref.html
@@ -1,9 +1,9 @@
-<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="doctype">&lt;!DOCTYPE html&gt;</span>
+<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://content-accessible/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="doctype">&lt;!DOCTYPE html&gt;</span>
 <span id></span><span>&lt;<span class="start-tag">html</span>&gt;</span>
 <span id></span><span>&lt;<span class="start-tag">head</span>&gt;</span>
 <span id></span><span>&lt;<span class="start-tag">title</span>&gt;</span><span>Title</span><span>&lt;/<span class="end-tag">title</span>&gt;</span>
 <span id></span><span>&lt;<span class="start-tag">script</span>&gt;</span>
 <span id></span>var lt = "&lt;";
 <span id></span>&lt;!--
 <span id></span>var s = "&lt;script&gt;foo&lt;/script&gt;";
 <span id></span>--&gt;
--- a/parser/htmlparser/tests/reftest/bug482921-2-ref.html
+++ b/parser/htmlparser/tests/reftest/bug482921-2-ref.html
@@ -1,9 +1,9 @@
-<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="pi">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
+<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://content-accessible/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="pi">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
 <span id></span><span class="pi">&lt;?foo bar?&gt;</span>
 <span id></span><span>&lt;<span class="start-tag">html</span>&gt;</span>
 <span id></span><span>&lt;<span class="start-tag">head</span>&gt;</span>
 <span id></span><span>&lt;<span class="start-tag">title</span>&gt;</span><span>Title</span><span>&lt;/<span class="end-tag">title</span>&gt;</span>
 <span id></span><span>&lt;<span class="start-tag">script</span>&gt;</span>
 <span id></span>var s = "<span>&lt;<span class="start-tag">script</span>&gt;</span><span>foo</span><span>&lt;/<span class="end-tag">script</span>&gt;</span>";
 <span id></span><span class="comment">&lt;!--
 <span id></span>var s = "&lt;script&gt;foo&lt;/script&gt;";
--- a/parser/htmlparser/tests/reftest/bug535530-2-ref.html
+++ b/parser/htmlparser/tests/reftest/bug535530-2-ref.html
@@ -1,9 +1,9 @@
-<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="doctype">&lt;!DOCTYPE html&gt;</span>
+<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://content-accessible/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="doctype">&lt;!DOCTYPE html&gt;</span>
 <span id></span>XX<span class="error">&amp;</span>XX
 <span id></span>XX<span class="error">&amp;</span>nXX
 <span id></span>XX<span class="error">&amp;</span>noXX
 <span id></span>XX<span class="error entity">&amp;not</span>XX
 <span id></span>XX<span class="error entity">&amp;noti</span>XX
 <span id></span>XX<span class="error entity">&amp;notin</span>XX
 <span id></span>XX<span class="error">&amp;</span>;XX
 <span id></span>XX<span class="error">&amp;</span>n;XX
--- a/parser/htmlparser/tests/reftest/bug704667-1-ref.html
+++ b/parser/htmlparser/tests/reftest/bug704667-1-ref.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="error comment">&lt;!--&gt;</span> <span class="error comment">&lt;!X&gt;</span>
+<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://content-accessible/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="error comment">&lt;!--&gt;</span> <span class="error comment">&lt;!X&gt;</span>
 <span id></span>
 </pre>
 <!-- View source CSS matches the <pre id> and <span id> elements and produces line numbers. -->
--- a/parser/htmlparser/tests/reftest/bug731234-1-ref.html
+++ b/parser/htmlparser/tests/reftest/bug731234-1-ref.html
@@ -1,9 +1,9 @@
-<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="doctype">&lt;!DOCTYPE html&gt;</span><span>
+<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://content-accessible/viewsource.css"></head><body id="viewsource" class="wrap highlight"><pre id><span class="doctype">&lt;!DOCTYPE html&gt;</span><span>
 <span id></span></span><span>&lt;<span class="start-tag">body</span>&gt;</span><span>
 <span id></span></span><span>&lt;<span class="start-tag">script</span>&gt;</span><span></span><span>&lt;/<span class="end-tag">script</span>&gt;</span><span>X
 <span id></span></span><span>&lt;<span class="start-tag">script</span>&gt;</span><span></span><span>&lt;/<span class="end-tag">script</span> &gt;</span><span>X
 <span id></span></span><span>&lt;<span class="start-tag">script</span>&gt;</span><span></span><span>&lt;/<span class="end-tag">script</span>
 <span id></span>&gt;</span><span>X
 <span id></span></span><span>&lt;<span class="start-tag">script</span>&gt;</span><span></span><span title="End tag had attributes." class="error">&lt;/<span class="end-tag">script</span> <span class="attribute-name">foo</span>&gt;</span><span>X
 <span id></span></span><span>&lt;<span class="start-tag">script</span>&gt;</span><span></span><span title="End tag had attributes." class="error">&lt;/<span class="end-tag">script</span> <span class="attribute-name">foo</span>=<a class="attribute-value">bar</a>&gt;</span><span>X
 <span id></span></span><span>&lt;<span class="start-tag">script</span>&gt;</span><span></span><span title="End tag had attributes." class="error">&lt;/<span class="end-tag">script</span> <span class="attribute-name">foo</span>="<a class="attribute-value">bar</a>"&gt;</span><span>X
--- a/parser/htmlparser/tests/reftest/bug910588-1-ref.html
+++ b/parser/htmlparser/tests/reftest/bug910588-1-ref.html
@@ -1,2 +1,2 @@
-<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://gre-resources/viewsource.css"></head><body id="viewsource" class="highlight" style="-moz-tab-size: 4"><pre id="line1"><span></span><span class="doctype">&lt;!DOCTYPE html&gt;</span><span></span><span>&lt;<span class="start-tag">table</span>&gt;</span><span></span><span title="Start tag “input” seen in “table”." class="error">&lt;<span class="start-tag">input</span> <span class="attribute-name">type</span>=<a class="attribute-value">hidden</a>&gt;</span><span></span><span>&lt;/<span class="end-tag">table</span>&gt;</span><span>
+<!DOCTYPE html><html><head><title></title><link rel="stylesheet" type="text/css" href="resource://content-accessible/viewsource.css"></head><body id="viewsource" class="highlight" style="-moz-tab-size: 4"><pre id="line1"><span></span><span class="doctype">&lt;!DOCTYPE html&gt;</span><span></span><span>&lt;<span class="start-tag">table</span>&gt;</span><span></span><span title="Start tag “input” seen in “table”." class="error">&lt;<span class="start-tag">input</span> <span class="attribute-name">type</span>=<a class="attribute-value">hidden</a>&gt;</span><span></span><span>&lt;/<span class="end-tag">table</span>&gt;</span><span>
 <span id="line2"></span></span></pre></body></html>
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -2085,16 +2085,24 @@ SPECIAL_VARIABLES = {
         subdirectory they should be exported to. For example, to export
         ``foo.res`` to the top-level directory, and ``bar.res`` to ``fonts/``,
         append to ``RESOURCE_FILES`` like so::
 
            RESOURCE_FILES += ['foo.res']
            RESOURCE_FILES.fonts += ['bar.res']
         """),
 
+    'CONTENT_ACCESSIBLE_FILES': (lambda context: context['FINAL_TARGET_FILES'].contentaccessible, list,
+        """List of files which can be accessed by web content through resource:// URIs.
+
+        ``CONTENT_ACCESSIBLE_FILES`` is used to list the files to be exported
+        to ``dist/bin/contentaccessible``. Files can also be appended to a
+        field to indicate which subdirectory they should be exported to.
+        """),
+
     'EXTRA_JS_MODULES': (lambda context: context['FINAL_TARGET_FILES'].modules, list,
         """Additional JavaScript files to distribute.
 
         This variable contains a list of files to copy into
         ``$(FINAL_TARGET)/modules.
         """),
 
     'EXTRA_PP_JS_MODULES': (lambda context: context['FINAL_TARGET_PP_FILES'].modules, list,
--- a/python/mozbuild/mozpack/chrome/manifest.py
+++ b/python/mozbuild/mozpack/chrome/manifest.py
@@ -33,16 +33,17 @@ class ManifestEntry(object):
         'application',
         'platformversion',
         'os',
         'osversion',
         'abi',
         'xpcnativewrappers',
         'tablet',
         'process',
+        'contentaccessible',
     ]
 
     def __init__(self, base, *flags):
         '''
         Initialize a manifest entry with the given base path and flags.
         '''
         self.base = base
         self.flags = Flags(*flags)
--- a/python/mozbuild/mozpack/packager/formats.py
+++ b/python/mozbuild/mozpack/packager/formats.py
@@ -335,9 +335,10 @@ class OmniJarSubFormatter(PiecemealForma
                 not (path[2] == 'channel-prefs.js' and
                      path[1] in ['pref', 'preferences'])
         return path[0] in [
             'modules',
             'greprefs.js',
             'hyphenation',
             'localization',
             'update.locale',
+            'contentaccessible',
         ]
new file mode 100644
--- /dev/null
+++ b/security/certverifier/BTInclusionProof.h
@@ -0,0 +1,41 @@
+/* -*- 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 BTInclusionProof_h
+#define BTInclusionProof_h
+
+#include "Buffer.h"
+#include "mozilla/Vector.h"
+
+namespace mozilla { namespace ct {
+
+// Represents a Merkle inclusion proof for purposes of serialization,
+// deserialization, and verification of the proof.  The format for inclusion
+// proofs in RFC 6962-bis is as follows:
+//
+//    opaque LogID<2..127>;
+//    opaque NodeHash<32..2^8-1>;
+//
+//     struct {
+//         LogID log_id;
+//         uint64 tree_size;
+//         uint64 leaf_index;
+//         NodeHash inclusion_path<1..2^16-1>;
+//     } InclusionProofDataV2;
+
+const uint64_t kInitialPathLengthCapacity = 32;
+
+struct InclusionProofDataV2
+{
+  Buffer logId;
+  uint64_t treeSize;
+  uint64_t leafIndex;
+  Vector<Buffer, kInitialPathLengthCapacity> inclusionPath;
+};
+
+} } // namespace mozilla:ct
+
+#endif // BTInclusionProof_h
new file mode 100644
--- /dev/null
+++ b/security/certverifier/BTVerifier.cpp
@@ -0,0 +1,104 @@
+/* -*- 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 "BTVerifier.h"
+#include "CTUtils.h"
+#include "SignedCertificateTimestamp.h"
+
+#include <stdint.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Move.h"
+#include "mozilla/TypeTraits.h"
+
+namespace mozilla { namespace ct {
+
+using namespace mozilla::pkix;
+
+typedef mozilla::pkix::Result Result;
+
+// Members of a Inclusion Proof struct
+static const size_t kLogIdPrefixLengthBytes = 1;
+static const size_t kProofTreeSizeLength = 8;
+static const size_t kLeafIndexLength = 8;
+static const size_t kInclusionPathLengthBytes = 2;
+static const size_t kNodeHashPrefixLengthBytes = 1;
+
+Result
+DecodeInclusionProof(pkix::Reader& reader, InclusionProofDataV2& output)
+{
+  InclusionProofDataV2 result;
+
+  Input logId;
+  Result rv = ReadVariableBytes<kLogIdPrefixLengthBytes>(reader, logId);
+  if (rv != Success) {
+    return rv;
+  }
+
+  rv = ReadUint<kProofTreeSizeLength>(reader, result.treeSize);
+  if (rv != Success) {
+    return rv;
+  }
+
+  if (result.treeSize < 1) {
+    return pkix::Result::ERROR_BAD_DER;
+  }
+
+  rv = ReadUint<kLeafIndexLength>(reader, result.leafIndex);
+  if (rv != Success) {
+    return rv;
+  }
+
+  if (result.leafIndex >= result.treeSize) {
+    return pkix::Result::ERROR_BAD_DER;
+  }
+
+  Input pathInput;
+  rv = ReadVariableBytes<kInclusionPathLengthBytes>(reader, pathInput);
+  if (rv != Success) {
+    return rv;
+  }
+
+  if (pathInput.GetLength() < 1) {
+    return pkix::Result::ERROR_BAD_DER;
+  }
+
+  Reader pathReader(pathInput);
+  Vector<Buffer, kInitialPathLengthCapacity> inclusionPath;
+
+  while (!pathReader.AtEnd()) {
+    Input hash;
+    rv = ReadVariableBytes<kNodeHashPrefixLengthBytes>(pathReader, hash);
+    if (rv != Success) {
+      return rv;
+    }
+
+    Buffer hashBuffer;
+    rv = InputToBuffer(hash, hashBuffer);
+    if (rv != Success) {
+      return rv;
+    }
+
+    if (!inclusionPath.append(Move(hashBuffer))) {
+      return pkix::Result::FATAL_ERROR_NO_MEMORY;
+    }
+  }
+
+  if (!reader.AtEnd()){
+    return pkix::Result::ERROR_BAD_DER;
+  }
+
+  rv = InputToBuffer(logId, result.logId);
+  if (rv != Success) {
+    return rv;
+  }
+
+  result.inclusionPath = Move(inclusionPath);
+
+  output = Move(result);
+  return Success;
+}
+} } //namespace mozilla::ct
new file mode 100644
--- /dev/null
+++ b/security/certverifier/BTVerifier.h
@@ -0,0 +1,23 @@
+/* -*- 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 BTVerifier_h
+#define BTVerifier_h
+
+#include "BTInclusionProof.h"
+#include "pkix/Input.h"
+#include "pkix/Result.h"
+
+namespace mozilla { namespace ct {
+
+// Decodes an Inclusion Proof (InclusionProofDataV2 as defined in RFC
+// 6962-bis). This consumes the entirety of the input.
+pkix::Result DecodeInclusionProof(pkix::Reader& input,
+  InclusionProofDataV2& output);
+
+} } // namespace mozilla::ct
+
+#endif // BTVerifier_h
new file mode 100644
--- /dev/null
+++ b/security/certverifier/Buffer.cpp
@@ -0,0 +1,24 @@
+/* -*- 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 "Buffer.h"
+
+namespace mozilla {
+
+bool
+operator==(const ct::Buffer& a, const ct::Buffer& b)
+{
+  return (a.empty() && b.empty()) ||
+    (a.length() == b.length() && memcmp(a.begin(), b.begin(), a.length()) == 0);
+}
+
+bool
+operator!=(const ct::Buffer& a, const ct::Buffer& b)
+{
+  return !(a == b);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/security/certverifier/Buffer.h
@@ -0,0 +1,28 @@
+/* -*- 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 Buffer_h
+#define Buffer_h
+
+#include "mozilla/Vector.h"
+
+namespace mozilla { namespace ct {
+
+typedef Vector<uint8_t> Buffer;
+
+} } // namespace mozilla::ct
+
+namespace mozilla {
+
+// Comparison operators are placed under mozilla namespace since
+// mozilla::ct::Buffer is actually mozilla::Vector.
+bool operator==(const ct::Buffer& a, const ct::Buffer& b);
+bool operator!=(const ct::Buffer& a, const ct::Buffer& b);
+
+} // namespace mozilla
+
+
+#endif // Buffer_h
--- a/security/certverifier/CTSerialization.cpp
+++ b/security/certverifier/CTSerialization.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 "CTSerialization.h"
+#include "CTUtils.h"
 
 #include <stdint.h>
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Move.h"
 #include "mozilla/TypeTraits.h"
 
 namespace mozilla { namespace ct {
@@ -67,17 +68,17 @@ UncheckedReadUint(size_t length, Reader&
     result = (result << 8) | value;
   }
   out = result;
   return Success;
 }
 
 // Performs overflow sanity checks and calls UncheckedReadUint.
 template <size_t length, typename T>
-static inline Result
+Result
 ReadUint(Reader& in, T& out)
 {
   uint64_t value;
   static_assert(mozilla::IsUnsigned<T>::value, "T must be unsigned");
   static_assert(length <= 8, "At most 8 byte integers can be read");
   static_assert(sizeof(T) >= length, "T must be able to hold <length> bytes");
   Result rv = UncheckedReadUint(length, in, value);
   if (rv != Success) {
@@ -93,17 +94,17 @@ ReadFixedBytes(size_t length, Reader& in
 {
   return in.Skip(length, out);
 }
 
 // Reads a length-prefixed variable amount of bytes from |in|, updating |out|
 // on success. |prefixLength| indicates the number of bytes needed to represent
 // the length.
 template <size_t prefixLength>
-static inline Result
+Result
 ReadVariableBytes(Reader& in, Input& out)
 {
   size_t length;
   Result rv = ReadUint<prefixLength>(in, length);
   if (rv != Success) {
     return rv;
   }
   return ReadFixedBytes(length, in, out);
new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTUtils.h
@@ -0,0 +1,30 @@
+/* -*- 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 CTUtils_h
+#define CTUtils_h
+
+#include "pkix/Input.h"
+#include "pkix/Result.h"
+
+namespace mozilla { namespace ct {
+
+// Reads a TLS-encoded variable length unsigned integer from |in|.
+// The integer is expected to be in big-endian order, which is used by TLS.
+// Note: checks if the output parameter overflows while reading.
+// |length| indicates the size (in bytes) of the serialized integer.
+template <size_t length, typename T>
+pkix::Result ReadUint(Reader& in, T& out);
+
+// Reads a length-prefixed variable amount of bytes from |in|, updating |out|
+// on success. |prefixLength| indicates the number of bytes needed to represent
+// the length.
+template <size_t prefixLength>
+pkix::Result ReadVariableBytes(Reader& in, Input& out);
+
+} } // namespace mozilla::ct
+
+#endif //CTUtils_h
--- a/security/certverifier/SignedCertificateTimestamp.cpp
+++ b/security/certverifier/SignedCertificateTimestamp.cpp
@@ -21,26 +21,8 @@ bool
 DigitallySigned::SignatureParametersMatch(HashAlgorithm aHashAlgorithm,
   SignatureAlgorithm aSignatureAlgorithm) const
 {
   return (hashAlgorithm == aHashAlgorithm) &&
          (signatureAlgorithm == aSignatureAlgorithm);
 }
 
 } } // namespace mozilla::ct
-
-
-namespace mozilla {
-
-bool
-operator==(const ct::Buffer& a, const ct::Buffer& b)
-{
-  return (a.empty() && b.empty()) ||
-    (a.length() == b.length() && memcmp(a.begin(), b.begin(), a.length()) == 0);
-}
-
-bool
-operator!=(const ct::Buffer& a, const ct::Buffer& b)
-{
-  return !(a == b);
-}
-
-} // namespace mozilla
--- a/security/certverifier/SignedCertificateTimestamp.h
+++ b/security/certverifier/SignedCertificateTimestamp.h
@@ -2,25 +2,24 @@
 /* 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 SignedCertificateTimestamp_h
 #define SignedCertificateTimestamp_h
 
+#include "Buffer.h"
 #include "mozilla/Vector.h"
 #include "pkix/Input.h"
 #include "pkix/Result.h"
 
 // Structures related to Certificate Transparency (RFC 6962).
 namespace mozilla { namespace ct {
 
-typedef Vector<uint8_t> Buffer;
-
 // LogEntry struct in RFC 6962, Section 3.1.
 struct LogEntry
 {
 
   // LogEntryType enum in RFC 6962, Section 3.1.
   enum class Type {
     X509 = 0,
     Precert = 1
@@ -101,18 +100,9 @@ inline pkix::Result InputToBuffer(pkix::
   if (!buffer.append(input.UnsafeGetData(), input.GetLength())) {
     return pkix::Result::FATAL_ERROR_NO_MEMORY;
   }
   return pkix::Success;
 }
 
 } } // namespace mozilla::ct
 
-namespace mozilla {
-
-// Comparison operators are placed under mozilla namespace since
-// mozilla::ct::Buffer is actually mozilla::Vector.
-bool operator==(const ct::Buffer& a, const ct::Buffer& b);
-bool operator!=(const ct::Buffer& a, const ct::Buffer& b);
-
-} // namespace mozilla
-
 #endif // SignedCertificateTimestamp_h
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -4,27 +4,32 @@
 # 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/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "Security: PSM")
 
 EXPORTS += [
     'BRNameMatchingPolicy.h',
+    'BTInclusionProof.h',
+    'BTVerifier.h',
+    'Buffer.h',
     'CertVerifier.h',
     'CTLog.h',
     'CTPolicyEnforcer.h',
     'CTVerifyResult.h',
     'OCSPCache.h',
     'SignedCertificateTimestamp.h',
     'SignedTreeHead.h',
 ]
 
 UNIFIED_SOURCES += [
     'BRNameMatchingPolicy.cpp',
+    'BTVerifier.cpp',
+    'Buffer.cpp',
     'CertVerifier.cpp',
     'CTDiversityPolicy.cpp',
     'CTLogVerifier.cpp',
     'CTObjectsExtractor.cpp',
     'CTPolicyEnforcer.cpp',
     'CTSerialization.cpp',
     'CTVerifyResult.cpp',
     'MultiLogCTVerifier.cpp',
new file mode 100644
--- /dev/null
+++ b/security/certverifier/tests/gtest/BTSerializationTest.cpp
@@ -0,0 +1,160 @@
+/* -*- 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 "BTVerifier.h"
+#include "CTTestUtils.h"
+#include "gtest/gtest.h"
+
+namespace mozilla { namespace ct {
+
+using namespace pkix;
+
+class BTSerializationTest : public ::testing::Test
+{
+public:
+  void SetUp() override
+  {
+    mTestInclusionProof = GetTestInclusionProof();
+    mTestInclusionProofUnexpectedData = GetTestInclusionProofUnexpectedData();
+    mTestInclusionProofInvalidHashSize = GetTestInclusionProofInvalidHashSize();
+    mTestInclusionProofInvalidHash = GetTestInclusionProofInvalidHash();
+    mTestInclusionProofMissingLogId = GetTestInclusionProofMissingLogId();
+    mTestInclusionProofNullPathLength = GetTestInclusionProofNullPathLength();
+    mTestInclusionProofPathLengthTooSmall = GetTestInclusionProofPathLengthTooSmall();
+    mTestInclusionProofPathLengthTooLarge = GetTestInclusionProofPathLengthTooLarge();
+    mTestInclusionProofNullTreeSize = GetTestInclusionProofNullTreeSize();
+    mTestInclusionProofLeafIndexOutOfBounds = GetTestInclusionProofLeafIndexOutOfBounds();
+    mTestInclusionProofExtraData = GetTestInclusionProofExtraData();
+  }
+
+protected:
+  Buffer mTestInclusionProof;
+  Buffer mTestInclusionProofUnexpectedData;
+  Buffer mTestInclusionProofInvalidHashSize;
+  Buffer mTestInclusionProofInvalidHash;
+  Buffer mTestInclusionProofMissingLogId;
+  Buffer mTestInclusionProofNullPathLength;
+  Buffer mTestInclusionProofPathLengthTooSmall;
+  Buffer mTestInclusionProofPathLengthTooLarge;
+  Buffer mTestInclusionProofNullTreeSize;
+  Buffer mTestInclusionProofLeafIndexOutOfBounds;
+  Buffer mTestInclusionProofExtraData;
+};
+
+TEST_F(BTSerializationTest, DecodesInclusionProof)
+{
+  const uint64_t expectedTreeSize = 4;
+  const uint64_t expectedLeafIndex = 2;
+  const uint64_t expectedInclusionPathElements = 2;
+
+  const uint8_t EXPECTED_LOGID[] = { 0x01, 0x00 };
+  Buffer expectedLogId;
+  MOZ_RELEASE_ASSERT(expectedLogId.append(EXPECTED_LOGID, 2));
+
+
+  Input encodedProofInput = InputForBuffer(mTestInclusionProof);
+  Reader encodedProofReader(encodedProofInput);
+
+  InclusionProofDataV2 ipr;
+  ASSERT_EQ(Success, DecodeInclusionProof(encodedProofReader, ipr));
+  EXPECT_EQ(expectedLogId, ipr.logId);
+  EXPECT_EQ(expectedTreeSize, ipr.treeSize);
+  EXPECT_EQ(expectedLeafIndex, ipr.leafIndex);
+  EXPECT_EQ(expectedInclusionPathElements, ipr.inclusionPath.length());
+  EXPECT_EQ(GetTestNodeHash0(), ipr.inclusionPath[0]);
+  EXPECT_EQ(GetTestNodeHash1(), ipr.inclusionPath[1]);
+}
+
+TEST_F(BTSerializationTest, FailsDecodingInclusionProofUnexpectedData)
+{
+  Input encodedProofInput = InputForBuffer(mTestInclusionProofUnexpectedData);
+  Reader encodedProofReader(encodedProofInput);
+  InclusionProofDataV2 ipr;
+
+  ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingInvalidHashSize)
+{
+  Input encodedProofInput = InputForBuffer(mTestInclusionProofInvalidHashSize);
+  Reader encodedProofReader(encodedProofInput);
+  InclusionProofDataV2 ipr;
+
+  ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingInvalidHash)
+{
+  Input encodedProofInput = InputForBuffer(mTestInclusionProofInvalidHash);
+  Reader encodedProofReader(encodedProofInput);
+  InclusionProofDataV2 ipr;
+
+  ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingMissingLogId)
+{
+  Input encodedProofInput = InputForBuffer(mTestInclusionProofMissingLogId);
+  Reader encodedProofReader(encodedProofInput);
+  InclusionProofDataV2 ipr;
+
+  ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingNullPathLength)
+{
+  Input encodedProofInput = InputForBuffer(mTestInclusionProofNullPathLength);
+  Reader encodedProofReader(encodedProofInput);
+  InclusionProofDataV2 ipr;
+
+  ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingPathLengthTooSmall)
+{
+  Input encodedProofInput = InputForBuffer(mTestInclusionProofPathLengthTooSmall);
+  Reader encodedProofReader(encodedProofInput);
+  InclusionProofDataV2 ipr;
+
+  ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingPathLengthTooLarge)
+{
+  Input encodedProofInput = InputForBuffer(mTestInclusionProofPathLengthTooLarge);
+  Reader encodedProofReader(encodedProofInput);
+  InclusionProofDataV2 ipr;
+
+  ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingNullTreeSize)
+{
+  Input encodedProofInput = InputForBuffer(mTestInclusionProofNullTreeSize);
+  Reader encodedProofReader(encodedProofInput);
+  InclusionProofDataV2 ipr;
+
+  ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingLeafIndexOutOfBounds)
+{
+  Input encodedProofInput = InputForBuffer(mTestInclusionProofLeafIndexOutOfBounds);
+  Reader encodedProofReader(encodedProofInput);
+  InclusionProofDataV2 ipr;
+
+  ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingExtraData)
+{
+  Input encodedProofInput = InputForBuffer(mTestInclusionProofExtraData);
+  Reader encodedProofReader(encodedProofInput);
+  InclusionProofDataV2 ipr;
+
+  ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
+}
+} } // namespace mozilla::ct
--- a/security/certverifier/tests/gtest/CTSerializationTest.cpp
+++ b/security/certverifier/tests/gtest/CTSerializationTest.cpp
@@ -262,10 +262,9 @@ TEST_F(CTSerializationTest, EncodesValid
     // sha256 root hash should follow
   };
   Buffer expectedBuffer;
   MOZ_RELEASE_ASSERT(expectedBuffer.append(EXPECTED_BYTES_PREFIX, 18));
   Buffer hash = GetSampleSTHSHA256RootHash();
   MOZ_RELEASE_ASSERT(expectedBuffer.append(hash.begin(), hash.length()));
   EXPECT_EQ(expectedBuffer, encoded);
 }
-
-} }  // namespace mozilla::ct
+} } // namespace mozilla::ct
--- a/security/certverifier/tests/gtest/CTTestUtils.cpp
+++ b/security/certverifier/tests/gtest/CTTestUtils.cpp
@@ -4,16 +4,17 @@
  * 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 "CTTestUtils.h"
 
 #include <stdint.h>
 #include <iomanip>
 
+#include "BTInclusionProof.h"
 #include "CTSerialization.h"
 #include "gtest/gtest.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Move.h"
 #include "mozilla/Vector.h"
 #include "pkix/Input.h"
 #include "pkix/pkix.h"
 #include "pkix/pkixnss.h"
@@ -321,16 +322,114 @@ const char kTestEmbeddedWithIntermediate
   "43eb3002200b76fe475138d8cf76833831304dabf043eb1213c96e13ff4f"
   "a37f7cd3c8dc1f300d06092a864886f70d01010505000381810088ee4e9e"
   "5eed6b112cc764b151ed929400e9406789c15fbbcfcdab2f10b400234139"
   "e6ce65c1e51b47bf7c8950f80bccd57168567954ed35b0ce9346065a5eae"
   "5bf95d41da8e27cee9eeac688f4bd343f9c2888327abd8b9f68dcb1e3050"
   "041d31bda8e2dd6d39b3664de5ce0870f5fc7e6a00d6ed00528458d953d2"
   "37586d73";
 
+// Given the ordered set of data [ 0x00, 0x01, 0x02, deadbeef ],
+// the 'inclusion proof' of the leaf of index '2' (for '0x02') is created from
+// the Merkle Tree generated for that set of data.
+// A Merkle inclusion proof for a leaf in a Merkle Tree is the shortest list
+// of additional nodes in the Merkle Tree required to compute the Merkle Tree
+// Hash (also called 'Merkle Tree head') for that tree.
+// This follows the structure defined in RFC 6962-bis.
+//
+// https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-24#section-2.1
+
+const char kTestInclusionProof[] =
+  "020100" // logId
+  "0000000000000004" // tree size
+  "0000000000000002" // leaf index
+  "0042" // inclusion path length
+  "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
+  "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a"; // node hash 1
+
+const char kTestNodeHash0[] =
+  "48c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729";
+
+const char kTestNodeHash1[] =
+  "a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a";
+
+const char kTestInclusionProofUnexpectedData[] = "12345678";
+
+const char kTestInclusionProofInvalidHashSize[] =
+  "020100" // logId
+  "0000000000000004" // treesize
+  "0000000000000002" // leafindex
+  "0042" // inclusion path length
+  "3048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // invalid hash size
+  "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a"; // node hash 1
+
+const char kTestInclusionProofInvalidHash[] =
+  "020100" // logId
+  "0000000000000004" // treesize
+  "0000000000000002" // leafindex
+  "0042" // inclusion path length
+  "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
+  "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427"; // truncated node hash 1
+
+const char kTestInclusionProofMissingLogId[] =
+  "0000000000000004" // treesize
+  "0000000000000002" // leafindex
+  "0042"
+  "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
+  "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a"; // node hash 1
+
+const char kTestInclusionProofNullPathLength[] =
+  "020100"
+  "0000000000000004" // treesize
+  "0000000000000002" // leafindex
+  "0000"
+  "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
+  "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a"; // node hash 1
+
+const char kTestInclusionProofPathLengthTooSmall[] =
+  "020100"
+  "0000000000000004" // treesize
+  "0000000000000002" // leafindex
+  "0036"
+  "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
+  "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d