Merge autoland to central, a=merge

MozReview-Commit-ID: 5tolFjvaHmd
This commit is contained in:
Wes Kocher 2017-08-28 17:38:53 -07:00
commit 02b3fbee7e
267 changed files with 5179 additions and 1625 deletions

View File

@ -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 = [];

View File

@ -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.

View File

@ -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]);

View File

@ -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});

View File

@ -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."
}

View File

@ -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]

View File

@ -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);
});

View File

@ -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]

View File

@ -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>

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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");
});

View File

@ -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;
}

View File

@ -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

View File

@ -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);
});

View File

@ -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/*)

View File

@ -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

View File

@ -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",

View File

@ -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"
]

View File

@ -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",

View File

@ -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"
}

View File

@ -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"

View File

@ -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
}

View File

@ -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;
}
}
}
}

View File

@ -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>

View File

@ -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;

View File

@ -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'.",

View File

@ -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;
}

View File

@ -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;
}
/**

View File

@ -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) {

View 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);

View 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',
)

View 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,
};
}
};

View File

@ -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);

View File

@ -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;

View 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',
)

View 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;

View 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']

View 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',
)

View 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);
};

View File

@ -0,0 +1,6 @@
"use strict";
module.exports = {
// Extend from the shared list of defined globals for mochitests.
"extends": "../../../../.eslintrc.mochitests.js"
};

View 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]

View File

@ -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();
});

View 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);

View File

@ -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() {},

View File

@ -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>

View File

@ -6,6 +6,7 @@ DIRS += [
'boxmodel',
'components',
'computed',
'extensions',
'fonts',
'grids',
'layout',

View File

@ -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");

View File

@ -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";

View File

@ -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"
}
});

View File

@ -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,
}));
},

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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();

View 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>

View 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>

View 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>

View File

@ -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]

View 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>

View File

@ -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() {

View File

@ -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"));
}
}

View File

@ -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"));
}

View File

@ -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

View File

@ -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()) {

View File

@ -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
{

View File

@ -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;
{

View File

@ -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;

View File

@ -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
{

View File

@ -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);
}
}

View File

@ -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

View File

@ -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)

View File

@ -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;

View File

@ -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) {

View File

@ -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");

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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"}

View File

@ -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,

View File

@ -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()))
}
}

View 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,
});
}
}
}

View File

@ -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,

View File

@ -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;
}

View File

@ -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

View File

@ -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 },

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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()

View File

@ -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));

View File

@ -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