mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 20:35:50 +00:00
Merge autoland to central, a=merge
MozReview-Commit-ID: 5tolFjvaHmd
This commit is contained in:
commit
02b3fbee7e
@ -176,7 +176,6 @@ var whitelist = [
|
||||
{file: "resource://gre/modules/accessibility/AccessFu.jsm"},
|
||||
// Bug 1351637
|
||||
{file: "resource://gre/modules/sdk/bootstrap.js"},
|
||||
|
||||
];
|
||||
|
||||
whitelist = new Set(whitelist.filter(item =>
|
||||
@ -478,7 +477,8 @@ function findChromeUrlsFromArray(array, prefix) {
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@ -540,6 +540,8 @@ add_task(async function checkAllTheFiles() {
|
||||
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 = [];
|
||||
|
@ -17,7 +17,7 @@ let whitelist = [
|
||||
{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,
|
||||
@ -288,41 +288,49 @@ add_task(async function checkAllTheCSS() {
|
||||
// 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.
|
||||
|
@ -133,6 +133,100 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
@ -140,7 +234,31 @@ this.devtools_panels = class extends ExtensionAPI {
|
||||
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]);
|
||||
|
@ -267,6 +267,111 @@ class DevToolsSelectionObserver extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -274,6 +379,10 @@ this.devtools_panels = class extends ExtensionAPI {
|
||||
|
||||
const toolboxSelectionObserver = new DevToolsSelectionObserver(context);
|
||||
|
||||
function newBasePanelId() {
|
||||
return `${context.extension.id}-${context.contextId}-${nextPanelId++}`;
|
||||
}
|
||||
|
||||
return {
|
||||
devtools: {
|
||||
panels: {
|
||||
@ -288,6 +397,32 @@ this.devtools_panels = class extends ExtensionAPI {
|
||||
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.
|
||||
@ -300,8 +435,7 @@ this.devtools_panels = class extends ExtensionAPI {
|
||||
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});
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
"functions": [
|
||||
{
|
||||
"name": "createSidebarPane",
|
||||
"unsupported": true,
|
||||
"async": "callback",
|
||||
"type": "function",
|
||||
"description": "Creates a pane within panel's sidebar.",
|
||||
"parameters": [
|
||||
@ -181,6 +181,7 @@
|
||||
{
|
||||
"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": [
|
||||
@ -205,7 +206,7 @@
|
||||
},
|
||||
{
|
||||
"name": "setObject",
|
||||
"unsupported": true,
|
||||
"async": "callback",
|
||||
"type": "function",
|
||||
"description": "Sets a JSON-compliant object to be displayed in the sidebar pane.",
|
||||
"parameters": [
|
||||
@ -245,7 +246,6 @@
|
||||
"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": [
|
||||
@ -260,7 +260,6 @@
|
||||
},
|
||||
{
|
||||
"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."
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ skip-if = (os == 'win' && !debug) # bug 1352668
|
||||
[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]
|
||||
|
@ -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);
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
[DEFAULT]
|
||||
tags = resistfingerprinting
|
||||
|
||||
support-files =
|
||||
file_animation_api.html
|
||||
worker_child.js
|
||||
@ -15,3 +16,4 @@ scheme = https
|
||||
support-files = test_hide_gamepad_info_iframe.html
|
||||
[test_speech_synthesis.html]
|
||||
[test_bug1382499_touch_api.html]
|
||||
[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>
|
@ -42,11 +42,11 @@ function test() {
|
||||
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);
|
||||
|
@ -41,11 +41,11 @@ function test() {
|
||||
});
|
||||
});
|
||||
|
||||
// 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);
|
||||
|
@ -3,7 +3,7 @@
|
||||
# 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)
|
||||
|
@ -68,3 +68,8 @@ document.getElementById("onboarding-overlay")
|
||||
Mozilla.UITour.hideHighlight();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("onboarding-overlay-button").addEventListener("Agent:Destroy", () => {
|
||||
Mozilla.UITour.hideHighlight();
|
||||
Mozilla.UITour.hideMenu("urlbar");
|
||||
});
|
||||
|
@ -603,13 +603,14 @@ class Onboarding {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -3,7 +3,9 @@
|
||||
# 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
|
||||
|
@ -25,6 +25,13 @@ function assertTourCompleted(tourId, expectComplete, browser) {
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@ -88,3 +95,79 @@ add_task(async function test_click_action_button_to_set_tour_completed() {
|
||||
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);
|
||||
});
|
||||
|
@ -7,7 +7,7 @@
|
||||
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/*)
|
||||
|
@ -683,9 +683,6 @@
|
||||
@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
|
||||
@ -716,6 +713,9 @@
|
||||
@RESPATH@/res/MainMenu.nib/
|
||||
#endif
|
||||
|
||||
; Content-accessible resources.
|
||||
@RESPATH@/contentaccessible/*
|
||||
|
||||
; svg
|
||||
@RESPATH@/res/svg.css
|
||||
@RESPATH@/components/dom_svg.xpt
|
||||
|
@ -10,10 +10,10 @@
|
||||
"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",
|
||||
|
@ -10,10 +10,10 @@
|
||||
"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"
|
||||
]
|
||||
|
@ -11,14 +11,14 @@
|
||||
"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",
|
||||
|
@ -12,8 +12,8 @@
|
||||
"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"
|
||||
}
|
||||
|
@ -13,13 +13,13 @@
|
||||
"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"
|
||||
|
@ -336,16 +336,16 @@
|
||||
|
||||
|
||||
# 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
|
||||
@ -358,12 +358,12 @@
|
||||
|
||||
|
||||
# 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
|
||||
@ -383,11 +383,11 @@
|
||||
# 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
|
||||
@ -406,14 +406,14 @@
|
||||
# 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
|
||||
@ -435,7 +435,7 @@
|
||||
# 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
|
||||
@ -473,11 +473,11 @@
|
||||
# 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
|
||||
@ -502,7 +502,7 @@
|
||||
# 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)
|
||||
@ -511,7 +511,7 @@
|
||||
# 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
|
||||
@ -530,8 +530,8 @@
|
||||
#
|
||||
# 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
|
||||
@ -605,18 +605,6 @@
|
||||
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
|
||||
@ -634,3 +622,13 @@
|
||||
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
|
||||
}
|
||||
|
@ -53,6 +53,7 @@
|
||||
#include "nsIURIFixup.h"
|
||||
#include "nsCDefaultURIFixup.h"
|
||||
#include "nsIChromeRegistry.h"
|
||||
#include "nsIResProtocolHandler.h"
|
||||
#include "nsIContentSecurityPolicy.h"
|
||||
#include "nsIAsyncVerifyRedirectCallback.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
@ -915,10 +916,9 @@ nsScriptSecurityManager::CheckLoadURIFlags(nsIURI *aSourceURI,
|
||||
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;
|
||||
}
|
||||
|
||||
@ -939,15 +939,51 @@ nsScriptSecurityManager::CheckLoadURIFlags(nsIURI *aSourceURI,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,12 @@ function loadImage(uri, expect, callback) {
|
||||
}
|
||||
|
||||
// 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>
|
||||
|
@ -42,12 +42,14 @@ 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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -140,19 +142,23 @@ struct ParamTraits<SubstitutionMapping>
|
||||
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;
|
||||
|
@ -927,7 +927,15 @@ nsChromeRegistryChrome::ManifestResource(ManifestProcessingContext& cx, int line
|
||||
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'.",
|
||||
|
@ -114,7 +114,7 @@ nsChromeRegistryContent::RegisterSubstitution(const SubstitutionMapping& aSubsti
|
||||
return;
|
||||
}
|
||||
|
||||
rv = sph->SetSubstitution(aSubstitution.path, resolvedURI);
|
||||
rv = sph->SetSubstitutionWithFlags(aSubstitution.path, resolvedURI, aSubstitution.flags);
|
||||
if (NS_FAILED(rv))
|
||||
return;
|
||||
}
|
||||
|
@ -354,14 +354,25 @@ function waitForServiceWorkerRegistered(tab) {
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,6 +98,7 @@ function Toolbox(target, selectedTool, hostType, contentWindow, frameId) {
|
||||
this.frameId = frameId;
|
||||
|
||||
this._toolPanels = new Map();
|
||||
this._inspectorExtensionSidebars = new Map();
|
||||
this._telemetry = new Telemetry();
|
||||
|
||||
this._initInspector = null;
|
||||
@ -1455,6 +1456,61 @@ Toolbox.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -1481,9 +1537,7 @@ Toolbox.prototype = {
|
||||
*/
|
||||
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();
|
||||
@ -2306,7 +2360,7 @@ Toolbox.prototype = {
|
||||
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) {
|
||||
|
17
devtools/client/inspector/extensions/actions/index.js
Normal file
17
devtools/client/inspector/extensions/actions/index.js
Normal file
@ -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);
|
10
devtools/client/inspector/extensions/actions/moz.build
Normal file
10
devtools/client/inspector/extensions/actions/moz.build
Normal file
@ -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',
|
||||
)
|
35
devtools/client/inspector/extensions/actions/sidebar.js
Normal file
35
devtools/client/inspector/extensions/actions/sidebar.js
Normal file
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
};
|
@ -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);
|
@ -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;
|
10
devtools/client/inspector/extensions/components/moz.build
Normal file
10
devtools/client/inspector/extensions/components/moz.build
Normal file
@ -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',
|
||||
)
|
100
devtools/client/inspector/extensions/extension-sidebar.js
Normal file
100
devtools/client/inspector/extensions/extension-sidebar.js
Normal file
@ -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;
|
17
devtools/client/inspector/extensions/moz.build
Normal file
17
devtools/client/inspector/extensions/moz.build
Normal file
@ -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']
|
9
devtools/client/inspector/extensions/reducers/moz.build
Normal file
9
devtools/client/inspector/extensions/reducers/moz.build
Normal file
@ -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',
|
||||
)
|
41
devtools/client/inspector/extensions/reducers/sidebar.js
Normal file
41
devtools/client/inspector/extensions/reducers/sidebar.js
Normal file
@ -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);
|
||||
};
|
6
devtools/client/inspector/extensions/test/.eslintrc.js
Normal file
6
devtools/client/inspector/extensions/test/.eslintrc.js
Normal file
@ -0,0 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../../.eslintrc.mochitests.js"
|
||||
};
|
13
devtools/client/inspector/extensions/test/browser.ini
Normal file
13
devtools/client/inspector/extensions/test/browser.ini
Normal file
@ -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]
|
@ -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();
|
||||
});
|
14
devtools/client/inspector/extensions/test/head.js
Normal file
14
devtools/client/inspector/extensions/test/head.js
Normal file
@ -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);
|
@ -22,6 +22,7 @@ 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");
|
||||
@ -293,6 +294,7 @@ Inspector.prototype = {
|
||||
|
||||
this.setupSearchBox();
|
||||
this.setupSidebar();
|
||||
this.setupExtensionSidebars();
|
||||
});
|
||||
},
|
||||
|
||||
@ -556,6 +558,8 @@ Inspector.prototype = {
|
||||
// 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);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -653,6 +657,69 @@ Inspector.prototype = {
|
||||
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
|
||||
@ -2110,6 +2177,10 @@ const buildFakeToolbox = Task.async(function* (
|
||||
viewSourceInDebugger: notImplemented,
|
||||
viewSourceInStyleEditor: notImplemented,
|
||||
|
||||
get inspectorExtensionSidebars() {
|
||||
notImplemented();
|
||||
},
|
||||
|
||||
// For attachThread:
|
||||
highlightTool() {},
|
||||
unhighlightTool() {},
|
||||
|
@ -23,6 +23,7 @@
|
||||
<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>
|
||||
|
@ -6,6 +6,7 @@ DIRS += [
|
||||
'boxmodel',
|
||||
'components',
|
||||
'computed',
|
||||
'extensions',
|
||||
'fonts',
|
||||
'grids',
|
||||
'layout',
|
||||
|
@ -8,6 +8,7 @@
|
||||
// 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");
|
||||
|
@ -208,7 +208,7 @@ function initialHTML(doc) {
|
||||
|
||||
// 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";
|
||||
|
@ -21,14 +21,14 @@
|
||||
* 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"
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -122,8 +122,15 @@ let Tabbar = createClass({
|
||||
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,
|
||||
}));
|
||||
},
|
||||
|
||||
|
@ -4,30 +4,31 @@
|
||||
"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;
|
||||
}
|
||||
@ -35,65 +36,67 @@ exports.Framerate = Class({
|
||||
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;
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
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);
|
||||
@ -31,27 +31,24 @@ loader.lazyRequireGetter(this, "ChildProcessActor",
|
||||
* 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);
|
||||
|
||||
@ -426,5 +423,6 @@ exports.Memory = Class({
|
||||
return (this.parent.isRootActor ? this.parent.docShell :
|
||||
this.parent.originalDocShell).now();
|
||||
},
|
||||
};
|
||||
|
||||
});
|
||||
exports.Memory = Memory;
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
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);
|
||||
@ -406,82 +406,83 @@ const ProfilerManager = (function () {
|
||||
/**
|
||||
* 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
|
||||
@ -494,7 +495,7 @@ var Profiler = exports.Profiler = Class({
|
||||
* @param {Array<string>} data.event
|
||||
* @return {object}
|
||||
*/
|
||||
registerEventNotifications: function (data = {}) {
|
||||
registerEventNotifications(data = {}) {
|
||||
let response = [];
|
||||
(data.events || []).forEach(e => {
|
||||
if (!this.subscribedEvents.has(e)) {
|
||||
@ -506,7 +507,7 @@ var Profiler = exports.Profiler = Class({
|
||||
}
|
||||
});
|
||||
return { registered: response };
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes this instance to one of several events defined in
|
||||
@ -515,7 +516,7 @@ var Profiler = exports.Profiler = Class({
|
||||
* @param {Array<string>} data.event
|
||||
* @return {object}
|
||||
*/
|
||||
unregisterEventNotifications: function (data = {}) {
|
||||
unregisterEventNotifications(data = {}) {
|
||||
let response = [];
|
||||
(data.events || []).forEach(e => {
|
||||
if (this.subscribedEvents.has(e)) {
|
||||
@ -527,16 +528,16 @@ var Profiler = exports.Profiler = Class({
|
||||
}
|
||||
});
|
||||
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.
|
||||
@ -572,3 +573,5 @@ function sanitizeHandler(handler, identifier) {
|
||||
return handler.call(this, subject, topic, data);
|
||||
}, identifier);
|
||||
}
|
||||
|
||||
exports.Profiler = Profiler;
|
||||
|
@ -47,20 +47,20 @@ const DRAIN_ALLOCATIONS_TIMEOUT = 2000;
|
||||
* @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.
|
||||
@ -479,7 +479,7 @@ exports.PerformanceRecorder = Class({
|
||||
},
|
||||
|
||||
toString: () => "[object PerformanceRecorder]"
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an object of configurations based off of
|
||||
@ -498,3 +498,5 @@ function getPerformanceRecordingPrefs() {
|
||||
Services.prefs.getIntPref("devtools.performance.memory.max-log-length")
|
||||
};
|
||||
}
|
||||
|
||||
exports.PerformanceRecorder = PerformanceRecorder;
|
||||
|
@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
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);
|
||||
@ -38,25 +38,23 @@ 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._isRecording = false;
|
||||
this._stackFrames = null;
|
||||
this._memory = null;
|
||||
this._framerate = null;
|
||||
this.tabActor = tabActor;
|
||||
|
||||
// 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);
|
||||
},
|
||||
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);
|
||||
}
|
||||
|
||||
Timeline.prototype = {
|
||||
/**
|
||||
* Destroys this actor, stopping recording first.
|
||||
*/
|
||||
@ -357,4 +355,6 @@ exports.Timeline = Class({
|
||||
};
|
||||
}), endTime);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
exports.Timeline = Timeline;
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
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
|
||||
|
@ -15,12 +15,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1290230
|
||||
"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";
|
||||
|
||||
@ -31,6 +27,25 @@ function doCopy(e) {
|
||||
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);
|
||||
|
@ -9928,8 +9928,9 @@ nsDocShell::InternalLoad(nsIURI* aURI,
|
||||
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;
|
||||
@ -10741,16 +10742,17 @@ nsDocShell::InternalLoad(nsIURI* aURI,
|
||||
// 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
|
||||
@ -11973,12 +11975,16 @@ nsDocShell::OnNewURI(nsIURI* aURI, nsIChannel* aChannel,
|
||||
} else if (mOSHE) {
|
||||
mOSHE->SetCacheKey(cacheKey);
|
||||
}
|
||||
|
||||
// Since we're force-reloading, clear all the sub frame history.
|
||||
ClearFrameHistory(mLSHE);
|
||||
ClearFrameHistory(mOSHE);
|
||||
}
|
||||
|
||||
// Clear subframe history on refresh or reload.
|
||||
// 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);
|
||||
}
|
||||
|
@ -927,7 +927,8 @@ protected:
|
||||
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;
|
||||
|
||||
|
@ -121,7 +121,7 @@
|
||||
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');
|
||||
@ -137,11 +137,11 @@
|
||||
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();
|
||||
|
8
docshell/test/navigation/file_bug1375833-frame1.html
Normal file
8
docshell/test/navigation/file_bug1375833-frame1.html
Normal file
@ -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>
|
8
docshell/test/navigation/file_bug1375833-frame2.html
Normal file
8
docshell/test/navigation/file_bug1375833-frame2.html
Normal file
@ -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>
|
22
docshell/test/navigation/file_bug1375833.html
Normal file
22
docshell/test/navigation/file_bug1375833.html
Normal file
@ -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>
|
@ -47,6 +47,9 @@ support-files =
|
||||
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]
|
||||
@ -58,6 +61,7 @@ skip-if = toolkit == "android" || toolkit == "windows" # disabled on Windows bec
|
||||
[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]
|
||||
|
101
docshell/test/navigation/test_bug1375833.html
Normal file
101
docshell/test/navigation/test_bug1375833.html
Normal file
@ -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>
|
@ -29,8 +29,8 @@ function finishTest() {
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -279,9 +279,9 @@ ImageDocument::SetScriptGlobalObject(nsIScriptGlobalObject* aScriptGlobalObject)
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ VideoDocument::SetScriptGlobalObject(nsIScriptGlobalObject* 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"));
|
||||
}
|
||||
|
@ -29,15 +29,19 @@ static inline uint32_t SamplesToFrames(uint32_t aChannels, uint32_t aSamples) {
|
||||
* 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.
|
||||
@ -46,7 +50,7 @@ public:
|
||||
MOZ_ASSERT(!mBuffer && !mSamples,
|
||||
"SetBuffer called twice.");
|
||||
mBuffer = aBuffer;
|
||||
mSamples = FramesToSamples(CHANNELS, aFrames);
|
||||
mSamples = FramesToSamples(mChannels, aFrames);
|
||||
mSampleWriteOffset = 0;
|
||||
}
|
||||
|
||||
@ -58,16 +62,16 @@ public:
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,7 +92,7 @@ public:
|
||||
"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;
|
||||
@ -105,6 +109,7 @@ private:
|
||||
/* 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;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -113,27 +118,32 @@ private:
|
||||
* 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);
|
||||
}
|
||||
|
||||
@ -143,22 +153,24 @@ public:
|
||||
* 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
|
||||
|
@ -65,6 +65,9 @@ AudioConverter::CanWorkInPlace() const
|
||||
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()) {
|
||||
|
@ -131,13 +131,8 @@ public:
|
||||
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);
|
||||
@ -196,6 +191,38 @@ public:
|
||||
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
|
||||
{
|
||||
|
@ -560,6 +560,9 @@ StreamAndPromiseForOperation::StreamAndPromiseForOperation(MediaStream* aStream,
|
||||
|
||||
AudioCallbackDriver::AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl)
|
||||
: GraphDriver(aGraphImpl)
|
||||
, mOuputChannels(mGraphImpl->AudioChannelCount())
|
||||
, mScratchBuffer(mOuputChannels)
|
||||
, mBuffer(mOuputChannels)
|
||||
, mSampleRate(0)
|
||||
, mInputChannels(1)
|
||||
, mIterationDurationMS(MEDIA_GRAPH_TARGET_PERIOD_MS)
|
||||
@ -626,15 +629,14 @@ AudioCallbackDriver::Init()
|
||||
|
||||
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) {
|
||||
@ -923,7 +925,7 @@ AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer,
|
||||
// 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();
|
||||
@ -1010,7 +1012,7 @@ AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer,
|
||||
// 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;
|
||||
{
|
||||
|
@ -470,18 +470,18 @@ private:
|
||||
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;
|
||||
|
@ -453,8 +453,10 @@ public:
|
||||
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
|
||||
{
|
||||
|
@ -6,30 +6,26 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
@ -40,18 +36,25 @@ TEST(AudioBuffers, Test)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -205,8 +205,10 @@ AudioContext::Constructor(const GlobalObject& aGlobal,
|
||||
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;
|
||||
@ -622,7 +624,8 @@ AudioContext::UpdatePannerSource()
|
||||
uint32_t
|
||||
AudioContext::MaxChannelCount() const
|
||||
{
|
||||
return mIsOffline ? mNumberOfChannels : CubebUtils::MaxNumberOfChannels();
|
||||
return std::min<uint32_t>(WebAudioUtils::MaxChannelCount,
|
||||
mIsOffline ? mNumberOfChannels : CubebUtils::MaxNumberOfChannels());
|
||||
}
|
||||
|
||||
uint32_t
|
||||
|
@ -325,7 +325,7 @@ 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)
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "AudioMixer.h"
|
||||
#include "MediaData.h"
|
||||
|
||||
namespace webrtc {
|
||||
class SingleRwFifo;
|
||||
@ -50,6 +51,7 @@ private:
|
||||
// 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;
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "MediaTrackConstraints.h"
|
||||
#include "mtransport/runnable_utils.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "AudioConverter.h"
|
||||
|
||||
// scoped_ptr.h uses FF
|
||||
#ifdef FF
|
||||
@ -63,6 +64,7 @@ AudioOutputObserver::AudioOutputObserver()
|
||||
, 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);
|
||||
@ -101,13 +103,19 @@ 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)) {
|
||||
@ -135,7 +143,7 @@ AudioOutputObserver::InsertFarEnd(const AudioDataValue *aBuffer, uint32_t aFrame
|
||||
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;
|
||||
@ -145,8 +153,14 @@ AudioOutputObserver::InsertFarEnd(const AudioDataValue *aBuffer, uint32_t aFrame
|
||||
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) {
|
||||
|
@ -19,8 +19,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=803225
|
||||
|
||||
//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");
|
||||
|
@ -894,10 +894,19 @@ private:
|
||||
{
|
||||
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);
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
* 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;
|
||||
|
@ -79,4 +79,4 @@ to make sure that mozjs_sys also has its Cargo.lock file updated if needed, henc
|
||||
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
|
||||
|
@ -538,7 +538,7 @@ private:
|
||||
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);
|
||||
|
@ -10,6 +10,7 @@ build = "build.rs"
|
||||
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"
|
||||
@ -29,6 +30,10 @@ 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"}
|
||||
|
@ -8,8 +8,6 @@ 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,
|
||||
@ -65,7 +63,7 @@ pub trait Example {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -113,7 +111,7 @@ pub fn main_wrapper(example: &mut Example,
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
@ -162,19 +160,19 @@ pub fn main_wrapper(example: &mut Example,
|
||||
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,
|
||||
|
@ -12,7 +12,6 @@ mod boilerplate;
|
||||
use boilerplate::{Example, HandyDandyRectBuilder};
|
||||
use std::mem;
|
||||
use webrender::api::*;
|
||||
use webrender::renderer::{ExternalImage, ExternalImageSource, ExternalImageHandler};
|
||||
|
||||
struct ImageGenerator {
|
||||
patterns: [[u8; 3]; 6],
|
||||
@ -57,15 +56,15 @@ impl ImageGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -270,7 +269,7 @@ impl Example for App {
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
143
gfx/webrender/src/debug_server.rs
Normal file
143
gfx/webrender/src/debug_server.rs
Normal file
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -443,7 +443,8 @@ 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 {
|
||||
@ -451,142 +452,148 @@ pub trait NamedTag {
|
||||
}
|
||||
|
||||
#[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 add_marker(&mut self, tag: T) -> GpuMarker
|
||||
where T: NamedTag {
|
||||
fn done_marker(&mut self) {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -594,7 +601,8 @@ 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 => {},
|
||||
}
|
||||
@ -611,18 +619,19 @@ impl<T> 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
|
||||
}
|
||||
@ -643,6 +652,15 @@ impl<T> GpuProfiler<T> {
|
||||
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]
|
||||
@ -689,6 +707,7 @@ impl Drop for GpuMarker {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum VertexUsageHint {
|
||||
Static,
|
||||
@ -805,6 +824,14 @@ impl Device {
|
||||
&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,
|
||||
|
@ -994,22 +994,15 @@ impl Frame {
|
||||
// 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;
|
||||
|
||||
@ -1043,7 +1036,7 @@ impl Frame {
|
||||
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.
|
||||
@ -1056,7 +1049,7 @@ impl Frame {
|
||||
stretched_tile_size,
|
||||
(leftover.width as f32) / tile_size_f32,
|
||||
1.0,
|
||||
repeat_x, repeat_y);
|
||||
shader_repeat_x, shader_repeat_y);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1072,8 +1065,8 @@ impl Frame {
|
||||
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 {
|
||||
@ -1087,8 +1080,8 @@ impl Frame {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1103,16 +1096,16 @@ impl Frame {
|
||||
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,
|
||||
@ -1127,12 +1120,12 @@ impl Frame {
|
||||
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;
|
||||
}
|
||||
|
@ -468,6 +468,10 @@ impl<'a> GpuDataRequest<'a> {
|
||||
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
|
||||
|
@ -2,6 +2,7 @@
|
||||
* 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;
|
||||
@ -185,6 +186,7 @@ impl RendererFrame {
|
||||
}
|
||||
|
||||
pub enum ResultMsg {
|
||||
DebugCommand(DebugCommand),
|
||||
RefreshShader(PathBuf),
|
||||
NewFrame(DocumentId, RendererFrame, TextureUpdateList, BackendProfileCounters),
|
||||
UpdateResources { updates: TextureUpdateList, cancel_rendering: bool },
|
||||
|
@ -12,7 +12,7 @@ but it can also be used as such in a standalone application.
|
||||
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.
|
||||
@ -55,6 +55,8 @@ 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;
|
||||
@ -72,6 +74,7 @@ mod profiler;
|
||||
mod record;
|
||||
mod render_backend;
|
||||
mod render_task;
|
||||
mod renderer;
|
||||
mod resource_cache;
|
||||
mod scene;
|
||||
mod spring;
|
||||
@ -108,8 +111,6 @@ mod platform {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod renderer;
|
||||
|
||||
#[cfg(target_os="macos")]
|
||||
extern crate core_graphics;
|
||||
#[cfg(target_os="macos")]
|
||||
@ -133,12 +134,21 @@ 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;
|
||||
|
||||
|
@ -593,6 +593,8 @@ impl TextRunPrimitiveCpu {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
* 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;
|
||||
@ -36,6 +36,11 @@ trait ProfileCounter {
|
||||
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,
|
||||
@ -43,7 +48,7 @@ pub struct IntProfileCounter {
|
||||
}
|
||||
|
||||
impl IntProfileCounter {
|
||||
fn new(description: &'static str) -> IntProfileCounter {
|
||||
fn new(description: &'static str) -> Self {
|
||||
IntProfileCounter {
|
||||
description,
|
||||
value: 0,
|
||||
@ -84,6 +89,21 @@ impl ProfileCounter for IntProfileCounter {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@ -92,7 +112,7 @@ pub struct ResourceProfileCounter {
|
||||
}
|
||||
|
||||
impl ResourceProfileCounter {
|
||||
fn new(description: &'static str) -> ResourceProfileCounter {
|
||||
fn new(description: &'static str) -> Self {
|
||||
ResourceProfileCounter {
|
||||
description,
|
||||
value: 0,
|
||||
@ -144,7 +164,7 @@ impl<'a> Drop for Timer<'a> {
|
||||
}
|
||||
|
||||
impl TimeProfileCounter {
|
||||
pub fn new(description: &'static str, invert: bool) -> TimeProfileCounter {
|
||||
pub fn new(description: &'static str, invert: bool) -> Self {
|
||||
TimeProfileCounter {
|
||||
description,
|
||||
nanoseconds: 0,
|
||||
@ -212,7 +232,7 @@ pub struct AverageTimeProfileCounter {
|
||||
}
|
||||
|
||||
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,
|
||||
@ -269,6 +289,7 @@ impl ProfileCounter for AverageTimeProfileCounter {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct FrameProfileCounters {
|
||||
pub total_primitives: IntProfileCounter,
|
||||
pub visible_primitives: IntProfileCounter,
|
||||
@ -278,7 +299,7 @@ pub struct FrameProfileCounters {
|
||||
}
|
||||
|
||||
impl FrameProfileCounters {
|
||||
pub fn new() -> FrameProfileCounters {
|
||||
pub fn new() -> Self {
|
||||
FrameProfileCounters {
|
||||
total_primitives: IntProfileCounter::new("Total Primitives"),
|
||||
visible_primitives: IntProfileCounter::new("Visible Primitives"),
|
||||
@ -298,7 +319,7 @@ pub struct TextureCacheProfileCounters {
|
||||
}
|
||||
|
||||
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"),
|
||||
@ -315,7 +336,7 @@ pub struct GpuCacheProfileCounters {
|
||||
}
|
||||
|
||||
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"),
|
||||
@ -349,12 +370,13 @@ pub struct IpcProfileCounters {
|
||||
|
||||
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;
|
||||
@ -367,7 +389,7 @@ impl IpcProfileCounters {
|
||||
}
|
||||
|
||||
impl BackendProfileCounters {
|
||||
pub fn new() -> BackendProfileCounters {
|
||||
pub fn new() -> Self {
|
||||
BackendProfileCounters {
|
||||
total_time: TimeProfileCounter::new("Backend CPU Time", false),
|
||||
resources: ResourceProfileCounters {
|
||||
@ -407,11 +429,11 @@ pub struct RendererProfileCounters {
|
||||
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),
|
||||
@ -428,7 +450,7 @@ impl RendererProfileCounters {
|
||||
}
|
||||
|
||||
impl RendererProfileTimers {
|
||||
pub fn new() -> RendererProfileTimers {
|
||||
pub fn new() -> Self {
|
||||
RendererProfileTimers {
|
||||
cpu_time: TimeProfileCounter::new("Compositor CPU Time", false),
|
||||
gpu_samples: Vec::new(),
|
||||
@ -449,7 +471,7 @@ struct ProfileGraph {
|
||||
}
|
||||
|
||||
impl ProfileGraph {
|
||||
fn new(max_samples: usize) -> ProfileGraph {
|
||||
fn new(max_samples: usize) -> Self {
|
||||
ProfileGraph {
|
||||
max_samples,
|
||||
values: VecDeque::new(),
|
||||
@ -485,10 +507,12 @@ impl ProfileGraph {
|
||||
}
|
||||
|
||||
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);
|
||||
@ -563,7 +587,7 @@ impl ProfileGraph {
|
||||
|
||||
struct GpuFrame {
|
||||
total_time: u64,
|
||||
samples: Vec<GpuSample<GpuProfileTag>>,
|
||||
samples: Vec<GpuTimer<GpuProfileTag>>,
|
||||
}
|
||||
|
||||
struct GpuFrameCollection {
|
||||
@ -571,13 +595,13 @@ struct GpuFrameCollection {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
@ -590,9 +614,11 @@ impl GpuFrameCollection {
|
||||
|
||||
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));
|
||||
@ -654,7 +680,7 @@ pub struct Profiler {
|
||||
}
|
||||
|
||||
impl Profiler {
|
||||
pub fn new() -> Profiler {
|
||||
pub fn new() -> Self {
|
||||
Profiler {
|
||||
x_left: 0.0,
|
||||
y_left: 0.0,
|
||||
@ -668,10 +694,11 @@ impl Profiler {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -733,13 +760,15 @@ impl Profiler {
|
||||
}
|
||||
|
||||
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;
|
||||
@ -754,11 +783,11 @@ impl Profiler {
|
||||
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,
|
||||
@ -778,6 +807,7 @@ impl Profiler {
|
||||
&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(&[
|
||||
@ -785,7 +815,6 @@ impl Profiler {
|
||||
&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(&[
|
||||
@ -799,6 +828,21 @@ impl Profiler {
|
||||
&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);
|
||||
|
@ -461,6 +461,15 @@ impl RenderBackend {
|
||||
// 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()
|
||||
|
@ -248,6 +248,15 @@ impl RenderTask {
|
||||
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));
|
||||
|
@ -9,10 +9,17 @@
|
||||
//!
|
||||
//! [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;
|
||||
@ -27,6 +34,8 @@ 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;
|
||||
@ -41,7 +50,7 @@ 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};
|
||||
@ -80,6 +89,34 @@ const GPU_TAG_PRIM_BORDER_EDGE: GpuProfileTag = GpuProfileTag { label: "BorderEd
|
||||
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 {
|
||||
@ -138,7 +175,6 @@ enum VertexArrayKind {
|
||||
pub enum VertexFormat {
|
||||
PrimitiveInstances,
|
||||
Blur,
|
||||
Clip,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -213,10 +249,10 @@ pub struct GpuProfile {
|
||||
}
|
||||
|
||||
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,
|
||||
@ -730,7 +766,6 @@ fn create_prim_shader(name: &'static str,
|
||||
let vertex_descriptor = match vertex_format {
|
||||
VertexFormat::PrimitiveInstances => DESC_PRIM_INSTANCES,
|
||||
VertexFormat::Blur => DESC_BLUR,
|
||||
VertexFormat::Clip => DESC_CLIP,
|
||||
};
|
||||
|
||||
device.create_program(name,
|
||||
@ -780,6 +815,7 @@ pub enum ReadPixelsFormat {
|
||||
/// RenderBackend.
|
||||
pub struct Renderer {
|
||||
result_rx: Receiver<ResultMsg>,
|
||||
debug_server: DebugServer,
|
||||
device: Device,
|
||||
pending_texture_updates: Vec<TextureUpdateList>,
|
||||
pending_gpu_cache_updates: Vec<GpuCacheUpdateList>,
|
||||
@ -918,6 +954,7 @@ impl Renderer {
|
||||
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(),
|
||||
@ -1337,6 +1374,7 @@ impl Renderer {
|
||||
|
||||
let renderer = Renderer {
|
||||
result_rx,
|
||||
debug_server,
|
||||
device,
|
||||
current_frame: None,
|
||||
pending_texture_updates: Vec::new(),
|
||||
@ -1481,6 +1519,95 @@ impl Renderer {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1507,20 +1634,22 @@ impl Renderer {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1566,11 +1695,15 @@ impl Renderer {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -1721,22 +1854,23 @@ impl Renderer {
|
||||
}
|
||||
|
||||
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
|
||||
@ -1766,7 +1900,7 @@ impl Renderer {
|
||||
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);
|
||||
}
|
||||
@ -1826,11 +1960,11 @@ impl Renderer {
|
||||
};
|
||||
|
||||
// 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
|
||||
@ -1887,18 +2021,19 @@ impl Renderer {
|
||||
}
|
||||
|
||||
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));
|
||||
@ -1989,11 +2124,15 @@ impl Renderer {
|
||||
&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();
|
||||
@ -2003,10 +2142,12 @@ impl Renderer {
|
||||
// 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,
|
||||
@ -2014,8 +2155,9 @@ impl Renderer {
|
||||
}
|
||||
|
||||
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 => {
|
||||
@ -2037,7 +2179,8 @@ impl Renderer {
|
||||
prev_blend_mode = batch.key.blend_mode;
|
||||
}
|
||||
|
||||
self.submit_batch(batch,
|
||||
self.submit_batch(&batch.key,
|
||||
&batch.instances,
|
||||
&projection,
|
||||
render_tasks,
|
||||
render_target,
|
||||
@ -2046,14 +2189,18 @@ impl Renderer {
|
||||
|
||||
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));
|
||||
@ -2130,6 +2277,8 @@ impl Renderer {
|
||||
&textures);
|
||||
}
|
||||
}
|
||||
|
||||
self.gpu_profile.done_sampler();
|
||||
}
|
||||
|
||||
fn update_deferred_resolves(&mut self, frame: &mut Frame) {
|
||||
@ -2159,6 +2308,10 @@ impl Renderer {
|
||||
}
|
||||
};
|
||||
|
||||
// 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."),
|
||||
@ -2600,3 +2753,13 @@ impl Default for RendererOptions {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "debugger"))]
|
||||
pub struct DebugServer;
|
||||
|
||||
#[cfg(not(feature = "debugger"))]
|
||||
impl DebugServer {
|
||||
pub fn new(_: MsgSender<ApiMsg>) -> DebugServer {
|
||||
DebugServer
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user