mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 03:15:11 +00:00
Merge mozilla-central to autoland. r=merge a=merge
This commit is contained in:
commit
443416f881
5
accessible/tests/crashtests/1072792.xhtml
Normal file
5
accessible/tests/crashtests/1072792.xhtml
Normal file
@ -0,0 +1,5 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<body onload="document.body.appendChild(x);">
|
||||
<table><tbody><div id="x"></div></tbody></table>
|
||||
</body>
|
||||
</html>
|
21
accessible/tests/crashtests/884202.html
Normal file
21
accessible/tests/crashtests/884202.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
function f()
|
||||
{
|
||||
window.removeEventListener("pagehide", f, false);
|
||||
var root = document.documentElement;
|
||||
document.removeChild(root);
|
||||
document.appendChild(root);
|
||||
}
|
||||
|
||||
function boom()
|
||||
{
|
||||
window.addEventListener("pagehide", f, false);
|
||||
window.location = "data:text/html,4";
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="boom();"></body>
|
||||
</html>
|
15
accessible/tests/crashtests/893515.html
Normal file
15
accessible/tests/crashtests/893515.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script>
|
||||
function boom()
|
||||
{
|
||||
document.designMode = 'on';
|
||||
var otherDoc = document.implementation.createDocument("", "", null);
|
||||
otherDoc.adoptNode(document.getElementById("t"));
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="setTimeout(boom, 0);"><textarea id="t"></textarea></body>
|
||||
</html>
|
@ -1,5 +1,9 @@
|
||||
load 448064.xhtml # This test instantiates a11y, so be careful about adding tests before it
|
||||
load 471493.xul
|
||||
asserts-if(!browserIsRemote,2) load 884202.html
|
||||
load 893515.html
|
||||
load 1072792.xhtml
|
||||
|
||||
# last_test_to_unload_testsuite.xul MUST be the last test in the list because it
|
||||
# is responsible for shutting down accessibility service affecting later tests.
|
||||
load last_test_to_unload_testsuite.xul
|
||||
|
@ -61,6 +61,7 @@ subsuite = clipboard
|
||||
skip-if = true # temporarily disabled for breaking the treeview - bug 658744
|
||||
[browser_sort_in_library.js]
|
||||
[browser_stayopenmenu.js]
|
||||
skip-if = os == "mac" && debug # bug 1400323
|
||||
[browser_toolbar_drop_text.js]
|
||||
[browser_toolbar_overflow.js]
|
||||
[browser_toolbarbutton_menu_context.js]
|
||||
|
@ -217,6 +217,8 @@ this.SiteDataManager = {
|
||||
cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(null, "browser:purge-domain-data", principal.URI.host);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -32,7 +32,8 @@ function promiseStateChangeURI() {
|
||||
if (!(flags & docStart) || !webProgress.isTopLevel)
|
||||
return;
|
||||
|
||||
if (req.originalURI.spec == "about:blank")
|
||||
let spec = req.originalURI.spec;
|
||||
if (spec == "about:blank")
|
||||
return;
|
||||
|
||||
gBrowser.removeProgressListener(listener);
|
||||
@ -44,7 +45,7 @@ function promiseStateChangeURI() {
|
||||
req.cancel(Components.results.NS_ERROR_FAILURE);
|
||||
|
||||
executeSoon(() => {
|
||||
resolve(req.originalURI.spec);
|
||||
resolve(spec);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -82,7 +82,8 @@ function promiseStateChangeURI() {
|
||||
if (!(flags & docStart) || !webProgress.isTopLevel)
|
||||
return;
|
||||
|
||||
if (req.originalURI.spec == "about:blank")
|
||||
let spec = req.originalURI.spec;
|
||||
if (spec == "about:blank")
|
||||
return;
|
||||
|
||||
gBrowser.removeProgressListener(listener);
|
||||
@ -94,7 +95,7 @@ function promiseStateChangeURI() {
|
||||
req.cancel(Components.results.NS_ERROR_FAILURE);
|
||||
|
||||
executeSoon(() => {
|
||||
resolve(req.originalURI.spec);
|
||||
resolve(spec);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -17,8 +17,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Utils",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
|
||||
"resource://gre/modules/sessionstore/PrivacyLevel.jsm");
|
||||
|
||||
// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision.
|
||||
const MAX_EXPIRY = Math.pow(2, 62);
|
||||
const MAX_EXPIRY = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
/**
|
||||
* The external API implemented by the SessionCookies module.
|
||||
|
2
config/external/nspr/pr/moz.build
vendored
2
config/external/nspr/pr/moz.build
vendored
@ -40,7 +40,7 @@ elif CONFIG['OS_TARGET'] in ('FreeBSD', 'OpenBSD', 'NetBSD'):
|
||||
SOURCES += ['/nsprpub/pr/src/md/unix/%s.c' % CONFIG['OS_TARGET'].lower()]
|
||||
elif CONFIG['OS_TARGET'] == 'Darwin':
|
||||
OS_LIBS += ['-framework CoreServices']
|
||||
if CONFIG['HOST_MAJOR_VERSION'] == '15':
|
||||
if CONFIG['HOST_MAJOR_VERSION'] >= '15':
|
||||
DEFINES.update(
|
||||
HAS_CONNECTX=True,
|
||||
)
|
||||
|
111
devtools/client/aboutdebugging/components/Aboutdebugging.js
Normal file
111
devtools/client/aboutdebugging/components/Aboutdebugging.js
Normal file
@ -0,0 +1,111 @@
|
||||
/* 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-env browser */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { createFactory, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const Services = require("Services");
|
||||
|
||||
const PanelMenu = createFactory(require("./PanelMenu"));
|
||||
|
||||
loader.lazyGetter(this, "AddonsPanel",
|
||||
() => createFactory(require("./addons/Panel")));
|
||||
loader.lazyGetter(this, "TabsPanel",
|
||||
() => createFactory(require("./tabs/Panel")));
|
||||
loader.lazyGetter(this, "WorkersPanel",
|
||||
() => createFactory(require("./workers/Panel")));
|
||||
|
||||
loader.lazyRequireGetter(this, "DebuggerClient",
|
||||
"devtools/shared/client/debugger-client", true);
|
||||
loader.lazyRequireGetter(this, "Telemetry",
|
||||
"devtools/client/shared/telemetry");
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const panels = [{
|
||||
id: "addons",
|
||||
name: Strings.GetStringFromName("addons"),
|
||||
icon: "chrome://devtools/skin/images/debugging-addons.svg",
|
||||
component: AddonsPanel
|
||||
}, {
|
||||
id: "tabs",
|
||||
name: Strings.GetStringFromName("tabs"),
|
||||
icon: "chrome://devtools/skin/images/debugging-tabs.svg",
|
||||
component: TabsPanel
|
||||
}, {
|
||||
id: "workers",
|
||||
name: Strings.GetStringFromName("workers"),
|
||||
icon: "chrome://devtools/skin/images/debugging-workers.svg",
|
||||
component: WorkersPanel
|
||||
}];
|
||||
|
||||
const defaultPanelId = "addons";
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "AboutDebuggingApp",
|
||||
|
||||
propTypes: {
|
||||
client: PropTypes.instanceOf(DebuggerClient).isRequired,
|
||||
telemetry: PropTypes.instanceOf(Telemetry).isRequired
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
selectedPanelId: defaultPanelId
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener("hashchange", this.onHashChange);
|
||||
this.onHashChange();
|
||||
this.props.telemetry.toolOpened("aboutdebugging");
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("hashchange", this.onHashChange);
|
||||
this.props.telemetry.toolClosed("aboutdebugging");
|
||||
this.props.telemetry.destroy();
|
||||
},
|
||||
|
||||
onHashChange() {
|
||||
this.setState({
|
||||
selectedPanelId: window.location.hash.substr(1) || defaultPanelId
|
||||
});
|
||||
},
|
||||
|
||||
selectPanel(panelId) {
|
||||
window.location.hash = "#" + panelId;
|
||||
},
|
||||
|
||||
render() {
|
||||
let { client } = this.props;
|
||||
let { selectedPanelId } = this.state;
|
||||
let selectPanel = this.selectPanel;
|
||||
let selectedPanel = panels.find(p => p.id == selectedPanelId);
|
||||
let panel;
|
||||
|
||||
if (selectedPanel) {
|
||||
panel = selectedPanel.component({ client, id: selectedPanel.id });
|
||||
} else {
|
||||
panel = (
|
||||
dom.div({ className: "error-page" },
|
||||
dom.h1({ className: "header-name" },
|
||||
Strings.GetStringFromName("pageNotFound")
|
||||
),
|
||||
dom.h4({ className: "error-page-details" },
|
||||
Strings.formatStringFromName("doesNotExist", [selectedPanelId], 1))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return dom.div({ className: "app" },
|
||||
PanelMenu({ panels, selectedPanelId, selectPanel }),
|
||||
dom.div({ className: "main-content" }, panel)
|
||||
);
|
||||
}
|
||||
});
|
41
devtools/client/aboutdebugging/components/PanelMenu.js
Normal file
41
devtools/client/aboutdebugging/components/PanelMenu.js
Normal file
@ -0,0 +1,41 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const PanelMenuEntry = createFactory(require("./PanelMenuEntry"));
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "PanelMenu",
|
||||
|
||||
propTypes: {
|
||||
panels: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
icon: PropTypes.string.isRequired,
|
||||
component: PropTypes.func.isRequired
|
||||
})).isRequired,
|
||||
selectPanel: PropTypes.func.isRequired,
|
||||
selectedPanelId: PropTypes.string
|
||||
},
|
||||
|
||||
render() {
|
||||
let { panels, selectedPanelId, selectPanel } = this.props;
|
||||
let panelLinks = panels.map(({ id, name, icon }) => {
|
||||
let selected = id == selectedPanelId;
|
||||
return PanelMenuEntry({
|
||||
id,
|
||||
name,
|
||||
icon,
|
||||
selected,
|
||||
selectPanel
|
||||
});
|
||||
});
|
||||
|
||||
// "categories" id used for styling purposes
|
||||
return dom.div({ id: "categories", role: "tablist" }, panelLinks);
|
||||
},
|
||||
});
|
@ -1,111 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { createFactory, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const Services = require("Services");
|
||||
|
||||
const PanelMenu = createFactory(require("./panel-menu"));
|
||||
|
||||
loader.lazyGetter(this, "AddonsPanel",
|
||||
() => createFactory(require("./addons/panel")));
|
||||
loader.lazyGetter(this, "TabsPanel",
|
||||
() => createFactory(require("./tabs/panel")));
|
||||
loader.lazyGetter(this, "WorkersPanel",
|
||||
() => createFactory(require("./workers/panel")));
|
||||
|
||||
loader.lazyRequireGetter(this, "DebuggerClient",
|
||||
"devtools/shared/client/debugger-client", true);
|
||||
loader.lazyRequireGetter(this, "Telemetry",
|
||||
"devtools/client/shared/telemetry");
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const panels = [{
|
||||
id: "addons",
|
||||
name: Strings.GetStringFromName("addons"),
|
||||
icon: "chrome://devtools/skin/images/debugging-addons.svg",
|
||||
component: AddonsPanel
|
||||
}, {
|
||||
id: "tabs",
|
||||
name: Strings.GetStringFromName("tabs"),
|
||||
icon: "chrome://devtools/skin/images/debugging-tabs.svg",
|
||||
component: TabsPanel
|
||||
}, {
|
||||
id: "workers",
|
||||
name: Strings.GetStringFromName("workers"),
|
||||
icon: "chrome://devtools/skin/images/debugging-workers.svg",
|
||||
component: WorkersPanel
|
||||
}];
|
||||
|
||||
const defaultPanelId = "addons";
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "AboutDebuggingApp",
|
||||
|
||||
propTypes: {
|
||||
client: PropTypes.instanceOf(DebuggerClient).isRequired,
|
||||
telemetry: PropTypes.instanceOf(Telemetry).isRequired
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
selectedPanelId: defaultPanelId
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener("hashchange", this.onHashChange);
|
||||
this.onHashChange();
|
||||
this.props.telemetry.toolOpened("aboutdebugging");
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("hashchange", this.onHashChange);
|
||||
this.props.telemetry.toolClosed("aboutdebugging");
|
||||
this.props.telemetry.destroy();
|
||||
},
|
||||
|
||||
onHashChange() {
|
||||
this.setState({
|
||||
selectedPanelId: window.location.hash.substr(1) || defaultPanelId
|
||||
});
|
||||
},
|
||||
|
||||
selectPanel(panelId) {
|
||||
window.location.hash = "#" + panelId;
|
||||
},
|
||||
|
||||
render() {
|
||||
let { client } = this.props;
|
||||
let { selectedPanelId } = this.state;
|
||||
let selectPanel = this.selectPanel;
|
||||
let selectedPanel = panels.find(p => p.id == selectedPanelId);
|
||||
let panel;
|
||||
|
||||
if (selectedPanel) {
|
||||
panel = selectedPanel.component({ client, id: selectedPanel.id });
|
||||
} else {
|
||||
panel = (
|
||||
dom.div({ className: "error-page" },
|
||||
dom.h1({ className: "header-name" },
|
||||
Strings.GetStringFromName("pageNotFound")
|
||||
),
|
||||
dom.h4({ className: "error-page-details" },
|
||||
Strings.formatStringFromName("doesNotExist", [selectedPanelId], 1))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return dom.div({ className: "app" },
|
||||
PanelMenu({ panels, selectedPanelId, selectPanel }),
|
||||
dom.div({ className: "main-content" }, panel)
|
||||
);
|
||||
}
|
||||
});
|
113
devtools/client/aboutdebugging/components/addons/Controls.js
Normal file
113
devtools/client/aboutdebugging/components/addons/Controls.js
Normal file
@ -0,0 +1,113 @@
|
||||
/* 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-env browser */
|
||||
/* globals AddonManager */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyImporter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const { createFactory, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const Services = require("Services");
|
||||
const AddonsInstallError = createFactory(require("./InstallError"));
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const MORE_INFO_URL = "https://developer.mozilla.org/docs/Tools" +
|
||||
"/about:debugging#Enabling_add-on_debugging";
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "AddonsControls",
|
||||
|
||||
propTypes: {
|
||||
debugDisabled: PropTypes.bool
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
installError: null,
|
||||
};
|
||||
},
|
||||
|
||||
onEnableAddonDebuggingChange(event) {
|
||||
let enabled = event.target.checked;
|
||||
Services.prefs.setBoolPref("devtools.chrome.enabled", enabled);
|
||||
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", enabled);
|
||||
},
|
||||
|
||||
loadAddonFromFile() {
|
||||
this.setState({ installError: null });
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window,
|
||||
Strings.GetStringFromName("selectAddonFromFile2"),
|
||||
Ci.nsIFilePicker.modeOpen);
|
||||
fp.open(res => {
|
||||
if (res == Ci.nsIFilePicker.returnCancel || !fp.file) {
|
||||
return;
|
||||
}
|
||||
let file = fp.file;
|
||||
// AddonManager.installTemporaryAddon accepts either
|
||||
// addon directory or final xpi file.
|
||||
if (!file.isDirectory() && !file.leafName.endsWith(".xpi")) {
|
||||
file = file.parent;
|
||||
}
|
||||
|
||||
this.installAddon(file);
|
||||
});
|
||||
},
|
||||
|
||||
retryInstall() {
|
||||
this.setState({ installError: null });
|
||||
this.installAddon(this.state.lastInstallErrorFile);
|
||||
},
|
||||
|
||||
installAddon(file) {
|
||||
AddonManager.installTemporaryAddon(file)
|
||||
.then(() => {
|
||||
this.setState({ lastInstallErrorFile: null });
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
this.setState({ installError: e.message, lastInstallErrorFile: file });
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
let { debugDisabled } = this.props;
|
||||
|
||||
return dom.div({ className: "addons-top" },
|
||||
dom.div({ className: "addons-controls" },
|
||||
dom.div({ className: "addons-options toggle-container-with-text" },
|
||||
dom.input({
|
||||
id: "enable-addon-debugging",
|
||||
type: "checkbox",
|
||||
checked: !debugDisabled,
|
||||
onChange: this.onEnableAddonDebuggingChange,
|
||||
role: "checkbox",
|
||||
}),
|
||||
dom.label({
|
||||
className: "addons-debugging-label",
|
||||
htmlFor: "enable-addon-debugging",
|
||||
title: Strings.GetStringFromName("addonDebugging.tooltip")
|
||||
}, Strings.GetStringFromName("addonDebugging.label")),
|
||||
dom.a({ href: MORE_INFO_URL, target: "_blank" },
|
||||
Strings.GetStringFromName("addonDebugging.learnMore")
|
||||
),
|
||||
),
|
||||
dom.button({
|
||||
id: "load-addon-from-file",
|
||||
onClick: this.loadAddonFromFile,
|
||||
}, Strings.GetStringFromName("loadTemporaryAddon"))
|
||||
),
|
||||
AddonsInstallError({
|
||||
error: this.state.installError,
|
||||
retryInstall: this.retryInstall,
|
||||
}));
|
||||
}
|
||||
});
|
180
devtools/client/aboutdebugging/components/addons/Panel.js
Normal file
180
devtools/client/aboutdebugging/components/addons/Panel.js
Normal file
@ -0,0 +1,180 @@
|
||||
/* 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 { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
|
||||
const { Management } = require("resource://gre/modules/Extension.jsm");
|
||||
const { createFactory, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const Services = require("Services");
|
||||
|
||||
const AddonsControls = createFactory(require("./Controls"));
|
||||
const AddonTarget = createFactory(require("./Target"));
|
||||
const PanelHeader = createFactory(require("../PanelHeader"));
|
||||
const TargetList = createFactory(require("../TargetList"));
|
||||
|
||||
loader.lazyRequireGetter(this, "DebuggerClient",
|
||||
"devtools/shared/client/debugger-client", true);
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
|
||||
const CHROME_ENABLED_PREF = "devtools.chrome.enabled";
|
||||
const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
|
||||
const WEB_EXT_URL = "https://developer.mozilla.org/Add-ons" +
|
||||
"/WebExtensions/Getting_started_with_web-ext";
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "AddonsPanel",
|
||||
|
||||
propTypes: {
|
||||
client: PropTypes.instanceOf(DebuggerClient).isRequired,
|
||||
id: PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
extensions: [],
|
||||
debugDisabled: false,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
AddonManager.addAddonListener(this);
|
||||
// Listen to startup since that's when errors and warnings
|
||||
// get populated on the extension.
|
||||
Management.on("startup", this.updateAddonsList);
|
||||
|
||||
Services.prefs.addObserver(CHROME_ENABLED_PREF,
|
||||
this.updateDebugStatus);
|
||||
Services.prefs.addObserver(REMOTE_ENABLED_PREF,
|
||||
this.updateDebugStatus);
|
||||
|
||||
this.updateDebugStatus();
|
||||
this.updateAddonsList();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
AddonManager.removeAddonListener(this);
|
||||
Management.off("startup", this.updateAddonsList);
|
||||
|
||||
Services.prefs.removeObserver(CHROME_ENABLED_PREF,
|
||||
this.updateDebugStatus);
|
||||
Services.prefs.removeObserver(REMOTE_ENABLED_PREF,
|
||||
this.updateDebugStatus);
|
||||
},
|
||||
|
||||
updateDebugStatus() {
|
||||
let debugDisabled =
|
||||
!Services.prefs.getBoolPref(CHROME_ENABLED_PREF) ||
|
||||
!Services.prefs.getBoolPref(REMOTE_ENABLED_PREF);
|
||||
|
||||
this.setState({ debugDisabled });
|
||||
},
|
||||
|
||||
updateAddonsList() {
|
||||
this.props.client.listAddons()
|
||||
.then(({addons}) => {
|
||||
let extensions = addons.filter(addon => addon.debuggable).map(addon => {
|
||||
return {
|
||||
name: addon.name,
|
||||
icon: addon.iconURL || ExtensionIcon,
|
||||
addonID: addon.id,
|
||||
addonActor: addon.actor,
|
||||
temporarilyInstalled: addon.temporarilyInstalled,
|
||||
url: addon.url,
|
||||
manifestURL: addon.manifestURL,
|
||||
warnings: addon.warnings,
|
||||
};
|
||||
});
|
||||
|
||||
this.setState({ extensions });
|
||||
}, error => {
|
||||
throw new Error("Client error while listing addons: " + error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Mandatory callback as AddonManager listener.
|
||||
*/
|
||||
onInstalled() {
|
||||
this.updateAddonsList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Mandatory callback as AddonManager listener.
|
||||
*/
|
||||
onUninstalled() {
|
||||
this.updateAddonsList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Mandatory callback as AddonManager listener.
|
||||
*/
|
||||
onEnabled() {
|
||||
this.updateAddonsList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Mandatory callback as AddonManager listener.
|
||||
*/
|
||||
onDisabled() {
|
||||
this.updateAddonsList();
|
||||
},
|
||||
|
||||
render() {
|
||||
let { client, id } = this.props;
|
||||
let { debugDisabled, extensions: targets } = this.state;
|
||||
let installedName = Strings.GetStringFromName("extensions");
|
||||
let temporaryName = Strings.GetStringFromName("temporaryExtensions");
|
||||
let targetClass = AddonTarget;
|
||||
|
||||
const installedTargets = targets.filter((target) => !target.temporarilyInstalled);
|
||||
const temporaryTargets = targets.filter((target) => target.temporarilyInstalled);
|
||||
|
||||
return dom.div({
|
||||
id: id + "-panel",
|
||||
className: "panel",
|
||||
role: "tabpanel",
|
||||
"aria-labelledby": id + "-header"
|
||||
},
|
||||
PanelHeader({
|
||||
id: id + "-header",
|
||||
name: Strings.GetStringFromName("addons")
|
||||
}),
|
||||
AddonsControls({ debugDisabled }),
|
||||
dom.div({ id: "temporary-addons" },
|
||||
TargetList({
|
||||
id: "temporary-extensions",
|
||||
name: temporaryName,
|
||||
targets: temporaryTargets,
|
||||
client,
|
||||
debugDisabled,
|
||||
targetClass,
|
||||
sort: true
|
||||
}),
|
||||
dom.div({ className: "addons-tip"},
|
||||
dom.span({
|
||||
className: "addons-web-ext-tip",
|
||||
}, Strings.GetStringFromName("webExtTip")),
|
||||
dom.a({ href: WEB_EXT_URL, target: "_blank" },
|
||||
Strings.GetStringFromName("webExtTip.learnMore")
|
||||
)
|
||||
)
|
||||
),
|
||||
dom.div({ id: "addons" },
|
||||
TargetList({
|
||||
id: "extensions",
|
||||
name: installedName,
|
||||
targets: installedTargets,
|
||||
client,
|
||||
debugDisabled,
|
||||
targetClass,
|
||||
sort: true
|
||||
})
|
||||
));
|
||||
}
|
||||
});
|
@ -1,113 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* eslint-env browser */
|
||||
/* globals AddonManager */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyImporter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const { createFactory, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const Services = require("Services");
|
||||
const AddonsInstallError = createFactory(require("./install-error"));
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const MORE_INFO_URL = "https://developer.mozilla.org/docs/Tools" +
|
||||
"/about:debugging#Enabling_add-on_debugging";
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "AddonsControls",
|
||||
|
||||
propTypes: {
|
||||
debugDisabled: PropTypes.bool
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
installError: null,
|
||||
};
|
||||
},
|
||||
|
||||
onEnableAddonDebuggingChange(event) {
|
||||
let enabled = event.target.checked;
|
||||
Services.prefs.setBoolPref("devtools.chrome.enabled", enabled);
|
||||
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", enabled);
|
||||
},
|
||||
|
||||
loadAddonFromFile() {
|
||||
this.setState({ installError: null });
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window,
|
||||
Strings.GetStringFromName("selectAddonFromFile2"),
|
||||
Ci.nsIFilePicker.modeOpen);
|
||||
fp.open(res => {
|
||||
if (res == Ci.nsIFilePicker.returnCancel || !fp.file) {
|
||||
return;
|
||||
}
|
||||
let file = fp.file;
|
||||
// AddonManager.installTemporaryAddon accepts either
|
||||
// addon directory or final xpi file.
|
||||
if (!file.isDirectory() && !file.leafName.endsWith(".xpi")) {
|
||||
file = file.parent;
|
||||
}
|
||||
|
||||
this.installAddon(file);
|
||||
});
|
||||
},
|
||||
|
||||
retryInstall() {
|
||||
this.setState({ installError: null });
|
||||
this.installAddon(this.state.lastInstallErrorFile);
|
||||
},
|
||||
|
||||
installAddon(file) {
|
||||
AddonManager.installTemporaryAddon(file)
|
||||
.then(() => {
|
||||
this.setState({ lastInstallErrorFile: null });
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
this.setState({ installError: e.message, lastInstallErrorFile: file });
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
let { debugDisabled } = this.props;
|
||||
|
||||
return dom.div({ className: "addons-top" },
|
||||
dom.div({ className: "addons-controls" },
|
||||
dom.div({ className: "addons-options toggle-container-with-text" },
|
||||
dom.input({
|
||||
id: "enable-addon-debugging",
|
||||
type: "checkbox",
|
||||
checked: !debugDisabled,
|
||||
onChange: this.onEnableAddonDebuggingChange,
|
||||
role: "checkbox",
|
||||
}),
|
||||
dom.label({
|
||||
className: "addons-debugging-label",
|
||||
htmlFor: "enable-addon-debugging",
|
||||
title: Strings.GetStringFromName("addonDebugging.tooltip")
|
||||
}, Strings.GetStringFromName("addonDebugging.label")),
|
||||
dom.a({ href: MORE_INFO_URL, target: "_blank" },
|
||||
Strings.GetStringFromName("addonDebugging.learnMore")
|
||||
),
|
||||
),
|
||||
dom.button({
|
||||
id: "load-addon-from-file",
|
||||
onClick: this.loadAddonFromFile,
|
||||
}, Strings.GetStringFromName("loadTemporaryAddon"))
|
||||
),
|
||||
AddonsInstallError({
|
||||
error: this.state.installError,
|
||||
retryInstall: this.retryInstall,
|
||||
}));
|
||||
}
|
||||
});
|
@ -3,8 +3,8 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'controls.js',
|
||||
'install-error.js',
|
||||
'panel.js',
|
||||
'target.js',
|
||||
'Controls.js',
|
||||
'InstallError.js',
|
||||
'Panel.js',
|
||||
'Target.js',
|
||||
)
|
||||
|
@ -1,180 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
|
||||
const { Management } = require("resource://gre/modules/Extension.jsm");
|
||||
const { createFactory, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const Services = require("Services");
|
||||
|
||||
const AddonsControls = createFactory(require("./controls"));
|
||||
const AddonTarget = createFactory(require("./target"));
|
||||
const PanelHeader = createFactory(require("../panel-header"));
|
||||
const TargetList = createFactory(require("../target-list"));
|
||||
|
||||
loader.lazyRequireGetter(this, "DebuggerClient",
|
||||
"devtools/shared/client/debugger-client", true);
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
|
||||
const CHROME_ENABLED_PREF = "devtools.chrome.enabled";
|
||||
const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
|
||||
const WEB_EXT_URL = "https://developer.mozilla.org/Add-ons" +
|
||||
"/WebExtensions/Getting_started_with_web-ext";
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "AddonsPanel",
|
||||
|
||||
propTypes: {
|
||||
client: PropTypes.instanceOf(DebuggerClient).isRequired,
|
||||
id: PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
extensions: [],
|
||||
debugDisabled: false,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
AddonManager.addAddonListener(this);
|
||||
// Listen to startup since that's when errors and warnings
|
||||
// get populated on the extension.
|
||||
Management.on("startup", this.updateAddonsList);
|
||||
|
||||
Services.prefs.addObserver(CHROME_ENABLED_PREF,
|
||||
this.updateDebugStatus);
|
||||
Services.prefs.addObserver(REMOTE_ENABLED_PREF,
|
||||
this.updateDebugStatus);
|
||||
|
||||
this.updateDebugStatus();
|
||||
this.updateAddonsList();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
AddonManager.removeAddonListener(this);
|
||||
Management.off("startup", this.updateAddonsList);
|
||||
|
||||
Services.prefs.removeObserver(CHROME_ENABLED_PREF,
|
||||
this.updateDebugStatus);
|
||||
Services.prefs.removeObserver(REMOTE_ENABLED_PREF,
|
||||
this.updateDebugStatus);
|
||||
},
|
||||
|
||||
updateDebugStatus() {
|
||||
let debugDisabled =
|
||||
!Services.prefs.getBoolPref(CHROME_ENABLED_PREF) ||
|
||||
!Services.prefs.getBoolPref(REMOTE_ENABLED_PREF);
|
||||
|
||||
this.setState({ debugDisabled });
|
||||
},
|
||||
|
||||
updateAddonsList() {
|
||||
this.props.client.listAddons()
|
||||
.then(({addons}) => {
|
||||
let extensions = addons.filter(addon => addon.debuggable).map(addon => {
|
||||
return {
|
||||
name: addon.name,
|
||||
icon: addon.iconURL || ExtensionIcon,
|
||||
addonID: addon.id,
|
||||
addonActor: addon.actor,
|
||||
temporarilyInstalled: addon.temporarilyInstalled,
|
||||
url: addon.url,
|
||||
manifestURL: addon.manifestURL,
|
||||
warnings: addon.warnings,
|
||||
};
|
||||
});
|
||||
|
||||
this.setState({ extensions });
|
||||
}, error => {
|
||||
throw new Error("Client error while listing addons: " + error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Mandatory callback as AddonManager listener.
|
||||
*/
|
||||
onInstalled() {
|
||||
this.updateAddonsList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Mandatory callback as AddonManager listener.
|
||||
*/
|
||||
onUninstalled() {
|
||||
this.updateAddonsList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Mandatory callback as AddonManager listener.
|
||||
*/
|
||||
onEnabled() {
|
||||
this.updateAddonsList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Mandatory callback as AddonManager listener.
|
||||
*/
|
||||
onDisabled() {
|
||||
this.updateAddonsList();
|
||||
},
|
||||
|
||||
render() {
|
||||
let { client, id } = this.props;
|
||||
let { debugDisabled, extensions: targets } = this.state;
|
||||
let installedName = Strings.GetStringFromName("extensions");
|
||||
let temporaryName = Strings.GetStringFromName("temporaryExtensions");
|
||||
let targetClass = AddonTarget;
|
||||
|
||||
const installedTargets = targets.filter((target) => !target.temporarilyInstalled);
|
||||
const temporaryTargets = targets.filter((target) => target.temporarilyInstalled);
|
||||
|
||||
return dom.div({
|
||||
id: id + "-panel",
|
||||
className: "panel",
|
||||
role: "tabpanel",
|
||||
"aria-labelledby": id + "-header"
|
||||
},
|
||||
PanelHeader({
|
||||
id: id + "-header",
|
||||
name: Strings.GetStringFromName("addons")
|
||||
}),
|
||||
AddonsControls({ debugDisabled }),
|
||||
dom.div({ id: "temporary-addons" },
|
||||
TargetList({
|
||||
id: "temporary-extensions",
|
||||
name: temporaryName,
|
||||
targets: temporaryTargets,
|
||||
client,
|
||||
debugDisabled,
|
||||
targetClass,
|
||||
sort: true
|
||||
}),
|
||||
dom.div({ className: "addons-tip"},
|
||||
dom.span({
|
||||
className: "addons-web-ext-tip",
|
||||
}, Strings.GetStringFromName("webExtTip")),
|
||||
dom.a({ href: WEB_EXT_URL, target: "_blank" },
|
||||
Strings.GetStringFromName("webExtTip.learnMore")
|
||||
)
|
||||
)
|
||||
),
|
||||
dom.div({ id: "addons" },
|
||||
TargetList({
|
||||
id: "extensions",
|
||||
name: installedName,
|
||||
targets: installedTargets,
|
||||
client,
|
||||
debugDisabled,
|
||||
targetClass,
|
||||
sort: true
|
||||
})
|
||||
));
|
||||
}
|
||||
});
|
@ -9,9 +9,9 @@ DIRS += [
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'aboutdebugging.js',
|
||||
'panel-header.js',
|
||||
'panel-menu-entry.js',
|
||||
'panel-menu.js',
|
||||
'target-list.js',
|
||||
'Aboutdebugging.js',
|
||||
'PanelHeader.js',
|
||||
'PanelMenu.js',
|
||||
'PanelMenuEntry.js',
|
||||
'TargetList.js',
|
||||
)
|
||||
|
@ -1,41 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const PanelMenuEntry = createFactory(require("./panel-menu-entry"));
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "PanelMenu",
|
||||
|
||||
propTypes: {
|
||||
panels: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
icon: PropTypes.string.isRequired,
|
||||
component: PropTypes.func.isRequired
|
||||
})).isRequired,
|
||||
selectPanel: PropTypes.func.isRequired,
|
||||
selectedPanelId: PropTypes.string
|
||||
},
|
||||
|
||||
render() {
|
||||
let { panels, selectedPanelId, selectPanel } = this.props;
|
||||
let panelLinks = panels.map(({ id, name, icon }) => {
|
||||
let selected = id == selectedPanelId;
|
||||
return PanelMenuEntry({
|
||||
id,
|
||||
name,
|
||||
icon,
|
||||
selected,
|
||||
selectPanel
|
||||
});
|
||||
});
|
||||
|
||||
// "categories" id used for styling purposes
|
||||
return dom.div({ id: "categories", role: "tablist" }, panelLinks);
|
||||
},
|
||||
});
|
98
devtools/client/aboutdebugging/components/tabs/Panel.js
Normal file
98
devtools/client/aboutdebugging/components/tabs/Panel.js
Normal file
@ -0,0 +1,98 @@
|
||||
/* 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-env browser */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const Services = require("Services");
|
||||
|
||||
const PanelHeader = createFactory(require("../PanelHeader"));
|
||||
const TargetList = createFactory(require("../TargetList"));
|
||||
const TabTarget = createFactory(require("./Target"));
|
||||
|
||||
loader.lazyRequireGetter(this, "DebuggerClient",
|
||||
"devtools/shared/client/debugger-client", true);
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "TabsPanel",
|
||||
|
||||
propTypes: {
|
||||
client: PropTypes.instanceOf(DebuggerClient).isRequired,
|
||||
id: PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
tabs: []
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
let { client } = this.props;
|
||||
client.addListener("tabListChanged", this.update);
|
||||
this.update();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
let { client } = this.props;
|
||||
client.removeListener("tabListChanged", this.update);
|
||||
},
|
||||
|
||||
update() {
|
||||
this.props.client.mainRoot.listTabs().then(({ tabs }) => {
|
||||
// Filter out closed tabs (represented as `null`).
|
||||
tabs = tabs.filter(tab => !!tab);
|
||||
tabs.forEach(tab => {
|
||||
// FIXME Also try to fetch low-res favicon. But we should use actor
|
||||
// support for this to get the high-res one (bug 1061654).
|
||||
let url = new URL(tab.url);
|
||||
if (url.protocol.startsWith("http")) {
|
||||
let prePath = url.origin;
|
||||
let idx = url.pathname.lastIndexOf("/");
|
||||
if (idx === -1) {
|
||||
prePath += url.pathname;
|
||||
} else {
|
||||
prePath += url.pathname.substr(0, idx);
|
||||
}
|
||||
tab.icon = prePath + "/favicon.ico";
|
||||
} else {
|
||||
tab.icon = "chrome://devtools/skin/images/globe.svg";
|
||||
}
|
||||
});
|
||||
this.setState({ tabs });
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
let { client, id } = this.props;
|
||||
let { tabs } = this.state;
|
||||
|
||||
return dom.div({
|
||||
id: id + "-panel",
|
||||
className: "panel",
|
||||
role: "tabpanel",
|
||||
"aria-labelledby": id + "-header"
|
||||
},
|
||||
PanelHeader({
|
||||
id: id + "-header",
|
||||
name: Strings.GetStringFromName("tabs")
|
||||
}),
|
||||
dom.div({},
|
||||
TargetList({
|
||||
client,
|
||||
id: "tabs",
|
||||
name: Strings.GetStringFromName("tabs"),
|
||||
sort: false,
|
||||
targetClass: TabTarget,
|
||||
targets: tabs
|
||||
})
|
||||
));
|
||||
}
|
||||
});
|
@ -3,6 +3,6 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'panel.js',
|
||||
'target.js',
|
||||
'Panel.js',
|
||||
'Target.js',
|
||||
)
|
||||
|
@ -1,98 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const Services = require("Services");
|
||||
|
||||
const PanelHeader = createFactory(require("../panel-header"));
|
||||
const TargetList = createFactory(require("../target-list"));
|
||||
const TabTarget = createFactory(require("./target"));
|
||||
|
||||
loader.lazyRequireGetter(this, "DebuggerClient",
|
||||
"devtools/shared/client/debugger-client", true);
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "TabsPanel",
|
||||
|
||||
propTypes: {
|
||||
client: PropTypes.instanceOf(DebuggerClient).isRequired,
|
||||
id: PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
tabs: []
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
let { client } = this.props;
|
||||
client.addListener("tabListChanged", this.update);
|
||||
this.update();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
let { client } = this.props;
|
||||
client.removeListener("tabListChanged", this.update);
|
||||
},
|
||||
|
||||
update() {
|
||||
this.props.client.mainRoot.listTabs().then(({ tabs }) => {
|
||||
// Filter out closed tabs (represented as `null`).
|
||||
tabs = tabs.filter(tab => !!tab);
|
||||
tabs.forEach(tab => {
|
||||
// FIXME Also try to fetch low-res favicon. But we should use actor
|
||||
// support for this to get the high-res one (bug 1061654).
|
||||
let url = new URL(tab.url);
|
||||
if (url.protocol.startsWith("http")) {
|
||||
let prePath = url.origin;
|
||||
let idx = url.pathname.lastIndexOf("/");
|
||||
if (idx === -1) {
|
||||
prePath += url.pathname;
|
||||
} else {
|
||||
prePath += url.pathname.substr(0, idx);
|
||||
}
|
||||
tab.icon = prePath + "/favicon.ico";
|
||||
} else {
|
||||
tab.icon = "chrome://devtools/skin/images/globe.svg";
|
||||
}
|
||||
});
|
||||
this.setState({ tabs });
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
let { client, id } = this.props;
|
||||
let { tabs } = this.state;
|
||||
|
||||
return dom.div({
|
||||
id: id + "-panel",
|
||||
className: "panel",
|
||||
role: "tabpanel",
|
||||
"aria-labelledby": id + "-header"
|
||||
},
|
||||
PanelHeader({
|
||||
id: id + "-header",
|
||||
name: Strings.GetStringFromName("tabs")
|
||||
}),
|
||||
dom.div({},
|
||||
TargetList({
|
||||
client,
|
||||
id: "tabs",
|
||||
name: Strings.GetStringFromName("tabs"),
|
||||
sort: false,
|
||||
targetClass: TabTarget,
|
||||
targets: tabs
|
||||
})
|
||||
));
|
||||
}
|
||||
});
|
258
devtools/client/aboutdebugging/components/workers/Panel.js
Normal file
258
devtools/client/aboutdebugging/components/workers/Panel.js
Normal file
@ -0,0 +1,258 @@
|
||||
/* 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/. */
|
||||
/* globals window */
|
||||
"use strict";
|
||||
|
||||
loader.lazyImporter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
const { Ci } = require("chrome");
|
||||
const { createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const { getWorkerForms } = require("../../modules/worker");
|
||||
const Services = require("Services");
|
||||
|
||||
const PanelHeader = createFactory(require("../PanelHeader"));
|
||||
const TargetList = createFactory(require("../TargetList"));
|
||||
const WorkerTarget = createFactory(require("./Target"));
|
||||
const MultiE10SWarning = createFactory(require("./MultiE10sWarning"));
|
||||
const ServiceWorkerTarget = createFactory(require("./ServiceWorkerTarget"));
|
||||
|
||||
loader.lazyImporter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
loader.lazyRequireGetter(this, "DebuggerClient",
|
||||
"devtools/shared/client/debugger-client", true);
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const WorkerIcon = "chrome://devtools/skin/images/debugging-workers.svg";
|
||||
const MORE_INFO_URL = "https://developer.mozilla.org/en-US/docs/Tools/about%3Adebugging" +
|
||||
"#Service_workers_not_compatible";
|
||||
const PROCESS_COUNT_PREF = "dom.ipc.processCount";
|
||||
const MULTI_OPTOUT_PREF = "dom.ipc.multiOptOut";
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "WorkersPanel",
|
||||
|
||||
propTypes: {
|
||||
client: PropTypes.instanceOf(DebuggerClient).isRequired,
|
||||
id: PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
workers: {
|
||||
service: [],
|
||||
shared: [],
|
||||
other: []
|
||||
},
|
||||
processCount: 1,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
let client = this.props.client;
|
||||
client.addListener("workerListChanged", this.updateWorkers);
|
||||
client.addListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
|
||||
client.addListener("processListChanged", this.updateWorkers);
|
||||
client.addListener("registration-changed", this.updateWorkers);
|
||||
|
||||
// Some notes about these observers:
|
||||
// - nsIPrefBranch.addObserver observes prefixes. In reality, watching
|
||||
// PROCESS_COUNT_PREF watches two separate prefs:
|
||||
// dom.ipc.processCount *and* dom.ipc.processCount.web. Because these
|
||||
// are the two ways that we control the number of content processes,
|
||||
// that works perfectly fine.
|
||||
// - The user might opt in or out of multi by setting the multi opt out
|
||||
// pref. That affects whether we need to show our warning, so we need to
|
||||
// update our state when that pref changes.
|
||||
// - In all cases, we don't have to manually check which pref changed to
|
||||
// what. The platform code in nsIXULRuntime.maxWebProcessCount does all
|
||||
// of that for us.
|
||||
Services.prefs.addObserver(PROCESS_COUNT_PREF, this.updateMultiE10S);
|
||||
Services.prefs.addObserver(MULTI_OPTOUT_PREF, this.updateMultiE10S);
|
||||
|
||||
this.updateMultiE10S();
|
||||
this.updateWorkers();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
let client = this.props.client;
|
||||
client.removeListener("processListChanged", this.updateWorkers);
|
||||
client.removeListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
|
||||
client.removeListener("workerListChanged", this.updateWorkers);
|
||||
client.removeListener("registration-changed", this.updateWorkers);
|
||||
|
||||
Services.prefs.removeObserver(PROCESS_COUNT_PREF, this.updateMultiE10S);
|
||||
Services.prefs.removeObserver(MULTI_OPTOUT_PREF, this.updateMultiE10S);
|
||||
},
|
||||
|
||||
updateMultiE10S() {
|
||||
// We watch the pref but set the state based on
|
||||
// nsIXULRuntime.maxWebProcessCount.
|
||||
let processCount = Services.appinfo.maxWebProcessCount;
|
||||
this.setState({ processCount });
|
||||
},
|
||||
|
||||
updateWorkers() {
|
||||
let workers = this.getInitialState().workers;
|
||||
|
||||
getWorkerForms(this.props.client).then(forms => {
|
||||
forms.registrations.forEach(form => {
|
||||
workers.service.push({
|
||||
icon: WorkerIcon,
|
||||
name: form.url,
|
||||
url: form.url,
|
||||
scope: form.scope,
|
||||
fetch: form.fetch,
|
||||
registrationActor: form.actor,
|
||||
active: form.active
|
||||
});
|
||||
});
|
||||
|
||||
forms.workers.forEach(form => {
|
||||
let worker = {
|
||||
icon: WorkerIcon,
|
||||
name: form.url,
|
||||
url: form.url,
|
||||
workerActor: form.actor
|
||||
};
|
||||
switch (form.type) {
|
||||
case Ci.nsIWorkerDebugger.TYPE_SERVICE:
|
||||
let registration = this.getRegistrationForWorker(form, workers.service);
|
||||
if (registration) {
|
||||
// XXX: Race, sometimes a ServiceWorkerRegistrationInfo doesn't
|
||||
// have a scriptSpec, but its associated WorkerDebugger does.
|
||||
if (!registration.url) {
|
||||
registration.name = registration.url = form.url;
|
||||
}
|
||||
registration.workerActor = form.actor;
|
||||
} else {
|
||||
worker.fetch = form.fetch;
|
||||
|
||||
// If a service worker registration could not be found, this means we are in
|
||||
// e10s, and registrations are not forwarded to other processes until they
|
||||
// reach the activated state. Augment the worker as a registration worker to
|
||||
// display it in aboutdebugging.
|
||||
worker.scope = form.scope;
|
||||
worker.active = false;
|
||||
workers.service.push(worker);
|
||||
}
|
||||
break;
|
||||
case Ci.nsIWorkerDebugger.TYPE_SHARED:
|
||||
workers.shared.push(worker);
|
||||
break;
|
||||
default:
|
||||
workers.other.push(worker);
|
||||
}
|
||||
});
|
||||
|
||||
// XXX: Filter out the service worker registrations for which we couldn't
|
||||
// find the scriptSpec.
|
||||
workers.service = workers.service.filter(reg => !!reg.url);
|
||||
|
||||
this.setState({ workers });
|
||||
});
|
||||
},
|
||||
|
||||
getRegistrationForWorker(form, registrations) {
|
||||
for (let registration of registrations) {
|
||||
if (registration.scope === form.scope) {
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
isE10S() {
|
||||
return Services.appinfo.browserTabsRemoteAutostart;
|
||||
},
|
||||
|
||||
renderServiceWorkersError() {
|
||||
let isWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(window);
|
||||
let isPrivateBrowsingMode = PrivateBrowsingUtils.permanentPrivateBrowsing;
|
||||
let isServiceWorkerDisabled = !Services.prefs
|
||||
.getBoolPref("dom.serviceWorkers.enabled");
|
||||
|
||||
let isDisabled = isWindowPrivate || isPrivateBrowsingMode || isServiceWorkerDisabled;
|
||||
if (!isDisabled) {
|
||||
return "";
|
||||
}
|
||||
return dom.p(
|
||||
{
|
||||
className: "service-worker-disabled"
|
||||
},
|
||||
dom.div({ className: "warning" }),
|
||||
dom.span(
|
||||
{
|
||||
className: "service-worker-disabled-label",
|
||||
},
|
||||
Strings.GetStringFromName("configurationIsNotCompatible.label")
|
||||
),
|
||||
dom.a(
|
||||
{
|
||||
href: MORE_INFO_URL,
|
||||
target: "_blank"
|
||||
},
|
||||
Strings.GetStringFromName("configurationIsNotCompatible.learnMore")
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
render() {
|
||||
let { client, id } = this.props;
|
||||
let { workers, processCount } = this.state;
|
||||
|
||||
let isE10S = Services.appinfo.browserTabsRemoteAutostart;
|
||||
let isMultiE10S = isE10S && processCount > 1;
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
id: id + "-panel",
|
||||
className: "panel",
|
||||
role: "tabpanel",
|
||||
"aria-labelledby": id + "-header"
|
||||
},
|
||||
PanelHeader({
|
||||
id: id + "-header",
|
||||
name: Strings.GetStringFromName("workers")
|
||||
}),
|
||||
isMultiE10S ? MultiE10SWarning() : "",
|
||||
dom.div(
|
||||
{
|
||||
id: "workers",
|
||||
className: "inverted-icons"
|
||||
},
|
||||
TargetList({
|
||||
client,
|
||||
debugDisabled: isMultiE10S,
|
||||
error: this.renderServiceWorkersError(),
|
||||
id: "service-workers",
|
||||
name: Strings.GetStringFromName("serviceWorkers"),
|
||||
sort: true,
|
||||
targetClass: ServiceWorkerTarget,
|
||||
targets: workers.service
|
||||
}),
|
||||
TargetList({
|
||||
client,
|
||||
id: "shared-workers",
|
||||
name: Strings.GetStringFromName("sharedWorkers"),
|
||||
sort: true,
|
||||
targetClass: WorkerTarget,
|
||||
targets: workers.shared
|
||||
}),
|
||||
TargetList({
|
||||
client,
|
||||
id: "other-workers",
|
||||
name: Strings.GetStringFromName("otherWorkers"),
|
||||
sort: true,
|
||||
targetClass: WorkerTarget,
|
||||
targets: workers.other
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
@ -3,8 +3,8 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'multi-e10s-warning.js',
|
||||
'panel.js',
|
||||
'service-worker-target.js',
|
||||
'target.js',
|
||||
'MultiE10sWarning.js',
|
||||
'Panel.js',
|
||||
'ServiceWorkerTarget.js',
|
||||
'Target.js',
|
||||
)
|
||||
|
@ -1,258 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* globals window */
|
||||
"use strict";
|
||||
|
||||
loader.lazyImporter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
const { Ci } = require("chrome");
|
||||
const { createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const { getWorkerForms } = require("../../modules/worker");
|
||||
const Services = require("Services");
|
||||
|
||||
const PanelHeader = createFactory(require("../panel-header"));
|
||||
const TargetList = createFactory(require("../target-list"));
|
||||
const WorkerTarget = createFactory(require("./target"));
|
||||
const MultiE10SWarning = createFactory(require("./multi-e10s-warning"));
|
||||
const ServiceWorkerTarget = createFactory(require("./service-worker-target"));
|
||||
|
||||
loader.lazyImporter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
loader.lazyRequireGetter(this, "DebuggerClient",
|
||||
"devtools/shared/client/debugger-client", true);
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const WorkerIcon = "chrome://devtools/skin/images/debugging-workers.svg";
|
||||
const MORE_INFO_URL = "https://developer.mozilla.org/en-US/docs/Tools/about%3Adebugging" +
|
||||
"#Service_workers_not_compatible";
|
||||
const PROCESS_COUNT_PREF = "dom.ipc.processCount";
|
||||
const MULTI_OPTOUT_PREF = "dom.ipc.multiOptOut";
|
||||
|
||||
module.exports = createClass({
|
||||
displayName: "WorkersPanel",
|
||||
|
||||
propTypes: {
|
||||
client: PropTypes.instanceOf(DebuggerClient).isRequired,
|
||||
id: PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
workers: {
|
||||
service: [],
|
||||
shared: [],
|
||||
other: []
|
||||
},
|
||||
processCount: 1,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
let client = this.props.client;
|
||||
client.addListener("workerListChanged", this.updateWorkers);
|
||||
client.addListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
|
||||
client.addListener("processListChanged", this.updateWorkers);
|
||||
client.addListener("registration-changed", this.updateWorkers);
|
||||
|
||||
// Some notes about these observers:
|
||||
// - nsIPrefBranch.addObserver observes prefixes. In reality, watching
|
||||
// PROCESS_COUNT_PREF watches two separate prefs:
|
||||
// dom.ipc.processCount *and* dom.ipc.processCount.web. Because these
|
||||
// are the two ways that we control the number of content processes,
|
||||
// that works perfectly fine.
|
||||
// - The user might opt in or out of multi by setting the multi opt out
|
||||
// pref. That affects whether we need to show our warning, so we need to
|
||||
// update our state when that pref changes.
|
||||
// - In all cases, we don't have to manually check which pref changed to
|
||||
// what. The platform code in nsIXULRuntime.maxWebProcessCount does all
|
||||
// of that for us.
|
||||
Services.prefs.addObserver(PROCESS_COUNT_PREF, this.updateMultiE10S);
|
||||
Services.prefs.addObserver(MULTI_OPTOUT_PREF, this.updateMultiE10S);
|
||||
|
||||
this.updateMultiE10S();
|
||||
this.updateWorkers();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
let client = this.props.client;
|
||||
client.removeListener("processListChanged", this.updateWorkers);
|
||||
client.removeListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
|
||||
client.removeListener("workerListChanged", this.updateWorkers);
|
||||
client.removeListener("registration-changed", this.updateWorkers);
|
||||
|
||||
Services.prefs.removeObserver(PROCESS_COUNT_PREF, this.updateMultiE10S);
|
||||
Services.prefs.removeObserver(MULTI_OPTOUT_PREF, this.updateMultiE10S);
|
||||
},
|
||||
|
||||
updateMultiE10S() {
|
||||
// We watch the pref but set the state based on
|
||||
// nsIXULRuntime.maxWebProcessCount.
|
||||
let processCount = Services.appinfo.maxWebProcessCount;
|
||||
this.setState({ processCount });
|
||||
},
|
||||
|
||||
updateWorkers() {
|
||||
let workers = this.getInitialState().workers;
|
||||
|
||||
getWorkerForms(this.props.client).then(forms => {
|
||||
forms.registrations.forEach(form => {
|
||||
workers.service.push({
|
||||
icon: WorkerIcon,
|
||||
name: form.url,
|
||||
url: form.url,
|
||||
scope: form.scope,
|
||||
fetch: form.fetch,
|
||||
registrationActor: form.actor,
|
||||
active: form.active
|
||||
});
|
||||
});
|
||||
|
||||
forms.workers.forEach(form => {
|
||||
let worker = {
|
||||
icon: WorkerIcon,
|
||||
name: form.url,
|
||||
url: form.url,
|
||||
workerActor: form.actor
|
||||
};
|
||||
switch (form.type) {
|
||||
case Ci.nsIWorkerDebugger.TYPE_SERVICE:
|
||||
let registration = this.getRegistrationForWorker(form, workers.service);
|
||||
if (registration) {
|
||||
// XXX: Race, sometimes a ServiceWorkerRegistrationInfo doesn't
|
||||
// have a scriptSpec, but its associated WorkerDebugger does.
|
||||
if (!registration.url) {
|
||||
registration.name = registration.url = form.url;
|
||||
}
|
||||
registration.workerActor = form.actor;
|
||||
} else {
|
||||
worker.fetch = form.fetch;
|
||||
|
||||
// If a service worker registration could not be found, this means we are in
|
||||
// e10s, and registrations are not forwarded to other processes until they
|
||||
// reach the activated state. Augment the worker as a registration worker to
|
||||
// display it in aboutdebugging.
|
||||
worker.scope = form.scope;
|
||||
worker.active = false;
|
||||
workers.service.push(worker);
|
||||
}
|
||||
break;
|
||||
case Ci.nsIWorkerDebugger.TYPE_SHARED:
|
||||
workers.shared.push(worker);
|
||||
break;
|
||||
default:
|
||||
workers.other.push(worker);
|
||||
}
|
||||
});
|
||||
|
||||
// XXX: Filter out the service worker registrations for which we couldn't
|
||||
// find the scriptSpec.
|
||||
workers.service = workers.service.filter(reg => !!reg.url);
|
||||
|
||||
this.setState({ workers });
|
||||
});
|
||||
},
|
||||
|
||||
getRegistrationForWorker(form, registrations) {
|
||||
for (let registration of registrations) {
|
||||
if (registration.scope === form.scope) {
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
isE10S() {
|
||||
return Services.appinfo.browserTabsRemoteAutostart;
|
||||
},
|
||||
|
||||
renderServiceWorkersError() {
|
||||
let isWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(window);
|
||||
let isPrivateBrowsingMode = PrivateBrowsingUtils.permanentPrivateBrowsing;
|
||||
let isServiceWorkerDisabled = !Services.prefs
|
||||
.getBoolPref("dom.serviceWorkers.enabled");
|
||||
|
||||
let isDisabled = isWindowPrivate || isPrivateBrowsingMode || isServiceWorkerDisabled;
|
||||
if (!isDisabled) {
|
||||
return "";
|
||||
}
|
||||
return dom.p(
|
||||
{
|
||||
className: "service-worker-disabled"
|
||||
},
|
||||
dom.div({ className: "warning" }),
|
||||
dom.span(
|
||||
{
|
||||
className: "service-worker-disabled-label",
|
||||
},
|
||||
Strings.GetStringFromName("configurationIsNotCompatible.label")
|
||||
),
|
||||
dom.a(
|
||||
{
|
||||
href: MORE_INFO_URL,
|
||||
target: "_blank"
|
||||
},
|
||||
Strings.GetStringFromName("configurationIsNotCompatible.learnMore")
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
render() {
|
||||
let { client, id } = this.props;
|
||||
let { workers, processCount } = this.state;
|
||||
|
||||
let isE10S = Services.appinfo.browserTabsRemoteAutostart;
|
||||
let isMultiE10S = isE10S && processCount > 1;
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
id: id + "-panel",
|
||||
className: "panel",
|
||||
role: "tabpanel",
|
||||
"aria-labelledby": id + "-header"
|
||||
},
|
||||
PanelHeader({
|
||||
id: id + "-header",
|
||||
name: Strings.GetStringFromName("workers")
|
||||
}),
|
||||
isMultiE10S ? MultiE10SWarning() : "",
|
||||
dom.div(
|
||||
{
|
||||
id: "workers",
|
||||
className: "inverted-icons"
|
||||
},
|
||||
TargetList({
|
||||
client,
|
||||
debugDisabled: isMultiE10S,
|
||||
error: this.renderServiceWorkersError(),
|
||||
id: "service-workers",
|
||||
name: Strings.GetStringFromName("serviceWorkers"),
|
||||
sort: true,
|
||||
targetClass: ServiceWorkerTarget,
|
||||
targets: workers.service
|
||||
}),
|
||||
TargetList({
|
||||
client,
|
||||
id: "shared-workers",
|
||||
name: Strings.GetStringFromName("sharedWorkers"),
|
||||
sort: true,
|
||||
targetClass: WorkerTarget,
|
||||
targets: workers.shared
|
||||
}),
|
||||
TargetList({
|
||||
client,
|
||||
id: "other-workers",
|
||||
name: Strings.GetStringFromName("otherWorkers"),
|
||||
sort: true,
|
||||
targetClass: WorkerTarget,
|
||||
targets: workers.other
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
@ -29,7 +29,7 @@ const { require } = BrowserLoader({
|
||||
const { createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const { render, unmountComponentAtNode } = require("devtools/client/shared/vendor/react-dom");
|
||||
|
||||
const AboutDebuggingApp = createFactory(require("./components/aboutdebugging"));
|
||||
const AboutDebuggingApp = createFactory(require("./components/Aboutdebugging"));
|
||||
|
||||
var AboutDebugging = {
|
||||
init() {
|
||||
|
@ -4,8 +4,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
createFactory,
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const BoxModelInfo = createFactory(require("./BoxModelInfo"));
|
||||
const BoxModelMain = createFactory(require("./BoxModelMain"));
|
||||
@ -13,22 +17,24 @@ const BoxModelProperties = createFactory(require("./BoxModelProperties"));
|
||||
|
||||
const Types = require("../types");
|
||||
|
||||
module.exports = createClass({
|
||||
class BoxModel extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
showBoxModelProperties: PropTypes.bool.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
onToggleGeometryEditor: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
displayName: "BoxModel",
|
||||
|
||||
propTypes: {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
showBoxModelProperties: PropTypes.bool.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
onToggleGeometryEditor: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
onKeyDown(event) {
|
||||
let { target } = event;
|
||||
@ -36,7 +42,7 @@ module.exports = createClass({
|
||||
if (target == this.boxModelContainer) {
|
||||
this.boxModelMain.onKeyDown(event);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
@ -83,5 +89,7 @@ module.exports = createClass({
|
||||
:
|
||||
null
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BoxModel;
|
||||
|
@ -5,10 +5,12 @@
|
||||
"use strict";
|
||||
|
||||
const Services = require("Services");
|
||||
const { addons, createClass, createFactory, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
createFactory,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
|
||||
const Accordion =
|
||||
@ -22,22 +24,19 @@ const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
|
||||
|
||||
const BOXMODEL_OPENED_PREF = "devtools.computed.boxmodel.opened";
|
||||
|
||||
const BoxModelApp = createClass({
|
||||
|
||||
displayName: "BoxModelApp",
|
||||
|
||||
propTypes: {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
showBoxModelProperties: PropTypes.bool.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
onToggleGeometryEditor: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
class BoxModelApp extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
showBoxModelProperties: PropTypes.bool.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
onToggleGeometryEditor: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return Accordion({
|
||||
@ -54,8 +53,7 @@ const BoxModelApp = createClass({
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(state => state)(BoxModelApp);
|
||||
|
@ -4,27 +4,27 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { editableItem } = require("devtools/client/shared/inplace-editor");
|
||||
|
||||
const LONG_TEXT_ROTATE_LIMIT = 3;
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
displayName: "BoxModelEditable",
|
||||
|
||||
propTypes: {
|
||||
box: PropTypes.string.isRequired,
|
||||
direction: PropTypes.string,
|
||||
focusable: PropTypes.bool.isRequired,
|
||||
level: PropTypes.string,
|
||||
property: PropTypes.string.isRequired,
|
||||
textContent: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
class BoxModelEditable extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
box: PropTypes.string.isRequired,
|
||||
direction: PropTypes.string,
|
||||
focusable: PropTypes.bool.isRequired,
|
||||
level: PropTypes.string,
|
||||
property: PropTypes.string.isRequired,
|
||||
textContent: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let { property, onShowBoxModelEditor } = this.props;
|
||||
@ -34,7 +34,7 @@ module.exports = createClass({
|
||||
}, (element, event) => {
|
||||
onShowBoxModelEditor(element, event, property);
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
@ -70,6 +70,7 @@ module.exports = createClass({
|
||||
textContent
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = BoxModelEditable;
|
||||
|
@ -4,9 +4,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const { addons, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
const Types = require("../types");
|
||||
|
||||
@ -16,20 +19,22 @@ const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
|
||||
const SHARED_STRINGS_URI = "devtools/client/locales/shared.properties";
|
||||
const SHARED_L10N = new LocalizationHelper(SHARED_STRINGS_URI);
|
||||
|
||||
module.exports = createClass({
|
||||
class BoxModelInfo extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
onToggleGeometryEditor: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
displayName: "BoxModelInfo",
|
||||
|
||||
propTypes: {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
onToggleGeometryEditor: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onToggleGeometryEditor = this.onToggleGeometryEditor.bind(this);
|
||||
}
|
||||
|
||||
onToggleGeometryEditor(e) {
|
||||
this.props.onToggleGeometryEditor();
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
let { boxModel } = this.props;
|
||||
@ -76,6 +81,7 @@ module.exports = createClass({
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = BoxModelInfo;
|
||||
|
@ -4,11 +4,14 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
createFactory,
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
|
||||
const { KeyCodes } = require("devtools/client/shared/keycodes");
|
||||
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
|
||||
const BoxModelEditable = createFactory(require("./BoxModelEditable"));
|
||||
@ -21,26 +24,39 @@ const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
|
||||
const SHARED_STRINGS_URI = "devtools/client/locales/shared.properties";
|
||||
const SHARED_L10N = new LocalizationHelper(SHARED_STRINGS_URI);
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
displayName: "BoxModelMain",
|
||||
|
||||
propTypes: {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
boxModelContainer: PropTypes.object,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
getInitialState() {
|
||||
class BoxModelMain extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
boxModelContainer: PropTypes.object,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
activeDescendant: null,
|
||||
focusable: false,
|
||||
};
|
||||
},
|
||||
|
||||
this.getAriaActiveDescendant = this.getAriaActiveDescendant.bind(this);
|
||||
this.getBorderOrPaddingValue = this.getBorderOrPaddingValue.bind(this);
|
||||
this.getContextBox = this.getContextBox.bind(this);
|
||||
this.getDisplayPosition = this.getDisplayPosition.bind(this);
|
||||
this.getHeightValue = this.getHeightValue.bind(this);
|
||||
this.getMarginValue = this.getMarginValue.bind(this);
|
||||
this.getPositionValue = this.getPositionValue.bind(this);
|
||||
this.getWidthValue = this.getWidthValue.bind(this);
|
||||
this.moveFocus = this.moveFocus.bind(this);
|
||||
this.setAriaActive = this.setAriaActive.bind(this);
|
||||
this.onHighlightMouseOver = this.onHighlightMouseOver.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onLevelClick = this.onLevelClick.bind(this);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
let displayPosition = this.getDisplayPosition();
|
||||
@ -83,7 +99,7 @@ module.exports = createClass({
|
||||
["click", this.contentLayout]
|
||||
])
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getAriaActiveDescendant() {
|
||||
let { activeDescendant } = this.state;
|
||||
@ -96,12 +112,12 @@ module.exports = createClass({
|
||||
}
|
||||
|
||||
return activeDescendant;
|
||||
},
|
||||
}
|
||||
|
||||
getBorderOrPaddingValue(property) {
|
||||
let { layout } = this.props.boxModel;
|
||||
return layout[property] ? parseFloat(layout[property]) : "-";
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the layout box sizing is context box and false otherwise.
|
||||
@ -109,7 +125,7 @@ module.exports = createClass({
|
||||
getContextBox() {
|
||||
let { layout } = this.props.boxModel;
|
||||
return layout["box-sizing"] == "content-box";
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the position is displayed and false otherwise.
|
||||
@ -117,7 +133,7 @@ module.exports = createClass({
|
||||
getDisplayPosition() {
|
||||
let { layout } = this.props.boxModel;
|
||||
return layout.position && layout.position != "static";
|
||||
},
|
||||
}
|
||||
|
||||
getHeightValue(property) {
|
||||
if (property == undefined) {
|
||||
@ -133,23 +149,7 @@ module.exports = createClass({
|
||||
property = parseFloat(property.toPrecision(6));
|
||||
|
||||
return property;
|
||||
},
|
||||
|
||||
getWidthValue(property) {
|
||||
if (property == undefined) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
let { layout } = this.props.boxModel;
|
||||
|
||||
property -= parseFloat(layout["border-left-width"]) +
|
||||
parseFloat(layout["border-right-width"]) +
|
||||
parseFloat(layout["padding-left"]) +
|
||||
parseFloat(layout["padding-right"]);
|
||||
property = parseFloat(property.toPrecision(6));
|
||||
|
||||
return property;
|
||||
},
|
||||
}
|
||||
|
||||
getMarginValue(property, direction) {
|
||||
let { layout } = this.props.boxModel;
|
||||
@ -172,7 +172,7 @@ module.exports = createClass({
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
}
|
||||
|
||||
getPositionValue(property) {
|
||||
let { layout } = this.props.boxModel;
|
||||
@ -192,7 +192,23 @@ module.exports = createClass({
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
}
|
||||
|
||||
getWidthValue(property) {
|
||||
if (property == undefined) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
let { layout } = this.props.boxModel;
|
||||
|
||||
property -= parseFloat(layout["border-left-width"]) +
|
||||
parseFloat(layout["border-right-width"]) +
|
||||
parseFloat(layout["padding-left"]) +
|
||||
parseFloat(layout["padding-right"]);
|
||||
property = parseFloat(property.toPrecision(6));
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the focus to the next/previous editable element of the current layout.
|
||||
@ -204,7 +220,7 @@ module.exports = createClass({
|
||||
* @param {String} level
|
||||
* Current active layout
|
||||
*/
|
||||
moveFocus: function ({ target, shiftKey }, level) {
|
||||
moveFocus({ target, shiftKey }, level) {
|
||||
let editBoxes = [
|
||||
...findDOMNode(this).querySelectorAll(`[data-box="${level}"].boxmodel-editable`)
|
||||
];
|
||||
@ -227,7 +243,7 @@ module.exports = createClass({
|
||||
if (editingMode) {
|
||||
editBox.click();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Active aria-level set to current layout.
|
||||
@ -246,7 +262,7 @@ module.exports = createClass({
|
||||
this.setState({
|
||||
activeDescendant: nextLayout.getAttribute("data-box"),
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
onHighlightMouseOver(event) {
|
||||
let region = event.target.getAttribute("data-box");
|
||||
@ -271,7 +287,7 @@ module.exports = createClass({
|
||||
showOnly: region,
|
||||
onlyRegionArea: true,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle keyboard navigation and focus for box model layouts.
|
||||
@ -334,7 +350,7 @@ module.exports = createClass({
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Update aria-active on mouse click.
|
||||
@ -360,7 +376,7 @@ module.exports = createClass({
|
||||
if (target && target._editable) {
|
||||
target.blur();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
@ -686,6 +702,7 @@ module.exports = createClass({
|
||||
}),
|
||||
contentBox
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = BoxModelMain;
|
||||
|
@ -4,9 +4,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
const {
|
||||
createFactory,
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
|
||||
const ComputedProperty = createFactory(require("./ComputedProperty"));
|
||||
@ -16,24 +19,26 @@ const Types = require("../types");
|
||||
const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
|
||||
const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
displayName: "BoxModelProperties",
|
||||
|
||||
propTypes: {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
getInitialState() {
|
||||
class BoxModelProperties extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
boxModel: PropTypes.shape(Types.boxModel).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isOpen: true,
|
||||
};
|
||||
},
|
||||
|
||||
this.getReferenceElement = this.getReferenceElement.bind(this);
|
||||
this.onToggleExpander = this.onToggleExpander.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Various properties can display a reference element. E.g. position displays an offset
|
||||
@ -59,13 +64,13 @@ module.exports = createClass({
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
}
|
||||
|
||||
onToggleExpander() {
|
||||
this.setState({
|
||||
isOpen: !this.state.isOpen,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
@ -121,6 +126,7 @@ module.exports = createClass({
|
||||
properties
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = BoxModelProperties;
|
||||
|
@ -4,26 +4,63 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
|
||||
const { Rep } = REPS;
|
||||
|
||||
module.exports = createClass({
|
||||
class ComputedProperty extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
name: PropTypes.string.isRequired,
|
||||
referenceElement: PropTypes.object,
|
||||
referenceElementType: PropTypes.string,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
displayName: "ComputedProperty",
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.renderReferenceElementPreview = this.renderReferenceElementPreview.bind(this);
|
||||
this.translateNodeFrontToGrip = this.translateNodeFrontToGrip.bind(this);
|
||||
this.onFocus = this.onFocus.bind(this);
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string,
|
||||
referenceElement: PropTypes.object,
|
||||
referenceElementType: PropTypes.string,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
},
|
||||
renderReferenceElementPreview() {
|
||||
let {
|
||||
referenceElement,
|
||||
referenceElementType,
|
||||
setSelectedNode,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
onHideBoxModelHighlighter
|
||||
} = this.props;
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
if (!referenceElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "reference-element"
|
||||
},
|
||||
dom.span({ className: "reference-element-type" }, referenceElementType),
|
||||
Rep({
|
||||
defaultRep: referenceElement,
|
||||
mode: MODE.TINY,
|
||||
object: this.translateNodeFrontToGrip(referenceElement),
|
||||
onInspectIconClick: () => setSelectedNode(referenceElement, "box-model"),
|
||||
onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(referenceElement),
|
||||
onDOMNodeMouseOut: () => onHideBoxModelHighlighter(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* While waiting for a reps fix in https://github.com/devtools-html/reps/issues/92,
|
||||
@ -56,40 +93,11 @@ module.exports = createClass({
|
||||
isConnected: true,
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
onFocus() {
|
||||
this.container.focus();
|
||||
},
|
||||
|
||||
renderReferenceElementPreview() {
|
||||
let {
|
||||
referenceElement,
|
||||
referenceElementType,
|
||||
setSelectedNode,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
onHideBoxModelHighlighter
|
||||
} = this.props;
|
||||
|
||||
if (!referenceElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "reference-element"
|
||||
},
|
||||
dom.span({ className: "reference-element-type" }, referenceElementType),
|
||||
Rep({
|
||||
defaultRep: referenceElement,
|
||||
mode: MODE.TINY,
|
||||
object: this.translateNodeFrontToGrip(referenceElement),
|
||||
onInspectIconClick: () => setSelectedNode(referenceElement, "box-model"),
|
||||
onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(referenceElement),
|
||||
onDOMNodeMouseOut: () => onHideBoxModelHighlighter(),
|
||||
})
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name, value } = this.props;
|
||||
@ -133,6 +141,7 @@ module.exports = createClass({
|
||||
this.renderReferenceElementPreview()
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = ComputedProperty;
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { DOM, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { DOM, Component, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Shortcuts
|
||||
const { div } = DOM;
|
||||
@ -16,25 +16,25 @@ const { div } = DOM;
|
||||
* as the content. It's used by Sidebar as well as SplitBox
|
||||
* components.
|
||||
*/
|
||||
var InspectorTabPanel = createClass({
|
||||
displayName: "InspectorTabPanel",
|
||||
class InspectorTabPanel extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
// ID of the node that should be rendered as the content.
|
||||
id: PropTypes.string.isRequired,
|
||||
// Optional prefix for panel IDs.
|
||||
idPrefix: PropTypes.string,
|
||||
// Optional mount callback
|
||||
onMount: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
// ID of the node that should be rendered as the content.
|
||||
id: PropTypes.string.isRequired,
|
||||
// Optional prefix for panel IDs.
|
||||
idPrefix: PropTypes.string,
|
||||
// Optional mount callback
|
||||
onMount: PropTypes.func,
|
||||
},
|
||||
|
||||
getDefaultProps: function () {
|
||||
static get defaultProps() {
|
||||
return {
|
||||
idPrefix: "",
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
componentDidMount: function () {
|
||||
componentDidMount() {
|
||||
let doc = this.refs.content.ownerDocument;
|
||||
let panel = doc.getElementById(this.props.idPrefix + this.props.id);
|
||||
|
||||
@ -44,17 +44,17 @@ var InspectorTabPanel = createClass({
|
||||
if (this.props.onMount) {
|
||||
this.props.onMount(this.refs.content, this.props);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
componentWillUnmount: function () {
|
||||
componentWillUnmount() {
|
||||
let doc = this.refs.content.ownerDocument;
|
||||
let panels = doc.getElementById("tabpanels");
|
||||
|
||||
// Move panel's content node back into list of tab panels.
|
||||
panels.appendChild(this.refs.content.firstChild);
|
||||
},
|
||||
}
|
||||
|
||||
render: function () {
|
||||
render() {
|
||||
return (
|
||||
div({
|
||||
ref: "content",
|
||||
@ -62,6 +62,6 @@ var InspectorTabPanel = createClass({
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = InspectorTabPanel;
|
||||
|
@ -5,12 +5,11 @@
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
addons,
|
||||
createClass, createFactory,
|
||||
createFactory,
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const ObjectTreeView = createFactory(require("./ObjectTreeView"));
|
||||
@ -24,15 +23,13 @@ const ObjectTreeView = createFactory(require("./ObjectTreeView"));
|
||||
*
|
||||
* TODO: implement the ExtensionPage viewMode.
|
||||
*/
|
||||
const ExtensionSidebar = createClass({
|
||||
displayName: "ExtensionSidebar",
|
||||
|
||||
propTypes: {
|
||||
id: PropTypes.string.isRequired,
|
||||
extensionsSidebar: PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
class ExtensionSidebar extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
id: PropTypes.string.isRequired,
|
||||
extensionsSidebar: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { id, extensionsSidebar } = this.props;
|
||||
@ -58,6 +55,6 @@ const ExtensionSidebar = createClass({
|
||||
|
||||
return dom.div({ id, className }, sidebarContentEl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = connect(state => state)(ExtensionSidebar);
|
||||
|
@ -5,9 +5,9 @@
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
addons,
|
||||
createClass, createFactory,
|
||||
createFactory,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
|
||||
@ -19,14 +19,12 @@ 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 ],
|
||||
class ObjectTreeView extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
object: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { object } = this.props;
|
||||
@ -55,7 +53,7 @@ const ObjectTreeView = createClass({
|
||||
maxLevel: 1, maxNodes: 1,
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ObjectTreeView;
|
||||
|
@ -4,8 +4,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
createFactory,
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const SearchBox = createFactory(require("devtools/client/shared/components/SearchBox"));
|
||||
@ -16,17 +20,14 @@ const Types = require("../types");
|
||||
|
||||
const PREVIEW_UPDATE_DELAY = 150;
|
||||
|
||||
const App = createClass({
|
||||
|
||||
displayName: "App",
|
||||
|
||||
propTypes: {
|
||||
fonts: PropTypes.arrayOf(PropTypes.shape(Types.font)).isRequired,
|
||||
onPreviewFonts: PropTypes.func.isRequired,
|
||||
onShowAllFont: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
class App extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
fonts: PropTypes.arrayOf(PropTypes.shape(Types.font)).isRequired,
|
||||
onPreviewFonts: PropTypes.func.isRequired,
|
||||
onShowAllFont: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
@ -71,6 +72,6 @@ const App = createClass({
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = connect(state => state)(App);
|
||||
|
@ -4,18 +4,29 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const { getStr } = require("../utils/l10n");
|
||||
const Types = require("../types");
|
||||
|
||||
module.exports = createClass({
|
||||
class Font extends PureComponent {
|
||||
static get propTypes() {
|
||||
return PropTypes.shape(Types.font).isRequired;
|
||||
}
|
||||
|
||||
displayName: "Font",
|
||||
|
||||
propTypes: PropTypes.shape(Types.font).isRequired,
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.getSectionClasses = this.getSectionClasses.bind(this);
|
||||
this.renderFontCSS = this.renderFontCSS.bind(this);
|
||||
this.renderFontCSSCode = this.renderFontCSSCode.bind(this);
|
||||
this.renderFontFormatURL = this.renderFontFormatURL.bind(this);
|
||||
this.renderFontName = this.renderFontName.bind(this);
|
||||
this.renderFontPreview = this.renderFontPreview.bind(this);
|
||||
}
|
||||
|
||||
getSectionClasses() {
|
||||
let { font } = this.props;
|
||||
@ -28,7 +39,7 @@ module.exports = createClass({
|
||||
}
|
||||
|
||||
return classes.join(" ");
|
||||
},
|
||||
}
|
||||
|
||||
renderFontCSS(cssFamilyName) {
|
||||
return dom.p(
|
||||
@ -48,7 +59,7 @@ module.exports = createClass({
|
||||
),
|
||||
"\""
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderFontCSSCode(rule, ruleText) {
|
||||
return dom.pre(
|
||||
@ -57,7 +68,7 @@ module.exports = createClass({
|
||||
},
|
||||
rule ? ruleText : null
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderFontFormatURL(url, format) {
|
||||
return dom.p(
|
||||
@ -88,7 +99,7 @@ module.exports = createClass({
|
||||
format
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderFontName(name) {
|
||||
return dom.h1(
|
||||
@ -97,7 +108,7 @@ module.exports = createClass({
|
||||
},
|
||||
name
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderFontPreview(previewUrl) {
|
||||
return dom.div(
|
||||
@ -111,7 +122,7 @@ module.exports = createClass({
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
let { font } = this.props;
|
||||
@ -153,4 +164,6 @@ module.exports = createClass({
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = Font;
|
||||
|
@ -4,22 +4,23 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
createFactory,
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const Font = createFactory(require("./Font"));
|
||||
|
||||
const Types = require("../types");
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
displayName: "FontList",
|
||||
|
||||
propTypes: {
|
||||
fonts: PropTypes.arrayOf(PropTypes.shape(Types.font)).isRequired
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
class FontList extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
fonts: PropTypes.arrayOf(PropTypes.shape(Types.font)).isRequired
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
let { fonts } = this.props;
|
||||
@ -38,6 +39,7 @@ module.exports = createClass({
|
||||
}))
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = FontList;
|
||||
|
@ -4,8 +4,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
createFactory,
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const GridDisplaySettings = createFactory(require("./GridDisplaySettings"));
|
||||
const GridList = createFactory(require("./GridList"));
|
||||
@ -14,28 +18,25 @@ const GridOutline = createFactory(require("./GridOutline"));
|
||||
const Types = require("../types");
|
||||
const { getStr } = require("../utils/l10n");
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
displayName: "Grid",
|
||||
|
||||
propTypes: {
|
||||
getSwatchColorPickerTooltip: PropTypes.func.isRequired,
|
||||
grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
|
||||
highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onSetGridOverlayColor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
onShowGridAreaHighlight: PropTypes.func.isRequired,
|
||||
onShowGridCellHighlight: PropTypes.func.isRequired,
|
||||
onShowGridLineNamesHighlight: PropTypes.func.isRequired,
|
||||
onToggleGridHighlighter: PropTypes.func.isRequired,
|
||||
onToggleShowGridAreas: PropTypes.func.isRequired,
|
||||
onToggleShowGridLineNumbers: PropTypes.func.isRequired,
|
||||
onToggleShowInfiniteLines: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
class Grid extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
getSwatchColorPickerTooltip: PropTypes.func.isRequired,
|
||||
grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
|
||||
highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onSetGridOverlayColor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
onShowGridAreaHighlight: PropTypes.func.isRequired,
|
||||
onShowGridCellHighlight: PropTypes.func.isRequired,
|
||||
onShowGridLineNamesHighlight: PropTypes.func.isRequired,
|
||||
onToggleGridHighlighter: PropTypes.func.isRequired,
|
||||
onToggleShowGridAreas: PropTypes.func.isRequired,
|
||||
onToggleShowGridLineNumbers: PropTypes.func.isRequired,
|
||||
onToggleShowInfiniteLines: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
@ -92,6 +93,7 @@ module.exports = createClass({
|
||||
},
|
||||
getStr("layout.noGridsOnThisPage")
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = Grid;
|
||||
|
@ -4,24 +4,33 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const Types = require("../types");
|
||||
const { getStr } = require("../utils/l10n");
|
||||
|
||||
module.exports = createClass({
|
||||
class GridDisplaySettings extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
|
||||
onToggleShowGridAreas: PropTypes.func.isRequired,
|
||||
onToggleShowGridLineNumbers: PropTypes.func.isRequired,
|
||||
onToggleShowInfiniteLines: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
displayName: "GridDisplaySettings",
|
||||
|
||||
propTypes: {
|
||||
highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
|
||||
onToggleShowGridAreas: PropTypes.func.isRequired,
|
||||
onToggleShowGridLineNumbers: PropTypes.func.isRequired,
|
||||
onToggleShowInfiniteLines: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onShowGridAreasCheckboxClick = this.onShowGridAreasCheckboxClick.bind(this);
|
||||
this.onShowGridLineNumbersCheckboxClick =
|
||||
this.onShowGridLineNumbersCheckboxClick.bind(this);
|
||||
this.onShowInfiniteLinesCheckboxClick =
|
||||
this.onShowInfiniteLinesCheckboxClick.bind(this);
|
||||
}
|
||||
|
||||
onShowGridAreasCheckboxClick() {
|
||||
let {
|
||||
@ -30,7 +39,7 @@ module.exports = createClass({
|
||||
} = this.props;
|
||||
|
||||
onToggleShowGridAreas(!highlighterSettings.showGridAreasOverlay);
|
||||
},
|
||||
}
|
||||
|
||||
onShowGridLineNumbersCheckboxClick() {
|
||||
let {
|
||||
@ -39,7 +48,7 @@ module.exports = createClass({
|
||||
} = this.props;
|
||||
|
||||
onToggleShowGridLineNumbers(!highlighterSettings.showGridLineNumbers);
|
||||
},
|
||||
}
|
||||
|
||||
onShowInfiniteLinesCheckboxClick() {
|
||||
let {
|
||||
@ -48,7 +57,7 @@ module.exports = createClass({
|
||||
} = this.props;
|
||||
|
||||
onToggleShowInfiniteLines(!highlighterSettings.showInfiniteLines);
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
@ -118,6 +127,7 @@ module.exports = createClass({
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = GridDisplaySettings;
|
||||
|
@ -4,7 +4,11 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
|
||||
|
||||
// Reps
|
||||
@ -14,25 +18,29 @@ const ElementNode = REPS.ElementNode;
|
||||
|
||||
const Types = require("../types");
|
||||
|
||||
module.exports = createClass({
|
||||
class GridItem extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
getSwatchColorPickerTooltip: PropTypes.func.isRequired,
|
||||
grid: PropTypes.shape(Types.grid).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onSetGridOverlayColor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
onToggleGridHighlighter: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
displayName: "GridItem",
|
||||
|
||||
propTypes: {
|
||||
getSwatchColorPickerTooltip: PropTypes.func.isRequired,
|
||||
grid: PropTypes.shape(Types.grid).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onSetGridOverlayColor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
onToggleGridHighlighter: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.setGridColor = this.setGridColor.bind(this);
|
||||
this.translateNodeFrontToGrip = this.translateNodeFrontToGrip.bind(this);
|
||||
this.onGridCheckboxClick = this.onGridCheckboxClick.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let tooltip = this.props.getSwatchColorPickerTooltip();
|
||||
let swatchEl = findDOMNode(this).querySelector(".grid-color-swatch");
|
||||
let tooltip = this.props.getSwatchColorPickerTooltip();
|
||||
|
||||
let previousColor;
|
||||
tooltip.addSwatch(swatchEl, {
|
||||
@ -45,18 +53,18 @@ module.exports = createClass({
|
||||
previousColor = this.props.grid.color;
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
let tooltip = this.props.getSwatchColorPickerTooltip();
|
||||
let swatchEl = findDOMNode(this).querySelector(".grid-color-swatch");
|
||||
let tooltip = this.props.getSwatchColorPickerTooltip();
|
||||
tooltip.removeSwatch(swatchEl);
|
||||
},
|
||||
}
|
||||
|
||||
setGridColor() {
|
||||
let color = findDOMNode(this).querySelector(".grid-color-value").textContent;
|
||||
this.props.onSetGridOverlayColor(this.props.grid.nodeFront, color);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* While waiting for a reps fix in https://github.com/devtools-html/reps/issues/92,
|
||||
@ -88,7 +96,7 @@ module.exports = createClass({
|
||||
nodeType: nodeFront.nodeType,
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
onGridCheckboxClick(e) {
|
||||
// If the click was on the svg icon to select the node in the inspector, bail out.
|
||||
@ -106,14 +114,14 @@ module.exports = createClass({
|
||||
} = this.props;
|
||||
|
||||
onToggleGridHighlighter(grid.nodeFront);
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
grid,
|
||||
setSelectedNode,
|
||||
onHideBoxModelHighlighter,
|
||||
onShowBoxModelHighlighterForNode,
|
||||
setSelectedNode,
|
||||
} = this.props;
|
||||
let { nodeFront } = grid;
|
||||
|
||||
@ -123,9 +131,9 @@ module.exports = createClass({
|
||||
{},
|
||||
dom.input(
|
||||
{
|
||||
checked: grid.highlighted,
|
||||
type: "checkbox",
|
||||
value: grid.id,
|
||||
checked: grid.highlighted,
|
||||
onChange: this.onGridCheckboxClick,
|
||||
}
|
||||
),
|
||||
@ -160,6 +168,7 @@ module.exports = createClass({
|
||||
grid.color
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = GridItem;
|
||||
|
@ -4,29 +4,30 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
createFactory,
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const GridItem = createFactory(require("./GridItem"));
|
||||
|
||||
const Types = require("../types");
|
||||
const { getStr } = require("../utils/l10n");
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
displayName: "GridList",
|
||||
|
||||
propTypes: {
|
||||
getSwatchColorPickerTooltip: PropTypes.func.isRequired,
|
||||
grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onSetGridOverlayColor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
onToggleGridHighlighter: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
class GridList extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
getSwatchColorPickerTooltip: PropTypes.func.isRequired,
|
||||
grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onSetGridOverlayColor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
onToggleGridHighlighter: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
@ -64,6 +65,7 @@ module.exports = createClass({
|
||||
}))
|
||||
)
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = GridList;
|
||||
|
@ -4,10 +4,13 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { addons, createClass, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
const Services = require("Services");
|
||||
const {
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const Types = require("../types");
|
||||
const { getStr } = require("../utils/l10n");
|
||||
|
||||
@ -31,26 +34,37 @@ const GRID_CELL_SCALE_FACTOR = 50;
|
||||
const VIEWPORT_MIN_HEIGHT = 100;
|
||||
const VIEWPORT_MAX_HEIGHT = 150;
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
displayName: "GridOutline",
|
||||
|
||||
propTypes: {
|
||||
grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
|
||||
onShowGridAreaHighlight: PropTypes.func.isRequired,
|
||||
onShowGridCellHighlight: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
getInitialState() {
|
||||
class GridOutline extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
|
||||
onShowGridAreaHighlight: PropTypes.func.isRequired,
|
||||
onShowGridCellHighlight: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
height: 0,
|
||||
selectedGrid: null,
|
||||
showOutline: true,
|
||||
width: 0,
|
||||
};
|
||||
},
|
||||
|
||||
this.doHighlightCell = this.doHighlightCell.bind(this);
|
||||
this.getGridAreaName = this.getGridAreaName.bind(this);
|
||||
this.getHeight = this.getHeight.bind(this);
|
||||
this.getTotalWidthAndHeight = this.getTotalWidthAndHeight.bind(this);
|
||||
this.renderCannotShowOutlineText = this.renderCannotShowOutlineText.bind(this);
|
||||
this.renderGrid = this.renderGrid.bind(this);
|
||||
this.renderGridCell = this.renderGridCell.bind(this);
|
||||
this.renderGridOutline = this.renderGridOutline.bind(this);
|
||||
this.renderGridOutlineBorder = this.renderGridOutlineBorder.bind(this);
|
||||
this.renderOutline = this.renderOutline.bind(this);
|
||||
this.onHighlightCell = this.onHighlightCell.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps({ grids }) {
|
||||
let selectedGrid = grids.find(grid => grid.highlighted);
|
||||
@ -73,90 +87,7 @@ module.exports = createClass({
|
||||
}
|
||||
|
||||
this.setState({ height, width, selectedGrid, showOutline });
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the width and height of a given grid.
|
||||
*
|
||||
* @param {Object} grid
|
||||
* A single grid container in the document.
|
||||
* @return {Object} An object like { width, height }
|
||||
*/
|
||||
getTotalWidthAndHeight(grid) {
|
||||
// TODO: We are drawing the first fragment since only one is currently being stored.
|
||||
// In the future we will need to iterate over all fragments of a grid.
|
||||
const { gridFragments } = grid;
|
||||
const { rows, cols } = gridFragments[0];
|
||||
|
||||
let height = 0;
|
||||
for (let i = 0; i < rows.lines.length - 1; i++) {
|
||||
height += GRID_CELL_SCALE_FACTOR * (rows.tracks[i].breadth / 100);
|
||||
}
|
||||
|
||||
let width = 0;
|
||||
for (let i = 0; i < cols.lines.length - 1; i++) {
|
||||
width += GRID_CELL_SCALE_FACTOR * (cols.tracks[i].breadth / 100);
|
||||
}
|
||||
|
||||
return { width, height };
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the grid area name if the given grid cell is part of a grid area, otherwise
|
||||
* null.
|
||||
*
|
||||
* @param {Number} columnNumber
|
||||
* The column number of the grid cell.
|
||||
* @param {Number} rowNumber
|
||||
* The row number of the grid cell.
|
||||
* @param {Array} areas
|
||||
* Array of grid areas data stored in the grid fragment.
|
||||
* @return {String} If there is a grid area return area name, otherwise null.
|
||||
*/
|
||||
getGridAreaName(columnNumber, rowNumber, areas) {
|
||||
const gridArea = areas.find(area =>
|
||||
(area.rowStart <= rowNumber && area.rowEnd > rowNumber) &&
|
||||
(area.columnStart <= columnNumber && area.columnEnd > columnNumber)
|
||||
);
|
||||
|
||||
if (!gridArea) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return gridArea.name;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the height of the grid outline ranging between a minimum and maximum height.
|
||||
*
|
||||
* @return {Number} The height of the grid outline.
|
||||
*/
|
||||
getHeight() {
|
||||
const { height } = this.state;
|
||||
|
||||
if (height >= VIEWPORT_MAX_HEIGHT) {
|
||||
return VIEWPORT_MAX_HEIGHT;
|
||||
} else if (height <= VIEWPORT_MIN_HEIGHT) {
|
||||
return VIEWPORT_MIN_HEIGHT;
|
||||
}
|
||||
|
||||
return height;
|
||||
},
|
||||
|
||||
onHighlightCell({ target, type }) {
|
||||
// Debounce the highlighting of cells.
|
||||
// This way we don't end up sending many requests to the server for highlighting when
|
||||
// cells get hovered in a rapid succession We only send a request if the user settles
|
||||
// on a cell for some time.
|
||||
if (this.highlightTimeout) {
|
||||
clearTimeout(this.highlightTimeout);
|
||||
}
|
||||
|
||||
this.highlightTimeout = setTimeout(() => {
|
||||
this.doHighlightCell(target, type === "mouseleave");
|
||||
this.highlightTimeout = null;
|
||||
}, GRID_HIGHLIGHTING_DEBOUNCE);
|
||||
},
|
||||
}
|
||||
|
||||
doHighlightCell(target, hide) {
|
||||
const {
|
||||
@ -186,7 +117,75 @@ module.exports = createClass({
|
||||
onShowGridCellHighlight(grids[id].nodeFront, color, fragmentIndex,
|
||||
rowNumber, columnNumber);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the grid area name if the given grid cell is part of a grid area, otherwise
|
||||
* null.
|
||||
*
|
||||
* @param {Number} columnNumber
|
||||
* The column number of the grid cell.
|
||||
* @param {Number} rowNumber
|
||||
* The row number of the grid cell.
|
||||
* @param {Array} areas
|
||||
* Array of grid areas data stored in the grid fragment.
|
||||
* @return {String} If there is a grid area return area name, otherwise null.
|
||||
*/
|
||||
getGridAreaName(columnNumber, rowNumber, areas) {
|
||||
const gridArea = areas.find(area =>
|
||||
(area.rowStart <= rowNumber && area.rowEnd > rowNumber) &&
|
||||
(area.columnStart <= columnNumber && area.columnEnd > columnNumber)
|
||||
);
|
||||
|
||||
if (!gridArea) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return gridArea.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of the grid outline ranging between a minimum and maximum height.
|
||||
*
|
||||
* @return {Number} The height of the grid outline.
|
||||
*/
|
||||
getHeight() {
|
||||
const { height } = this.state;
|
||||
|
||||
if (height >= VIEWPORT_MAX_HEIGHT) {
|
||||
return VIEWPORT_MAX_HEIGHT;
|
||||
} else if (height <= VIEWPORT_MIN_HEIGHT) {
|
||||
return VIEWPORT_MIN_HEIGHT;
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width and height of a given grid.
|
||||
*
|
||||
* @param {Object} grid
|
||||
* A single grid container in the document.
|
||||
* @return {Object} An object like { width, height }
|
||||
*/
|
||||
getTotalWidthAndHeight(grid) {
|
||||
// TODO: We are drawing the first fragment since only one is currently being stored.
|
||||
// In the future we will need to iterate over all fragments of a grid.
|
||||
const { gridFragments } = grid;
|
||||
const { rows, cols } = gridFragments[0];
|
||||
|
||||
let height = 0;
|
||||
for (let i = 0; i < rows.lines.length - 1; i++) {
|
||||
height += GRID_CELL_SCALE_FACTOR * (rows.tracks[i].breadth / 100);
|
||||
}
|
||||
|
||||
let width = 0;
|
||||
for (let i = 0; i < cols.lines.length - 1; i++) {
|
||||
width += GRID_CELL_SCALE_FACTOR * (cols.tracks[i].breadth / 100);
|
||||
}
|
||||
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a message text "Cannot show outline for this grid".
|
||||
@ -204,7 +203,7 @@ module.exports = createClass({
|
||||
),
|
||||
getStr("layout.cannotShowGridOutline")
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the grid outline for the given grid container object.
|
||||
@ -253,7 +252,7 @@ module.exports = createClass({
|
||||
rectangles.unshift(border);
|
||||
|
||||
return rectangles;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the grid cell of a grid fragment.
|
||||
@ -297,7 +296,7 @@ module.exports = createClass({
|
||||
onMouseLeave: this.onHighlightCell,
|
||||
}
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderGridOutline(grid) {
|
||||
let { color } = grid;
|
||||
@ -311,7 +310,7 @@ module.exports = createClass({
|
||||
},
|
||||
this.renderGrid(grid)
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderGridOutlineBorder(borderWidth, borderHeight, color) {
|
||||
return dom.rect(
|
||||
@ -324,7 +323,7 @@ module.exports = createClass({
|
||||
height: borderHeight
|
||||
}
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderOutline() {
|
||||
const {
|
||||
@ -346,7 +345,22 @@ module.exports = createClass({
|
||||
)
|
||||
:
|
||||
this.renderCannotShowOutlineText();
|
||||
},
|
||||
}
|
||||
|
||||
onHighlightCell({ target, type }) {
|
||||
// Debounce the highlighting of cells.
|
||||
// This way we don't end up sending many requests to the server for highlighting when
|
||||
// cells get hovered in a rapid succession We only send a request if the user settles
|
||||
// on a cell for some time.
|
||||
if (this.highlightTimeout) {
|
||||
clearTimeout(this.highlightTimeout);
|
||||
}
|
||||
|
||||
this.highlightTimeout = setTimeout(() => {
|
||||
this.doHighlightCell(target, type === "mouseleave");
|
||||
this.highlightTimeout = null;
|
||||
}, GRID_HIGHLIGHTING_DEBOUNCE);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedGrid } = this.state;
|
||||
@ -361,6 +375,7 @@ module.exports = createClass({
|
||||
)
|
||||
:
|
||||
null;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = GridOutline;
|
||||
|
@ -55,8 +55,8 @@ function GridInspector(inspector, window) {
|
||||
this.getSwatchColorPickerTooltip = this.getSwatchColorPickerTooltip.bind(this);
|
||||
this.updateGridPanel = this.updateGridPanel.bind(this);
|
||||
|
||||
this.onNavigate = this.onNavigate.bind(this);
|
||||
this.onHighlighterChange = this.onHighlighterChange.bind(this);
|
||||
this.onNavigate = this.onNavigate.bind(this);
|
||||
this.onReflow = throttle(this.onReflow, 500, this);
|
||||
this.onSetGridOverlayColor = this.onSetGridOverlayColor.bind(this);
|
||||
this.onShowGridAreaHighlight = this.onShowGridAreaHighlight.bind(this);
|
||||
@ -215,6 +215,32 @@ GridInspector.prototype = {
|
||||
return this.swatchColorPickerTooltip;
|
||||
},
|
||||
|
||||
/**
|
||||
* Given a list of new grid fronts, and if we have a currently highlighted grid, check
|
||||
* if its fragments have changed.
|
||||
*
|
||||
* @param {Array} newGridFronts
|
||||
* A list of GridFront objects.
|
||||
* @return {Boolean}
|
||||
*/
|
||||
haveCurrentFragmentsChanged(newGridFronts) {
|
||||
const currentNode = this.highlighters.gridHighlighterShown;
|
||||
if (!currentNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const newGridFront = newGridFronts.find(g => g.containerNodeFront === currentNode);
|
||||
if (!newGridFront) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { grids } = this.store.getState();
|
||||
const oldFragments = grids.find(g => g.nodeFront === currentNode).gridFragments;
|
||||
const newFragments = newGridFront.gridFragments;
|
||||
|
||||
return !compareFragmentsGeometry(oldFragments, newFragments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the layout panel is visible, and false otherwise.
|
||||
*/
|
||||
@ -317,16 +343,6 @@ GridInspector.prototype = {
|
||||
this.store.dispatch(updateGrids(grids));
|
||||
}),
|
||||
|
||||
/**
|
||||
* Handler for "new-root" event fired by the inspector, which indicates a page
|
||||
* navigation. Updates grid panel contents.
|
||||
*/
|
||||
onNavigate() {
|
||||
if (this.isPanelVisible()) {
|
||||
this.updateGridPanel();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for "grid-highlighter-shown" and "grid-highlighter-hidden" events emitted
|
||||
* from the HighlightersOverlay. Updates the NodeFront's grid highlighted state.
|
||||
@ -361,29 +377,13 @@ GridInspector.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Given a list of new grid fronts, and if we have a currently highlighted grid, check
|
||||
* if its fragments have changed.
|
||||
*
|
||||
* @param {Array} newGridFronts
|
||||
* A list of GridFront objects.
|
||||
* @return {Boolean}
|
||||
* Handler for "new-root" event fired by the inspector, which indicates a page
|
||||
* navigation. Updates grid panel contents.
|
||||
*/
|
||||
haveCurrentFragmentsChanged(newGridFronts) {
|
||||
const currentNode = this.highlighters.gridHighlighterShown;
|
||||
if (!currentNode) {
|
||||
return false;
|
||||
onNavigate() {
|
||||
if (this.isPanelVisible()) {
|
||||
this.updateGridPanel();
|
||||
}
|
||||
|
||||
const newGridFront = newGridFronts.find(g => g.containerNodeFront === currentNode);
|
||||
if (!newGridFront) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { grids } = this.store.getState();
|
||||
const oldFragments = grids.find(g => g.nodeFront === currentNode).gridFragments;
|
||||
const newFragments = newGridFront.gridFragments;
|
||||
|
||||
return !compareFragmentsGeometry(oldFragments, newFragments);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -10,25 +10,30 @@
|
||||
"use strict";
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { DOM: dom, PropTypes } = React;
|
||||
const { PureComponent, DOM: dom, PropTypes } = React;
|
||||
|
||||
const { div, span } = dom;
|
||||
|
||||
const Accordion = React.createClass({
|
||||
displayName: "Accordion",
|
||||
class Accordion extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
items: PropTypes.array
|
||||
};
|
||||
}
|
||||
|
||||
propTypes: {
|
||||
items: PropTypes.array
|
||||
},
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
mixins: [ React.addons.PureRenderMixin ],
|
||||
this.state = {
|
||||
opened: props.items.map(item => item.opened),
|
||||
created: []
|
||||
};
|
||||
|
||||
getInitialState: function () {
|
||||
return { opened: this.props.items.map(item => item.opened),
|
||||
created: [] };
|
||||
},
|
||||
this.handleHeaderClick = this.handleHeaderClick.bind(this);
|
||||
this.renderContainer = this.renderContainer.bind(this);
|
||||
}
|
||||
|
||||
handleHeaderClick: function (i) {
|
||||
handleHeaderClick(i) {
|
||||
const opened = [...this.state.opened];
|
||||
const created = [...this.state.created];
|
||||
const item = this.props.items[i];
|
||||
@ -45,9 +50,9 @@ const Accordion = React.createClass({
|
||||
}
|
||||
|
||||
this.setState({ opened, created });
|
||||
},
|
||||
}
|
||||
|
||||
renderContainer: function (item, i) {
|
||||
renderContainer(item, i) {
|
||||
const { opened, created } = this.state;
|
||||
const containerClassName =
|
||||
item.header.toLowerCase().replace(/\s/g, "-") + "-pane";
|
||||
@ -75,14 +80,14 @@ const Accordion = React.createClass({
|
||||
) :
|
||||
null
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
render: function () {
|
||||
render() {
|
||||
return div(
|
||||
{ className: "accordion" },
|
||||
this.props.items.map(this.renderContainer)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = Accordion;
|
||||
|
@ -5,10 +5,13 @@
|
||||
"use strict";
|
||||
|
||||
const Services = require("Services");
|
||||
const { addons, createClass, createFactory, DOM: dom, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
createFactory,
|
||||
DOM: dom,
|
||||
PropTypes,
|
||||
PureComponent,
|
||||
} = require("devtools/client/shared/vendor/react");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
|
||||
const BoxModel = createFactory(require("devtools/client/inspector/boxmodel/components/BoxModel"));
|
||||
@ -28,28 +31,25 @@ const LAYOUT_L10N = new LocalizationHelper(LAYOUT_STRINGS_URI);
|
||||
const BOXMODEL_OPENED_PREF = "devtools.layout.boxmodel.opened";
|
||||
const GRID_OPENED_PREF = "devtools.layout.grid.opened";
|
||||
|
||||
const App = createClass({
|
||||
|
||||
displayName: "App",
|
||||
|
||||
propTypes: {
|
||||
boxModel: PropTypes.shape(BoxModelTypes.boxModel).isRequired,
|
||||
getSwatchColorPickerTooltip: PropTypes.func.isRequired,
|
||||
grids: PropTypes.arrayOf(PropTypes.shape(GridTypes.grid)).isRequired,
|
||||
highlighterSettings: PropTypes.shape(GridTypes.highlighterSettings).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
showBoxModelProperties: PropTypes.bool.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onSetGridOverlayColor: PropTypes.func.isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
onToggleGridHighlighter: PropTypes.func.isRequired,
|
||||
onToggleShowGridLineNumbers: PropTypes.func.isRequired,
|
||||
onToggleShowInfiniteLines: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
class App extends PureComponent {
|
||||
static get propTypes() {
|
||||
return {
|
||||
boxModel: PropTypes.shape(BoxModelTypes.boxModel).isRequired,
|
||||
getSwatchColorPickerTooltip: PropTypes.func.isRequired,
|
||||
grids: PropTypes.arrayOf(PropTypes.shape(GridTypes.grid)).isRequired,
|
||||
highlighterSettings: PropTypes.shape(GridTypes.highlighterSettings).isRequired,
|
||||
setSelectedNode: PropTypes.func.isRequired,
|
||||
showBoxModelProperties: PropTypes.bool.isRequired,
|
||||
onHideBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onSetGridOverlayColor: PropTypes.func.isRequired,
|
||||
onShowBoxModelEditor: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighter: PropTypes.func.isRequired,
|
||||
onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
|
||||
onToggleGridHighlighter: PropTypes.func.isRequired,
|
||||
onToggleShowGridLineNumbers: PropTypes.func.isRequired,
|
||||
onToggleShowInfiniteLines: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return dom.div(
|
||||
@ -59,9 +59,9 @@ const App = createClass({
|
||||
Accordion({
|
||||
items: [
|
||||
{
|
||||
header: LAYOUT_L10N.getStr("layout.header"),
|
||||
component: Grid,
|
||||
componentProps: this.props,
|
||||
header: LAYOUT_L10N.getStr("layout.header"),
|
||||
opened: Services.prefs.getBoolPref(GRID_OPENED_PREF),
|
||||
onToggled: () => {
|
||||
let opened = Services.prefs.getBoolPref(GRID_OPENED_PREF);
|
||||
@ -69,9 +69,9 @@ const App = createClass({
|
||||
}
|
||||
},
|
||||
{
|
||||
header: BOXMODEL_L10N.getStr("boxmodel.title"),
|
||||
component: BoxModel,
|
||||
componentProps: this.props,
|
||||
header: BOXMODEL_L10N.getStr("boxmodel.title"),
|
||||
opened: Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
|
||||
onToggled: () => {
|
||||
let opened = Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF);
|
||||
@ -81,8 +81,7 @@ const App = createClass({
|
||||
]
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect(state => state)(App);
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
const PREF_WHITELIST = [
|
||||
"devtools",
|
||||
"layout.css.grid.enabled"
|
||||
];
|
||||
|
||||
const acceptLine = function (line) {
|
||||
|
@ -44,7 +44,7 @@ const EventEmitter = require("devtools-modules/src/utils/event-emitter");
|
||||
EventEmitter.decorate(window);
|
||||
|
||||
const { configureStore } = require("./src/utils/create-store");
|
||||
const App = require("./src/components/app");
|
||||
const App = require("./src/components/App");
|
||||
const { Connector } = require("./src/connector/index");
|
||||
const connector = new Connector();
|
||||
const store = configureStore(connector);
|
||||
|
@ -27,7 +27,6 @@ const {LocalizationHelper} = require("devtools/shared/l10n");
|
||||
const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const CSS_GRID_ENABLED_PREF = "layout.css.grid.enabled";
|
||||
const CSS_SHAPES_ENABLED_PREF = "devtools.inspector.shapesHighlighter.enabled";
|
||||
const CSS_SHAPE_OUTSIDE_ENABLED_PREF = "layout.css.shape-outside.enabled";
|
||||
|
||||
@ -371,8 +370,7 @@ OutputParser.prototype = {
|
||||
if (options.expectCubicBezier &&
|
||||
BEZIER_KEYWORDS.indexOf(token.text) >= 0) {
|
||||
this._appendCubicBezier(token.text, options);
|
||||
} else if (Services.prefs.getBoolPref(CSS_GRID_ENABLED_PREF) &&
|
||||
this._isDisplayGrid(text, token, options)) {
|
||||
} else if (this._isDisplayGrid(text, token, options)) {
|
||||
this._appendGrid(token.text, options);
|
||||
} else if (colorOK() &&
|
||||
colorUtils.isValidCSSColor(token.text, this.cssColor4)) {
|
||||
|
@ -707,10 +707,11 @@ HighlighterEnvironment.prototype = {
|
||||
register("BoxModelHighlighter", "box-model");
|
||||
register("CssGridHighlighter", "css-grid");
|
||||
register("CssTransformHighlighter", "css-transform");
|
||||
register("SelectorHighlighter", "selector");
|
||||
register("GeometryEditorHighlighter", "geometry-editor");
|
||||
register("RulersHighlighter", "rulers");
|
||||
register("MeasuringToolHighlighter", "measuring-tool");
|
||||
register("EyeDropper", "eye-dropper");
|
||||
register("FlexboxHighlighter", "flexbox");
|
||||
register("GeometryEditorHighlighter", "geometry-editor");
|
||||
register("MeasuringToolHighlighter", "measuring-tool");
|
||||
register("PausedDebuggerOverlay", "paused-debugger");
|
||||
register("RulersHighlighter", "rulers");
|
||||
register("SelectorHighlighter", "selector");
|
||||
register("ShapesHighlighter", "shapes");
|
||||
|
@ -6,34 +6,37 @@
|
||||
|
||||
const Services = require("Services");
|
||||
const { AutoRefreshHighlighter } = require("./auto-refresh");
|
||||
const {
|
||||
CANVAS_SIZE,
|
||||
drawBubbleRect,
|
||||
drawLine,
|
||||
drawRect,
|
||||
drawRoundedRect,
|
||||
getBoundsFromPoints,
|
||||
getCanvasPosition,
|
||||
getCurrentMatrix,
|
||||
getPathDescriptionFromPoints,
|
||||
getPointsFromDiagonal,
|
||||
updateCanvasElement,
|
||||
} = require("./utils/canvas");
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper,
|
||||
createNode,
|
||||
createSVGNode,
|
||||
moveInfobar,
|
||||
} = require("./utils/markup");
|
||||
const { apply } = require("devtools/shared/layout/dom-matrix-2d");
|
||||
const {
|
||||
getCurrentZoom,
|
||||
getDisplayPixelRatio,
|
||||
setIgnoreLayoutChanges,
|
||||
getViewportDimensions,
|
||||
} = require("devtools/shared/layout/utils");
|
||||
const {
|
||||
identity,
|
||||
apply,
|
||||
translate,
|
||||
multiply,
|
||||
scale,
|
||||
isIdentity,
|
||||
getNodeTransformationMatrix,
|
||||
} = require("devtools/shared/layout/dom-matrix-2d");
|
||||
const { stringifyGridFragments } = require("devtools/server/actors/utils/css-grid-utils");
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
|
||||
const LAYOUT_STRINGS_URI = "devtools/client/locales/layout.properties";
|
||||
const LAYOUT_L10N = new LocalizationHelper(LAYOUT_STRINGS_URI);
|
||||
|
||||
const CSS_GRID_ENABLED_PREF = "layout.css.grid.enabled";
|
||||
const NEGATIVE_LINE_NUMBERS_PREF = "devtools.gridinspector.showNegativeLineNumbers";
|
||||
|
||||
const DEFAULT_GRID_COLOR = "#4B0082";
|
||||
@ -75,263 +78,6 @@ const GRID_GAP_ALPHA = 0.5;
|
||||
*/
|
||||
const gCachedGridPattern = new Map();
|
||||
|
||||
// We create a <canvas> element that has always 4096x4096 physical pixels, to displays
|
||||
// our grid's overlay.
|
||||
// Then, we move the element around when needed, to give the perception that it always
|
||||
// covers the screen (See bug 1345434).
|
||||
//
|
||||
// This canvas size value is the safest we can use because most GPUs can handle it.
|
||||
// It's also far from the maximum canvas memory allocation limit (4096x4096x4 is
|
||||
// 67.108.864 bytes, where the limit is 500.000.000 bytes, see:
|
||||
// http://searchfox.org/mozilla-central/source/gfx/thebes/gfxPrefs.h#401).
|
||||
//
|
||||
// Note:
|
||||
// Once bug 1232491 lands, we could try to refactor this code to use the values from
|
||||
// the displayport API instead.
|
||||
//
|
||||
// Using a fixed value should also solve bug 1348293.
|
||||
const CANVAS_SIZE = 4096;
|
||||
|
||||
/**
|
||||
* Returns an array containing the four coordinates of a rectangle, given its diagonal
|
||||
* as input; optionally applying a matrix, and a function to each of the coordinates'
|
||||
* value.
|
||||
*
|
||||
* @param {Number} x1
|
||||
* The x-axis coordinate of the rectangle's diagonal start point.
|
||||
* @param {Number} y1
|
||||
* The y-axis coordinate of the rectangle's diagonal start point.
|
||||
* @param {Number} x2
|
||||
* The x-axis coordinate of the rectangle's diagonal end point.
|
||||
* @param {Number} y2
|
||||
* The y-axis coordinate of the rectangle's diagonal end point.
|
||||
* @param {Array} [matrix=identity()]
|
||||
* A transformation matrix to apply.
|
||||
* @return {Array}
|
||||
* The rect four corners' points transformed by the matrix given.
|
||||
*/
|
||||
function getPointsFromDiagonal(x1, y1, x2, y2, matrix = identity()) {
|
||||
return [
|
||||
[x1, y1],
|
||||
[x2, y1],
|
||||
[x2, y2],
|
||||
[x1, y2]
|
||||
].map(point => {
|
||||
let transformedPoint = apply(matrix, point);
|
||||
|
||||
return {x: transformedPoint[0], y: transformedPoint[1]};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array of four points and returns a DOMRect-like object, represent the
|
||||
* boundaries defined by the points given.
|
||||
*
|
||||
* @param {Array} points
|
||||
* The four points.
|
||||
* @return {Object}
|
||||
* A DOMRect-like object.
|
||||
*/
|
||||
function getBoundsFromPoints(points) {
|
||||
let bounds = {};
|
||||
|
||||
bounds.left = Math.min(points[0].x, points[1].x, points[2].x, points[3].x);
|
||||
bounds.right = Math.max(points[0].x, points[1].x, points[2].x, points[3].x);
|
||||
bounds.top = Math.min(points[0].y, points[1].y, points[2].y, points[3].y);
|
||||
bounds.bottom = Math.max(points[0].y, points[1].y, points[2].y, points[3].y);
|
||||
|
||||
bounds.x = bounds.left;
|
||||
bounds.y = bounds.top;
|
||||
bounds.width = bounds.right - bounds.left;
|
||||
bounds.height = bounds.bottom - bounds.top;
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array of four points and returns a string represent a path description.
|
||||
*
|
||||
* @param {Array} points
|
||||
* The four points.
|
||||
* @return {String}
|
||||
* A Path Description that can be used in svg's <path> element.
|
||||
*/
|
||||
function getPathDescriptionFromPoints(points) {
|
||||
return "M" + points[0].x + "," + points[0].y + " " +
|
||||
"L" + points[1].x + "," + points[1].y + " " +
|
||||
"L" + points[2].x + "," + points[2].y + " " +
|
||||
"L" + points[3].x + "," + points[3].y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a line to the context given, applying a transformation matrix if passed.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* The 2d canvas context.
|
||||
* @param {Number} x1
|
||||
* The x-axis of the coordinate for the begin of the line.
|
||||
* @param {Number} y1
|
||||
* The y-axis of the coordinate for the begin of the line.
|
||||
* @param {Number} x2
|
||||
* The x-axis of the coordinate for the end of the line.
|
||||
* @param {Number} y2
|
||||
* The y-axis of the coordinate for the end of the line.
|
||||
* @param {Object} [options]
|
||||
* The options object.
|
||||
* @param {Array} [options.matrix=identity()]
|
||||
* The transformation matrix to apply.
|
||||
* @param {Array} [options.extendToBoundaries]
|
||||
* If set, the line will be extended to reach the boundaries specified.
|
||||
*/
|
||||
function drawLine(ctx, x1, y1, x2, y2, options) {
|
||||
let matrix = options.matrix || identity();
|
||||
|
||||
let p1 = apply(matrix, [x1, y1]);
|
||||
let p2 = apply(matrix, [x2, y2]);
|
||||
|
||||
x1 = p1[0];
|
||||
y1 = p1[1];
|
||||
x2 = p2[0];
|
||||
y2 = p2[1];
|
||||
|
||||
if (options.extendToBoundaries) {
|
||||
if (p1[1] === p2[1]) {
|
||||
x1 = options.extendToBoundaries[0];
|
||||
x2 = options.extendToBoundaries[2];
|
||||
} else {
|
||||
y1 = options.extendToBoundaries[1];
|
||||
x1 = (p2[0] - p1[0]) * (y1 - p1[1]) / (p2[1] - p1[1]) + p1[0];
|
||||
y2 = options.extendToBoundaries[3];
|
||||
x2 = (p2[0] - p1[0]) * (y2 - p1[1]) / (p2[1] - p1[1]) + p1[0];
|
||||
}
|
||||
}
|
||||
|
||||
ctx.moveTo(Math.round(x1), Math.round(y1));
|
||||
ctx.lineTo(Math.round(x2), Math.round(y2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a rect to the context given, applying a transformation matrix if passed.
|
||||
* The coordinates are the start and end points of the rectangle's diagonal.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* The 2d canvas context.
|
||||
* @param {Number} x1
|
||||
* The x-axis coordinate of the rectangle's diagonal start point.
|
||||
* @param {Number} y1
|
||||
* The y-axis coordinate of the rectangle's diagonal start point.
|
||||
* @param {Number} x2
|
||||
* The x-axis coordinate of the rectangle's diagonal end point.
|
||||
* @param {Number} y2
|
||||
* The y-axis coordinate of the rectangle's diagonal end point.
|
||||
* @param {Array} [matrix=identity()]
|
||||
* The transformation matrix to apply.
|
||||
*/
|
||||
function drawRect(ctx, x1, y1, x2, y2, matrix = identity()) {
|
||||
let p = getPointsFromDiagonal(x1, y1, x2, y2, matrix);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(Math.round(p[0].x), Math.round(p[0].y));
|
||||
ctx.lineTo(Math.round(p[1].x), Math.round(p[1].y));
|
||||
ctx.lineTo(Math.round(p[2].x), Math.round(p[2].y));
|
||||
ctx.lineTo(Math.round(p[3].x), Math.round(p[3].y));
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to draw a rounded rectangle in the provided canvas context.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* The 2d canvas context.
|
||||
* @param {Number} x
|
||||
* The x-axis origin of the rectangle.
|
||||
* @param {Number} y
|
||||
* The y-axis origin of the rectangle.
|
||||
* @param {Number} width
|
||||
* The width of the rectangle.
|
||||
* @param {Number} height
|
||||
* The height of the rectangle.
|
||||
* @param {Number} radius
|
||||
* The radius of the rounding.
|
||||
*/
|
||||
function drawRoundedRect(ctx, x, y, width, height, radius) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y + radius);
|
||||
ctx.lineTo(x, y + height - radius);
|
||||
ctx.arcTo(x, y + height, x + radius, y + height, radius);
|
||||
ctx.lineTo(x + width - radius, y + height);
|
||||
ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
|
||||
ctx.lineTo(x + width, y + radius);
|
||||
ctx.arcTo(x + width, y, x + width - radius, y, radius);
|
||||
ctx.lineTo(x + radius, y);
|
||||
ctx.arcTo(x, y, x, y + radius, radius);
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to draw an arrow-bubble rectangle in the provided canvas context.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* The 2d canvas context.
|
||||
* @param {Number} x
|
||||
* The x-axis origin of the rectangle.
|
||||
* @param {Number} y
|
||||
* The y-axis origin of the rectangle.
|
||||
* @param {Number} width
|
||||
* The width of the rectangle.
|
||||
* @param {Number} height
|
||||
* The height of the rectangle.
|
||||
* @param {Number} radius
|
||||
* The radius of the rounding.
|
||||
* @param {Number} margin
|
||||
* The distance of the origin point from the pointer.
|
||||
* @param {Number} arrowSize
|
||||
* The size of the arrow.
|
||||
* @param {String} alignment
|
||||
* The alignment of the rectangle in relation to its position to the grid.
|
||||
*/
|
||||
function drawBubbleRect(ctx, x, y, width, height, radius, margin, arrowSize, alignment) {
|
||||
let angle = 0;
|
||||
|
||||
if (alignment === "bottom") {
|
||||
angle = 180;
|
||||
} else if (alignment === "right") {
|
||||
angle = 90;
|
||||
[width, height] = [height, width];
|
||||
} else if (alignment === "left") {
|
||||
[width, height] = [height, width];
|
||||
angle = 270;
|
||||
}
|
||||
|
||||
let originX = x;
|
||||
let originY = y;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(originX, originY);
|
||||
ctx.rotate(angle * (Math.PI / 180));
|
||||
ctx.translate(-originX, -originY);
|
||||
ctx.translate(-width / 2, -height - arrowSize - margin);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y + radius);
|
||||
ctx.lineTo(x, y + height - radius);
|
||||
ctx.arcTo(x, y + height, x + radius, y + height, radius);
|
||||
ctx.lineTo(x + width / 2 - arrowSize, y + height);
|
||||
ctx.lineTo(x + width / 2, y + height + arrowSize);
|
||||
ctx.lineTo(x + width / 2 + arrowSize, y + height);
|
||||
ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
|
||||
ctx.lineTo(x + width, y + radius);
|
||||
ctx.arcTo(x + width, y, x + width - radius, y, radius);
|
||||
ctx.lineTo(x + radius, y);
|
||||
ctx.arcTo(x, y, x, y + radius, radius);
|
||||
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* The CssGridHighlighter is the class that overlays a visual grid on top of
|
||||
* display:[inline-]grid elements.
|
||||
@ -430,9 +176,12 @@ class CssGridHighlighter extends AutoRefreshHighlighter {
|
||||
y: 0
|
||||
};
|
||||
|
||||
// Calling `calculateCanvasPosition` anyway since the highlighter could be initialized
|
||||
// Calling `getCanvasPosition` anyway since the highlighter could be initialized
|
||||
// on a page that has scrolled already.
|
||||
this.calculateCanvasPosition();
|
||||
let { canvasX, canvasY } = getCanvasPosition(this._canvasPosition, this._scroll,
|
||||
this.win, this._winDimensions);
|
||||
this._canvasPosition.x = canvasX;
|
||||
this._canvasPosition.y = canvasY;
|
||||
}
|
||||
|
||||
_buildMarkup() {
|
||||
@ -760,7 +509,7 @@ class CssGridHighlighter extends AutoRefreshHighlighter {
|
||||
}
|
||||
|
||||
_show() {
|
||||
if (Services.prefs.getBoolPref(CSS_GRID_ENABLED_PREF) && !this.isGrid()) {
|
||||
if (!this.isGrid()) {
|
||||
this.hide();
|
||||
return false;
|
||||
}
|
||||
@ -901,14 +650,17 @@ class CssGridHighlighter extends AutoRefreshHighlighter {
|
||||
|
||||
// Updates the <canvas> element's position and size.
|
||||
// It also clear the <canvas>'s drawing context.
|
||||
this.updateCanvasElement();
|
||||
updateCanvasElement(this.canvas, this._canvasPosition, this.win.devicePixelRatio);
|
||||
|
||||
// Clear the grid area highlights.
|
||||
this.clearGridAreas();
|
||||
this.clearGridCell();
|
||||
|
||||
// Update the current matrix used in our canvas' rendering
|
||||
this.updateCurrentMatrix();
|
||||
let { currentMatrix, hasNodeTransformations } = getCurrentMatrix(this.currentNode,
|
||||
this.win);
|
||||
this.currentMatrix = currentMatrix;
|
||||
this.hasNodeTransformations = hasNodeTransformations;
|
||||
|
||||
// Start drawing the grid fragments.
|
||||
for (let i = 0; i < this.gridData.length; i++) {
|
||||
@ -936,7 +688,7 @@ class CssGridHighlighter extends AutoRefreshHighlighter {
|
||||
this._showGridElements();
|
||||
|
||||
root.setAttribute("style",
|
||||
`position:absolute; width:${width}px;height:${height}px; overflow:hidden`);
|
||||
`position: absolute; width: ${width}px; height: ${height}px; overflow: hidden`);
|
||||
|
||||
setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
|
||||
return true;
|
||||
@ -1020,134 +772,14 @@ class CssGridHighlighter extends AutoRefreshHighlighter {
|
||||
* to give the illusion that it always covers the viewport.
|
||||
*/
|
||||
_scrollUpdate() {
|
||||
let hasPositionChanged = this.calculateCanvasPosition();
|
||||
let { hasUpdated } = getCanvasPosition(this._canvasPosition, this._scroll, this.win,
|
||||
this._winDimensions);
|
||||
|
||||
if (hasPositionChanged) {
|
||||
if (hasUpdated) {
|
||||
this._update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is responsible to do the math that updates the <canvas>'s position,
|
||||
* in accordance with the page's scroll, document's size, canvas size, and
|
||||
* viewport's size.
|
||||
* It's called when a page's scroll is detected.
|
||||
*
|
||||
* @return {Boolean} `true` if the <canvas> position was updated, `false` otherwise.
|
||||
*/
|
||||
calculateCanvasPosition() {
|
||||
let cssCanvasSize = CANVAS_SIZE / this.win.devicePixelRatio;
|
||||
let viewportSize = getViewportDimensions(this.win);
|
||||
let documentSize = this._winDimensions;
|
||||
let pageX = this._scroll.x;
|
||||
let pageY = this._scroll.y;
|
||||
let canvasWidth = cssCanvasSize;
|
||||
let canvasHeight = cssCanvasSize;
|
||||
let hasUpdated = false;
|
||||
|
||||
// Those values indicates the relative horizontal and vertical space the page can
|
||||
// scroll before we have to reposition the <canvas>; they're 1/4 of the delta between
|
||||
// the canvas' size and the viewport's size: that's because we want to consider both
|
||||
// sides (top/bottom, left/right; so 1/2 for each side) and also we don't want to
|
||||
// shown the edges of the canvas in case of fast scrolling (to avoid showing undraw
|
||||
// areas, therefore another 1/2 here).
|
||||
let bufferSizeX = (canvasWidth - viewportSize.width) >> 2;
|
||||
let bufferSizeY = (canvasHeight - viewportSize.height) >> 2;
|
||||
|
||||
let { x, y } = this._canvasPosition;
|
||||
|
||||
// Defines the boundaries for the canvas.
|
||||
let topBoundary = 0;
|
||||
let bottomBoundary = documentSize.height - canvasHeight;
|
||||
let leftBoundary = 0;
|
||||
let rightBoundary = documentSize.width - canvasWidth;
|
||||
|
||||
// Defines the thresholds that triggers the canvas' position to be updated.
|
||||
let topThreshold = pageY - bufferSizeY;
|
||||
let bottomThreshold = pageY - canvasHeight + viewportSize.height + bufferSizeY;
|
||||
let leftThreshold = pageX - bufferSizeX;
|
||||
let rightThreshold = pageX - canvasWidth + viewportSize.width + bufferSizeX;
|
||||
|
||||
if (y < bottomBoundary && y < bottomThreshold) {
|
||||
this._canvasPosition.y = Math.min(topThreshold, bottomBoundary);
|
||||
hasUpdated = true;
|
||||
} else if (y > topBoundary && y > topThreshold) {
|
||||
this._canvasPosition.y = Math.max(bottomThreshold, topBoundary);
|
||||
hasUpdated = true;
|
||||
}
|
||||
|
||||
if (x < rightBoundary && x < rightThreshold) {
|
||||
this._canvasPosition.x = Math.min(leftThreshold, rightBoundary);
|
||||
hasUpdated = true;
|
||||
} else if (x > leftBoundary && x > leftThreshold) {
|
||||
this._canvasPosition.x = Math.max(rightThreshold, leftBoundary);
|
||||
hasUpdated = true;
|
||||
}
|
||||
|
||||
return hasUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the <canvas> element's style in accordance with the current window's
|
||||
* devicePixelRatio, and the position calculated in `calculateCanvasPosition`; it also
|
||||
* clears the drawing context.
|
||||
*/
|
||||
updateCanvasElement() {
|
||||
let size = CANVAS_SIZE / this.win.devicePixelRatio;
|
||||
let { x, y } = this._canvasPosition;
|
||||
|
||||
// Resize the canvas taking the dpr into account so as to have crisp lines, and
|
||||
// translating it to give the perception that it always covers the viewport.
|
||||
this.canvas.setAttribute("style",
|
||||
`width:${size}px;height:${size}px; transform: translate(${x}px, ${y}px);`);
|
||||
|
||||
this.ctx.clearRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current matrices for both canvas drawing and SVG, taking in account the
|
||||
* following transformations, in this order:
|
||||
* 1. The scale given by the display pixel ratio.
|
||||
* 2. The translation to the top left corner of the element.
|
||||
* 3. The scale given by the current zoom.
|
||||
* 4. The translation given by the top and left padding of the element.
|
||||
* 5. Any CSS transformation applied directly to the element (only 2D
|
||||
* transformation; the 3D transformation are flattened, see `dom-matrix-2d` module
|
||||
* for further details.)
|
||||
*
|
||||
* The transformations of the element's ancestors are not currently computed (see
|
||||
* bug 1355675).
|
||||
*/
|
||||
updateCurrentMatrix() {
|
||||
let computedStyle = this.currentNode.ownerGlobal.getComputedStyle(this.currentNode);
|
||||
|
||||
let paddingTop = parseFloat(computedStyle.paddingTop);
|
||||
let paddingLeft = parseFloat(computedStyle.paddingLeft);
|
||||
let borderTop = parseFloat(computedStyle.borderTopWidth);
|
||||
let borderLeft = parseFloat(computedStyle.borderLeftWidth);
|
||||
|
||||
let nodeMatrix = getNodeTransformationMatrix(this.currentNode,
|
||||
this.win.document.documentElement);
|
||||
|
||||
let m = identity();
|
||||
|
||||
// First, we scale based on the device pixel ratio.
|
||||
m = multiply(m, scale(this.win.devicePixelRatio));
|
||||
// Then, we apply the current node's transformation matrix, relative to the
|
||||
// inspected window's root element, but only if it's not a identity matrix.
|
||||
if (isIdentity(nodeMatrix)) {
|
||||
this.hasNodeTransformations = false;
|
||||
} else {
|
||||
m = multiply(m, nodeMatrix);
|
||||
this.hasNodeTransformations = true;
|
||||
}
|
||||
|
||||
// Finally, we translate the origin based on the node's padding and border values.
|
||||
m = multiply(m, translate(paddingLeft + borderLeft, paddingTop + borderTop));
|
||||
|
||||
this.currentMatrix = m;
|
||||
}
|
||||
|
||||
getFirstRowLinePos(fragment) {
|
||||
return fragment.rows.lines[0].start;
|
||||
}
|
||||
|
199
devtools/server/actors/highlighters/flexbox.js
Normal file
199
devtools/server/actors/highlighters/flexbox.js
Normal file
@ -0,0 +1,199 @@
|
||||
/* 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 { AutoRefreshHighlighter } = require("./auto-refresh");
|
||||
const {
|
||||
CANVAS_SIZE,
|
||||
getCanvasPosition,
|
||||
getCurrentMatrix,
|
||||
updateCanvasElement,
|
||||
} = require("./utils/canvas");
|
||||
const {
|
||||
CanvasFrameAnonymousContentHelper,
|
||||
createNode,
|
||||
} = require("./utils/markup");
|
||||
const {
|
||||
setIgnoreLayoutChanges,
|
||||
} = require("devtools/shared/layout/utils");
|
||||
|
||||
class FlexboxHighlighter extends AutoRefreshHighlighter {
|
||||
constructor(highlighterEnv) {
|
||||
super(highlighterEnv);
|
||||
|
||||
this.ID_CLASS_PREFIX = "flexbox-";
|
||||
|
||||
this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv,
|
||||
this._buildMarkup.bind(this));
|
||||
|
||||
this.onPageHide = this.onPageHide.bind(this);
|
||||
this.onWillNavigate = this.onWillNavigate.bind(this);
|
||||
|
||||
this.highlighterEnv.on("will-navigate", this.onWillNavigate);
|
||||
|
||||
let { pageListenerTarget } = highlighterEnv;
|
||||
pageListenerTarget.addEventListener("pagehide", this.onPageHide);
|
||||
|
||||
// Initialize the <canvas> position to the top left corner of the page
|
||||
this._canvasPosition = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
|
||||
// Calling `getCanvasPosition` anyway since the highlighter could be initialized
|
||||
// on a page that has scrolled already.
|
||||
let { canvasX, canvasY } = getCanvasPosition(this._canvasPosition, this._scroll,
|
||||
this.win, this._winDimensions);
|
||||
this._canvasPosition.x = canvasX;
|
||||
this._canvasPosition.y = canvasY;
|
||||
}
|
||||
|
||||
_buildMarkup() {
|
||||
let container = createNode(this.win, {
|
||||
attributes: {
|
||||
"class": "highlighter-container"
|
||||
}
|
||||
});
|
||||
|
||||
let root = createNode(this.win, {
|
||||
parent: container,
|
||||
attributes: {
|
||||
"id": "root",
|
||||
"class": "root"
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
// We use a <canvas> element because there is an arbitrary number of items and texts
|
||||
// to draw which wouldn't be possible with HTML or SVG without having to insert and
|
||||
// remove the whole markup on every update.
|
||||
createNode(this.win, {
|
||||
parent: root,
|
||||
nodeType: "canvas",
|
||||
attributes: {
|
||||
"id": "canvas",
|
||||
"class": "canvas",
|
||||
"hidden": "true",
|
||||
"width": CANVAS_SIZE,
|
||||
"height": CANVAS_SIZE
|
||||
},
|
||||
prefix: this.ID_CLASS_PREFIX
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
let { highlighterEnv } = this;
|
||||
highlighterEnv.off("will-navigate", this.onWillNavigate);
|
||||
|
||||
let { pageListenerTarget } = highlighterEnv;
|
||||
if (pageListenerTarget) {
|
||||
pageListenerTarget.removeEventListener("pagehide", this.onPageHide);
|
||||
}
|
||||
|
||||
this.markup.destroy();
|
||||
|
||||
AutoRefreshHighlighter.prototype.destroy.call(this);
|
||||
}
|
||||
|
||||
get canvas() {
|
||||
return this.getElement("canvas");
|
||||
}
|
||||
|
||||
get ctx() {
|
||||
return this.canvas.getCanvasContext("2d");
|
||||
}
|
||||
|
||||
getElement(id) {
|
||||
return this.markup.getElement(this.ID_CLASS_PREFIX + id);
|
||||
}
|
||||
|
||||
_hide() {
|
||||
setIgnoreLayoutChanges(true);
|
||||
this._hideFlexbox();
|
||||
setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
|
||||
}
|
||||
|
||||
_hideFlexbox() {
|
||||
this.getElement("canvas").setAttribute("hidden", "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* The <canvas>'s position needs to be updated if the page scrolls too much, in order
|
||||
* to give the illusion that it always covers the viewport.
|
||||
*/
|
||||
_scrollUpdate() {
|
||||
let { hasUpdated } = getCanvasPosition(this._canvasPosition, this._scroll, this.win,
|
||||
this._winDimensions);
|
||||
|
||||
if (hasUpdated) {
|
||||
this._update();
|
||||
}
|
||||
}
|
||||
|
||||
_show() {
|
||||
this._hide();
|
||||
return this._update();
|
||||
}
|
||||
|
||||
_showFlexbox() {
|
||||
this.getElement("canvas").removeAttribute("hidden");
|
||||
}
|
||||
|
||||
/**
|
||||
* If a page hide event is triggered for current window's highlighter, hide the
|
||||
* highlighter.
|
||||
*/
|
||||
onPageHide({ target }) {
|
||||
if (target.defaultView === this.win) {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the page will-navigate. Used to hide the flexbox highlighter and clear
|
||||
* the cached gap patterns and avoid using DeadWrapper obejcts as gap patterns the
|
||||
* next time.
|
||||
*/
|
||||
onWillNavigate({ isTopLevel }) {
|
||||
if (isTopLevel) {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
_update() {
|
||||
setIgnoreLayoutChanges(true);
|
||||
|
||||
let root = this.getElement("root");
|
||||
|
||||
// Hide the root element and force the reflow in order to get the proper window's
|
||||
// dimensions without increasing them.
|
||||
root.setAttribute("style", "display: none");
|
||||
this.win.document.documentElement.offsetWidth;
|
||||
|
||||
let { width, height } = this._winDimensions;
|
||||
|
||||
// Updates the <canvas> element's position and size.
|
||||
// It also clear the <canvas>'s drawing context.
|
||||
updateCanvasElement(this.canvas, this._canvasPosition, this.win.devicePixelRatio);
|
||||
|
||||
// Update the current matrix used in our canvas' rendering
|
||||
let { currentMatrix, hasNodeTransformations } = getCurrentMatrix(this.currentNode,
|
||||
this.win);
|
||||
this.currentMatrix = currentMatrix;
|
||||
this.hasNodeTransformations = hasNodeTransformations;
|
||||
|
||||
this._showFlexbox();
|
||||
|
||||
root.setAttribute("style",
|
||||
`position: absolute; width: ${width}px; height: ${height}px; overflow: hidden`);
|
||||
|
||||
setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
exports.FlexboxHighlighter = FlexboxHighlighter;
|
@ -14,6 +14,7 @@ DevToolsModules(
|
||||
'css-grid.js',
|
||||
'css-transform.js',
|
||||
'eye-dropper.js',
|
||||
'flexbox.js',
|
||||
'geometry-editor.js',
|
||||
'measuring-tool.js',
|
||||
'paused-debugger.js',
|
||||
|
440
devtools/server/actors/highlighters/utils/canvas.js
Normal file
440
devtools/server/actors/highlighters/utils/canvas.js
Normal file
@ -0,0 +1,440 @@
|
||||
/* 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 {
|
||||
apply,
|
||||
getNodeTransformationMatrix,
|
||||
identity,
|
||||
isIdentity,
|
||||
multiply,
|
||||
scale,
|
||||
translate,
|
||||
} = require("devtools/shared/layout/dom-matrix-2d");
|
||||
const { getViewportDimensions } = require("devtools/shared/layout/utils");
|
||||
|
||||
// A set of utility functions for highlighters that render their content to a <canvas>
|
||||
// element.
|
||||
|
||||
// We create a <canvas> element that has always 4096x4096 physical pixels, to displays
|
||||
// our grid's overlay.
|
||||
// Then, we move the element around when needed, to give the perception that it always
|
||||
// covers the screen (See bug 1345434).
|
||||
//
|
||||
// This canvas size value is the safest we can use because most GPUs can handle it.
|
||||
// It's also far from the maximum canvas memory allocation limit (4096x4096x4 is
|
||||
// 67.108.864 bytes, where the limit is 500.000.000 bytes, see:
|
||||
// http://searchfox.org/mozilla-central/source/gfx/thebes/gfxPrefs.h#401).
|
||||
//
|
||||
// Note:
|
||||
// Once bug 1232491 lands, we could try to refactor this code to use the values from
|
||||
// the displayport API instead.
|
||||
//
|
||||
// Using a fixed value should also solve bug 1348293.
|
||||
const CANVAS_SIZE = 4096;
|
||||
|
||||
/**
|
||||
* Draws an arrow-bubble rectangle in the provided canvas context.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* The 2D canvas context.
|
||||
* @param {Number} x
|
||||
* The x-axis origin of the rectangle.
|
||||
* @param {Number} y
|
||||
* The y-axis origin of the rectangle.
|
||||
* @param {Number} width
|
||||
* The width of the rectangle.
|
||||
* @param {Number} height
|
||||
* The height of the rectangle.
|
||||
* @param {Number} radius
|
||||
* The radius of the rounding.
|
||||
* @param {Number} margin
|
||||
* The distance of the origin point from the pointer.
|
||||
* @param {Number} arrowSize
|
||||
* The size of the arrow.
|
||||
* @param {String} alignment
|
||||
* The alignment of the rectangle in relation to its position to the grid.
|
||||
*/
|
||||
function drawBubbleRect(ctx, x, y, width, height, radius, margin, arrowSize, alignment) {
|
||||
let angle = 0;
|
||||
|
||||
if (alignment === "bottom") {
|
||||
angle = 180;
|
||||
} else if (alignment === "right") {
|
||||
angle = 90;
|
||||
[width, height] = [height, width];
|
||||
} else if (alignment === "left") {
|
||||
[width, height] = [height, width];
|
||||
angle = 270;
|
||||
}
|
||||
|
||||
let originX = x;
|
||||
let originY = y;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(originX, originY);
|
||||
ctx.rotate(angle * (Math.PI / 180));
|
||||
ctx.translate(-originX, -originY);
|
||||
ctx.translate(-width / 2, -height - arrowSize - margin);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y + radius);
|
||||
ctx.lineTo(x, y + height - radius);
|
||||
ctx.arcTo(x, y + height, x + radius, y + height, radius);
|
||||
ctx.lineTo(x + width / 2 - arrowSize, y + height);
|
||||
ctx.lineTo(x + width / 2, y + height + arrowSize);
|
||||
ctx.lineTo(x + width / 2 + arrowSize, y + height);
|
||||
ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
|
||||
ctx.lineTo(x + width, y + radius);
|
||||
ctx.arcTo(x + width, y, x + width - radius, y, radius);
|
||||
ctx.lineTo(x + radius, y);
|
||||
ctx.arcTo(x, y, x, y + radius, radius);
|
||||
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a line to the context given and applies a transformation matrix if passed.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* The 2D canvas context.
|
||||
* @param {Number} x1
|
||||
* The x-axis of the coordinate for the begin of the line.
|
||||
* @param {Number} y1
|
||||
* The y-axis of the coordinate for the begin of the line.
|
||||
* @param {Number} x2
|
||||
* The x-axis of the coordinate for the end of the line.
|
||||
* @param {Number} y2
|
||||
* The y-axis of the coordinate for the end of the line.
|
||||
* @param {Object} [options]
|
||||
* The options object.
|
||||
* @param {Array} [options.matrix=identity()]
|
||||
* The transformation matrix to apply.
|
||||
* @param {Array} [options.extendToBoundaries]
|
||||
* If set, the line will be extended to reach the boundaries specified.
|
||||
*/
|
||||
function drawLine(ctx, x1, y1, x2, y2, options) {
|
||||
let matrix = options.matrix || identity();
|
||||
|
||||
let p1 = apply(matrix, [x1, y1]);
|
||||
let p2 = apply(matrix, [x2, y2]);
|
||||
|
||||
x1 = p1[0];
|
||||
y1 = p1[1];
|
||||
x2 = p2[0];
|
||||
y2 = p2[1];
|
||||
|
||||
if (options.extendToBoundaries) {
|
||||
if (p1[1] === p2[1]) {
|
||||
x1 = options.extendToBoundaries[0];
|
||||
x2 = options.extendToBoundaries[2];
|
||||
} else {
|
||||
y1 = options.extendToBoundaries[1];
|
||||
x1 = (p2[0] - p1[0]) * (y1 - p1[1]) / (p2[1] - p1[1]) + p1[0];
|
||||
y2 = options.extendToBoundaries[3];
|
||||
x2 = (p2[0] - p1[0]) * (y2 - p1[1]) / (p2[1] - p1[1]) + p1[0];
|
||||
}
|
||||
}
|
||||
|
||||
ctx.moveTo(Math.round(x1), Math.round(y1));
|
||||
ctx.lineTo(Math.round(x2), Math.round(y2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a rect to the context given and applies a transformation matrix if passed.
|
||||
* The coordinates are the start and end points of the rectangle's diagonal.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* The 2D canvas context.
|
||||
* @param {Number} x1
|
||||
* The x-axis coordinate of the rectangle's diagonal start point.
|
||||
* @param {Number} y1
|
||||
* The y-axis coordinate of the rectangle's diagonal start point.
|
||||
* @param {Number} x2
|
||||
* The x-axis coordinate of the rectangle's diagonal end point.
|
||||
* @param {Number} y2
|
||||
* The y-axis coordinate of the rectangle's diagonal end point.
|
||||
* @param {Array} [matrix=identity()]
|
||||
* The transformation matrix to apply.
|
||||
*/
|
||||
function drawRect(ctx, x1, y1, x2, y2, matrix = identity()) {
|
||||
let p = getPointsFromDiagonal(x1, y1, x2, y2, matrix);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(Math.round(p[0].x), Math.round(p[0].y));
|
||||
ctx.lineTo(Math.round(p[1].x), Math.round(p[1].y));
|
||||
ctx.lineTo(Math.round(p[2].x), Math.round(p[2].y));
|
||||
ctx.lineTo(Math.round(p[3].x), Math.round(p[3].y));
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a rounded rectangle in the provided canvas context.
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* The 2D canvas context.
|
||||
* @param {Number} x
|
||||
* The x-axis origin of the rectangle.
|
||||
* @param {Number} y
|
||||
* The y-axis origin of the rectangle.
|
||||
* @param {Number} width
|
||||
* The width of the rectangle.
|
||||
* @param {Number} height
|
||||
* The height of the rectangle.
|
||||
* @param {Number} radius
|
||||
* The radius of the rounding.
|
||||
*/
|
||||
function drawRoundedRect(ctx, x, y, width, height, radius) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y + radius);
|
||||
ctx.lineTo(x, y + height - radius);
|
||||
ctx.arcTo(x, y + height, x + radius, y + height, radius);
|
||||
ctx.lineTo(x + width - radius, y + height);
|
||||
ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
|
||||
ctx.lineTo(x + width, y + radius);
|
||||
ctx.arcTo(x + width, y, x + width - radius, y, radius);
|
||||
ctx.lineTo(x + radius, y);
|
||||
ctx.arcTo(x, y, x, y + radius, radius);
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of four points and returns a DOMRect-like object representing the
|
||||
* boundaries defined by the four points.
|
||||
*
|
||||
* @param {Array} points
|
||||
* An array with 4 pointer objects {x, y} representing the box quads.
|
||||
* @return {Object} DOMRect-like object of the 4 points.
|
||||
*/
|
||||
function getBoundsFromPoints(points) {
|
||||
let bounds = {};
|
||||
|
||||
bounds.left = Math.min(points[0].x, points[1].x, points[2].x, points[3].x);
|
||||
bounds.right = Math.max(points[0].x, points[1].x, points[2].x, points[3].x);
|
||||
bounds.top = Math.min(points[0].y, points[1].y, points[2].y, points[3].y);
|
||||
bounds.bottom = Math.max(points[0].y, points[1].y, points[2].y, points[3].y);
|
||||
|
||||
bounds.x = bounds.left;
|
||||
bounds.y = bounds.top;
|
||||
bounds.width = bounds.right - bounds.left;
|
||||
bounds.height = bounds.bottom - bounds.top;
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates and returns the <canvas>'s position in accordance with the page's scroll,
|
||||
* document's size, canvas size, and viewport's size. This is called when a page's scroll
|
||||
* is detected.
|
||||
*
|
||||
* @param {Object} canvasPosition
|
||||
* A pointer object {x, y} representing the <canvas> position to the top left
|
||||
* corner of the page.
|
||||
* @param {Object} scrollPosition
|
||||
* A pointer object {x, y} representing the window's pageXOffset and pageYOffset.
|
||||
* @param {Window} window
|
||||
* The window object.
|
||||
* @param {Object} windowDimensions
|
||||
* An object {width, height} representing the window's dimensions for the
|
||||
* `window` given.
|
||||
* @return {Object} An object with the following properties:
|
||||
* - {Boolean} hasUpdated
|
||||
* true if the <canvas> position was updated and false otherwise.
|
||||
* - {Number} canvasX
|
||||
* The canvas' x position.
|
||||
* - {Number} canvasY
|
||||
* The canvas' y position.
|
||||
*/
|
||||
function getCanvasPosition(canvasPosition, scrollPosition, window, windowDimensions) {
|
||||
let { x: canvasX, y: canvasY } = canvasPosition;
|
||||
let { x: scrollX, y: scrollY } = scrollPosition;
|
||||
let cssCanvasSize = CANVAS_SIZE / window.devicePixelRatio;
|
||||
let viewportSize = getViewportDimensions(window);
|
||||
let { height, width } = windowDimensions;
|
||||
let canvasWidth = cssCanvasSize;
|
||||
let canvasHeight = cssCanvasSize;
|
||||
let hasUpdated = false;
|
||||
|
||||
// Those values indicates the relative horizontal and vertical space the page can
|
||||
// scroll before we have to reposition the <canvas>; they're 1/4 of the delta between
|
||||
// the canvas' size and the viewport's size: that's because we want to consider both
|
||||
// sides (top/bottom, left/right; so 1/2 for each side) and also we don't want to
|
||||
// shown the edges of the canvas in case of fast scrolling (to avoid showing undraw
|
||||
// areas, therefore another 1/2 here).
|
||||
let bufferSizeX = (canvasWidth - viewportSize.width) >> 2;
|
||||
let bufferSizeY = (canvasHeight - viewportSize.height) >> 2;
|
||||
|
||||
// Defines the boundaries for the canvas.
|
||||
let leftBoundary = 0;
|
||||
let rightBoundary = width - canvasWidth;
|
||||
let topBoundary = 0;
|
||||
let bottomBoundary = height - canvasHeight;
|
||||
|
||||
// Defines the thresholds that triggers the canvas' position to be updated.
|
||||
let leftThreshold = scrollX - bufferSizeX;
|
||||
let rightThreshold = scrollX - canvasWidth + viewportSize.width + bufferSizeX;
|
||||
let topThreshold = scrollY - bufferSizeY;
|
||||
let bottomThreshold = scrollY - canvasHeight + viewportSize.height + bufferSizeY;
|
||||
|
||||
if (canvasX < rightBoundary && canvasX < rightThreshold) {
|
||||
canvasX = Math.min(leftThreshold, rightBoundary);
|
||||
hasUpdated = true;
|
||||
} else if (canvasX > leftBoundary && canvasX > leftThreshold) {
|
||||
canvasX = Math.max(rightThreshold, leftBoundary);
|
||||
hasUpdated = true;
|
||||
}
|
||||
|
||||
if (canvasY < bottomBoundary && canvasY < bottomThreshold) {
|
||||
canvasY = Math.min(topThreshold, bottomBoundary);
|
||||
hasUpdated = true;
|
||||
} else if (canvasY > topBoundary && canvasY > topThreshold) {
|
||||
canvasY = Math.max(bottomThreshold, topBoundary);
|
||||
hasUpdated = true;
|
||||
}
|
||||
|
||||
return { canvasX, canvasY, hasUpdated };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current matrices for both canvas drawing and SVG taking into account the
|
||||
* following transformations, in this order:
|
||||
* 1. The scale given by the display pixel ratio.
|
||||
* 2. The translation to the top left corner of the element.
|
||||
* 3. The scale given by the current zoom.
|
||||
* 4. The translation given by the top and left padding of the element.
|
||||
* 5. Any CSS transformation applied directly to the element (only 2D
|
||||
* transformation; the 3D transformation are flattened, see `dom-matrix-2d` module
|
||||
* for further details.)
|
||||
*
|
||||
* The transformations of the element's ancestors are not currently computed (see
|
||||
* bug 1355675).
|
||||
*
|
||||
* @param {Element} element
|
||||
* The current element.
|
||||
* @param {Window} window
|
||||
* The window object.
|
||||
* @return {Object} An object with the following properties:
|
||||
* - {Array} currentMatrix
|
||||
* The current matrix.
|
||||
* - {Boolean} hasNodeTransformations
|
||||
* true if the node has transformed and false otherwise.
|
||||
*/
|
||||
function getCurrentMatrix(element, window) {
|
||||
let computedStyle = element.ownerGlobal.getComputedStyle(element);
|
||||
|
||||
let paddingTop = parseFloat(computedStyle.paddingTop);
|
||||
let paddingLeft = parseFloat(computedStyle.paddingLeft);
|
||||
let borderTop = parseFloat(computedStyle.borderTopWidth);
|
||||
let borderLeft = parseFloat(computedStyle.borderLeftWidth);
|
||||
|
||||
let nodeMatrix = getNodeTransformationMatrix(element, window.document.documentElement);
|
||||
|
||||
let currentMatrix = identity();
|
||||
let hasNodeTransformations = false;
|
||||
|
||||
// First, we scale based on the device pixel ratio.
|
||||
currentMatrix = multiply(currentMatrix, scale(window.devicePixelRatio));
|
||||
|
||||
// Then, we apply the current node's transformation matrix, relative to the
|
||||
// inspected window's root element, but only if it's not a identity matrix.
|
||||
if (isIdentity(nodeMatrix)) {
|
||||
hasNodeTransformations = false;
|
||||
} else {
|
||||
currentMatrix = multiply(currentMatrix, nodeMatrix);
|
||||
hasNodeTransformations = true;
|
||||
}
|
||||
|
||||
// Finally, we translate the origin based on the node's padding and border values.
|
||||
currentMatrix = multiply(currentMatrix,
|
||||
translate(paddingLeft + borderLeft, paddingTop + borderTop));
|
||||
|
||||
return { currentMatrix, hasNodeTransformations };
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of four points, returns a string represent a path description.
|
||||
*
|
||||
* @param {Array} points
|
||||
* An array with 4 pointer objects {x, y} representing the box quads.
|
||||
* @return {String} a Path Description that can be used in svg's <path> element.
|
||||
*/
|
||||
function getPathDescriptionFromPoints(points) {
|
||||
return "M" + points[0].x + "," + points[0].y + " " +
|
||||
"L" + points[1].x + "," + points[1].y + " " +
|
||||
"L" + points[2].x + "," + points[2].y + " " +
|
||||
"L" + points[3].x + "," + points[3].y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the rectangle's diagonal start and end coordinates, returns an array containing
|
||||
* the four coordinates of a rectangle. If a matrix is provided, applies the matrix
|
||||
* function to each of the coordinates' value.
|
||||
*
|
||||
* @param {Number} x1
|
||||
* The x-axis coordinate of the rectangle's diagonal start point.
|
||||
* @param {Number} y1
|
||||
* The y-axis coordinate of the rectangle's diagonal start point.
|
||||
* @param {Number} x2
|
||||
* The x-axis coordinate of the rectangle's diagonal end point.
|
||||
* @param {Number} y2
|
||||
* The y-axis coordinate of the rectangle's diagonal end point.
|
||||
* @param {Array} [matrix=identity()]
|
||||
* A transformation matrix to apply.
|
||||
* @return {Array} the four coordinate points of the given rectangle transformed by the
|
||||
* matrix given.
|
||||
*/
|
||||
function getPointsFromDiagonal(x1, y1, x2, y2, matrix = identity()) {
|
||||
return [
|
||||
[x1, y1],
|
||||
[x2, y1],
|
||||
[x2, y2],
|
||||
[x1, y2]
|
||||
].map(point => {
|
||||
let transformedPoint = apply(matrix, point);
|
||||
|
||||
return { x: transformedPoint[0], y: transformedPoint[1] };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the <canvas> element's style in accordance with the current window's
|
||||
* devicePixelRatio, and the position calculated in `getCanvasPosition`. It also
|
||||
* clears the drawing context. This is called on canvas update after a scroll event where
|
||||
* `getCanvasPosition` updates the new canvasPosition.
|
||||
*
|
||||
* @param {Canvas} canvas
|
||||
* The <canvas> element.
|
||||
* @param {Object} canvasPosition
|
||||
* A pointer object {x, y} representing the <canvas> position to the top left
|
||||
* corner of the page.
|
||||
* @param {Number} devicePixelRatio
|
||||
* The device pixel ratio.
|
||||
*/
|
||||
function updateCanvasElement(canvas, canvasPosition, devicePixelRatio) {
|
||||
let { x, y } = canvasPosition;
|
||||
let size = CANVAS_SIZE / devicePixelRatio;
|
||||
|
||||
// Resize the canvas taking the dpr into account so as to have crisp lines, and
|
||||
// translating it to give the perception that it always covers the viewport.
|
||||
canvas.setAttribute("style",
|
||||
`width: ${size}px; height: ${size}px; transform: translate(${x}px, ${y}px);`);
|
||||
canvas.getCanvasContext("2d").clearRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);
|
||||
}
|
||||
|
||||
exports.CANVAS_SIZE = CANVAS_SIZE;
|
||||
exports.drawBubbleRect = drawBubbleRect;
|
||||
exports.drawLine = drawLine;
|
||||
exports.drawRect = drawRect;
|
||||
exports.drawRoundedRect = drawRoundedRect;
|
||||
exports.getBoundsFromPoints = getBoundsFromPoints;
|
||||
exports.getCanvasPosition = getCanvasPosition;
|
||||
exports.getCurrentMatrix = getCurrentMatrix;
|
||||
exports.getPathDescriptionFromPoints = getPathDescriptionFromPoints;
|
||||
exports.getPointsFromDiagonal = getPointsFromDiagonal;
|
||||
exports.updateCanvasElement = updateCanvasElement;
|
@ -5,5 +5,6 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'canvas.js',
|
||||
'markup.js'
|
||||
)
|
||||
|
31
dom/base/crashtests/675516.xhtml
Normal file
31
dom/base/crashtests/675516.xhtml
Normal file
@ -0,0 +1,31 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<script>
|
||||
<![CDATA[
|
||||
|
||||
function boom()
|
||||
{
|
||||
var p = document.body;
|
||||
var t1 = document.createTextNode("1");
|
||||
p.appendChild(t1);
|
||||
var t2 = document.createTextNode("2");
|
||||
p.appendChild(t2);
|
||||
var t3 = document.createTextNode("3");
|
||||
p.appendChild(t3);
|
||||
|
||||
function f() {
|
||||
};
|
||||
window.addEventListener("DOMSubtreeModified", f, false);
|
||||
|
||||
function g() {
|
||||
window.removeEventListener("DOMNodeRemoved", g, false);
|
||||
p.removeChild(t3);
|
||||
};
|
||||
window.addEventListener("DOMNodeRemoved", g, false);
|
||||
|
||||
document.normalize();
|
||||
}
|
||||
|
||||
]]>
|
||||
</script>
|
||||
<body onload="boom();"></body>
|
||||
</html>
|
@ -126,6 +126,7 @@ load 646184.html
|
||||
load 658845-1.svg
|
||||
load 666869.html
|
||||
load 667336-1.html
|
||||
load 675516.xhtml
|
||||
load 675621-1.html
|
||||
load 677194.html
|
||||
load 679459.html
|
||||
|
@ -346,7 +346,9 @@ BroadcastChannel::Constructor(const GlobalObject& aGlobal,
|
||||
// Register this component to PBackground.
|
||||
PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
|
||||
if (NS_WARN_IF(!actorChild)) {
|
||||
MOZ_CRASH("Failed to create a PBackgroundChild actor!");
|
||||
// Firefox is probably shutting down. Let's return a 'generic' error.
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PBroadcastChannelChild* actor =
|
||||
|
@ -466,8 +466,9 @@ MutableBlobStorage::Append(const void* aData, uint32_t aLength)
|
||||
|
||||
// If eInMemory is the current Storage state, we could maybe migrate to
|
||||
// a temporary file.
|
||||
if (mStorageState == eInMemory && ShouldBeTemporaryStorage(aLength)) {
|
||||
MaybeCreateTemporaryFile();
|
||||
if (mStorageState == eInMemory && ShouldBeTemporaryStorage(aLength) &&
|
||||
!MaybeCreateTemporaryFile()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// If we are already in the temporaryFile mode, we have to dispatch a
|
||||
@ -547,7 +548,7 @@ MutableBlobStorage::ShouldBeTemporaryStorage(uint64_t aSize) const
|
||||
return bufferSize.value() >= mMaxMemory;
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
MutableBlobStorage::MaybeCreateTemporaryFile()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
@ -557,7 +558,7 @@ MutableBlobStorage::MaybeCreateTemporaryFile()
|
||||
mozilla::ipc::PBackgroundChild* actorChild =
|
||||
mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
|
||||
if (NS_WARN_IF(!actorChild)) {
|
||||
MOZ_CRASH("Failed to create a PBackgroundChild actor!");
|
||||
return false;
|
||||
}
|
||||
|
||||
mActor = new TemporaryIPCBlobChild(this);
|
||||
@ -569,6 +570,8 @@ MutableBlobStorage::MaybeCreateTemporaryFile()
|
||||
mActor.get()->AddRef();
|
||||
|
||||
// The actor will call us when the FileDescriptor is received.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -83,7 +83,7 @@ private:
|
||||
|
||||
bool ShouldBeTemporaryStorage(uint64_t aSize) const;
|
||||
|
||||
void MaybeCreateTemporaryFile();
|
||||
bool MaybeCreateTemporaryFile();
|
||||
|
||||
void DispatchToIOThread(already_AddRefed<nsIRunnable> aRunnable);
|
||||
|
||||
|
@ -140,7 +140,8 @@ FileSystemTaskChildBase::Start()
|
||||
mozilla::ipc::PBackgroundChild* actor =
|
||||
mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
|
||||
if (NS_WARN_IF(!actor)) {
|
||||
MOZ_CRASH("Failed to create a PBackgroundChild actor!");
|
||||
// We are probably shutting down.
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoString serialization;
|
||||
|
@ -142,14 +142,15 @@ GamepadManager::AddListener(nsGlobalWindow* aWindow)
|
||||
if (mChannelChildren.IsEmpty()) {
|
||||
PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread();
|
||||
if (NS_WARN_IF(!actor)) {
|
||||
MOZ_CRASH("Failed to create a PBackgroundChild actor!");
|
||||
// We are probably shutting down.
|
||||
return;
|
||||
}
|
||||
|
||||
GamepadEventChannelChild *child = new GamepadEventChannelChild();
|
||||
PGamepadEventChannelChild *initedChild =
|
||||
actor->SendPGamepadEventChannelConstructor(child);
|
||||
if (NS_WARN_IF(!initedChild)) {
|
||||
MOZ_CRASH("Failed to create a PBackgroundChild actor!");
|
||||
// We are probably shutting down.
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1141,7 +1141,8 @@ ContentChild::InitXPCOM(const XPCOMInitData& aXPCOMInit,
|
||||
|
||||
PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
|
||||
if (NS_WARN_IF(!actorChild)) {
|
||||
MOZ_CRASH("Failed to create PBackgroundChild!");
|
||||
MOZ_ASSERT_UNREACHABLE("PBackground init can't fail at this point");
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIConsoleService> svc(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
|
||||
|
@ -311,7 +311,10 @@ MessagePort::Initialize(const nsID& aUUID,
|
||||
}
|
||||
|
||||
if (mState == eStateEntangling) {
|
||||
ConnectToPBackground();
|
||||
if (!ConnectToPBackground()) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(mState == eStateUnshippedEntangled);
|
||||
}
|
||||
@ -781,7 +784,11 @@ MessagePort::CloneAndDisentangle(MessagePortIdentifier& aIdentifier)
|
||||
MOZ_ASSERT(mMessagesForTheOtherPort.IsEmpty());
|
||||
|
||||
// Disconnect the entangled port and connect it to PBackground.
|
||||
mUnshippedEntangledPort->ConnectToPBackground();
|
||||
if (!mUnshippedEntangledPort->ConnectToPBackground()) {
|
||||
// We are probably shutting down. We cannot proceed.
|
||||
return;
|
||||
}
|
||||
|
||||
mUnshippedEntangledPort = nullptr;
|
||||
|
||||
// In this case, we don't need to be connected to the PBackground service.
|
||||
@ -794,7 +801,10 @@ MessagePort::CloneAndDisentangle(MessagePortIdentifier& aIdentifier)
|
||||
}
|
||||
|
||||
// Register this component to PBackground.
|
||||
ConnectToPBackground();
|
||||
if (!ConnectToPBackground()) {
|
||||
// We are probably shutting down. We cannot proceed.
|
||||
return;
|
||||
}
|
||||
|
||||
mState = eStateEntanglingForDisentangle;
|
||||
return;
|
||||
@ -827,7 +837,7 @@ MessagePort::Closed()
|
||||
UpdateMustKeepAlive();
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
MessagePort::ConnectToPBackground()
|
||||
{
|
||||
mState = eStateEntangling;
|
||||
@ -835,18 +845,22 @@ MessagePort::ConnectToPBackground()
|
||||
mozilla::ipc::PBackgroundChild* actorChild =
|
||||
mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
|
||||
if (NS_WARN_IF(!actorChild)) {
|
||||
MOZ_CRASH("Failed to create a PBackgroundChild actor!");
|
||||
return false;
|
||||
}
|
||||
|
||||
PMessagePortChild* actor =
|
||||
actorChild->SendPMessagePortConstructor(mIdentifier->uuid(),
|
||||
mIdentifier->destinationUuid(),
|
||||
mIdentifier->sequenceId());
|
||||
if (NS_WARN_IF(!actor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mActor = static_cast<MessagePortChild*>(actor);
|
||||
MOZ_ASSERT(mActor);
|
||||
|
||||
mActor->SetPort(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -141,7 +141,7 @@ private:
|
||||
uint32_t aSequenceID, bool mNeutered, State aState,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void ConnectToPBackground();
|
||||
bool ConnectToPBackground();
|
||||
|
||||
// Dispatch events from the Message Queue using a nsRunnable.
|
||||
void Dispatch();
|
||||
|
@ -226,7 +226,11 @@ OpenFileNameIPC::AddToOfn(LPOPENFILENAMEW aLpofn) const
|
||||
aLpofn->nMaxCustFilter = 0;
|
||||
}
|
||||
aLpofn->nFilterIndex = mFilterIndex;
|
||||
wcscpy(aLpofn->lpstrFile, mFile.c_str());
|
||||
if (mNMaxFile > 0) {
|
||||
wcsncpy(aLpofn->lpstrFile, mFile.c_str(),
|
||||
std::min(static_cast<uint32_t>(mFile.size()+1), mNMaxFile));
|
||||
aLpofn->lpstrFile[mNMaxFile - 1] = L'\0';
|
||||
}
|
||||
aLpofn->nMaxFile = mNMaxFile;
|
||||
aLpofn->nMaxFileTitle = mNMaxFileTitle;
|
||||
if (mHasInitialDir) {
|
||||
@ -252,7 +256,7 @@ OpenFileNameIPC::AllocateOfnStrings(LPOPENFILENAMEW aLpofn) const
|
||||
}
|
||||
if (mHasCustomFilter) {
|
||||
aLpofn->lpstrCustomFilter =
|
||||
static_cast<LPTSTR>(moz_xmalloc(sizeof(wchar_t) * (mCustomFilterIn.size() + 1) + mNMaxCustFilterOut));
|
||||
static_cast<LPTSTR>(moz_xmalloc(sizeof(wchar_t) * (mCustomFilterIn.size() + 1 + mNMaxCustFilterOut)));
|
||||
}
|
||||
aLpofn->lpstrFile =
|
||||
static_cast<LPTSTR>(moz_xmalloc(sizeof(wchar_t) * mNMaxFile));
|
||||
|
@ -104,12 +104,6 @@ U2FManager::ClearTransaction()
|
||||
}
|
||||
|
||||
mTransaction.reset();
|
||||
|
||||
if (mChild) {
|
||||
RefPtr<U2FTransactionChild> c;
|
||||
mChild.swap(c);
|
||||
c->Send__delete__(c);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -126,8 +120,8 @@ U2FManager::RejectTransaction(const nsresult& aError)
|
||||
void
|
||||
U2FManager::CancelTransaction(const nsresult& aError)
|
||||
{
|
||||
if (mChild) {
|
||||
mChild->SendRequestCancel();
|
||||
if (!NS_WARN_IF(!mChild || mTransaction.isNothing())) {
|
||||
mChild->SendRequestCancel(mTransaction.ref().mId);
|
||||
}
|
||||
|
||||
RejectTransaction(aError);
|
||||
@ -140,20 +134,26 @@ U2FManager::~U2FManager()
|
||||
if (mTransaction.isSome()) {
|
||||
RejectTransaction(NS_ERROR_ABORT);
|
||||
}
|
||||
|
||||
if (mChild) {
|
||||
RefPtr<U2FTransactionChild> c;
|
||||
mChild.swap(c);
|
||||
c->Send__delete__(c);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
U2FManager::GetOrCreateBackgroundActor()
|
||||
bool
|
||||
U2FManager::MaybeCreateBackgroundActor()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mChild) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
|
||||
if (NS_WARN_IF(!actorChild)) {
|
||||
MOZ_CRASH("Failed to create a PBackgroundChild actor!");
|
||||
return false;
|
||||
}
|
||||
|
||||
RefPtr<U2FTransactionChild> mgr(new U2FTransactionChild());
|
||||
@ -161,11 +161,13 @@ U2FManager::GetOrCreateBackgroundActor()
|
||||
actorChild->SendPWebAuthnTransactionConstructor(mgr);
|
||||
|
||||
if (NS_WARN_IF(!constructedMgr)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(constructedMgr == mgr);
|
||||
mChild = mgr.forget();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//static
|
||||
@ -263,6 +265,10 @@ U2FManager::Register(nsPIDOMWindowInner* aParent, const nsCString& aRpId,
|
||||
return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
|
||||
}
|
||||
|
||||
if (!MaybeCreateBackgroundActor()) {
|
||||
return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
|
||||
}
|
||||
|
||||
ListenForVisibilityEvents(aParent, this);
|
||||
|
||||
// Always blank for U2F
|
||||
@ -275,15 +281,9 @@ U2FManager::Register(nsPIDOMWindowInner* aParent, const nsCString& aRpId,
|
||||
extensions);
|
||||
|
||||
MOZ_ASSERT(mTransaction.isNothing());
|
||||
|
||||
mTransaction = Some(U2FTransaction(aParent, Move(info), aClientDataJSON));
|
||||
|
||||
GetOrCreateBackgroundActor();
|
||||
|
||||
if (mChild) {
|
||||
mChild->SendRequestRegister(mTransaction.ref().mInfo);
|
||||
}
|
||||
|
||||
mChild->SendRequestRegister(mTransaction.ref().mId, mTransaction.ref().mInfo);
|
||||
return mTransaction.ref().mPromise.Ensure(__func__);
|
||||
}
|
||||
|
||||
@ -307,6 +307,10 @@ U2FManager::Sign(nsPIDOMWindowInner* aParent,
|
||||
return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
|
||||
}
|
||||
|
||||
if (!MaybeCreateBackgroundActor()) {
|
||||
return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
|
||||
}
|
||||
|
||||
ListenForVisibilityEvents(aParent, this);
|
||||
|
||||
// Always blank for U2F
|
||||
@ -321,22 +325,18 @@ U2FManager::Sign(nsPIDOMWindowInner* aParent,
|
||||
MOZ_ASSERT(mTransaction.isNothing());
|
||||
mTransaction = Some(U2FTransaction(aParent, Move(info), aClientDataJSON));
|
||||
|
||||
GetOrCreateBackgroundActor();
|
||||
|
||||
if (mChild) {
|
||||
mChild->SendRequestSign(mTransaction.ref().mInfo);
|
||||
}
|
||||
|
||||
mChild->SendRequestSign(mTransaction.ref().mId, mTransaction.ref().mInfo);
|
||||
return mTransaction.ref().mPromise.Ensure(__func__);
|
||||
}
|
||||
|
||||
void
|
||||
U2FManager::FinishRegister(nsTArray<uint8_t>& aRegBuffer)
|
||||
U2FManager::FinishRegister(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>& aRegBuffer)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Check for a valid transaction.
|
||||
if (mTransaction.isNothing()) {
|
||||
if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -381,13 +381,14 @@ U2FManager::FinishRegister(nsTArray<uint8_t>& aRegBuffer)
|
||||
}
|
||||
|
||||
void
|
||||
U2FManager::FinishSign(nsTArray<uint8_t>& aCredentialId,
|
||||
U2FManager::FinishSign(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>& aCredentialId,
|
||||
nsTArray<uint8_t>& aSigBuffer)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Check for a valid transaction.
|
||||
if (mTransaction.isNothing()) {
|
||||
if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -440,11 +441,12 @@ U2FManager::FinishSign(nsTArray<uint8_t>& aCredentialId,
|
||||
}
|
||||
|
||||
void
|
||||
U2FManager::RequestAborted(const nsresult& aError)
|
||||
U2FManager::RequestAborted(const uint64_t& aTransactionId,
|
||||
const nsresult& aError)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mTransaction.isSome()) {
|
||||
if (mTransaction.isSome() && mTransaction.ref().mId == aTransactionId) {
|
||||
RejectTransaction(aError);
|
||||
}
|
||||
}
|
||||
@ -454,7 +456,6 @@ U2FManager::HandleEvent(nsIDOMEvent* aEvent)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aEvent);
|
||||
MOZ_ASSERT(mChild);
|
||||
|
||||
nsAutoString type;
|
||||
aEvent->GetType(type);
|
||||
@ -464,9 +465,11 @@ U2FManager::HandleEvent(nsIDOMEvent* aEvent)
|
||||
|
||||
nsCOMPtr<nsIDocument> doc =
|
||||
do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
|
||||
MOZ_ASSERT(doc);
|
||||
if (NS_WARN_IF(!doc)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (doc && doc->Hidden()) {
|
||||
if (doc->Hidden()) {
|
||||
MOZ_LOG(gU2FManagerLog, LogLevel::Debug,
|
||||
("Visibility change: U2F window is hidden, cancelling job."));
|
||||
|
||||
@ -479,6 +482,7 @@ U2FManager::HandleEvent(nsIDOMEvent* aEvent)
|
||||
void
|
||||
U2FManager::ActorDestroyed()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mChild = nullptr;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,10 @@ public:
|
||||
: mParent(aParent)
|
||||
, mInfo(aInfo)
|
||||
, mClientData(aClientData)
|
||||
{ }
|
||||
, mId(NextId())
|
||||
{
|
||||
MOZ_ASSERT(mId > 0);
|
||||
}
|
||||
|
||||
// Parent of the context we're running the transaction in.
|
||||
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
||||
@ -75,6 +78,18 @@ public:
|
||||
|
||||
// Client data used to assemble reply objects.
|
||||
nsCString mClientData;
|
||||
|
||||
// Unique transaction id.
|
||||
uint64_t mId;
|
||||
|
||||
private:
|
||||
// Generates a unique id for new transactions. This doesn't have to be unique
|
||||
// forever, it's sufficient to differentiate between temporally close
|
||||
// transactions, where messages can intersect. Can overflow.
|
||||
static uint64_t NextId() {
|
||||
static uint64_t id = 0;
|
||||
return ++id;
|
||||
}
|
||||
};
|
||||
|
||||
class U2FManager final : public nsIDOMEventListener
|
||||
@ -97,10 +112,12 @@ public:
|
||||
const uint32_t& aTimeoutMillis,
|
||||
const nsTArray<WebAuthnScopedCredentialDescriptor>& aKeyList);
|
||||
|
||||
void FinishRegister(nsTArray<uint8_t>& aRegBuffer);
|
||||
void FinishSign(nsTArray<uint8_t>& aCredentialId,
|
||||
void FinishRegister(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>& aRegBuffer);
|
||||
void FinishSign(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>& aCredentialId,
|
||||
nsTArray<uint8_t>& aSigBuffer);
|
||||
void RequestAborted(const nsresult& aError);
|
||||
void RequestAborted(const uint64_t& aTransactionId, const nsresult& aError);
|
||||
|
||||
// XXX This is exposed only until we fix bug 1410346.
|
||||
void MaybeCancelTransaction(const nsresult& aError) {
|
||||
@ -129,11 +146,9 @@ private:
|
||||
// parent) and rejects it by calling RejectTransaction().
|
||||
void CancelTransaction(const nsresult& aError);
|
||||
|
||||
typedef MozPromise<nsresult, nsresult, false> BackgroundActorPromise;
|
||||
bool MaybeCreateBackgroundActor();
|
||||
|
||||
void GetOrCreateBackgroundActor();
|
||||
|
||||
// IPC Channel for the current transaction.
|
||||
// IPC Channel to the parent process.
|
||||
RefPtr<U2FTransactionChild> mChild;
|
||||
|
||||
// The current transaction, if any.
|
||||
|
@ -18,30 +18,33 @@ U2FTransactionChild::U2FTransactionChild()
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
U2FTransactionChild::RecvConfirmRegister(nsTArray<uint8_t>&& aRegBuffer)
|
||||
U2FTransactionChild::RecvConfirmRegister(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>&& aRegBuffer)
|
||||
{
|
||||
RefPtr<U2FManager> mgr = U2FManager::Get();
|
||||
MOZ_ASSERT(mgr);
|
||||
mgr->FinishRegister(aRegBuffer);
|
||||
mgr->FinishRegister(aTransactionId, aRegBuffer);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
U2FTransactionChild::RecvConfirmSign(nsTArray<uint8_t>&& aCredentialId,
|
||||
U2FTransactionChild::RecvConfirmSign(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>&& aCredentialId,
|
||||
nsTArray<uint8_t>&& aBuffer)
|
||||
{
|
||||
RefPtr<U2FManager> mgr = U2FManager::Get();
|
||||
MOZ_ASSERT(mgr);
|
||||
mgr->FinishSign(aCredentialId, aBuffer);
|
||||
mgr->FinishSign(aTransactionId, aCredentialId, aBuffer);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
U2FTransactionChild::RecvAbort(const nsresult& aError)
|
||||
U2FTransactionChild::RecvAbort(const uint64_t& aTransactionId,
|
||||
const nsresult& aError)
|
||||
{
|
||||
RefPtr<U2FManager> mgr = U2FManager::Get();
|
||||
MOZ_ASSERT(mgr);
|
||||
mgr->RequestAborted(aError);
|
||||
mgr->RequestAborted(aTransactionId, aError);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -23,11 +23,21 @@ class U2FTransactionChild final : public PWebAuthnTransactionChild
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(U2FTransactionChild);
|
||||
U2FTransactionChild();
|
||||
mozilla::ipc::IPCResult RecvConfirmRegister(nsTArray<uint8_t>&& aRegBuffer) override;
|
||||
mozilla::ipc::IPCResult RecvConfirmSign(nsTArray<uint8_t>&& aCredentialId,
|
||||
nsTArray<uint8_t>&& aBuffer) override;
|
||||
mozilla::ipc::IPCResult RecvAbort(const nsresult& aError) override;
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RecvConfirmRegister(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>&& aRegBuffer) override;
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RecvConfirmSign(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>&& aCredentialId,
|
||||
nsTArray<uint8_t>&& aBuffer) override;
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RecvAbort(const uint64_t& aTransactionId, const nsresult& aError) override;
|
||||
|
||||
void ActorDestroy(ActorDestroyReason why) override;
|
||||
|
||||
private:
|
||||
~U2FTransactionChild() = default;
|
||||
};
|
||||
|
@ -12,29 +12,31 @@ namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
U2FTransactionParent::RecvRequestRegister(const WebAuthnTransactionInfo& aTransactionInfo)
|
||||
U2FTransactionParent::RecvRequestRegister(const uint64_t& aTransactionId,
|
||||
const WebAuthnTransactionInfo& aTransactionInfo)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
mgr->Register(this, aTransactionInfo);
|
||||
mgr->Register(this, aTransactionId, aTransactionInfo);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
U2FTransactionParent::RecvRequestSign(const WebAuthnTransactionInfo& aTransactionInfo)
|
||||
U2FTransactionParent::RecvRequestSign(const uint64_t& aTransactionId,
|
||||
const WebAuthnTransactionInfo& aTransactionInfo)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
mgr->Sign(this, aTransactionInfo);
|
||||
mgr->Sign(this, aTransactionId, aTransactionInfo);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
U2FTransactionParent::RecvRequestCancel()
|
||||
U2FTransactionParent::RecvRequestCancel(const uint64_t& aTransactionId)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
mgr->Cancel(this);
|
||||
mgr->Cancel(this, aTransactionId);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -23,12 +23,20 @@ class U2FTransactionParent final : public PWebAuthnTransactionParent
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(U2FTransactionParent);
|
||||
U2FTransactionParent() = default;
|
||||
|
||||
virtual mozilla::ipc::IPCResult
|
||||
RecvRequestRegister(const WebAuthnTransactionInfo& aTransactionInfo) override;
|
||||
RecvRequestRegister(const uint64_t& aTransactionId,
|
||||
const WebAuthnTransactionInfo& aTransactionInfo) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult
|
||||
RecvRequestSign(const WebAuthnTransactionInfo& aTransactionInfo) override;
|
||||
virtual mozilla::ipc::IPCResult RecvRequestCancel() override;
|
||||
RecvRequestSign(const uint64_t& aTransactionId,
|
||||
const WebAuthnTransactionInfo& aTransactionInfo) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult
|
||||
RecvRequestCancel(const uint64_t& aTransactionId) override;
|
||||
|
||||
virtual void ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
private:
|
||||
~U2FTransactionParent() = default;
|
||||
};
|
||||
|
@ -39,13 +39,13 @@ async protocol PWebAuthnTransaction {
|
||||
manager PBackground;
|
||||
parent:
|
||||
async __delete__();
|
||||
async RequestRegister(WebAuthnTransactionInfo aTransactionInfo);
|
||||
async RequestSign(WebAuthnTransactionInfo aTransactionInfo);
|
||||
async RequestCancel();
|
||||
async RequestRegister(uint64_t aTransactionId, WebAuthnTransactionInfo aTransactionInfo);
|
||||
async RequestSign(uint64_t aTransactionId, WebAuthnTransactionInfo aTransactionInfo);
|
||||
async RequestCancel(uint64_t aTransactionId);
|
||||
child:
|
||||
async ConfirmRegister(uint8_t[] RegBuffer);
|
||||
async ConfirmSign(uint8_t[] CredentialID, uint8_t[] ReplyBuffer);
|
||||
async Abort(nsresult Error);
|
||||
async ConfirmRegister(uint64_t aTransactionId, uint8_t[] RegBuffer);
|
||||
async ConfirmSign(uint64_t aTransactionId, uint8_t[] CredentialID, uint8_t[] ReplyBuffer);
|
||||
async Abort(uint64_t aTransactionId, nsresult Error);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ NS_IMPL_ISUPPORTS(U2FPrefManager, nsIObserver);
|
||||
|
||||
U2FTokenManager::U2FTokenManager()
|
||||
: mTransactionParent(nullptr)
|
||||
, mTransactionId(0)
|
||||
, mLastTransactionId(0)
|
||||
{
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
// Create on the main thread to make sure ClearOnShutdown() works.
|
||||
@ -158,9 +158,10 @@ U2FTokenManager::Get()
|
||||
}
|
||||
|
||||
void
|
||||
U2FTokenManager::AbortTransaction(const nsresult& aError)
|
||||
U2FTokenManager::AbortTransaction(const uint64_t& aTransactionId,
|
||||
const nsresult& aError)
|
||||
{
|
||||
Unused << mTransactionParent->SendAbort(aError);
|
||||
Unused << mTransactionParent->SendAbort(aTransactionId, aError);
|
||||
ClearTransaction();
|
||||
}
|
||||
|
||||
@ -183,8 +184,8 @@ U2FTokenManager::ClearTransaction()
|
||||
// Forget promises, if necessary.
|
||||
mRegisterPromise.DisconnectIfExists();
|
||||
mSignPromise.DisconnectIfExists();
|
||||
// Bump transaction id.
|
||||
mTransactionId++;
|
||||
// Clear transaction id.
|
||||
mLastTransactionId = 0;
|
||||
}
|
||||
|
||||
RefPtr<U2FTokenTransport>
|
||||
@ -218,6 +219,7 @@ U2FTokenManager::GetTokenManagerImpl()
|
||||
|
||||
void
|
||||
U2FTokenManager::Register(PWebAuthnTransactionParent* aTransactionParent,
|
||||
const uint64_t& aTransactionId,
|
||||
const WebAuthnTransactionInfo& aTransactionInfo)
|
||||
{
|
||||
MOZ_LOG(gU2FTokenManagerLog, LogLevel::Debug, ("U2FAuthRegister"));
|
||||
@ -227,7 +229,7 @@ U2FTokenManager::Register(PWebAuthnTransactionParent* aTransactionParent,
|
||||
mTokenManagerImpl = GetTokenManagerImpl();
|
||||
|
||||
if (!mTokenManagerImpl) {
|
||||
AbortTransaction(NS_ERROR_DOM_NOT_ALLOWED_ERR);
|
||||
AbortTransaction(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -237,11 +239,11 @@ U2FTokenManager::Register(PWebAuthnTransactionParent* aTransactionParent,
|
||||
|
||||
if ((aTransactionInfo.RpIdHash().Length() != SHA256_LENGTH) ||
|
||||
(aTransactionInfo.ClientDataHash().Length() != SHA256_LENGTH)) {
|
||||
AbortTransaction(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
AbortTransaction(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t tid = mTransactionId;
|
||||
uint64_t tid = mLastTransactionId = aTransactionId;
|
||||
mozilla::TimeStamp startTime = mozilla::TimeStamp::Now();
|
||||
mTokenManagerImpl->Register(aTransactionInfo.Descriptors(),
|
||||
aTransactionInfo.RpIdHash(),
|
||||
@ -270,36 +272,31 @@ U2FTokenManager::Register(PWebAuthnTransactionParent* aTransactionParent,
|
||||
}
|
||||
|
||||
void
|
||||
U2FTokenManager::MaybeConfirmRegister(uint64_t aTransactionId,
|
||||
U2FTokenManager::MaybeConfirmRegister(const uint64_t& aTransactionId,
|
||||
U2FRegisterResult& aResult)
|
||||
{
|
||||
if (mTransactionId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mLastTransactionId == aTransactionId);
|
||||
mRegisterPromise.Complete();
|
||||
|
||||
nsTArray<uint8_t> registration;
|
||||
aResult.ConsumeRegistration(registration);
|
||||
|
||||
Unused << mTransactionParent->SendConfirmRegister(registration);
|
||||
Unused << mTransactionParent->SendConfirmRegister(aTransactionId, registration);
|
||||
ClearTransaction();
|
||||
}
|
||||
|
||||
void
|
||||
U2FTokenManager::MaybeAbortRegister(uint64_t aTransactionId,
|
||||
U2FTokenManager::MaybeAbortRegister(const uint64_t& aTransactionId,
|
||||
const nsresult& aError)
|
||||
{
|
||||
if (mTransactionId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mLastTransactionId == aTransactionId);
|
||||
mRegisterPromise.Complete();
|
||||
AbortTransaction(aError);
|
||||
AbortTransaction(aTransactionId, aError);
|
||||
}
|
||||
|
||||
void
|
||||
U2FTokenManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
|
||||
const uint64_t& aTransactionId,
|
||||
const WebAuthnTransactionInfo& aTransactionInfo)
|
||||
{
|
||||
MOZ_LOG(gU2FTokenManagerLog, LogLevel::Debug, ("U2FAuthSign"));
|
||||
@ -309,17 +306,17 @@ U2FTokenManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
|
||||
mTokenManagerImpl = GetTokenManagerImpl();
|
||||
|
||||
if (!mTokenManagerImpl) {
|
||||
AbortTransaction(NS_ERROR_DOM_NOT_ALLOWED_ERR);
|
||||
AbortTransaction(aTransactionId, NS_ERROR_DOM_NOT_ALLOWED_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((aTransactionInfo.RpIdHash().Length() != SHA256_LENGTH) ||
|
||||
(aTransactionInfo.ClientDataHash().Length() != SHA256_LENGTH)) {
|
||||
AbortTransaction(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
AbortTransaction(aTransactionId, NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t tid = mTransactionId;
|
||||
uint64_t tid = mLastTransactionId = aTransactionId;
|
||||
mozilla::TimeStamp startTime = mozilla::TimeStamp::Now();
|
||||
mTokenManagerImpl->Sign(aTransactionInfo.Descriptors(),
|
||||
aTransactionInfo.RpIdHash(),
|
||||
@ -348,13 +345,10 @@ U2FTokenManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
|
||||
}
|
||||
|
||||
void
|
||||
U2FTokenManager::MaybeConfirmSign(uint64_t aTransactionId,
|
||||
U2FTokenManager::MaybeConfirmSign(const uint64_t& aTransactionId,
|
||||
U2FSignResult& aResult)
|
||||
{
|
||||
if (mTransactionId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mLastTransactionId == aTransactionId);
|
||||
mSignPromise.Complete();
|
||||
|
||||
nsTArray<uint8_t> keyHandle;
|
||||
@ -362,25 +356,24 @@ U2FTokenManager::MaybeConfirmSign(uint64_t aTransactionId,
|
||||
nsTArray<uint8_t> signature;
|
||||
aResult.ConsumeSignature(signature);
|
||||
|
||||
Unused << mTransactionParent->SendConfirmSign(keyHandle, signature);
|
||||
Unused << mTransactionParent->SendConfirmSign(aTransactionId, keyHandle, signature);
|
||||
ClearTransaction();
|
||||
}
|
||||
|
||||
void
|
||||
U2FTokenManager::MaybeAbortSign(uint64_t aTransactionId, const nsresult& aError)
|
||||
U2FTokenManager::MaybeAbortSign(const uint64_t& aTransactionId,
|
||||
const nsresult& aError)
|
||||
{
|
||||
if (mTransactionId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mLastTransactionId == aTransactionId);
|
||||
mSignPromise.Complete();
|
||||
AbortTransaction(aError);
|
||||
AbortTransaction(aTransactionId, aError);
|
||||
}
|
||||
|
||||
void
|
||||
U2FTokenManager::Cancel(PWebAuthnTransactionParent* aParent)
|
||||
U2FTokenManager::Cancel(PWebAuthnTransactionParent* aParent,
|
||||
const uint64_t& aTransactionId)
|
||||
{
|
||||
if (mTransactionParent != aParent) {
|
||||
if (mTransactionParent != aParent || mLastTransactionId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -31,23 +31,25 @@ public:
|
||||
NS_INLINE_DECL_REFCOUNTING(U2FTokenManager)
|
||||
static U2FTokenManager* Get();
|
||||
void Register(PWebAuthnTransactionParent* aTransactionParent,
|
||||
const uint64_t& aTransactionId,
|
||||
const WebAuthnTransactionInfo& aTransactionInfo);
|
||||
void Sign(PWebAuthnTransactionParent* aTransactionParent,
|
||||
const uint64_t& aTransactionId,
|
||||
const WebAuthnTransactionInfo& aTransactionInfo);
|
||||
void Cancel(PWebAuthnTransactionParent* aTransactionParent);
|
||||
void Cancel(PWebAuthnTransactionParent* aTransactionParent,
|
||||
const uint64_t& aTransactionId);
|
||||
void MaybeClearTransaction(PWebAuthnTransactionParent* aParent);
|
||||
static void Initialize();
|
||||
private:
|
||||
U2FTokenManager();
|
||||
~U2FTokenManager();
|
||||
RefPtr<U2FTokenTransport> GetTokenManagerImpl();
|
||||
void AbortTransaction(const nsresult& aError);
|
||||
void AbortTransaction(const uint64_t& aTransactionId, const nsresult& aError);
|
||||
void ClearTransaction();
|
||||
void MaybeConfirmRegister(uint64_t aTransactionId,
|
||||
U2FRegisterResult& aResult);
|
||||
void MaybeAbortRegister(uint64_t aTransactionId, const nsresult& aError);
|
||||
void MaybeConfirmSign(uint64_t aTransactionId, U2FSignResult& aResult);
|
||||
void MaybeAbortSign(uint64_t aTransactionId, const nsresult& aError);
|
||||
void MaybeConfirmRegister(const uint64_t& aTransactionId, U2FRegisterResult& aResult);
|
||||
void MaybeAbortRegister(const uint64_t& aTransactionId, const nsresult& aError);
|
||||
void MaybeConfirmSign(const uint64_t& aTransactionId, U2FSignResult& aResult);
|
||||
void MaybeAbortSign(const uint64_t& aTransactionId, const nsresult& aError);
|
||||
// Using a raw pointer here, as the lifetime of the IPC object is managed by
|
||||
// the PBackground protocol code. This means we cannot be left holding an
|
||||
// invalid IPC protocol object after the transaction is finished.
|
||||
@ -55,10 +57,10 @@ private:
|
||||
RefPtr<U2FTokenTransport> mTokenManagerImpl;
|
||||
MozPromiseRequestHolder<U2FRegisterPromise> mRegisterPromise;
|
||||
MozPromiseRequestHolder<U2FSignPromise> mSignPromise;
|
||||
// Guards the asynchronous promise resolution of token manager impls.
|
||||
// We don't need to protect this with a lock as it will only be modified
|
||||
// and checked on the PBackground thread in the parent process.
|
||||
uint64_t mTransactionId;
|
||||
// The last transaction id, non-zero if there's an active transaction. This
|
||||
// guards any cancel messages to ensure we don't cancel newer transactions
|
||||
// due to a stale message.
|
||||
uint64_t mLastTransactionId;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
@ -199,12 +199,6 @@ WebAuthnManager::ClearTransaction()
|
||||
}
|
||||
|
||||
mTransaction.reset();
|
||||
|
||||
if (mChild) {
|
||||
RefPtr<WebAuthnTransactionChild> c;
|
||||
mChild.swap(c);
|
||||
c->Send__delete__(c);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -220,8 +214,8 @@ WebAuthnManager::RejectTransaction(const nsresult& aError)
|
||||
void
|
||||
WebAuthnManager::CancelTransaction(const nsresult& aError)
|
||||
{
|
||||
if (mChild) {
|
||||
mChild->SendRequestCancel();
|
||||
if (!NS_WARN_IF(!mChild || mTransaction.isNothing())) {
|
||||
mChild->SendRequestCancel(mTransaction.ref().mId);
|
||||
}
|
||||
|
||||
RejectTransaction(aError);
|
||||
@ -234,20 +228,26 @@ WebAuthnManager::~WebAuthnManager()
|
||||
if (mTransaction.isSome()) {
|
||||
RejectTransaction(NS_ERROR_ABORT);
|
||||
}
|
||||
|
||||
if (mChild) {
|
||||
RefPtr<WebAuthnTransactionChild> c;
|
||||
mChild.swap(c);
|
||||
c->Send__delete__(c);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebAuthnManager::GetOrCreateBackgroundActor()
|
||||
bool
|
||||
WebAuthnManager::MaybeCreateBackgroundActor()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mChild) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread();
|
||||
if (NS_WARN_IF(!actor)) {
|
||||
MOZ_CRASH("Failed to create a PBackgroundChild actor!");
|
||||
return false;
|
||||
}
|
||||
|
||||
RefPtr<WebAuthnTransactionChild> mgr(new WebAuthnTransactionChild());
|
||||
@ -255,12 +255,13 @@ WebAuthnManager::GetOrCreateBackgroundActor()
|
||||
actor->SendPWebAuthnTransactionConstructor(mgr);
|
||||
|
||||
if (NS_WARN_IF(!constructedMgr)) {
|
||||
MOZ_CRASH("Failed to create a PBackgroundChild actor!");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(constructedMgr == mgr);
|
||||
mChild = mgr.forget();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//static
|
||||
@ -458,6 +459,11 @@ WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
|
||||
excludeList.AppendElement(c);
|
||||
}
|
||||
|
||||
if (!MaybeCreateBackgroundActor()) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// TODO: Add extension list building
|
||||
nsTArray<WebAuthnExtension> extensions;
|
||||
|
||||
@ -475,11 +481,7 @@ WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
|
||||
Move(info),
|
||||
Move(clientDataJSON)));
|
||||
|
||||
GetOrCreateBackgroundActor();
|
||||
if (mChild) {
|
||||
mChild->SendRequestRegister(mTransaction.ref().mInfo);
|
||||
}
|
||||
|
||||
mChild->SendRequestRegister(mTransaction.ref().mId, mTransaction.ref().mInfo);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
@ -601,6 +603,11 @@ WebAuthnManager::GetAssertion(nsPIDOMWindowInner* aParent,
|
||||
allowList.AppendElement(c);
|
||||
}
|
||||
|
||||
if (!MaybeCreateBackgroundActor()) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
// TODO: Add extension list building
|
||||
// If extensions was specified, process any extensions supported by this
|
||||
// client platform, to produce the extension data that needs to be sent to the
|
||||
@ -623,11 +630,7 @@ WebAuthnManager::GetAssertion(nsPIDOMWindowInner* aParent,
|
||||
Move(info),
|
||||
Move(clientDataJSON)));
|
||||
|
||||
GetOrCreateBackgroundActor();
|
||||
if (mChild) {
|
||||
mChild->SendRequestSign(mTransaction.ref().mInfo);
|
||||
}
|
||||
|
||||
mChild->SendRequestSign(mTransaction.ref().mId, mTransaction.ref().mInfo);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
@ -655,12 +658,13 @@ WebAuthnManager::Store(nsPIDOMWindowInner* aParent,
|
||||
}
|
||||
|
||||
void
|
||||
WebAuthnManager::FinishMakeCredential(nsTArray<uint8_t>& aRegBuffer)
|
||||
WebAuthnManager::FinishMakeCredential(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>& aRegBuffer)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Check for a valid transaction.
|
||||
if (mTransaction.isNothing()) {
|
||||
if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -782,13 +786,14 @@ WebAuthnManager::FinishMakeCredential(nsTArray<uint8_t>& aRegBuffer)
|
||||
}
|
||||
|
||||
void
|
||||
WebAuthnManager::FinishGetAssertion(nsTArray<uint8_t>& aCredentialId,
|
||||
WebAuthnManager::FinishGetAssertion(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>& aCredentialId,
|
||||
nsTArray<uint8_t>& aSigBuffer)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Check for a valid transaction.
|
||||
if (mTransaction.isNothing()) {
|
||||
if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -867,11 +872,12 @@ WebAuthnManager::FinishGetAssertion(nsTArray<uint8_t>& aCredentialId,
|
||||
}
|
||||
|
||||
void
|
||||
WebAuthnManager::RequestAborted(const nsresult& aError)
|
||||
WebAuthnManager::RequestAborted(const uint64_t& aTransactionId,
|
||||
const nsresult& aError)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mTransaction.isSome()) {
|
||||
if (mTransaction.isSome() && mTransaction.ref().mId == aTransactionId) {
|
||||
RejectTransaction(aError);
|
||||
}
|
||||
}
|
||||
@ -890,9 +896,11 @@ WebAuthnManager::HandleEvent(nsIDOMEvent* aEvent)
|
||||
|
||||
nsCOMPtr<nsIDocument> doc =
|
||||
do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
|
||||
MOZ_ASSERT(doc);
|
||||
if (NS_WARN_IF(!doc)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (doc && doc->Hidden()) {
|
||||
if (doc->Hidden()) {
|
||||
MOZ_LOG(gWebAuthnManagerLog, LogLevel::Debug,
|
||||
("Visibility change: WebAuthn window is hidden, cancelling job."));
|
||||
|
||||
|
@ -71,7 +71,10 @@ public:
|
||||
, mPromise(aPromise)
|
||||
, mInfo(aInfo)
|
||||
, mClientData(aClientData)
|
||||
{ }
|
||||
, mId(NextId())
|
||||
{
|
||||
MOZ_ASSERT(mId > 0);
|
||||
}
|
||||
|
||||
// Parent of the context we're running the transaction in.
|
||||
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
||||
@ -85,6 +88,18 @@ public:
|
||||
|
||||
// Client data used to assemble reply objects.
|
||||
nsCString mClientData;
|
||||
|
||||
// Unique transaction id.
|
||||
uint64_t mId;
|
||||
|
||||
private:
|
||||
// Generates a unique id for new transactions. This doesn't have to be unique
|
||||
// forever, it's sufficient to differentiate between temporally close
|
||||
// transactions, where messages can intersect. Can overflow.
|
||||
static uint64_t NextId() {
|
||||
static uint64_t id = 0;
|
||||
return ++id;
|
||||
}
|
||||
};
|
||||
|
||||
class WebAuthnManager final : public nsIDOMEventListener
|
||||
@ -108,14 +123,16 @@ public:
|
||||
Store(nsPIDOMWindowInner* aParent, const Credential& aCredential);
|
||||
|
||||
void
|
||||
FinishMakeCredential(nsTArray<uint8_t>& aRegBuffer);
|
||||
FinishMakeCredential(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>& aRegBuffer);
|
||||
|
||||
void
|
||||
FinishGetAssertion(nsTArray<uint8_t>& aCredentialId,
|
||||
FinishGetAssertion(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>& aCredentialId,
|
||||
nsTArray<uint8_t>& aSigBuffer);
|
||||
|
||||
void
|
||||
RequestAborted(const nsresult& aError);
|
||||
RequestAborted(const uint64_t& aTransactionId, const nsresult& aError);
|
||||
|
||||
void ActorDestroyed();
|
||||
|
||||
@ -131,11 +148,9 @@ private:
|
||||
// parent) and rejects it by calling RejectTransaction().
|
||||
void CancelTransaction(const nsresult& aError);
|
||||
|
||||
typedef MozPromise<nsresult, nsresult, false> BackgroundActorPromise;
|
||||
bool MaybeCreateBackgroundActor();
|
||||
|
||||
void GetOrCreateBackgroundActor();
|
||||
|
||||
// IPC Channel for the current transaction.
|
||||
// IPC Channel to the parent process.
|
||||
RefPtr<WebAuthnTransactionChild> mChild;
|
||||
|
||||
// The current transaction, if any.
|
||||
|
@ -5,7 +5,6 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/dom/WebAuthnTransactionChild.h"
|
||||
#include "mozilla/dom/WebAuthnManager.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -19,30 +18,33 @@ WebAuthnTransactionChild::WebAuthnTransactionChild()
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
WebAuthnTransactionChild::RecvConfirmRegister(nsTArray<uint8_t>&& aRegBuffer)
|
||||
WebAuthnTransactionChild::RecvConfirmRegister(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>&& aRegBuffer)
|
||||
{
|
||||
RefPtr<WebAuthnManager> mgr = WebAuthnManager::Get();
|
||||
MOZ_ASSERT(mgr);
|
||||
mgr->FinishMakeCredential(aRegBuffer);
|
||||
mgr->FinishMakeCredential(aTransactionId, aRegBuffer);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
WebAuthnTransactionChild::RecvConfirmSign(nsTArray<uint8_t>&& aCredentialId,
|
||||
WebAuthnTransactionChild::RecvConfirmSign(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>&& aCredentialId,
|
||||
nsTArray<uint8_t>&& aBuffer)
|
||||
{
|
||||
RefPtr<WebAuthnManager> mgr = WebAuthnManager::Get();
|
||||
MOZ_ASSERT(mgr);
|
||||
mgr->FinishGetAssertion(aCredentialId, aBuffer);
|
||||
mgr->FinishGetAssertion(aTransactionId, aCredentialId, aBuffer);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
WebAuthnTransactionChild::RecvAbort(const nsresult& aError)
|
||||
WebAuthnTransactionChild::RecvAbort(const uint64_t& aTransactionId,
|
||||
const nsresult& aError)
|
||||
{
|
||||
RefPtr<WebAuthnManager> mgr = WebAuthnManager::Get();
|
||||
MOZ_ASSERT(mgr);
|
||||
mgr->RequestAborted(aError);
|
||||
mgr->RequestAborted(aTransactionId, aError);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -24,11 +24,21 @@ class WebAuthnTransactionChild final : public PWebAuthnTransactionChild
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(WebAuthnTransactionChild);
|
||||
WebAuthnTransactionChild();
|
||||
mozilla::ipc::IPCResult RecvConfirmRegister(nsTArray<uint8_t>&& aRegBuffer) override;
|
||||
mozilla::ipc::IPCResult RecvConfirmSign(nsTArray<uint8_t>&& aCredentialId,
|
||||
nsTArray<uint8_t>&& aBuffer) override;
|
||||
mozilla::ipc::IPCResult RecvAbort(const nsresult& aError) override;
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RecvConfirmRegister(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>&& aRegBuffer) override;
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RecvConfirmSign(const uint64_t& aTransactionId,
|
||||
nsTArray<uint8_t>&& aCredentialId,
|
||||
nsTArray<uint8_t>&& aBuffer) override;
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
RecvAbort(const uint64_t& aTransactionId, const nsresult& aError) override;
|
||||
|
||||
void ActorDestroy(ActorDestroyReason why) override;
|
||||
|
||||
private:
|
||||
~WebAuthnTransactionChild() = default;
|
||||
};
|
||||
|
@ -12,29 +12,31 @@ namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
WebAuthnTransactionParent::RecvRequestRegister(const WebAuthnTransactionInfo& aTransactionInfo)
|
||||
WebAuthnTransactionParent::RecvRequestRegister(const uint64_t& aTransactionId,
|
||||
const WebAuthnTransactionInfo& aTransactionInfo)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
mgr->Register(this, aTransactionInfo);
|
||||
mgr->Register(this, aTransactionId, aTransactionInfo);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
WebAuthnTransactionParent::RecvRequestSign(const WebAuthnTransactionInfo& aTransactionInfo)
|
||||
WebAuthnTransactionParent::RecvRequestSign(const uint64_t& aTransactionId,
|
||||
const WebAuthnTransactionInfo& aTransactionInfo)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
mgr->Sign(this, aTransactionInfo);
|
||||
mgr->Sign(this, aTransactionId, aTransactionInfo);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
WebAuthnTransactionParent::RecvRequestCancel()
|
||||
WebAuthnTransactionParent::RecvRequestCancel(const uint64_t& aTransactionId)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
U2FTokenManager* mgr = U2FTokenManager::Get();
|
||||
mgr->Cancel(this);
|
||||
mgr->Cancel(this, aTransactionId);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -23,12 +23,20 @@ class WebAuthnTransactionParent final : public PWebAuthnTransactionParent
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(WebAuthnTransactionParent);
|
||||
WebAuthnTransactionParent() = default;
|
||||
|
||||
virtual mozilla::ipc::IPCResult
|
||||
RecvRequestRegister(const WebAuthnTransactionInfo& aTransactionInfo) override;
|
||||
RecvRequestRegister(const uint64_t& aTransactionId,
|
||||
const WebAuthnTransactionInfo& aTransactionInfo) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult
|
||||
RecvRequestSign(const WebAuthnTransactionInfo& aTransactionInfo) override;
|
||||
virtual mozilla::ipc::IPCResult RecvRequestCancel() override;
|
||||
RecvRequestSign(const uint64_t& aTransactionId,
|
||||
const WebAuthnTransactionInfo& aTransactionInfo) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult
|
||||
RecvRequestCancel(const uint64_t& aTransactionId) override;
|
||||
|
||||
virtual void ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
private:
|
||||
~WebAuthnTransactionParent() = default;
|
||||
};
|
||||
|
@ -232,20 +232,9 @@ function* run_test_2(generator)
|
||||
// succeeded.
|
||||
do_check_false(do_get_backup_file(profile).exists());
|
||||
|
||||
// Synchronously read in the first cookie. This will cause it to go into the
|
||||
// cookie table, whereupon it will be written out during database rebuild.
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
|
||||
|
||||
// Wait for the asynchronous read to choke, at which point the backup file
|
||||
// will be created and the database rebuilt.
|
||||
new _observer(sub_generator, "cookie-db-rebuilding");
|
||||
yield;
|
||||
do_execute_soon(function() { do_run_generator(sub_generator); });
|
||||
yield;
|
||||
|
||||
// At this point, the cookies should still be in memory.
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
|
||||
do_check_eq(do_count_cookies(), 1);
|
||||
// Recreate a new database since it was corrupted
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 0);
|
||||
do_check_eq(do_count_cookies(), 0);
|
||||
|
||||
// Close the profile.
|
||||
do_close_profile(sub_generator);
|
||||
@ -255,13 +244,11 @@ function* run_test_2(generator)
|
||||
do_check_true(do_get_backup_file(profile).exists());
|
||||
do_check_eq(do_get_backup_file(profile).fileSize, size);
|
||||
let db = Services.storage.openDatabase(do_get_cookie_file(profile));
|
||||
do_check_eq(do_count_cookies_in_db(db, "0.com"), 1);
|
||||
db.close();
|
||||
|
||||
// Load the profile, and check that it contains the new cookie.
|
||||
do_load_profile();
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
|
||||
do_check_eq(do_count_cookies(), 1);
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 0);
|
||||
do_check_eq(do_count_cookies(), 0);
|
||||
|
||||
// Close the profile.
|
||||
do_close_profile(sub_generator);
|
||||
@ -310,25 +297,16 @@ function* run_test_3(generator)
|
||||
// succeeded.
|
||||
do_check_false(do_get_backup_file(profile).exists());
|
||||
|
||||
// Synchronously read in the cookies for our two domains. The first should
|
||||
// succeed, but the second should fail midway through, resulting in none of
|
||||
// those cookies being present.
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("hither.com"), 10);
|
||||
// Recreate a new database since it was corrupted
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("hither.com"), 0);
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("haithur.com"), 0);
|
||||
|
||||
// Wait for the backup file to be created and the database rebuilt.
|
||||
do_check_false(do_get_backup_file(profile).exists());
|
||||
new _observer(sub_generator, "cookie-db-rebuilding");
|
||||
yield;
|
||||
do_execute_soon(function() { do_run_generator(sub_generator); });
|
||||
yield;
|
||||
|
||||
// Close the profile.
|
||||
do_close_profile(sub_generator);
|
||||
yield;
|
||||
let db = Services.storage.openDatabase(do_get_cookie_file(profile));
|
||||
do_check_eq(do_count_cookies_in_db(db, "hither.com"), 10);
|
||||
do_check_eq(do_count_cookies_in_db(db), 10);
|
||||
do_check_eq(do_count_cookies_in_db(db, "hither.com"), 0);
|
||||
do_check_eq(do_count_cookies_in_db(db), 0);
|
||||
db.close();
|
||||
|
||||
// Check that the original database was renamed.
|
||||
@ -346,13 +324,6 @@ function* run_test_3(generator)
|
||||
// Synchronously read in everything.
|
||||
do_check_eq(do_count_cookies(), 0);
|
||||
|
||||
// Wait for the backup file to be created and the database rebuilt.
|
||||
do_check_false(do_get_backup_file(profile).exists());
|
||||
new _observer(sub_generator, "cookie-db-rebuilding");
|
||||
yield;
|
||||
do_execute_soon(function() { do_run_generator(sub_generator); });
|
||||
yield;
|
||||
|
||||
// Close the profile.
|
||||
do_close_profile(sub_generator);
|
||||
yield;
|
||||
@ -397,26 +368,17 @@ function* run_test_4(generator)
|
||||
// succeeded.
|
||||
do_check_false(do_get_backup_file(profile).exists());
|
||||
|
||||
// Synchronously read in the first cookie. This will cause it to go into the
|
||||
// cookie table, whereupon it will be written out during database rebuild.
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
|
||||
// Recreate a new database since it was corrupted
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 0);
|
||||
|
||||
// Queue up an INSERT for the same base domain. This should also go into
|
||||
// memory and be written out during database rebuild.
|
||||
let uri = NetUtil.newURI("http://0.com/");
|
||||
Services.cookies.setCookieString(uri, null, "oh2=hai; max-age=1000", null);
|
||||
|
||||
// Wait for the asynchronous read to choke and the insert to fail shortly
|
||||
// thereafter, at which point the backup file will be created and the database
|
||||
// rebuilt.
|
||||
new _observer(sub_generator, "cookie-db-rebuilding");
|
||||
yield;
|
||||
do_execute_soon(function() { do_run_generator(sub_generator); });
|
||||
yield;
|
||||
|
||||
// At this point, the cookies should still be in memory.
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
|
||||
do_check_eq(do_count_cookies(), 2);
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
|
||||
do_check_eq(do_count_cookies(), 1);
|
||||
|
||||
// Close the profile.
|
||||
do_close_profile(sub_generator);
|
||||
@ -425,14 +387,11 @@ function* run_test_4(generator)
|
||||
// Check that the original database was renamed.
|
||||
do_check_true(do_get_backup_file(profile).exists());
|
||||
do_check_eq(do_get_backup_file(profile).fileSize, size);
|
||||
let db = Services.storage.openDatabase(do_get_cookie_file(profile));
|
||||
do_check_eq(do_count_cookies_in_db(db, "0.com"), 2);
|
||||
db.close();
|
||||
|
||||
// Load the profile, and check that it contains the new cookie.
|
||||
do_load_profile();
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
|
||||
do_check_eq(do_count_cookies(), 2);
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
|
||||
do_check_eq(do_count_cookies(), 1);
|
||||
|
||||
// Close the profile.
|
||||
do_close_profile(sub_generator);
|
||||
@ -474,22 +433,10 @@ function* run_test_5(generator)
|
||||
// succeeded.
|
||||
do_check_false(do_get_backup_file(profile).exists());
|
||||
|
||||
// Synchronously read in the first two cookies. This will cause them to go
|
||||
// into the cookie table, whereupon it will be written out during database
|
||||
// rebuild.
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
|
||||
|
||||
// Wait for the asynchronous read to choke, at which point the backup file
|
||||
// will be created and a new connection opened.
|
||||
new _observer(sub_generator, "cookie-db-rebuilding");
|
||||
yield;
|
||||
|
||||
// At this point, the cookies should still be in memory. (Note that these
|
||||
// calls are re-entrant into the cookie service, but it's OK!)
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
|
||||
do_check_eq(do_count_cookies(), 2);
|
||||
// Recreate a new database since it was corrupted
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 0);
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 0);
|
||||
do_check_eq(do_count_cookies(), 0);
|
||||
do_check_true(do_get_backup_file(profile).exists());
|
||||
do_check_eq(do_get_backup_file(profile).fileSize, size);
|
||||
do_check_false(do_get_rebuild_backup_file(profile).exists());
|
||||
@ -502,39 +449,24 @@ function* run_test_5(generator)
|
||||
do_check_eq(do_count_cookies_in_db(db.db), 1);
|
||||
db.close();
|
||||
|
||||
// Wait for the rebuild to bail and the database to be closed.
|
||||
new _observer(sub_generator, "cookie-db-closed");
|
||||
yield;
|
||||
|
||||
// Check that the original backup and the database itself are gone.
|
||||
do_check_true(do_get_rebuild_backup_file(profile).exists());
|
||||
do_check_true(do_get_backup_file(profile).exists());
|
||||
do_check_eq(do_get_backup_file(profile).fileSize, size);
|
||||
do_check_false(do_get_cookie_file(profile).exists());
|
||||
|
||||
// Check that the rebuild backup has the original bar.com cookie, and possibly
|
||||
// a 0.com cookie depending on whether it got written out first or second.
|
||||
db = new CookieDatabaseConnection(do_get_rebuild_backup_file(profile), 4);
|
||||
do_check_eq(do_count_cookies_in_db(db.db, "bar.com"), 1);
|
||||
let count = do_count_cookies_in_db(db.db);
|
||||
do_check_true(count == 1 ||
|
||||
count == 2 && do_count_cookies_in_db(db.db, "0.com") == 1);
|
||||
db.close();
|
||||
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
|
||||
do_check_eq(do_count_cookies(), 2);
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 0);
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 0);
|
||||
do_check_eq(do_count_cookies(), 0);
|
||||
|
||||
// Close the profile. We do not need to wait for completion, because the
|
||||
// database has already been closed.
|
||||
do_close_profile();
|
||||
// database has already been closed. Ensure the cookie file is unlocked.
|
||||
do_close_profile(sub_generator);
|
||||
yield;
|
||||
|
||||
// Clean up.
|
||||
do_get_cookie_file(profile).remove(false);
|
||||
do_get_backup_file(profile).remove(false);
|
||||
do_get_rebuild_backup_file(profile).remove(false);
|
||||
do_check_false(do_get_cookie_file(profile).exists());
|
||||
do_check_false(do_get_backup_file(profile).exists());
|
||||
do_check_false(do_get_rebuild_backup_file(profile).exists());
|
||||
do_run_generator(generator);
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,8 @@ function* do_run_test() {
|
||||
Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
|
||||
|
||||
// Start the cookieservice, to force creation of a database.
|
||||
Services.cookies;
|
||||
// Get the sessionEnumerator to join the initialization in cookie thread
|
||||
Services.cookiemgr.sessionEnumerator;
|
||||
|
||||
// Open a database connection now, after synchronous initialization has
|
||||
// completed. We may not be able to open one later once asynchronous writing
|
||||
|
@ -22,6 +22,17 @@ function* do_run_test() {
|
||||
// Set up a profile.
|
||||
let profile = do_get_profile();
|
||||
|
||||
// Start the cookieservice, to force creation of a database.
|
||||
// Get the sessionEnumerator to join the initialization in cookie thread
|
||||
Services.cookiemgr.sessionEnumerator;
|
||||
|
||||
// Close the profile.
|
||||
do_close_profile(test_generator);
|
||||
yield;
|
||||
|
||||
// Remove the cookie file in order to create another database file.
|
||||
do_get_cookie_file(profile).remove(false);
|
||||
|
||||
// Create a schema 2 database.
|
||||
let schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
|
||||
|
||||
@ -79,6 +90,8 @@ function* do_run_test() {
|
||||
|
||||
// Load the database, forcing migration to the current schema version. Then
|
||||
// test the expected set of cookies:
|
||||
do_load_profile();
|
||||
|
||||
// 1) All unexpired, unique cookies exist.
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
|
||||
|
||||
|
@ -22,6 +22,17 @@ function* do_run_test() {
|
||||
// Set up a profile.
|
||||
let profile = do_get_profile();
|
||||
|
||||
// Start the cookieservice, to force creation of a database.
|
||||
// Get the sessionEnumerator to join the initialization in cookie thread
|
||||
Services.cookiemgr.sessionEnumerator;
|
||||
|
||||
// Close the profile.
|
||||
do_close_profile(test_generator);
|
||||
yield;
|
||||
|
||||
// Remove the cookie file in order to create another database file.
|
||||
do_get_cookie_file(profile).remove(false);
|
||||
|
||||
// Create a schema 3 database.
|
||||
let schema3db = new CookieDatabaseConnection(do_get_cookie_file(profile), 3);
|
||||
|
||||
@ -79,6 +90,8 @@ function* do_run_test() {
|
||||
|
||||
// Load the database, forcing migration to the current schema version. Then
|
||||
// test the expected set of cookies:
|
||||
do_load_profile();
|
||||
|
||||
// 1) All unexpired, unique cookies exist.
|
||||
do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
|
||||
|
||||
|
@ -248,14 +248,15 @@ enum class OpenMode : uint8_t {
|
||||
OPEN_NONE = 0,
|
||||
OPEN_READ = 0x1,
|
||||
OPEN_WRITE = 0x2,
|
||||
OPEN_READ_WRITE = OPEN_READ|OPEN_WRITE,
|
||||
OPEN_READ_ONLY = OPEN_READ,
|
||||
OPEN_WRITE_ONLY = OPEN_WRITE,
|
||||
|
||||
// This is only used in conjunction with OMTP to indicate that the DrawTarget
|
||||
// that is being borrowed will be painted asynchronously, and so will outlive
|
||||
// the write lock.
|
||||
OPEN_ASYNC_WRITE = 0x04
|
||||
OPEN_ASYNC_WRITE = 0x04,
|
||||
|
||||
OPEN_READ_WRITE = OPEN_READ|OPEN_WRITE,
|
||||
OPEN_READ_ASYNC_WRITE = OPEN_READ|OPEN_WRITE|OPEN_ASYNC_WRITE,
|
||||
OPEN_READ_ONLY = OPEN_READ,
|
||||
OPEN_WRITE_ONLY = OPEN_WRITE,
|
||||
};
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(OpenMode)
|
||||
|
||||
|
@ -30,7 +30,7 @@ struct MOZ_STACK_CLASS AutoCapturedPaintSetup
|
||||
{
|
||||
AutoCapturedPaintSetup(CapturedPaintState* aState, CompositorBridgeChild* aBridge)
|
||||
: mState(aState)
|
||||
, mTarget(aState->mTarget)
|
||||
, mTarget(aState->mTargetDual)
|
||||
, mRestorePermitsSubpixelAA(mTarget->GetPermitSubpixelAA())
|
||||
, mOldTransform(mTarget->GetTransform())
|
||||
, mBridge(aBridge)
|
||||
@ -186,7 +186,7 @@ PaintThread::AsyncPaintContents(CompositorBridgeChild* aBridge,
|
||||
MOZ_ASSERT(IsOnPaintThread());
|
||||
MOZ_ASSERT(aState);
|
||||
|
||||
DrawTarget* target = aState->mTarget;
|
||||
DrawTarget* target = aState->mTargetDual;
|
||||
DrawTargetCapture* capture = aState->mCapture;
|
||||
|
||||
AutoCapturedPaintSetup setup(aState, aBridge);
|
||||
|
@ -28,12 +28,14 @@ class CapturedPaintState {
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CapturedPaintState)
|
||||
public:
|
||||
CapturedPaintState(nsIntRegion& aRegionToDraw,
|
||||
gfx::DrawTarget* aTargetDual,
|
||||
gfx::DrawTarget* aTarget,
|
||||
gfx::DrawTarget* aTargetOnWhite,
|
||||
const gfx::Matrix& aTargetTransform,
|
||||
SurfaceMode aSurfaceMode,
|
||||
gfxContentType aContentType)
|
||||
: mRegionToDraw(aRegionToDraw)
|
||||
, mTargetDual(aTargetDual)
|
||||
, mTarget(aTarget)
|
||||
, mTargetOnWhite(aTargetOnWhite)
|
||||
, mTargetTransform(aTargetTransform)
|
||||
@ -45,6 +47,7 @@ public:
|
||||
RefPtr<TextureClient> mTextureClient;
|
||||
RefPtr<TextureClient> mTextureClientOnWhite;
|
||||
RefPtr<gfx::DrawTargetCapture> mCapture;
|
||||
RefPtr<gfx::DrawTarget> mTargetDual;
|
||||
RefPtr<gfx::DrawTarget> mTarget;
|
||||
RefPtr<gfx::DrawTarget> mTargetOnWhite;
|
||||
gfx::Matrix mTargetTransform;
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "mozilla/gfx/Types.h" // for ExtendMode::ExtendMode::CLAMP, etc
|
||||
#include "mozilla/layers/ShadowLayers.h" // for ShadowableLayer
|
||||
#include "mozilla/layers/TextureClient.h" // for TextureClient
|
||||
#include "mozilla/Move.h" // for Move
|
||||
#include "mozilla/gfx/Point.h" // for IntSize
|
||||
#include "gfx2DGlue.h"
|
||||
#include "nsLayoutUtils.h" // for invalidation debugging
|
||||
@ -35,6 +36,20 @@ using namespace gfx;
|
||||
|
||||
namespace layers {
|
||||
|
||||
void
|
||||
BorrowDrawTarget::ReturnDrawTarget(gfx::DrawTarget*& aReturned)
|
||||
{
|
||||
MOZ_ASSERT(mLoanedDrawTarget);
|
||||
MOZ_ASSERT(aReturned == mLoanedDrawTarget);
|
||||
if (mLoanedDrawTarget) {
|
||||
if (mSetTransform) {
|
||||
mLoanedDrawTarget->SetTransform(mLoanedTransform);
|
||||
}
|
||||
mLoanedDrawTarget = nullptr;
|
||||
}
|
||||
aReturned = nullptr;
|
||||
}
|
||||
|
||||
IntRect
|
||||
RotatedBuffer::GetQuadrantRectangle(XSide aXSide, YSide aYSide) const
|
||||
{
|
||||
@ -182,23 +197,7 @@ RotatedBuffer::DrawBufferWithRotation(gfx::DrawTarget *aTarget, ContextSource aS
|
||||
DrawBufferQuadrant(aTarget, RIGHT, BOTTOM, aSource, aOpacity, aOperator,aMask, aMaskTransform);
|
||||
}
|
||||
|
||||
already_AddRefed<SourceSurface>
|
||||
SourceRotatedBuffer::GetSourceSurface(ContextSource aSource) const
|
||||
{
|
||||
RefPtr<SourceSurface> surf;
|
||||
if (aSource == BUFFER_BLACK) {
|
||||
surf = mSource;
|
||||
} else {
|
||||
MOZ_ASSERT(aSource == BUFFER_WHITE);
|
||||
surf = mSourceOnWhite;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(surf);
|
||||
return surf.forget();
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
RotatedContentBuffer::IsClippingCheap(DrawTarget* aTarget, const nsIntRegion& aRegion)
|
||||
bool IsClippingCheap(gfx::DrawTarget* aTarget, const nsIntRegion& aRegion)
|
||||
{
|
||||
// Assume clipping is cheap if the draw target just has an integer
|
||||
// translation, and the visible region is simple.
|
||||
@ -207,17 +206,13 @@ RotatedContentBuffer::IsClippingCheap(DrawTarget* aTarget, const nsIntRegion& aR
|
||||
}
|
||||
|
||||
void
|
||||
RotatedContentBuffer::DrawTo(PaintedLayer* aLayer,
|
||||
DrawTarget* aTarget,
|
||||
float aOpacity,
|
||||
CompositionOp aOp,
|
||||
SourceSurface* aMask,
|
||||
const Matrix* aMaskTransform)
|
||||
RotatedBuffer::DrawTo(PaintedLayer* aLayer,
|
||||
DrawTarget* aTarget,
|
||||
float aOpacity,
|
||||
CompositionOp aOp,
|
||||
SourceSurface* aMask,
|
||||
const Matrix* aMaskTransform)
|
||||
{
|
||||
if (!EnsureBuffer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool clipped = false;
|
||||
|
||||
// If the entire buffer is valid, we can just draw the whole thing,
|
||||
@ -243,12 +238,162 @@ RotatedContentBuffer::DrawTo(PaintedLayer* aLayer,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RotatedBuffer::UpdateDestinationFrom(const RotatedBuffer& aSource,
|
||||
const gfx::IntRect& aUpdateRect)
|
||||
{
|
||||
DrawIterator iter;
|
||||
while (DrawTarget* destDT =
|
||||
BorrowDrawTargetForQuadrantUpdate(aUpdateRect, BUFFER_BLACK, &iter)) {
|
||||
bool isClippingCheap = IsClippingCheap(destDT, iter.mDrawRegion);
|
||||
if (isClippingCheap) {
|
||||
gfxUtils::ClipToRegion(destDT, iter.mDrawRegion);
|
||||
}
|
||||
|
||||
aSource.DrawBufferWithRotation(destDT, BUFFER_BLACK, 1.0, CompositionOp::OP_SOURCE);
|
||||
if (isClippingCheap) {
|
||||
destDT->PopClip();
|
||||
}
|
||||
// Flush the destination before the sources become inaccessible (Unlock).
|
||||
destDT->Flush();
|
||||
ReturnDrawTarget(destDT);
|
||||
}
|
||||
|
||||
if (aSource.HaveBufferOnWhite() && HaveBufferOnWhite()) {
|
||||
DrawIterator whiteIter;
|
||||
while (DrawTarget* destDT =
|
||||
BorrowDrawTargetForQuadrantUpdate(aUpdateRect, BUFFER_WHITE, &whiteIter)) {
|
||||
bool isClippingCheap = IsClippingCheap(destDT, whiteIter.mDrawRegion);
|
||||
if (isClippingCheap) {
|
||||
gfxUtils::ClipToRegion(destDT, whiteIter.mDrawRegion);
|
||||
}
|
||||
|
||||
aSource.DrawBufferWithRotation(destDT, BUFFER_WHITE, 1.0, CompositionOp::OP_SOURCE);
|
||||
if (isClippingCheap) {
|
||||
destDT->PopClip();
|
||||
}
|
||||
// Flush the destination before the sources become inaccessible (Unlock).
|
||||
destDT->Flush();
|
||||
ReturnDrawTarget(destDT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
WrapRotationAxis(int32_t* aRotationPoint, int32_t aSize)
|
||||
{
|
||||
if (*aRotationPoint < 0) {
|
||||
*aRotationPoint += aSize;
|
||||
} else if (*aRotationPoint >= aSize) {
|
||||
*aRotationPoint -= aSize;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
RotatedBuffer::AdjustTo(const gfx::IntRect& aDestBufferRect,
|
||||
const gfx::IntRect& aDrawBounds,
|
||||
bool aCanHaveRotation,
|
||||
bool aCanDrawRotated)
|
||||
{
|
||||
IntRect keepArea;
|
||||
if (keepArea.IntersectRect(aDestBufferRect, mBufferRect)) {
|
||||
// Set mBufferRotation so that the pixels currently in mDTBuffer
|
||||
// will still be rendered in the right place when mBufferRect
|
||||
// changes to aDestBufferRect.
|
||||
IntPoint newRotation = mBufferRotation +
|
||||
(aDestBufferRect.TopLeft() - mBufferRect.TopLeft());
|
||||
WrapRotationAxis(&newRotation.x, mBufferRect.Width());
|
||||
WrapRotationAxis(&newRotation.y, mBufferRect.Height());
|
||||
NS_ASSERTION(gfx::IntRect(gfx::IntPoint(0,0), mBufferRect.Size()).Contains(newRotation),
|
||||
"newRotation out of bounds");
|
||||
|
||||
int32_t xBoundary = aDestBufferRect.XMost() - newRotation.x;
|
||||
int32_t yBoundary = aDestBufferRect.YMost() - newRotation.y;
|
||||
bool drawWrapsBuffer = (aDrawBounds.x < xBoundary && xBoundary < aDrawBounds.XMost()) ||
|
||||
(aDrawBounds.y < yBoundary && yBoundary < aDrawBounds.YMost());
|
||||
|
||||
if ((drawWrapsBuffer && !aCanDrawRotated) ||
|
||||
(newRotation != IntPoint(0,0) && !aCanHaveRotation)) {
|
||||
// The stuff we need to redraw will wrap around an edge of the
|
||||
// buffer (and the caller doesn't know how to support that), so
|
||||
// move the pixels we can keep into a position that lets us
|
||||
// redraw in just one quadrant.
|
||||
RefPtr<gfx::DrawTarget> dtBuffer = GetDTBuffer();
|
||||
RefPtr<gfx::DrawTarget> dtBufferOnWhite = GetDTBufferOnWhite();
|
||||
|
||||
if (mBufferRotation == IntPoint(0,0)) {
|
||||
IntRect srcRect(IntPoint(0, 0), mBufferRect.Size());
|
||||
IntPoint dest = mBufferRect.TopLeft() - aDestBufferRect.TopLeft();
|
||||
|
||||
MOZ_ASSERT(dtBuffer && dtBuffer->IsValid());
|
||||
dtBuffer->CopyRect(srcRect, dest);
|
||||
if (HaveBufferOnWhite()) {
|
||||
MOZ_ASSERT(dtBufferOnWhite && dtBufferOnWhite->IsValid());
|
||||
dtBufferOnWhite->CopyRect(srcRect, dest);
|
||||
}
|
||||
|
||||
mDidSelfCopy = true;
|
||||
mBufferRect = aDestBufferRect;
|
||||
} else {
|
||||
// With azure and a data surface perform an buffer unrotate
|
||||
// (SelfCopy).
|
||||
unsigned char* data;
|
||||
IntSize size;
|
||||
int32_t stride;
|
||||
SurfaceFormat format;
|
||||
|
||||
if (dtBuffer->LockBits(&data, &size, &stride, &format)) {
|
||||
uint8_t bytesPerPixel = BytesPerPixel(format);
|
||||
BufferUnrotate(data,
|
||||
size.width * bytesPerPixel,
|
||||
size.height, stride,
|
||||
newRotation.x * bytesPerPixel, newRotation.y);
|
||||
dtBuffer->ReleaseBits(data);
|
||||
|
||||
if (HaveBufferOnWhite()) {
|
||||
MOZ_ASSERT(dtBufferOnWhite && dtBufferOnWhite->IsValid());
|
||||
dtBufferOnWhite->LockBits(&data, &size, &stride, &format);
|
||||
uint8_t bytesPerPixel = BytesPerPixel(format);
|
||||
BufferUnrotate(data,
|
||||
size.width * bytesPerPixel,
|
||||
size.height, stride,
|
||||
newRotation.x * bytesPerPixel, newRotation.y);
|
||||
dtBufferOnWhite->ReleaseBits(data);
|
||||
}
|
||||
|
||||
// Buffer unrotate moves all the pixels
|
||||
mDidSelfCopy = true;
|
||||
mBufferRect = aDestBufferRect;
|
||||
mBufferRotation = IntPoint(0, 0);
|
||||
}
|
||||
|
||||
if (!mDidSelfCopy) {
|
||||
// We couldn't unrotate the buffer, so we need to create a
|
||||
// new one and start from scratch
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mBufferRect = aDestBufferRect;
|
||||
mBufferRotation = newRotation;
|
||||
}
|
||||
} else {
|
||||
// No pixels are going to be kept. The whole visible region
|
||||
// will be redrawn, so we don't need to copy anything, so we don't
|
||||
// set destBuffer.
|
||||
mBufferRect = aDestBufferRect;
|
||||
mBufferRotation = IntPoint(0,0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DrawTarget*
|
||||
RotatedContentBuffer::BorrowDrawTargetForQuadrantUpdate(const IntRect& aBounds,
|
||||
ContextSource aSource,
|
||||
DrawIterator* aIter,
|
||||
bool aSetTransform,
|
||||
Matrix* aOutMatrix)
|
||||
RotatedBuffer::BorrowDrawTargetForQuadrantUpdate(const IntRect& aBounds,
|
||||
ContextSource aSource,
|
||||
DrawIterator* aIter,
|
||||
bool aSetTransform,
|
||||
Matrix* aOutMatrix)
|
||||
{
|
||||
IntRect bounds = aBounds;
|
||||
if (aIter) {
|
||||
@ -273,25 +418,18 @@ RotatedContentBuffer::BorrowDrawTargetForQuadrantUpdate(const IntRect& aBounds,
|
||||
bounds = aIter->mDrawRegion.GetBounds();
|
||||
}
|
||||
|
||||
if (!EnsureBuffer()) {
|
||||
return nullptr;
|
||||
}
|
||||
gfx::DrawTarget* dtBuffer = GetDTBuffer();
|
||||
gfx::DrawTarget* dtBufferOnWhite = GetDTBufferOnWhite();
|
||||
|
||||
MOZ_ASSERT(!mLoanedDrawTarget, "draw target has been borrowed and not returned");
|
||||
if (aSource == BUFFER_BOTH && HaveBufferOnWhite()) {
|
||||
if (!EnsureBufferOnWhite()) {
|
||||
return nullptr;
|
||||
}
|
||||
MOZ_ASSERT(mDTBuffer && mDTBuffer->IsValid() && mDTBufferOnWhite && mDTBufferOnWhite->IsValid());
|
||||
mLoanedDrawTarget = Factory::CreateDualDrawTarget(mDTBuffer, mDTBufferOnWhite);
|
||||
MOZ_ASSERT(dtBuffer && dtBuffer->IsValid() && dtBufferOnWhite && dtBufferOnWhite->IsValid());
|
||||
mLoanedDrawTarget = Factory::CreateDualDrawTarget(dtBuffer, dtBufferOnWhite);
|
||||
} else if (aSource == BUFFER_WHITE) {
|
||||
if (!EnsureBufferOnWhite()) {
|
||||
return nullptr;
|
||||
}
|
||||
mLoanedDrawTarget = mDTBufferOnWhite;
|
||||
mLoanedDrawTarget = dtBufferOnWhite;
|
||||
} else {
|
||||
// BUFFER_BLACK, or BUFFER_BOTH with a single buffer.
|
||||
mLoanedDrawTarget = mDTBuffer;
|
||||
mLoanedDrawTarget = dtBuffer;
|
||||
}
|
||||
|
||||
// Figure out which quadrant to draw in
|
||||
@ -318,553 +456,155 @@ RotatedContentBuffer::BorrowDrawTargetForQuadrantUpdate(const IntRect& aBounds,
|
||||
return mLoanedDrawTarget;
|
||||
}
|
||||
|
||||
void
|
||||
BorrowDrawTarget::ReturnDrawTarget(gfx::DrawTarget*& aReturned)
|
||||
gfx::SurfaceFormat
|
||||
RemoteRotatedBuffer::GetFormat() const
|
||||
{
|
||||
MOZ_ASSERT(mLoanedDrawTarget);
|
||||
MOZ_ASSERT(aReturned == mLoanedDrawTarget);
|
||||
if (mLoanedDrawTarget) {
|
||||
if (mSetTransform) {
|
||||
mLoanedDrawTarget->SetTransform(mLoanedTransform);
|
||||
}
|
||||
mLoanedDrawTarget = nullptr;
|
||||
}
|
||||
aReturned = nullptr;
|
||||
}
|
||||
|
||||
gfxContentType
|
||||
RotatedContentBuffer::BufferContentType()
|
||||
{
|
||||
if (mBufferProvider || (mDTBuffer && mDTBuffer->IsValid())) {
|
||||
SurfaceFormat format = SurfaceFormat::B8G8R8A8;
|
||||
|
||||
if (mBufferProvider) {
|
||||
format = mBufferProvider->GetFormat();
|
||||
} else if (mDTBuffer && mDTBuffer->IsValid()) {
|
||||
format = mDTBuffer->GetFormat();
|
||||
}
|
||||
|
||||
return ContentForFormat(format);
|
||||
}
|
||||
return gfxContentType::SENTINEL;
|
||||
return mClient->GetFormat();
|
||||
}
|
||||
|
||||
bool
|
||||
RotatedContentBuffer::BufferSizeOkFor(const IntSize& aSize)
|
||||
RemoteRotatedBuffer::IsLocked()
|
||||
{
|
||||
return (aSize == mBufferRect.Size() ||
|
||||
(SizedToVisibleBounds != mBufferSizePolicy &&
|
||||
aSize < mBufferRect.Size()));
|
||||
return mClient->IsLocked();
|
||||
}
|
||||
|
||||
bool
|
||||
RotatedContentBuffer::EnsureBuffer()
|
||||
RemoteRotatedBuffer::Lock(OpenMode aMode)
|
||||
{
|
||||
NS_ASSERTION(!mLoanedDrawTarget, "Loaned draw target must be returned");
|
||||
if (!mDTBuffer || !mDTBuffer->IsValid()) {
|
||||
if (mBufferProvider) {
|
||||
mDTBuffer = mBufferProvider->BorrowDrawTarget();
|
||||
}
|
||||
MOZ_ASSERT(!mTarget);
|
||||
MOZ_ASSERT(!mTargetOnWhite);
|
||||
|
||||
bool locked = mClient->Lock(aMode) &&
|
||||
(!mClientOnWhite || mClientOnWhite->Lock(aMode));
|
||||
if (!locked) {
|
||||
Unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_WARNING_ASSERTION(mDTBuffer && mDTBuffer->IsValid(), "no buffer");
|
||||
return !!mDTBuffer;
|
||||
}
|
||||
|
||||
bool
|
||||
RotatedContentBuffer::EnsureBufferOnWhite()
|
||||
{
|
||||
NS_ASSERTION(!mLoanedDrawTarget, "Loaned draw target must be returned");
|
||||
if (!mDTBufferOnWhite) {
|
||||
if (mBufferProviderOnWhite) {
|
||||
mDTBufferOnWhite =
|
||||
mBufferProviderOnWhite->BorrowDrawTarget();
|
||||
}
|
||||
mTarget = mClient->BorrowDrawTarget();
|
||||
if (!mTarget || !mTarget->IsValid()) {
|
||||
gfxCriticalNote << "Invalid draw target " << hexa(mTarget)
|
||||
<< "in RemoteRotatedBuffer::Lock";
|
||||
Unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_WARNING_ASSERTION(mDTBufferOnWhite, "no buffer");
|
||||
return !!mDTBufferOnWhite;
|
||||
}
|
||||
|
||||
bool
|
||||
RotatedContentBuffer::HaveBuffer() const
|
||||
{
|
||||
return mBufferProvider || (mDTBuffer && mDTBuffer->IsValid());
|
||||
}
|
||||
|
||||
bool
|
||||
RotatedContentBuffer::HaveBufferOnWhite() const
|
||||
{
|
||||
return mBufferProviderOnWhite || (mDTBufferOnWhite && mDTBufferOnWhite->IsValid());
|
||||
}
|
||||
|
||||
static void
|
||||
WrapRotationAxis(int32_t* aRotationPoint, int32_t aSize)
|
||||
{
|
||||
if (*aRotationPoint < 0) {
|
||||
*aRotationPoint += aSize;
|
||||
} else if (*aRotationPoint >= aSize) {
|
||||
*aRotationPoint -= aSize;
|
||||
}
|
||||
}
|
||||
|
||||
static IntRect
|
||||
ComputeBufferRect(const IntRect& aRequestedRect)
|
||||
{
|
||||
IntRect rect(aRequestedRect);
|
||||
// Set a minimum width to guarantee a minimum size of buffers we
|
||||
// allocate (and work around problems on some platforms with smaller
|
||||
// dimensions). 64 used to be the magic number needed to work around
|
||||
// a rendering glitch on b2g (see bug 788411). Now that we don't support
|
||||
// this device anymore we should be fine with 8 pixels as the minimum.
|
||||
rect.SetWidth(std::max(aRequestedRect.Width(), 8));
|
||||
return rect;
|
||||
}
|
||||
|
||||
void
|
||||
RotatedContentBuffer::FlushBuffers()
|
||||
{
|
||||
if (mDTBuffer) {
|
||||
mDTBuffer->Flush();
|
||||
}
|
||||
if (mDTBufferOnWhite) {
|
||||
mDTBufferOnWhite->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
RotatedContentBuffer::PaintState
|
||||
RotatedContentBuffer::BeginPaint(PaintedLayer* aLayer,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
PaintState result;
|
||||
// We need to disable rotation if we're going to be resampled when
|
||||
// drawing, because we might sample across the rotation boundary.
|
||||
// Also disable buffer rotation when using webrender.
|
||||
bool canHaveRotation = gfxPlatform::BufferRotationEnabled() &&
|
||||
!(aFlags & (PAINT_WILL_RESAMPLE | PAINT_NO_ROTATION)) &&
|
||||
!(aLayer->Manager()->AsWebRenderLayerManager());
|
||||
|
||||
nsIntRegion validRegion = aLayer->GetValidRegion();
|
||||
|
||||
bool canUseOpaqueSurface = aLayer->CanUseOpaqueSurface();
|
||||
ContentType layerContentType =
|
||||
canUseOpaqueSurface ? gfxContentType::COLOR :
|
||||
gfxContentType::COLOR_ALPHA;
|
||||
|
||||
SurfaceMode mode;
|
||||
nsIntRegion neededRegion;
|
||||
IntRect destBufferRect;
|
||||
|
||||
bool canReuseBuffer = HaveBuffer();
|
||||
|
||||
while (true) {
|
||||
mode = aLayer->GetSurfaceMode();
|
||||
neededRegion = aLayer->GetVisibleRegion().ToUnknownRegion();
|
||||
canReuseBuffer &= BufferSizeOkFor(neededRegion.GetBounds().Size());
|
||||
result.mContentType = layerContentType;
|
||||
|
||||
if (canReuseBuffer) {
|
||||
if (mBufferRect.Contains(neededRegion.GetBounds())) {
|
||||
// We don't need to adjust mBufferRect.
|
||||
destBufferRect = mBufferRect;
|
||||
} else if (neededRegion.GetBounds().Size() <= mBufferRect.Size()) {
|
||||
// The buffer's big enough but doesn't contain everything that's
|
||||
// going to be visible. We'll move it.
|
||||
destBufferRect = IntRect(neededRegion.GetBounds().TopLeft(), mBufferRect.Size());
|
||||
} else {
|
||||
destBufferRect = neededRegion.GetBounds();
|
||||
}
|
||||
} else {
|
||||
// We won't be reusing the buffer. Compute a new rect.
|
||||
destBufferRect = ComputeBufferRect(neededRegion.GetBounds());
|
||||
}
|
||||
|
||||
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
#if defined(MOZ_GFX_OPTIMIZE_MOBILE)
|
||||
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
||||
#else
|
||||
if (!aLayer->GetParent() ||
|
||||
!aLayer->GetParent()->SupportsComponentAlphaChildren() ||
|
||||
!aLayer->AsShadowableLayer() ||
|
||||
!aLayer->AsShadowableLayer()->HasShadow()) {
|
||||
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
||||
} else {
|
||||
result.mContentType = gfxContentType::COLOR;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if ((aFlags & PAINT_WILL_RESAMPLE) &&
|
||||
(!neededRegion.GetBounds().IsEqualInterior(destBufferRect) ||
|
||||
neededRegion.GetNumRects() > 1))
|
||||
{
|
||||
// The area we add to neededRegion might not be painted opaquely.
|
||||
if (mode == SurfaceMode::SURFACE_OPAQUE) {
|
||||
result.mContentType = gfxContentType::COLOR_ALPHA;
|
||||
mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA;
|
||||
}
|
||||
|
||||
// We need to validate the entire buffer, to make sure that only valid
|
||||
// pixels are sampled.
|
||||
neededRegion = destBufferRect;
|
||||
}
|
||||
|
||||
// If we have an existing buffer, but the content type has changed or we
|
||||
// have transitioned into/out of component alpha, then we need to recreate it.
|
||||
if (canReuseBuffer &&
|
||||
(result.mContentType != BufferContentType() ||
|
||||
(mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != HaveBufferOnWhite()))
|
||||
{
|
||||
// Restart the decision process; we won't re-enter since we guard on
|
||||
// being able to re-use the buffer.
|
||||
canReuseBuffer = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (HaveBuffer() &&
|
||||
(result.mContentType != BufferContentType() ||
|
||||
(mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != HaveBufferOnWhite()))
|
||||
{
|
||||
// We're effectively clearing the valid region, so we need to draw
|
||||
// the entire needed region now.
|
||||
canReuseBuffer = false;
|
||||
result.mRegionToInvalidate = aLayer->GetValidRegion();
|
||||
validRegion.SetEmpty();
|
||||
Clear();
|
||||
|
||||
#if defined(MOZ_DUMP_PAINTING)
|
||||
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
|
||||
if (result.mContentType != BufferContentType()) {
|
||||
printf_stderr("Invalidating entire rotated buffer (layer %p): content type changed\n", aLayer);
|
||||
} else if ((mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != HaveBufferOnWhite()) {
|
||||
printf_stderr("Invalidating entire rotated buffer (layer %p): component alpha changed\n", aLayer);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
NS_ASSERTION(destBufferRect.Contains(neededRegion.GetBounds()),
|
||||
"Destination rect doesn't contain what we need to paint");
|
||||
|
||||
result.mRegionToDraw.Sub(neededRegion, validRegion);
|
||||
|
||||
if (result.mRegionToDraw.IsEmpty())
|
||||
return result;
|
||||
|
||||
if (HaveBuffer()) {
|
||||
if (LockBuffers()) {
|
||||
// Do not modify result.mRegionToDraw or result.mContentType after this call.
|
||||
// Do not modify mBufferRect, mBufferRotation, or mDidSelfCopy,
|
||||
// or call CreateBuffer before this call.
|
||||
FinalizeFrame(result.mRegionToDraw);
|
||||
} else {
|
||||
// Abandon everything and redraw it all. Ideally we'd reallocate and copy
|
||||
// the old to the new and then call FinalizeFrame on the new buffer so that
|
||||
// we only need to draw the latest bits, but we need a big refactor to support
|
||||
// that ordering.
|
||||
result.mRegionToDraw = neededRegion;
|
||||
canReuseBuffer = false;
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
|
||||
IntRect drawBounds = result.mRegionToDraw.GetBounds();
|
||||
RefPtr<DrawTarget> destDTBuffer;
|
||||
RefPtr<DrawTarget> destDTBufferOnWhite;
|
||||
uint32_t bufferFlags = 0;
|
||||
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
bufferFlags |= BUFFER_COMPONENT_ALPHA;
|
||||
}
|
||||
if (canReuseBuffer) {
|
||||
if (!EnsureBuffer()) {
|
||||
return result;
|
||||
}
|
||||
IntRect keepArea;
|
||||
if (keepArea.IntersectRect(destBufferRect, mBufferRect)) {
|
||||
// Set mBufferRotation so that the pixels currently in mDTBuffer
|
||||
// will still be rendered in the right place when mBufferRect
|
||||
// changes to destBufferRect.
|
||||
IntPoint newRotation = mBufferRotation +
|
||||
(destBufferRect.TopLeft() - mBufferRect.TopLeft());
|
||||
WrapRotationAxis(&newRotation.x, mBufferRect.Width());
|
||||
WrapRotationAxis(&newRotation.y, mBufferRect.Height());
|
||||
NS_ASSERTION(gfx::IntRect(gfx::IntPoint(0,0), mBufferRect.Size()).Contains(newRotation),
|
||||
"newRotation out of bounds");
|
||||
int32_t xBoundary = destBufferRect.XMost() - newRotation.x;
|
||||
int32_t yBoundary = destBufferRect.YMost() - newRotation.y;
|
||||
bool drawWrapsBuffer = (drawBounds.x < xBoundary && xBoundary < drawBounds.XMost()) ||
|
||||
(drawBounds.y < yBoundary && yBoundary < drawBounds.YMost());
|
||||
if ((drawWrapsBuffer && !(aFlags & PAINT_CAN_DRAW_ROTATED)) ||
|
||||
(newRotation != IntPoint(0,0) && !canHaveRotation)) {
|
||||
// The stuff we need to redraw will wrap around an edge of the
|
||||
// buffer (and the caller doesn't know how to support that), so
|
||||
// move the pixels we can keep into a position that lets us
|
||||
// redraw in just one quadrant.
|
||||
if (mBufferRotation == IntPoint(0,0)) {
|
||||
IntRect srcRect(IntPoint(0, 0), mBufferRect.Size());
|
||||
IntPoint dest = mBufferRect.TopLeft() - destBufferRect.TopLeft();
|
||||
MOZ_ASSERT(mDTBuffer && mDTBuffer->IsValid());
|
||||
mDTBuffer->CopyRect(srcRect, dest);
|
||||
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
if (!EnsureBufferOnWhite()) {
|
||||
return result;
|
||||
}
|
||||
MOZ_ASSERT(mDTBufferOnWhite && mDTBufferOnWhite->IsValid());
|
||||
mDTBufferOnWhite->CopyRect(srcRect, dest);
|
||||
}
|
||||
result.mDidSelfCopy = true;
|
||||
mDidSelfCopy = true;
|
||||
// Don't set destBuffer; we special-case self-copies, and
|
||||
// just did the necessary work above.
|
||||
mBufferRect = destBufferRect;
|
||||
} else {
|
||||
// With azure and a data surface perform an buffer unrotate
|
||||
// (SelfCopy).
|
||||
unsigned char* data;
|
||||
IntSize size;
|
||||
int32_t stride;
|
||||
SurfaceFormat format;
|
||||
|
||||
if (mDTBuffer->LockBits(&data, &size, &stride, &format)) {
|
||||
uint8_t bytesPerPixel = BytesPerPixel(format);
|
||||
BufferUnrotate(data,
|
||||
size.width * bytesPerPixel,
|
||||
size.height, stride,
|
||||
newRotation.x * bytesPerPixel, newRotation.y);
|
||||
mDTBuffer->ReleaseBits(data);
|
||||
|
||||
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
if (!EnsureBufferOnWhite()) {
|
||||
return result;
|
||||
}
|
||||
MOZ_ASSERT(mDTBufferOnWhite && mDTBufferOnWhite->IsValid());
|
||||
mDTBufferOnWhite->LockBits(&data, &size, &stride, &format);
|
||||
uint8_t bytesPerPixel = BytesPerPixel(format);
|
||||
BufferUnrotate(data,
|
||||
size.width * bytesPerPixel,
|
||||
size.height, stride,
|
||||
newRotation.x * bytesPerPixel, newRotation.y);
|
||||
mDTBufferOnWhite->ReleaseBits(data);
|
||||
}
|
||||
|
||||
// Buffer unrotate moves all the pixels, note that
|
||||
// we self copied for SyncBackToFrontBuffer
|
||||
result.mDidSelfCopy = true;
|
||||
mDidSelfCopy = true;
|
||||
mBufferRect = destBufferRect;
|
||||
mBufferRotation = IntPoint(0, 0);
|
||||
}
|
||||
|
||||
if (!result.mDidSelfCopy) {
|
||||
destBufferRect = ComputeBufferRect(neededRegion.GetBounds());
|
||||
CreateBuffer(result.mContentType, destBufferRect, bufferFlags,
|
||||
&destDTBuffer, &destDTBufferOnWhite);
|
||||
if (!destDTBuffer ||
|
||||
(!destDTBufferOnWhite && (bufferFlags & BUFFER_COMPONENT_ALPHA))) {
|
||||
if (Factory::ReasonableSurfaceSize(IntSize(destBufferRect.Width(), destBufferRect.Height()))) {
|
||||
gfxCriticalNote << "Failed 1 buffer db=" << hexa(destDTBuffer.get()) << " dw=" << hexa(destDTBufferOnWhite.get()) << " for " << destBufferRect.x << ", " << destBufferRect.y << ", " << destBufferRect.Width() << ", " << destBufferRect.Height();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mBufferRect = destBufferRect;
|
||||
mBufferRotation = newRotation;
|
||||
}
|
||||
} else {
|
||||
// No pixels are going to be kept. The whole visible region
|
||||
// will be redrawn, so we don't need to copy anything, so we don't
|
||||
// set destBuffer.
|
||||
mBufferRect = destBufferRect;
|
||||
mBufferRotation = IntPoint(0,0);
|
||||
}
|
||||
} else {
|
||||
// The buffer's not big enough, so allocate a new one
|
||||
CreateBuffer(result.mContentType, destBufferRect, bufferFlags,
|
||||
&destDTBuffer, &destDTBufferOnWhite);
|
||||
if (!destDTBuffer ||
|
||||
(!destDTBufferOnWhite && (bufferFlags & BUFFER_COMPONENT_ALPHA))) {
|
||||
if (Factory::ReasonableSurfaceSize(IntSize(destBufferRect.Width(), destBufferRect.Height()))) {
|
||||
gfxCriticalNote << "Failed 2 buffer db=" << hexa(destDTBuffer.get()) << " dw=" << hexa(destDTBufferOnWhite.get()) << " for " << destBufferRect.x << ", " << destBufferRect.y << ", " << destBufferRect.Width() << ", " << destBufferRect.Height();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
NS_ASSERTION(!(aFlags & PAINT_WILL_RESAMPLE) || destBufferRect == neededRegion.GetBounds(),
|
||||
"If we're resampling, we need to validate the entire buffer");
|
||||
|
||||
// If we have no buffered data already, then destBuffer will be a fresh buffer
|
||||
// and we do not need to clear it below.
|
||||
bool isClear = !HaveBuffer();
|
||||
|
||||
if (destDTBuffer) {
|
||||
if (!isClear && (mode != SurfaceMode::SURFACE_COMPONENT_ALPHA || HaveBufferOnWhite())) {
|
||||
// Copy the bits
|
||||
IntPoint offset = -destBufferRect.TopLeft();
|
||||
Matrix mat = Matrix::Translation(offset.x, offset.y);
|
||||
destDTBuffer->SetTransform(mat);
|
||||
if (!EnsureBuffer()) {
|
||||
return result;
|
||||
}
|
||||
MOZ_ASSERT(mDTBuffer && mDTBuffer->IsValid(), "Have we got a Thebes buffer for some reason?");
|
||||
DrawBufferWithRotation(destDTBuffer, BUFFER_BLACK, 1.0, CompositionOp::OP_SOURCE);
|
||||
destDTBuffer->SetTransform(Matrix());
|
||||
|
||||
if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
if (!destDTBufferOnWhite || !EnsureBufferOnWhite()) {
|
||||
return result;
|
||||
}
|
||||
MOZ_ASSERT(mDTBufferOnWhite && mDTBufferOnWhite->IsValid(), "Have we got a Thebes buffer for some reason?");
|
||||
destDTBufferOnWhite->SetTransform(mat);
|
||||
DrawBufferWithRotation(destDTBufferOnWhite, BUFFER_WHITE, 1.0, CompositionOp::OP_SOURCE);
|
||||
destDTBufferOnWhite->SetTransform(Matrix());
|
||||
}
|
||||
}
|
||||
|
||||
mDTBuffer = destDTBuffer.forget();
|
||||
mDTBufferOnWhite = destDTBufferOnWhite.forget();
|
||||
mBufferRect = destBufferRect;
|
||||
mBufferRotation = IntPoint(0,0);
|
||||
}
|
||||
NS_ASSERTION(canHaveRotation || mBufferRotation == IntPoint(0,0),
|
||||
"Rotation disabled, but we have nonzero rotation?");
|
||||
|
||||
nsIntRegion invalidate;
|
||||
invalidate.Sub(aLayer->GetValidRegion(), destBufferRect);
|
||||
result.mRegionToInvalidate.Or(result.mRegionToInvalidate, invalidate);
|
||||
result.mClip = DrawRegionClip::DRAW;
|
||||
result.mMode = mode;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
RefPtr<CapturedPaintState>
|
||||
RotatedContentBuffer::BorrowDrawTargetForRecording(PaintState& aPaintState,
|
||||
DrawIterator* aIter,
|
||||
bool aSetTransform)
|
||||
{
|
||||
if (aPaintState.mMode == SurfaceMode::SURFACE_NONE) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Matrix transform;
|
||||
DrawTarget* result = BorrowDrawTargetForQuadrantUpdate(aPaintState.mRegionToDraw.GetBounds(),
|
||||
BUFFER_BOTH, aIter,
|
||||
aSetTransform,
|
||||
&transform);
|
||||
if (!result) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsIntRegion regionToDraw =
|
||||
ExpandDrawRegion(aPaintState, aIter, result->GetBackendType());
|
||||
|
||||
RefPtr<CapturedPaintState> state =
|
||||
new CapturedPaintState(regionToDraw,
|
||||
result,
|
||||
mDTBufferOnWhite,
|
||||
transform,
|
||||
aPaintState.mMode,
|
||||
aPaintState.mContentType);
|
||||
return state;
|
||||
}
|
||||
|
||||
/*static */ bool
|
||||
RotatedContentBuffer::PrepareDrawTargetForPainting(CapturedPaintState* aState)
|
||||
{
|
||||
MOZ_ASSERT(aState);
|
||||
RefPtr<DrawTarget> target = aState->mTarget;
|
||||
RefPtr<DrawTarget> whiteTarget = aState->mTargetOnWhite;
|
||||
|
||||
if (aState->mSurfaceMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
|
||||
if (!target || !target->IsValid() ||
|
||||
!whiteTarget || !whiteTarget->IsValid()) {
|
||||
// This can happen in release builds if allocating one of the two buffers
|
||||
// failed. This in turn can happen if unreasonably large textures are
|
||||
// requested.
|
||||
if (mClientOnWhite) {
|
||||
mTargetOnWhite = mClientOnWhite->BorrowDrawTarget();
|
||||
if (!mTargetOnWhite || !mTargetOnWhite->IsValid()) {
|
||||
gfxCriticalNote << "Invalid draw target(s) " << hexa(mTarget)
|
||||
<< " and " << hexa(mTargetOnWhite)
|
||||
<< "in RemoteRotatedBuffer::Lock";
|
||||
Unlock();
|
||||
return false;
|
||||
}
|
||||
for (auto iter = aState->mRegionToDraw.RectIter(); !iter.Done(); iter.Next()) {
|
||||
const IntRect& rect = iter.Get();
|
||||
target->FillRect(Rect(rect.x, rect.y, rect.Width(), rect.Height()),
|
||||
ColorPattern(Color(0.0, 0.0, 0.0, 1.0)));
|
||||
whiteTarget->FillRect(Rect(rect.x, rect.y, rect.Width(), rect.Height()),
|
||||
ColorPattern(Color(1.0, 1.0, 1.0, 1.0)));
|
||||
}
|
||||
} else if (aState->mContentType == gfxContentType::COLOR_ALPHA &&
|
||||
target->IsValid()) {
|
||||
// HaveBuffer() => we have an existing buffer that we must clear
|
||||
for (auto iter = aState->mRegionToDraw.RectIter(); !iter.Done(); iter.Next()) {
|
||||
const IntRect& rect = iter.Get();
|
||||
target->ClearRect(Rect(rect.x, rect.y, rect.Width(), rect.Height()));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
nsIntRegion
|
||||
RotatedContentBuffer::ExpandDrawRegion(PaintState& aPaintState,
|
||||
DrawIterator* aIter,
|
||||
BackendType aBackendType)
|
||||
void
|
||||
RemoteRotatedBuffer::Unlock()
|
||||
{
|
||||
nsIntRegion* drawPtr = &aPaintState.mRegionToDraw;
|
||||
if (aIter) {
|
||||
// The iterators draw region currently only contains the bounds of the region,
|
||||
// this makes it the precise region.
|
||||
aIter->mDrawRegion.And(aIter->mDrawRegion, aPaintState.mRegionToDraw);
|
||||
drawPtr = &aIter->mDrawRegion;
|
||||
mTarget = nullptr;
|
||||
mTargetOnWhite = nullptr;
|
||||
|
||||
if (mClient->IsLocked()) {
|
||||
mClient->Unlock();
|
||||
}
|
||||
if (aBackendType == BackendType::DIRECT2D ||
|
||||
aBackendType == BackendType::DIRECT2D1_1) {
|
||||
// Simplify the draw region to avoid hitting expensive drawing paths
|
||||
// for complex regions.
|
||||
drawPtr->SimplifyOutwardByArea(100 * 100);
|
||||
if (mClientOnWhite && mClientOnWhite->IsLocked()) {
|
||||
mClientOnWhite->Unlock();
|
||||
}
|
||||
return *drawPtr;
|
||||
}
|
||||
|
||||
DrawTarget*
|
||||
RotatedContentBuffer::BorrowDrawTargetForPainting(PaintState& aPaintState,
|
||||
DrawIterator* aIter /* = nullptr */)
|
||||
void
|
||||
RemoteRotatedBuffer::SyncWithObject(SyncObjectClient* aSyncObject)
|
||||
{
|
||||
RefPtr<CapturedPaintState> capturedState =
|
||||
BorrowDrawTargetForRecording(aPaintState, aIter, true);
|
||||
|
||||
if (!capturedState) {
|
||||
return nullptr;
|
||||
mClient->SyncWithObject(aSyncObject);
|
||||
if (mClientOnWhite) {
|
||||
mClientOnWhite->SyncWithObject(aSyncObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (!RotatedContentBuffer::PrepareDrawTargetForPainting(capturedState)) {
|
||||
return nullptr;
|
||||
void
|
||||
RemoteRotatedBuffer::Clear()
|
||||
{
|
||||
MOZ_ASSERT(!mTarget && !mTargetOnWhite);
|
||||
mClient = nullptr;
|
||||
mClientOnWhite = nullptr;
|
||||
}
|
||||
|
||||
already_AddRefed<gfx::SourceSurface>
|
||||
RemoteRotatedBuffer::GetSourceSurface(ContextSource aSource) const
|
||||
{
|
||||
if (aSource == ContextSource::BUFFER_BLACK) {
|
||||
return mTarget->Snapshot();
|
||||
} else {
|
||||
MOZ_ASSERT(aSource == ContextSource::BUFFER_WHITE);
|
||||
return mTargetOnWhite->Snapshot();
|
||||
}
|
||||
}
|
||||
|
||||
return capturedState->mTarget;
|
||||
gfx::DrawTarget*
|
||||
RemoteRotatedBuffer::GetDTBuffer() const
|
||||
{
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
gfx::DrawTarget*
|
||||
RemoteRotatedBuffer::GetDTBufferOnWhite() const
|
||||
{
|
||||
return mTargetOnWhite;
|
||||
}
|
||||
|
||||
gfx::SurfaceFormat
|
||||
DrawTargetRotatedBuffer::GetFormat() const
|
||||
{
|
||||
return mTarget->GetFormat();
|
||||
}
|
||||
|
||||
already_AddRefed<gfx::SourceSurface>
|
||||
DrawTargetRotatedBuffer::GetSourceSurface(ContextSource aSource) const
|
||||
{
|
||||
if (aSource == ContextSource::BUFFER_BLACK) {
|
||||
return mTarget->Snapshot();
|
||||
} else {
|
||||
MOZ_ASSERT(aSource == ContextSource::BUFFER_WHITE);
|
||||
return mTargetOnWhite->Snapshot();
|
||||
}
|
||||
}
|
||||
|
||||
gfx::DrawTarget*
|
||||
DrawTargetRotatedBuffer::GetDTBuffer() const
|
||||
{
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
gfx::DrawTarget*
|
||||
DrawTargetRotatedBuffer::GetDTBufferOnWhite() const
|
||||
{
|
||||
return mTargetOnWhite;
|
||||
}
|
||||
|
||||
gfx::SurfaceFormat
|
||||
SourceRotatedBuffer::GetFormat() const
|
||||
{
|
||||
return mSource->GetFormat();
|
||||
}
|
||||
|
||||
already_AddRefed<SourceSurface>
|
||||
RotatedContentBuffer::GetSourceSurface(ContextSource aSource) const
|
||||
SourceRotatedBuffer::GetSourceSurface(ContextSource aSource) const
|
||||
{
|
||||
if (!mDTBuffer || !mDTBuffer->IsValid()) {
|
||||
gfxCriticalNote << "Invalid buffer in RotatedContentBuffer::GetSourceSurface " << gfx::hexa(mDTBuffer);
|
||||
return nullptr;
|
||||
RefPtr<SourceSurface> surf;
|
||||
if (aSource == BUFFER_BLACK) {
|
||||
surf = mSource;
|
||||
} else {
|
||||
MOZ_ASSERT(aSource == BUFFER_WHITE);
|
||||
surf = mSourceOnWhite;
|
||||
}
|
||||
|
||||
if (aSource == BUFFER_BLACK) {
|
||||
return mDTBuffer->Snapshot();
|
||||
} else {
|
||||
if (!mDTBufferOnWhite || !mDTBufferOnWhite->IsValid()) {
|
||||
gfxCriticalNote << "Invalid buffer on white in RotatedContentBuffer::GetSourceSurface " << gfx::hexa(mDTBufferOnWhite);
|
||||
return nullptr;
|
||||
}
|
||||
MOZ_ASSERT(aSource == BUFFER_WHITE);
|
||||
return mDTBufferOnWhite->Snapshot();
|
||||
}
|
||||
MOZ_ASSERT(surf);
|
||||
return surf.forget();
|
||||
}
|
||||
|
||||
} // namespace layers
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user